TypeScript API Reference

Required Configuration

All TypeScript snippets on this page use an API key that is validated at application startup:

const apiKey = process.env.JOY_TOKEN_API_KEY;
if (!apiKey) throw new Error("JOY_TOKEN_API_KEY is required");

Imports

import { JoyTokenClient, JoyTokenAPIError } from "@joytoken/client-sdk-ts";

JoyTokenClient

const joytoken = new JoyTokenClient({
apiKey,
apiBaseUrl: process.env.JOY_TOKEN_API_BASE_URL,
timeoutMs: 60_000,
});
OptionUse
apiKeyRequest auth, defaults to JOY_TOKEN_API_KEY
apiBaseUrlBase URL for model and pricing APIs; defaults to JOY_TOKEN_API_BASE_URL or https://api.joytokens.ai
openAIBaseUrlOpenAI-compatible Base URL; defaults to JOY_TOKEN_OPENAI_BASE_URL or <apiBaseUrl>/openai/v1
anthropicBaseUrlDeprecated source-compatibility option; Messages still use the Chat Completions endpoint
anthropicVersionDeprecated source-compatibility option; no Anthropic request header is sent
timeoutMsFull request and stream timeout; defaults to 60_000; use 0 to disable
maxRetriesAutomatic retries for HTTP 429, 5xx, and transport errors; defaults to 0; set a positive value to opt in
fetchCustom fetch implementation
defaultHeadersHeaders added to every request; SDK authentication headers remain authoritative

Model calls, model metadata and pricing require apiKey. If it is missing, the SDK throws a clear error before sending a request. models.list() is the only unauthenticated catalog method.

One JoyTokenClient exposes Chat Completions, Responses, and Anthropic-compatible Messages methods without a global protocol switch. Chat and Responses use the Gateway’s native OpenAI endpoints. Messages are converted locally and sent through Chat Completions.

Compatible API Methods

Configure openAIBaseUrl with JOY_TOKEN_OPENAI_BASE_URL.

chat.completions.create

await joytoken.chat.completions.create({
model: "auto",
messages: [{ role: "user", content: "Hello" }],
});

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

chat.completions.stream

for await (const chunk of joytoken.chat.completions.stream({
model: "auto",
messages: [{ role: "user", content: "Hello" }],
})) {
process.stdout.write(chunk.choices?.[0]?.delta?.content ?? "");
}

Calls the same endpoint and reads SSE chunks.

responses.create

const response = await joytoken.responses.create({
model: "auto",
input: "Explain JoyToken in one sentence.",
});
console.log(response.output?.[0]?.content?.[0]?.text);

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

responses.stream

for await (const event of joytoken.responses.stream({
model: "auto",
input: "Say hello",
})) {
if (event.type === "response.output_text.delta") {
process.stdout.write(event.delta ?? "");
}
}

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

images.generate

const image = await joytoken.images.generate({
model: "auto",
prompt: "A neon JoyToken logo on a black background",
size: "1024x1024",
});
console.log(image.data[0]?.url ?? image.data[0]?.b64_json);

Calls POST /openai/v1/images/generations. model and prompt are required. Image generation is non-streaming: images.generate resolves once with the complete JSON response; it does not return an AsyncIterable or SSE events. Do not pass stream: true. The response contains generated images in data and JoyToken routing and billing details in metadata when available.

images.edit

const edited = await joytoken.images.edit({
model: "auto",
image: "https://picsum.photos/512/512",
prompt: "Add a neon JoyToken logo in the top-right corner",
size: "1024x1024",
});
console.log(edited.data[0]?.url ?? edited.data[0]?.b64_json);

Calls POST /openai/v1/images/edits. prompt and image are required; model must be "auto". image accepts a single http(s) URL or base64 data URI, or an array of them to edit multiple images. Image editing is non-streaming: images.edit resolves once with the complete JSON response; it does not return an AsyncIterable or SSE events. Do not pass stream: true. Output defaults to b64_json; set response_format: "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.

Tools

Client tool options

OptionDefaultBehavior
toolsNoneClient-registered declarations and handlers
defaultLocalToolstrueEnables seven local defaults only when no user tools exist
defaultBuiltinToolsfalseOpts Responses into default web_search_preview
toolMaxSteps8Maximum executed tool rounds
excludedDefaultTools[]Removes names only from the SDK default set
fileWorkspaceCurrent directorySandbox root for local file tools
shellWorkspaceCurrent directoryWorking root for local shell
filePermissionDenyPer-call approval for file_write
shellPermissionDenyPer-call approval for shell

Tool execution methods

Primitive create and stream methods never execute request-level or Client-registered tools. create() may auto-run SDK defaults only when no user tool source exists.

chat.completions

MethodReturnsHandler execution
create(request)Promise<ChatCompletionResponse>User/registered tools: no; SDK defaults: automatic when selected
stream(request)AsyncIterable<ChatCompletionChunk>No
run(request)Promise<ChatCompletionResponse>Yes
executeTools(request)Same as runYes
runStream(request, options?)Promise<ChatCompletionResponse>Yes; model turns use streaming requests
executeToolsStream(request, options?)Same as runStreamYes

responses

Responses methods send native flat tool declarations and preserve native output items and SSE events.

