Go API Reference

Import

import (
"context"
"errors"
"fmt"
"io"
"os"
joytoken "github.com/jd-opensource/joytoken-sdk-go"
)

NewClient

ctx := context.Background()
client := joytoken.NewClient(
joytoken.WithAPIKey(os.Getenv("JOY_TOKEN_API_KEY")),
joytoken.WithAPIBaseURL(os.Getenv("JOY_TOKEN_API_BASE_URL")),
)
OptionUse
WithAPIKeyRequest auth
WithAPIBaseURLCommon JoyToken base; also derives <base>/openai/v1 for model calls
WithOpenAIBaseURLExplicit override for the derived model API Base URL
WithAnthropicBaseURLDeprecated no-op kept for source compatibility
WithAnthropicVersionDeprecated no-op kept for source compatibility
WithHTTPClientCustom HTTP client
WithHeaderAdd a default request header
WithTimeoutFull request and stream timeout; defaults to 60 seconds; use a non-positive duration to disable
WithMaxRetriesOpt into transient retries; defaults to 0 because model POSTs are non-idempotent
WithTools / WithToolHandlerRegister executable tools for explicit Run* loops; a non-empty set replaces defaults
WithDefaultLocalToolsToggle the built-in local tool set (calculator, datetime, file_read, list_dir, file_search, file_write, shell); defaults to enabled
WithDefaultBuiltinToolsOpt into hosted Responses web_search_preview; defaults to disabled
WithoutDefaultToolsExclude specific default tools by name (e.g. WithoutDefaultTools("shell", "file_write")); explicitly registered tools are never affected
WithFileWorkspace / WithShellWorkspaceConfigure sandbox roots; both default to the current directory
WithFilePermissionGrant a permission callback for the file_write tool; without it, file_write is declared but refused at execution time
WithShellPermissionGrant a permission callback for the shell tool; without it, shell is declared but refused at execution time

The Gateway has one Chat Completions model entry point. Responses uses its native /openai/v1/responses endpoint; Messages is converted locally to and from Chat Completions. No Anthropic HTTP request or anthropic-version header is emitted.

WithAPIKey is required for model calls, model metadata and pricing. If it is missing, these methods return joytoken.ErrMissingAPIKey before sending a request. ListModels is the only unauthenticated catalog method.

Method chooser

SurfaceOne requestExecute toolsRaw streamStreaming tool loop
ChatCreateChatCompletionRunChatCompletionStreamChatCompletionRunChatCompletionStream
ResponsesCreateResponseRunResponseStreamResponseRunResponseStream
MessagesCreateMessageRunMessageStreamMessageRunMessageStream

Primitive methods auto-run only SDK fallback tools selected when no request-level or Client-registered tools exist. Caller-owned tools require an explicit Run* method for SDK execution.

Compatible API Methods

Configure WithAPIBaseURL once. Use WithOpenAIBaseURL only for an explicit model URL override.

CreateChatCompletion

completion, err := client.CreateChatCompletion(ctx, joytoken.ChatCompletionRequest{
Model: joytoken.ModelAuto,
Messages: []joytoken.ChatMessage{
{Role: "user", Content: "Hello"},
},
Tools: []joytoken.ChatTool{},
})

Calls POST /openai/v1/chat/completions and returns a non-streaming Chat Completions response.

StreamChatCompletion

stream, err := client.StreamChatCompletion(ctx, joytoken.ChatCompletionRequest{
Model: joytoken.ModelAuto,
Messages: []joytoken.ChatMessage{
{Role: "user", Content: "Hello"},
},
Tools: []joytoken.ChatTool{},
})
if err != nil {
return err
}
defer stream.Close()
chunk, err := stream.Recv()

Reads SSE chunks. io.EOF means the stream is complete.

A metadata/usage-only SSE event has len(chunk.Choices) == 0. The SDK preserves the event, normalizes Choices to an empty slice, and may derive chunk.Usage from metadata.billing. Check the length or range over choices before indexing.

CreateResponse

response, err := client.CreateResponse(ctx, joytoken.ResponseRequest{
Model: joytoken.ModelAuto,
Input: "Explain JoyToken in one sentence.",
Tools: []joytoken.ResponseTool{},
})
if err != nil {
return err
}
fmt.Println(response.OutputText())

Calls POST /openai/v1/responses and returns a non-streaming Responses result. Input accepts a string or message input items.

StreamResponse