MethodReturnsHandler execution
create(request)Promise<Response>User/registered tools: no; SDK defaults: automatic when selected
stream(request)AsyncIterable<ResponseStreamEvent>No
run(request)Promise<Response>Yes
executeTools(request)Same as runYes
runStream(request, options?)Promise<Response>Yes
executeToolsStream(request, options?)Same as runStreamYes

Response.output_text joins all output_text content. Tool loops append function_call_output input items. Hosted tools preserve their native Responses shape.

messages

Messages methods expose Anthropic-compatible inputs, outputs, tool_use blocks, and streaming events while sending Gateway requests through Chat Completions.

MethodReturnsHandler execution
create(request)Promise<MessageResponse>User/registered tools: no; SDK defaults: automatic when selected
stream(request)AsyncIterable<MessageStreamEvent>No
run(request)Promise<MessageResponse>Yes
executeTools(request)Same as runYes
runStream(request, options?)Promise<MessageResponse>Yes
executeToolsStream(request, options?)Same as runStreamYes

Anthropic tool_choice maps as follows:

MessagesChat
{ type: "auto" }"auto"
{ type: "any" }"required"
{ type: "tool", name }Named function
{ type: "none" }"none"

Tool types and helpers

import {
calculator,
dateTime,
defineTool,
fileRead,
fileSearch,
fileWrite,
listDir,
shell,
type Tool,
type ToolCall,
type ToolCallResult,
type ToolRunStreamOptions,
} from "@joytoken/client-sdk-ts";

defineTool() returns the supplied tool unchanged and preserves generic types. A Tool has a name, optional description and JSON Schema parameters, plus an optional execute(input, context) handler.

ToolCall.extra_content?: Record<string, unknown> stores opaque provider extensions. The SDK preserves this field through non-streaming and streaming Chat continuations, Responses function_call items, Messages tool_use blocks, and tool handler context. Gemini thought_signature is one example; the type and forwarding behavior are provider-neutral.

When implementing a tool loop manually, reuse the complete returned ToolCall, Responses function_call, or Messages tool_use item. Do not reconstruct it from only id, name, and arguments.

const options: ToolRunStreamOptions = {
onTextDelta: (delta) => process.stdout.write(delta),
onToolResult: (result: ToolCallResult) => {
console.log(result.tool_name, result.is_error);
},
onOrchestrationEvent: (event) => {
if (event.type === "plan") console.log("plan", event.plan.length);
else console.log("stage", event.task_id, event.final);
},
};

An explicit runner without a matching registered handler returns a structured tool_handler_not_found tool result. It never falls back to a same-name SDK default implementation.

Tool precedence

request.tools !== undefined
→ use exactly request.tools (including [])
→ explicit runner may use only matching Client handlers
else Client tools exist
→ use exactly Client tools
→ execute only through an explicit runner
else
→ inject SDK defaults
→ default automatic loop is allowed

Hosted Responses file_search requires a non-empty vector_store_ids; the SDK never creates IDs. Local function file_search uses { type: "function", name: "file_search", ... } and is a different tool.

models.list

const models = await joytoken.models.list({ locale: "en" });

Calls the unauthenticated GET /api/v1/models endpoint. Use { locale: "zh" } for Chinese descriptions. Omitting locale uses the API default of English. The SDK preserves the HTTP envelope, so catalog entries are in models.data.models.

models.meta

const metadata = await joytoken.models.meta();

Calls GET /api/v1/models/meta with the configured API key and returns tiers, SKUs, capability tags, industry packs, providers, and the catalog update time.

pricing.retrieve

const pricing = await joytoken.pricing.retrieve();

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

Orchestration

When the gateway plans and runs multiple sub-tasks for a model: "auto" turn, the SDK aggregates the result instead of exposing a raw JSON payload.

import {
ORCHESTRATION_FINAL_TASK_ID,
type OrchestrationResult,
type OrchestrationInfo,
type OrchestrationEvent,
} from "@joytoken/client-sdk-ts";
SurfaceShapeNotes
ChatCompletionResponse.orchestration?OrchestrationResultAggregated plan plus every sub-task in stages, in arrival order
ChatCompletionChunk.orchestration?OrchestrationInfoPer-chunk metadata: task_id, task_seq, task_status, title, phase, and plan on planning chunks
runStream option onOrchestrationEvent(event: OrchestrationEvent) => voidFires a plan event once and a stage event per sub-task transition; never fires for non-orchestrated turns
ORCHESTRATION_FINAL_TASK_ID"__final__"Sentinel task_id for the user-facing answer stage

Non-streaming create() still returns the final answer text in choices[0].message.content; the breakdown lives on orchestration. Streaming chunks include intermediate sub-task text, so filter on chunk.orchestration?.task_id === ORCHESTRATION_FINAL_TASK_ID to render only the reply, or use runStream whose onTextDelta already emits only final-answer text.

JoyTokenAPIError

try {
await joytoken.chat.completions.create({
model: "auto",
messages: [{ role: "user", content: "Hello" }],
});
} catch (error) {
if (error instanceof JoyTokenAPIError) {
console.error(error.status, error.requestId, error.body);
}
}
FieldUse
statusHTTP status code
requestIdRequest ID
bodyError response body
contextOptional request-phase diagnostics for model calls

A failed orchestration run can arrive as an HTTP 200 response whose body or SSE stream carries an error envelope ({ "error": { ... }, "choices": [] }). The SDK detects this envelope on both non-streaming and streaming Chat paths and raises the same JoyTokenAPIError, with the gateway error object exposed on body.