stream, err := client.StreamResponse(ctx, joytoken.ResponseRequest{
Model: joytoken.ModelAuto,
Input: "Say hello",
Tools: []joytoken.ResponseTool{},
})
if err != nil {
return err
}
defer stream.Close()
for {
event, err := stream.Recv()
if errors.Is(err, io.EOF) {
break
}
if err != nil {
return err
}
fmt.Print(event.Delta)
}

Reads Responses SSE events including response.output_text.delta and response.completed.

GenerateImage

image, err := client.GenerateImage(ctx, joytoken.ImageGenerationRequest{
Model: joytoken.ModelAuto,
Prompt: "A neon JoyToken logo on a black background",
Size: "1024x1024",
})
if err != nil {
return err
}
if len(image.Data) == 0 {
return fmt.Errorf("image response contained no data")
}
fmt.Println(image.Data[0].URL)

Calls POST /openai/v1/images/generations. Model and Prompt are required. Image generation is non-streaming: GenerateImage returns once with the complete JSON response and does not expose an SSE stream. Do not pass stream: true. The response contains generated images in Data and JoyToken routing and billing details in Metadata when available.

EditImage

edited, err := client.EditImage(ctx, joytoken.ImageEditRequest{
Model: joytoken.ModelAuto,
Image: "https://picsum.photos/512/512",
Prompt: "Add a neon JoyToken logo in the top-right corner",
Size: "1024x1024",
})
if err != nil {
return err
}
if len(edited.Data) == 0 {
return fmt.Errorf("image response contained no data")
}
fmt.Println(edited.Data[0].URL)

Calls POST /openai/v1/images/edits. Prompt and Image are required; Model must be ModelAuto. Image accepts a single http(s) URL or base64 data URI, or multiple images as a slice. Image editing is non-streaming: EditImage returns once with the complete JSON response and does not expose an SSE stream. Do not pass stream: true. Output defaults to b64_json; set ResponseFormat to "url" only when you need a hosted URL. The response contains edited images in Data and JoyToken routing and billing details in Metadata when available.

Tool Calling

Tool ownership follows one precedence rule:

  1. Non-nil request Tools, including an empty slice, is copied unchanged and suppresses every default.
  2. Otherwise Client-registered tools replace defaults.
  3. Only when neither source exists does the SDK inject and automatically execute its fallback tools.

Create* and raw Stream* never execute request-level or Client-registered tools. Explicit Run* methods execute only matching handlers. Primitive Create* methods auto-run only SDK-owned fallback tools from rule 3.

Registering tools

Register your own tools with WithToolHandler (or WithTools) when constructing the client. A non-empty registered set replaces the default local set.

client := joytoken.NewClient(
joytoken.WithAPIKey(os.Getenv("JOY_TOKEN_API_KEY")),
joytoken.WithAPIBaseURL(os.Getenv("JOY_TOKEN_API_BASE_URL")),
joytoken.WithToolHandler(
"get_weather",
"Get the current weather for a city.",
map[string]any{
"type": "object",
"properties": map[string]any{
"city": map[string]any{"type": "string"},
},
"required": []string{"city"},
},
func(ctx context.Context, input any, _ joytoken.ToolExecutionContext) (any, error) {
args, _ := input.(map[string]any)
return map[string]any{"city": args["city"], "tempC": 22}, nil
},
),
)

RunChatCompletion

result, err := client.RunChatCompletion(ctx, joytoken.ChatCompletionRequest{
Model: joytoken.ModelAuto,
Messages: []joytoken.ChatMessage{
{Role: "user", Content: "What is 2+2?"},
},
}, joytoken.RunChatOptions{MaxSteps: 8})
if err != nil {
return err
}
fmt.Println(result.FinalText, result.StoppedBy, result.FinishReason)

Runs a bounded Chat Completions tool loop. RunChatOptions.MaxSteps bounds the rounds (zero uses the default of 8). Returns *RunChatResult with FinalText, Messages, per-turn Steps, StoppedBy, and a provider-neutral FinishReason.

RunResponse / RunMessage / streaming runs

RunResponse runs the loop over native Responses items, and RunMessage runs it over Messages-compatible content blocks. RunChatCompletionStream, RunResponseStream, and RunMessageStream accept callbacks for text deltas and tool results while returning the final collected response.

ToolCall.ExtraContent stores opaque provider extensions and is serialized as extra_content. The SDK preserves it across non-streaming and streaming continuations, Messages tool_use conversion, Responses function-call items, and Agent loops. Gemini google.thought_signature is one example. Manual loops must retain the complete returned tool call rather than reconstructing only its standard fields. A separate top-level thought_signature is exposed as ToolCall.ThoughtSignature (serialized as thought_signature) for providers such as Gemini that return it directly on the tool_call; it is preserved and echoed back verbatim on continuation turns.

When a later turn fails, explicit Run* methods return a non-nil partial result together with the error. Completed Steps, tool results, and the accumulated Messages or Input remain available for diagnostics and recovery.

Request declarations contain no executable Go code. If a Run* request includes Tools, only Client handlers registered under the same function names may execute. Unknown names produce a structured tool_handler_not_found result for the model.

Hosted Responses tools

WithDefaultBuiltinTools(true) opts into web_search_preview; it is disabled by default. Hosted file_search must always be supplied explicitly with valid vector_store_ids. Do not send multiple vendor aliases such as web_search, web_search_20250305, and web_search_preview together—unknown types may be rejected by the Gateway.

Confirm hosted search execution by checking for a web_search_call output item in the Responses result.

Side-effecting tools are gated

The default local tool set includes read-only tools that run freely (file_read, list_dir, file_search) plus two side-effecting tools that are always declared to the model but fail-safe at execution time: file_write and shell. They only run when you supply a permission callback.

client := joytoken.NewClient(
joytoken.WithAPIKey(os.Getenv("JOY_TOKEN_API_KEY")),
// Approve or deny each shell command; nil/no callback means every call is refused.
joytoken.WithShellPermission(func(ctx context.Context, req joytoken.ShellPermissionRequest) (bool, error) {
return req.Command == "ls" && req.WorkingDir == "/tmp", nil
}),
joytoken.WithFilePermission(func(ctx context.Context, req joytoken.FilePermissionRequest) (bool, error) {
return false, nil // deny all writes
}),
)

ShellPermissionRequest carries Command and WorkingDir; FilePermissionRequest carries the resolved Root. Configure roots with WithShellWorkspace and WithFileWorkspace so a host can show and enforce exactly where the call runs. To drop a tool entirely instead of gating it, exclude it by name:

// Remove shell and file_write from the default set; read-only tools remain.
joytoken.WithoutDefaultTools("shell", "file_write")

WithoutDefaultTools only filters default tools — tools you register with WithTools / WithToolHandler always take precedence and are never removed.

Run loops keep the resolved declarations on every continuation turn and append each tool result once. After the first tool call, a forced tool choice is relaxed to auto so the model can return a final answer. The default loop bound is eight turns.

ListModels

models, err := client.ListModelsWithOptions(ctx, joytoken.ListModelsOptions{
Locale: joytoken.ModelLocaleEN,
})

Calls the unauthenticated GET /api/v1/models endpoint. ModelLocaleEN returns English descriptions and ModelLocaleZH returns Chinese descriptions. ListModels(ctx) leaves locale unset and uses the API default of English. The SDK preserves the HTTP envelope, so catalog entries are in models.Data.Models.

GetModelMeta

metadata, err := client.GetModelMeta(ctx)

Calls GET /api/v1/models/meta with the configured API key and returns catalog filter metadata.

GetPricing

pricing, err := client.GetPricing(ctx)

Calls GET /api/v1/pricing with the configured API key and returns customer-facing tier exchange pricing metadata.

ErrMissingAPIKey

if errors.Is(err, joytoken.ErrMissingAPIKey) {
// Configure WithAPIKey or JOY_TOKEN_API_KEY before retrying.
}

Timeout and retry policy

WithTimeout covers the complete non-streaming request or lifetime of a consumed stream. A non-positive duration disables the SDK timeout.

WithMaxRetries(n) retries HTTP 429, 5xx, and transport failures using bounded exponential backoff with jitter and Retry-After support. The default is 0. Enable retries only when replaying a non-idempotent model POST—and possible duplicate execution or billing—is acceptable.

APIError

var apiErr *joytoken.APIError
if errors.As(err, &apiErr) {
fmt.Println(apiErr.StatusCode, apiErr.RequestID, apiErr.Body)
}
FieldUse
StatusCodeHTTP status code
RequestIDRequest ID
ResponseHeadersResponse headers
BodyError response body

ChatCompletionResponse.Metadata, streamed Chat metadata, and Messages-adapter metadata preserve Gateway routing and billing details when present. Log request IDs and stop reasons for diagnostics, but never log API keys or unredacted sensitive tool inputs.

Use RequestID() on Chat, Responses, Messages, stream chunks/events, and image responses, or call RequestIDFromMetadata. Body metadata.request_id is the primary source; a successful response request-ID header is used as a fallback. When protocol-level usage is absent, Chat and Responses derive token counts from metadata.billing, and the Messages adapter maps the normalized Chat usage.