@alvin0/ai-agent-sdk-protocol-openai-chat-completions 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +25 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -4
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -11,7 +11,8 @@ import { ModelTarget, ResolvedModelInfo } from "@alvin0/ai-agent-sdk-core/provid
|
|
|
11
11
|
interface ProtocolRequest {
|
|
12
12
|
readonly options: GenerateOptions;
|
|
13
13
|
readonly model: ResolvedModelInfo;
|
|
14
|
-
|
|
14
|
+
/** Absent when neither the caller, the model, nor the route names an output cap. */
|
|
15
|
+
readonly maxTokens?: number;
|
|
15
16
|
}
|
|
16
17
|
/** One decoded SSE event, expressed without depending on an HTTP transport. */
|
|
17
18
|
interface ProtocolSseEvent {
|
|
@@ -169,6 +170,10 @@ interface WireRequest {
|
|
|
169
170
|
stop?: string | string[];
|
|
170
171
|
seed?: number;
|
|
171
172
|
reasoning_effort?: string;
|
|
173
|
+
/** Sent alongside `reasoning_effort` by `dialect.reasoningFormat: 'deepseek'`. */
|
|
174
|
+
thinking?: {
|
|
175
|
+
type: 'enabled' | 'disabled';
|
|
176
|
+
};
|
|
172
177
|
tools?: WireTool[];
|
|
173
178
|
tool_choice?: WireToolChoice;
|
|
174
179
|
parallel_tool_calls?: boolean;
|
|
@@ -324,8 +329,24 @@ interface ChatCompletionsDialect {
|
|
|
324
329
|
readonly stop: boolean;
|
|
325
330
|
/** Send `seed`. */
|
|
326
331
|
readonly seed: boolean;
|
|
327
|
-
/**
|
|
328
|
-
|
|
332
|
+
/**
|
|
333
|
+
* How this endpoint wants to be told how hard to think, or `false` to send
|
|
334
|
+
* nothing regardless of the effort the caller asked for.
|
|
335
|
+
*
|
|
336
|
+
* `'openai'` sends `reasoning_effort` alone (the value passed through
|
|
337
|
+
* verbatim — see decision 1 in the redesign plan). `'deepseek'` matches an
|
|
338
|
+
* endpoint that reasons unless told not to: every effort except `'off'`
|
|
339
|
+
* sends `thinking: {type: 'enabled'}` beside `reasoning_effort`, and
|
|
340
|
+
* `'off'` sends `thinking: {type: 'disabled'}` and omits `reasoning_effort`
|
|
341
|
+
* entirely, mirroring `.temp/deepseek-harness`'s `thinkingFormat: 'deepseek'`
|
|
342
|
+
* (battle-tested there, not guessed here).
|
|
343
|
+
*
|
|
344
|
+
* `'openrouter' | 'qwen'` are deliberately NOT implemented yet: this SDK
|
|
345
|
+
* has no live credentials or verified wire capture for either endpoint's
|
|
346
|
+
* reasoning field, and decision 7 (honest behaviour, no guessing) rules out
|
|
347
|
+
* shipping an unverified shape under a name that promises otherwise.
|
|
348
|
+
*/
|
|
349
|
+
readonly reasoningFormat: 'openai' | 'deepseek' | false;
|
|
329
350
|
/** Prompt-cache key, sent when the endpoint accepts one. */
|
|
330
351
|
readonly promptCacheKey?: string;
|
|
331
352
|
/**
|
|
@@ -342,7 +363,7 @@ interface ChatCompletionsDialect {
|
|
|
342
363
|
*
|
|
343
364
|
* Conservative in one direction on purpose: a field an endpoint does not
|
|
344
365
|
* understand is usually a hard HTTP 400, while a field left unsent merely
|
|
345
|
-
* forgoes a feature. So `parallelToolCalls`, `seed` and `
|
|
366
|
+
* forgoes a feature. So `parallelToolCalls`, `seed` and `reasoningFormat` — the
|
|
346
367
|
* three fields older gateways most often reject — stay OFF until a provider
|
|
347
368
|
* opts in.
|
|
348
369
|
*
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/contract.ts","../src/wire.ts","../src/errors.ts","../src/protocol.ts","../src/serialize.ts","../src/translate.ts"],"mappings":";;;;;;;;;;UAuBiB;WACN,SAAS;WACT,OAAO
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/contract.ts","../src/wire.ts","../src/errors.ts","../src/protocol.ts","../src/serialize.ts","../src/translate.ts"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -376,6 +376,22 @@ function toolChoiceOf(choice) {
|
|
|
376
376
|
};
|
|
377
377
|
}
|
|
378
378
|
/**
|
|
379
|
+
* The reasoning-effort wire fields for one request, per `dialect.reasoningFormat`.
|
|
380
|
+
*
|
|
381
|
+
* The effort value itself is never translated — decision 1 sends whatever the
|
|
382
|
+
* caller passed, verbatim, as a string. Only the FIELD(S) it travels on change
|
|
383
|
+
* per format; see the `reasoningFormat` doc comment on {@link ChatCompletionsDialect}.
|
|
384
|
+
*/
|
|
385
|
+
function reasoningFieldsOf(format, effort) {
|
|
386
|
+
if (format === false || effort === void 0) return {};
|
|
387
|
+
if (format === "openai") return { reasoning_effort: effort };
|
|
388
|
+
if (effort === "off") return { thinking: { type: "disabled" } };
|
|
389
|
+
return {
|
|
390
|
+
thinking: { type: "enabled" },
|
|
391
|
+
reasoning_effort: effort
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
379
395
|
* Map the neutral output format onto `response_format`, per dialect state.
|
|
380
396
|
*
|
|
381
397
|
* A schema the endpoint cannot honour is an error rather than a downgrade to
|
|
@@ -422,11 +438,11 @@ function serializeChatCompletionsRequest(request, dialect) {
|
|
|
422
438
|
messages,
|
|
423
439
|
stream: true,
|
|
424
440
|
...dialect.streamUsage ? { stream_options: { include_usage: true } } : {},
|
|
425
|
-
...dialect.maxTokensField === false ? {} : { [dialect.maxTokensField]: request.maxTokens },
|
|
441
|
+
...dialect.maxTokensField === false || request.maxTokens === void 0 ? {} : { [dialect.maxTokensField]: request.maxTokens },
|
|
426
442
|
...dialect.sampling && options.temperature !== void 0 ? { temperature: options.temperature } : {},
|
|
427
443
|
...dialect.sampling && options.topP !== void 0 ? { top_p: options.topP } : {},
|
|
428
444
|
...dialect.stop && options.stop !== void 0 && options.stop.length > 0 ? { stop: [...options.stop] } : {},
|
|
429
|
-
...dialect.
|
|
445
|
+
...reasoningFieldsOf(dialect.reasoningFormat, options.reasoningEffort),
|
|
430
446
|
...tools === void 0 ? {} : { tools },
|
|
431
447
|
...tools === void 0 || options.toolChoice === void 0 ? {} : { tool_choice: toolChoiceOf(options.toolChoice) },
|
|
432
448
|
...tools !== void 0 && dialect.parallelToolCalls ? { parallel_tool_calls: true } : {},
|
|
@@ -742,7 +758,7 @@ async function* translateChatCompletionsStream(events, displayName) {
|
|
|
742
758
|
*
|
|
743
759
|
* Conservative in one direction on purpose: a field an endpoint does not
|
|
744
760
|
* understand is usually a hard HTTP 400, while a field left unsent merely
|
|
745
|
-
* forgoes a feature. So `parallelToolCalls`, `seed` and `
|
|
761
|
+
* forgoes a feature. So `parallelToolCalls`, `seed` and `reasoningFormat` — the
|
|
746
762
|
* three fields older gateways most often reject — stay OFF until a provider
|
|
747
763
|
* opts in.
|
|
748
764
|
*
|
|
@@ -760,7 +776,7 @@ const DEFAULT_DIALECT = Object.freeze({
|
|
|
760
776
|
systemRole: "system",
|
|
761
777
|
stop: true,
|
|
762
778
|
seed: false,
|
|
763
|
-
|
|
779
|
+
reasoningFormat: false,
|
|
764
780
|
path: "/chat/completions"
|
|
765
781
|
});
|
|
766
782
|
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/errors.ts","../src/serialize.ts","../src/translate.ts","../src/wire.ts","../src/protocol.ts"],"sourcesContent":["/**\n * Wire failures of Chat Completions, mapped onto the SDK's shared error codes.\n *\n * Two constraints shape this module. First, the code set is NOT new: every code\n * produced here already exists in `@alvin0/ai-agent-sdk-core`\n * ({@link MODEL_ERROR_CODES}, {@link QUOTA_EXCEEDED_CODE},\n * {@link CONTEXT_WINDOW_EXCEEDED_CODE}), because a caller routing on `code`\n * must not have to learn a second vocabulary just because the request went to\n * an OpenAI-compatible endpoint instead of a native one (Requirement 10.6).\n * Second, the mapping is a REPRODUCTION of the shared HTTP-to-taxonomy table\n * rather than an import of it: this package's only dependency is core (DD-10),\n * so it cannot reach into `provider-http`. The table is therefore kept\n * deliberately identical — same status ordering, same wording classifiers, same\n * `detail` construction — and a cross-provider property test pins the two\n * together so a future edit to one that is not made to the other fails CI.\n *\n * `retry-after` and the provider request id are read whenever the endpoint\n * sends them, because they are the two facts a retry decision and a support\n * ticket respectively cannot be reconstructed without (Requirement 13.5).\n *\n * @module ai-agent-sdk/protocols/openai-chat-completions/errors\n */\n\nimport {\n CONTEXT_WINDOW_EXCEEDED_CODE,\n MODEL_ERROR_CODES,\n ModelError,\n ProviderRequestId,\n QUOTA_EXCEEDED_CODE,\n isContextWindowExceededError,\n isQuotaExceededError,\n} from '@alvin0/ai-agent-sdk-core'\nimport type { WireErrorBody } from './wire.ts'\n\n/** Largest slice of an unparseable body kept as classifier input. */\nconst MAX_DETAIL_LENGTH = 2_048\n\n/**\n * Wording that identifies a moderation rejection rather than a bad request.\n *\n * Narrow on purpose. A false positive would tell the caller their prompt was\n * filtered when in fact their JSON schema was wrong, and the two have opposite\n * fixes. Endpoints in this family are consistent about the token\n * `content_filter` / `content_policy`, so nothing looser is needed.\n */\nconst CONTENT_FILTER_WORDING = new RegExp(\n String.raw`\\bcontent[\\s_-]?(?:filter(?:ed|ing)?|policy(?:[\\s_-]?violation)?)\\b`\n + String.raw`|\\bresponsible[\\s_-]?ai[\\s_-]?policy\\b`,\n 'i',\n)\n\n/**\n * Recognize a moderation rejection in provider error text.\n *\n * Classified as `UNSUPPORTED_CONTENT` rather than as a new `CONTENT_FILTERED`\n * code: the existing code already says exactly this — the request carried\n * content the selected model will not accept — and it is already outside the\n * default retryable set, which is the behaviour a filtered prompt needs.\n * @param detail - provider error code/type/message text joined into one string.\n * @returns true when the wording names content filtering or a content policy.\n */\nexport function isContentFilteredError(detail: string): boolean {\n return CONTENT_FILTER_WORDING.test(detail)\n}\n\n/**\n * Map an HTTP status plus the provider's own error text onto a stable code.\n *\n * The status alone is not enough anywhere interesting: a 429 is either \"slow\n * down\" (retry) or \"your balance is gone\" (never retry), and a 400 is either a\n * prompt that overflows the context window (compact and retry), a moderation\n * rejection, or a malformed request. All three distinctions are invisible in\n * the status and each changes what the caller should do next, so the wording\n * classifiers run in a fixed order ahead of the plain status buckets.\n * @param status - status of a non-2xx response.\n * @param detail - provider error code/type/message joined; empty when the body was unusable.\n * @returns the normalized code, or `HTTP_{status}` when nothing classified it.\n */\nexport function chatCompletionsErrorCode(status: number, detail = ''): string {\n if (status === 401 || status === 403) return MODEL_ERROR_CODES.AUTH\n if (status === 413) return MODEL_ERROR_CODES.INVALID_REQUEST\n // Ahead of 429: an exhausted quota is usually delivered as 429 but never\n // clears on its own, so retrying it burns latency and money for nothing.\n if (isQuotaExceededError(detail)) return QUOTA_EXCEEDED_CODE\n if (status === 429) return MODEL_ERROR_CODES.RATE_LIMIT\n if (status === 400 || status === 422) {\n if (isContextWindowExceededError(detail)) return CONTEXT_WINDOW_EXCEEDED_CODE\n // Only inside the request-rejected statuses: the same wording in a 500 body\n // describes what the endpoint was doing, not why it refused the caller.\n if (isContentFilteredError(detail)) return MODEL_ERROR_CODES.UNSUPPORTED_CONTENT\n return MODEL_ERROR_CODES.INVALID_REQUEST\n }\n // A model this endpoint does not serve, or a path this gateway does not\n // mount. Both are the caller's mistake, so neither may land in the retryable\n // SERVER bucket where a 404 would otherwise be retried five times.\n if (status === 404) return MODEL_ERROR_CODES.INVALID_REQUEST\n if (status >= 500) return MODEL_ERROR_CODES.SERVER\n return `HTTP_${status}`\n}\n\n/**\n * Parse a `retry-after` header into milliseconds.\n *\n * Both defined forms appear in practice — delta-seconds and an HTTP date — so\n * both are read. A date already in the past yields `undefined` rather than a\n * negative delay, because a negative delay would be rejected downstream and\n * take the whole diagnostic with it.\n * @param value - the raw header value, or `null` when absent.\n * @returns a positive finite delay in milliseconds, or `undefined` when absent or unusable.\n */\nexport function chatCompletionsRetryAfterMs(value: string | null): number | undefined {\n if (value === null) return undefined\n const trimmed = value.trim()\n if (/^\\d+$/.test(trimmed)) {\n const delay = Number(trimmed) * 1_000\n return Number.isFinite(delay) && delay > 0 ? delay : undefined\n }\n const delay = Date.parse(trimmed) - Date.now()\n return Number.isFinite(delay) && delay > 0 ? delay : undefined\n}\n\n/** Header names this endpoint family uses for a request correlation id, in priority order. */\nconst REQUEST_ID_HEADERS = [\n 'request-id',\n 'x-request-id',\n 'x-requestid',\n 'cf-ray',\n] as const\n\n/**\n * Extract a provider request id for diagnostics.\n *\n * Nothing programmatic reads it, and it is still worth carrying: when an\n * endpoint misbehaves, this id is the only handle its operators have for\n * finding the request.\n * @param headers - the response headers.\n * @returns the first non-empty id found, or `undefined`.\n */\nexport function chatCompletionsRequestId(headers: Headers): ProviderRequestId | undefined {\n for (const name of REQUEST_ID_HEADERS) {\n const value = headers.get(name)\n if (value !== null && value.length > 0) return ProviderRequestId(value)\n }\n return undefined\n}\n\n/** An error body reduced to the two things the mapping needs. */\nexport interface ParsedChatCompletionsError {\n /** Best human-readable message found, or `undefined` to fall back to the status. */\n readonly message: string | undefined\n /** Provider `code`/`type`/`message` joined, as input to the wording classifiers. */\n readonly detail: string\n}\n\n/** Read a string property from an unknown value without trusting its shape. */\nfunction stringField(source: unknown, key: string): string | undefined {\n if (typeof source !== 'object' || source === null) return undefined\n const value = (source as Record<string, unknown>)[key]\n return typeof value === 'string' && value.length > 0 ? value : undefined\n}\n\n/**\n * Reduce an error body to a message and a classifier detail string.\n *\n * Tolerates every shape actually seen on this wire: the canonical\n * `{error: {code, type, message}}`, a bare `{type, message}` with no wrapper, a\n * `{detail: \"...\"}` in the FastAPI style some compatible endpoints use, an\n * `{error: \"...\"}` carrying a plain string, and a body that is not JSON at all\n * — which is what a gateway or load balancer sitting in front of the endpoint\n * returns. In the last case the status stays authoritative and the raw text,\n * truncated, is still the best classifier input available.\n *\n * A bare-string `error` contributes to the MESSAGE only, never to `detail`.\n * That asymmetry is deliberate: `detail` is what decides the code, and it is\n * held byte-identical to the shared provider mapping so the same\n * `(status, body)` pair cannot classify differently here than it does for an\n * existing provider.\n * @param raw - the response body as text.\n * @returns the message and the joined detail.\n */\nexport function parseChatCompletionsErrorBody(raw: string): ParsedChatCompletionsError {\n let parsed: unknown\n try {\n parsed = JSON.parse(raw) as unknown\n } catch {\n return { message: undefined, detail: raw.slice(0, MAX_DETAIL_LENGTH) }\n }\n const error = typeof parsed === 'object' && parsed !== null\n && 'error' in (parsed as Record<string, unknown>)\n ? (parsed as Record<string, unknown>).error\n : parsed\n const code = stringField(error, 'code')\n const type = stringField(error, 'type')\n const message = stringField(error, 'message')\n const detailField = stringField(error, 'detail') ?? stringField(parsed, 'detail')\n const parts = [code, type, message ?? detailField]\n .filter((part): part is string => part !== undefined)\n const bareError = typeof error === 'string' && error.length > 0 ? error : undefined\n return {\n message: message ?? detailField ?? bareError,\n detail: parts.join(' '),\n }\n}\n\n/** Everything known about a non-2xx Chat Completions response. */\nexport interface ChatCompletionsHttpFailure {\n /** Status of the response. */\n readonly status: number\n /** The response body as text; empty when it could not be read. */\n readonly body: string\n /** Response headers, read for `retry-after` and the request id. */\n readonly headers: Headers\n /** Name used in the fallback message, e.g. the provider display name. */\n readonly displayName: string\n /** Request URL, included in the fallback message for diagnosis. */\n readonly url?: string\n}\n\n/**\n * Turn a non-2xx response into a fully populated {@link ModelError}.\n *\n * The provider's own message wins when there is one, because it is invariably\n * more specific than anything this layer could synthesize; the synthesized\n * fallback exists for bodies that carry no message at all. `cause` keeps the\n * raw body so a diagnosis is possible even when the classifier found nothing.\n * @param failure - the status, body, and headers of the failed response.\n * @returns a ModelError carrying the code, status, retry delay, and request id.\n */\nexport function chatCompletionsHttpError(failure: ChatCompletionsHttpFailure): ModelError {\n const { message, detail } = parseChatCompletionsErrorBody(failure.body)\n const delay = chatCompletionsRetryAfterMs(failure.headers.get('retry-after'))\n const id = chatCompletionsRequestId(failure.headers)\n const location = failure.url === undefined ? '' : ` from ${failure.url}`\n return new ModelError(\n message ?? `${failure.displayName} error (HTTP ${failure.status})${location}`,\n chatCompletionsErrorCode(failure.status, detail),\n {\n cause: new Error(failure.body.length > 0 ? failure.body : `HTTP ${failure.status}`),\n status: failure.status,\n ...delay === undefined ? {} : { providerRetryAfterMs: delay },\n ...id === undefined ? {} : { requestId: id },\n },\n )\n}\n\n/**\n * Map an error the endpoint inlined into a 200 stream onto the same taxonomy.\n *\n * Some gateways answer a rejected request with HTTP 200 and an `error` object\n * inside a `data:` frame. There is no status to classify from, so the wording\n * classifiers carry the whole decision, and the residual case is\n * `MALFORMED_RESPONSE`: an error arriving where content was promised is a\n * broken response, not a server fault to be retried.\n * @param body - the `error` payload of a stream chunk.\n * @param displayName - name used when the payload carries no message.\n * @returns a ModelError with no `status`, since the HTTP call itself succeeded.\n */\nexport function chatCompletionsStreamError(\n body: WireErrorBody,\n displayName: string,\n): ModelError {\n const code = typeof body.code === 'string' ? body.code : undefined\n const detail = [code, body.type, body.message]\n .filter((part): part is string => typeof part === 'string' && part.length > 0)\n .join(' ')\n const classified = isQuotaExceededError(detail)\n ? QUOTA_EXCEEDED_CODE\n : isContextWindowExceededError(detail)\n ? CONTEXT_WINDOW_EXCEEDED_CODE\n : isContentFilteredError(detail)\n ? MODEL_ERROR_CODES.UNSUPPORTED_CONTENT\n : MODEL_ERROR_CODES.MALFORMED_RESPONSE\n return new ModelError(\n body.message ?? `${displayName} inlined an error into the stream`,\n classified,\n { cause: new Error(detail.length > 0 ? detail : 'stream error') },\n )\n}\n\n/**\n * Classify a failure raised before any status was seen.\n *\n * Kept apart from the status mapping because the three outcomes are decided by\n * WHO ended the request, not by what the endpoint said: the caller's signal\n * (`ABORTED`), a deadline (`TIMEOUT`), or the network (`TRANSPORT`). Web\n * platform semantics supply the distinction — `AbortSignal.timeout` rejects\n * with a `TimeoutError`, an explicit `abort()` with an `AbortError` — so no\n * message parsing is involved.\n * @param error - the thrown value.\n * @param fallbackMessage - message used when the thrown value carries none.\n * @returns a ModelError coded ABORTED, TIMEOUT, or TRANSPORT.\n */\nexport function chatCompletionsTransportError(\n error: unknown,\n fallbackMessage: string,\n): ModelError {\n const name = error instanceof Error ? error.name : ''\n const code = name === 'TimeoutError'\n ? MODEL_ERROR_CODES.TIMEOUT\n : name === 'AbortError'\n ? MODEL_ERROR_CODES.ABORTED\n : MODEL_ERROR_CODES.TRANSPORT\n const message = error instanceof Error && error.message.length > 0\n ? error.message\n : fallbackMessage\n return new ModelError(message, code, { cause: error })\n}\n","/**\n * Normalized request to Chat Completions wire JSON.\n *\n * Two things make this different from the Responses serializer. First, the wire\n * wants MESSAGES rather than a flat item list, so an assistant turn that spoke\n * and called two tools stays ONE message carrying both `content` and\n * `tool_calls`, while a tool result becomes its own `role: 'tool'` message\n * correlated by `tool_call_id`. Second, every optional field is gated by a\n * {@link ChatCompletionsDialect} flag, and a disabled flag means the key is\n * ABSENT — not `null`, not a default value. Some gateways reject an\n * unknown-but-null key outright, and a default silently changes behaviour\n * nobody asked for.\n *\n * @module ai-agent-sdk/protocols/openai-chat-completions/serialize\n */\n\nimport { MODEL_ERROR_CODES, ModelError, isNativeToolSchema } from '@alvin0/ai-agent-sdk-core'\nimport type {\n ContentBlock,\n ImageBlock,\n Message,\n ModelOutputFormat,\n ModelToolSchema,\n TextBlock,\n ToolChoice,\n ToolSchema,\n} from '@alvin0/ai-agent-sdk-core'\nimport type { ProtocolRequest } from './contract.ts'\nimport type {\n ChatCompletionsDialect,\n WireContentPart,\n WireImageDetail,\n WireMessage,\n WireRequest,\n WireResponseFormat,\n WireTool,\n WireToolCall,\n WireToolChoice,\n} from './wire.ts'\n\n/** Separator used whenever several text blocks collapse into one string. */\nconst TEXT_JOINER = '\\n'\n\nfunction textBlocks(blocks: readonly ContentBlock[]): TextBlock[] {\n return blocks.filter((block): block is TextBlock => block.type === 'text')\n}\n\nfunction joinedText(blocks: readonly ContentBlock[]): string {\n return textBlocks(blocks).map(block => block.text).join(TEXT_JOINER)\n}\n\n/** `original` has no wire spelling here, so it travels as no detail at all. */\nfunction imageDetail(block: ImageBlock): WireImageDetail | undefined {\n const detail = block.detail\n if (detail === 'auto' || detail === 'low' || detail === 'high') return detail\n return undefined\n}\n\n/**\n * Convert one image block to an `image_url` part.\n *\n * A provider-side uploaded file has no representation in this API — the part\n * only accepts a URL — so a `file` source yields nothing rather than a\n * fabricated URL that would 400 at best and fetch the wrong bytes at worst.\n */\nfunction imagePart(block: ImageBlock): WireContentPart | undefined {\n if (block.source.kind === 'file') return undefined\n const url = block.source.kind === 'url'\n ? block.source.url\n : `data:${block.source.mediaType};base64,${block.source.data}`\n const detail = imageDetail(block)\n return { type: 'image_url', image_url: { url, ...detail === undefined ? {} : { detail } } }\n}\n\nfunction contentPart(block: ContentBlock): WireContentPart | undefined {\n if (block.type === 'text') return { type: 'text', text: block.text }\n if (block.type === 'image') return imagePart(block)\n // `document` included: Chat Completions has no document part, and rendering a\n // PDF as text here would drop the page images the caller asked the model to read.\n return undefined\n}\n\nfunction toolCall(block: Extract<ContentBlock, { type: 'tool-call' }>): WireToolCall {\n return {\n id: block.id,\n type: 'function',\n function: {\n name: block.name,\n // VERBATIM. The string is replayed exactly as the model produced it: a\n // `JSON.parse`/`JSON.stringify` round-trip reorders keys and renormalizes\n // numbers, and some models read that exact string as their own context.\n // Only a truly empty value is substituted, because `arguments` must still\n // be valid JSON.\n arguments: block.arguments.length > 0 ? block.arguments : '{}',\n },\n }\n}\n\n/**\n * Expand one message into one or more wire messages, appended in order.\n *\n * The accumulator is FLUSHED before a block that has to become its own\n * top-level message, which is what keeps \"spoke, called a tool, got a result,\n * spoke again\" in the order the model produced it.\n */\nfunction appendMessage(message: Message, messages: WireMessage[]): void {\n const role = message.role === 'assistant' ? 'assistant' : 'user'\n let parts: WireContentPart[] = []\n let calls: WireToolCall[] = []\n\n const flush = (): void => {\n if (parts.length === 0 && calls.length === 0) return\n if (role === 'assistant') {\n const text = parts\n .flatMap(part => part.type === 'text' ? [part.text] : [])\n .join(TEXT_JOINER)\n messages.push({\n role: 'assistant',\n // Absent on a turn that only called tools; an empty string there reads\n // to the model as \"I said nothing out loud\", which is a different claim.\n ...text.length === 0 ? {} : { content: text },\n ...calls.length === 0 ? {} : { tool_calls: calls },\n })\n } else if (parts.length > 0) {\n const onlyText = parts.every(part => part.type === 'text')\n messages.push({\n role: 'user',\n content: onlyText\n ? parts.flatMap(part => part.type === 'text' ? [part.text] : []).join(TEXT_JOINER)\n : parts,\n })\n }\n parts = []\n calls = []\n }\n\n for (const block of message.content) {\n switch (block.type) {\n case 'text':\n case 'image':\n case 'document': {\n const part = contentPart(block)\n if (part !== undefined) parts.push(part)\n break\n }\n case 'tool-call': {\n calls.push(toolCall(block))\n break\n }\n case 'tool-result': {\n flush()\n messages.push({\n role: 'tool',\n tool_call_id: block.toolCallId,\n // The wire accepts a string only; non-text result blocks have no slot.\n content: joinedText(block.content),\n })\n break\n }\n default:\n // `reasoning`, `native-tool-call`, and any block added by declaration\n // merging. Skipping is correct: this API has no field to replay them\n // into, and inventing one would corrupt the turn.\n break\n }\n }\n flush()\n}\n\n/** The system prompt, plus any system-role messages, in order. */\nfunction systemTextOf(request: ProtocolRequest): string {\n const fromMessages = request.options.messages\n .filter(message => message.role === 'system')\n .map(message => joinedText(message.content))\n .filter(text => text.length > 0)\n const all = request.options.system === undefined\n ? fromMessages\n : [request.options.system, ...fromMessages]\n return all.join('\\n\\n')\n}\n\n/** Map a tool schema; `strict` is left off because caller schemas are not vetted. */\nfunction functionTool(tool: ToolSchema): WireTool {\n return {\n type: 'function',\n function: {\n name: tool.name,\n description: tool.description,\n parameters: { ...tool.parameters },\n },\n }\n}\n\nfunction toolOf(tool: ModelToolSchema): WireTool {\n if (!isNativeToolSchema(tool)) return functionTool(tool)\n // Chat Completions runs no tools of its own. Dropping the request silently\n // would hand back an answer produced without the search the caller required.\n throw new ModelError(\n `Chat Completions does not support the provider-native tool \"${tool.name}\"`,\n MODEL_ERROR_CODES.INVALID_REQUEST,\n )\n}\n\nfunction toolChoiceOf(choice: ToolChoice): WireToolChoice {\n if (typeof choice === 'string') return choice\n if (choice.type === 'native') {\n throw new ModelError(\n `Chat Completions cannot be forced to call the provider-native tool \"${choice.name}\"`,\n MODEL_ERROR_CODES.INVALID_REQUEST,\n )\n }\n return { type: 'function', function: { name: choice.name } }\n}\n\n/**\n * Map the neutral output format onto `response_format`, per dialect state.\n *\n * A schema the endpoint cannot honour is an error rather than a downgrade to\n * free text: the caller is about to `JSON.parse` the answer.\n */\nfunction responseFormatOf(\n format: ModelOutputFormat | undefined,\n structuredOutputs: ChatCompletionsDialect['structuredOutputs'],\n): WireResponseFormat | undefined {\n if (format === undefined || structuredOutputs === false) {\n if (format !== undefined && format.type === 'json_schema' && structuredOutputs === false) {\n throw new ModelError(\n 'This Chat Completions endpoint does not support structured output',\n MODEL_ERROR_CODES.INVALID_REQUEST,\n )\n }\n return undefined\n }\n if (format.type === 'text') return { type: 'text' }\n // `json-object`: JSON mode without a schema. The endpoint guarantees valid\n // JSON but not this shape, which is still strictly better than free text.\n if (structuredOutputs === 'json-object') return { type: 'json_object' }\n return {\n type: 'json_schema',\n json_schema: { name: format.name, schema: format.schema, strict: true },\n }\n}\n\n/**\n * Build the Chat Completions request body.\n * @param request - the resolved request, model, and output cap.\n * @param dialect - which optional fields this endpoint accepts.\n * @returns the wire body, ready to serialize.\n */\nexport function serializeChatCompletionsRequest(\n request: ProtocolRequest,\n dialect: ChatCompletionsDialect,\n): WireRequest {\n const { options } = request\n const messages: WireMessage[] = []\n\n const system = systemTextOf(request)\n // The system prompt leads the array under whichever role this endpoint reads.\n if (system.length > 0) messages.push({ role: dialect.systemRole, content: system })\n\n for (const message of options.messages) {\n if (message.role === 'system') continue // already folded in above\n appendMessage(message, messages)\n }\n\n const tools = dialect.tools && options.tools !== undefined && options.tools.length > 0\n ? options.tools.map(toolOf)\n : undefined\n const responseFormat = responseFormatOf(options.outputFormat, dialect.structuredOutputs)\n\n return {\n model: options.model,\n messages,\n stream: true,\n ...dialect.streamUsage ? { stream_options: { include_usage: true } } : {},\n ...dialect.maxTokensField === false ? {} : { [dialect.maxTokensField]: request.maxTokens },\n ...dialect.sampling && options.temperature !== undefined\n ? { temperature: options.temperature }\n : {},\n ...dialect.sampling && options.topP !== undefined ? { top_p: options.topP } : {},\n // `frequency_penalty` and `presence_penalty` have no source in\n // `GenerateOptions`, so they are never sent. Same for `seed` and `user`:\n // `dialect.seed` exists so a provider that grows a source for it does not\n // have to re-thread the flag through the dialect first.\n ...dialect.stop && options.stop !== undefined && options.stop.length > 0\n ? { stop: [...options.stop] }\n : {},\n ...dialect.reasoningEffort && options.reasoningEffort !== undefined\n ? { reasoning_effort: String(options.reasoningEffort) }\n : {},\n ...tools === undefined ? {} : { tools },\n ...tools === undefined || options.toolChoice === undefined\n ? {}\n : { tool_choice: toolChoiceOf(options.toolChoice) },\n // Only meaningful alongside `tools`, and older gateways reject the key\n // outright, which is why the flag defaults off.\n ...tools !== undefined && dialect.parallelToolCalls ? { parallel_tool_calls: true } : {},\n ...responseFormat === undefined ? {} : { response_format: responseFormat },\n ...dialect.promptCacheKey === undefined\n ? {}\n : { prompt_cache_key: dialect.promptCacheKey },\n }\n}\n","/**\n * Chat Completions SSE events to the SDK's chunk protocol.\n *\n * Four rules drive everything below, and each one exists because the obvious\n * implementation is wrong.\n *\n * First, the tool-call accumulator is keyed on `index`, never on `id`. `id` and\n * `function.name` arrive ONCE, on the first fragment of that call, while\n * `function.arguments` arrives in many fragments that carry only `index`. So\n * `index` is the only correlation key present on every fragment.\n *\n * Second, `arguments` fragments are CONCATENATED as strings and never parsed\n * per fragment. A fragment can split in the middle of a JSON escape sequence or\n * in the middle of a multi-byte character, so an early parse fails on traffic\n * that is perfectly valid once joined.\n *\n * Third, a tool call is emitted only once `finish_reason === 'tool_calls'` has\n * arrived, and the accumulated `arguments` is parsed at exactly that moment. A\n * parse failure there is a protocol error, not an empty tool call: handing a\n * caller `{}` invents an argument list the model never produced.\n *\n * Fourth, `[DONE]` is NOT the terminal finish. Terminal finish is a chunk\n * carrying a `finish_reason` other than `null`. A stream that runs out of\n * events, or reaches `[DONE]`, or is cut mid-way without one is a failure —\n * because a truncated stream is byte-for-byte indistinguishable from a short\n * answer, and nothing above this layer can tell them apart if the translator\n * stays quiet.\n *\n * @module ai-agent-sdk/protocols/openai-chat-completions/translate\n */\n\nimport { MODEL_ERROR_CODES, ModelError, ToolCallId } from '@alvin0/ai-agent-sdk-core'\nimport type { ContentBlock, FinishReason, UsageCounters } from '@alvin0/ai-agent-sdk-core'\nimport type { ProtocolSseEvent, ProtocolStreamChunk } from './contract.ts'\nimport type {\n WireErrorBody,\n WireFinishReason,\n WireStreamChoice,\n WireStreamChunk,\n WireToolCallDelta,\n WireUsage,\n} from './wire.ts'\n\n/** The `data:` payload that ends the body; not a finish, and not a chunk. */\nconst DONE_SENTINEL = '[DONE]'\n\n/** Substituted when a zero-parameter tool sends no `arguments` at all. */\nconst EMPTY_ARGUMENTS = '{}'\n\n/** One in-flight tool call, correlated by the wire's `index`. */\ninterface OpenToolCall {\n /** Our own block index, assigned in first-seen order at emit time. */\n readonly order: number\n id: string | undefined\n name: string | undefined\n /** Fragments joined verbatim. Never parsed until the terminal finish. */\n args: string\n}\n\n/** One in-flight text or reasoning block. */\ninterface OpenTextBlock {\n readonly index: number\n text: string\n}\n\nfunction malformed(displayName: string, detail: string, cause?: unknown): ModelError {\n return new ModelError(\n `${displayName} sent a malformed stream event: ${detail}`,\n MODEL_ERROR_CODES.MALFORMED_RESPONSE,\n cause === undefined ? {} : { cause },\n )\n}\n\nfunction recordOrUndefined(value: unknown): Record<string, unknown> | undefined {\n return typeof value === 'object' && value !== null ? value as Record<string, unknown> : undefined\n}\n\n/**\n * Normalize usage, honouring the SDK's disjoint-count convention.\n *\n * This API reports `prompt_tokens` as the TOTAL input and\n * `prompt_tokens_details.cached_tokens` as a SUBSET of it, while the SDK's three\n * input figures are disjoint and sum to what is billed. So the cached portion is\n * subtracted back out here; skip that and every cost estimate double-counts\n * cache hits.\n *\n * Otherwise the counters travel RAW: malformed values are passed through rather\n * than dropped, and nothing here decides whether the report is complete. That\n * judgement belongs to the accounting boundary above, which is the only layer\n * that knows what the caller asked for.\n */\nfunction mapUsage(usage: WireUsage): UsageCounters | undefined {\n const source = usage as unknown as Record<string, unknown>\n const promptDetails = recordOrUndefined(source.prompt_tokens_details)\n const completionDetails = recordOrUndefined(source.completion_tokens_details)\n const promptTokens = source.prompt_tokens\n const outputTokens = source.completion_tokens\n const totalTokens = source.total_tokens\n const cacheRead = promptDetails?.cached_tokens\n const reasoning = completionDetails?.reasoning_tokens\n\n const present = [promptTokens, outputTokens, totalTokens, cacheRead, reasoning]\n .some(value => value !== undefined)\n if (!present) return undefined\n\n const counters: Record<string, unknown> = {\n ...outputTokens === undefined ? {} : { outputTokens },\n ...totalTokens === undefined ? {} : { totalTokens },\n // An omitted or zero cache figure is authoritative zero for this API; a\n // present-but-invalid one is retained for the accounting validator.\n ...cacheRead === undefined || cacheRead === 0 ? {} : { cacheReadTokens: cacheRead },\n ...reasoning === undefined || reasoning === 0 ? {} : { reasoningTokens: reasoning },\n }\n if (promptTokens !== undefined) {\n counters.inputTokens = typeof promptTokens === 'number'\n && (cacheRead === undefined || typeof cacheRead === 'number')\n ? promptTokens - (cacheRead ?? 0)\n : promptTokens\n }\n return counters as UsageCounters\n}\n\n/**\n * Map this API's finish reason onto ours.\n *\n * `content_filter` becomes a terminal `error` finish rather than a thrown\n * exception, so whatever text arrived before the filter tripped still reaches\n * the caller. `function_call` is the pre-`tools` spelling of `tool_calls` and\n * means the same thing.\n */\nfunction finishReasonOf(reason: WireFinishReason): FinishReason {\n switch (reason) {\n case 'tool_calls':\n case 'function_call':\n return { kind: 'tool-calls' }\n case 'length':\n return { kind: 'max-tokens' }\n case 'content_filter':\n return {\n kind: 'error',\n failure: {\n message: 'the endpoint filtered this response',\n code: MODEL_ERROR_CODES.INVALID_REQUEST,\n },\n }\n default:\n return { kind: 'stop' }\n }\n}\n\n/** Whether this finish reason is the one that releases accumulated tool calls. */\nfunction releasesToolCalls(reason: WireFinishReason): boolean {\n return reason === 'tool_calls' || reason === 'function_call'\n}\n\n/** Turn an inline stream error into a typed failure. */\nfunction inlineError(error: WireErrorBody, displayName: string): ModelError {\n const message = error.message ?? `${displayName} reported an error mid-stream`\n const code = error.code ?? error.type\n // An unrecognized inline error defaults to SERVER, which IS retryable: the\n // turn produced nothing usable, so repeating it is safe and often works.\n return new ModelError(\n code === undefined ? message : `${message} (${String(code)})`,\n MODEL_ERROR_CODES.SERVER,\n )\n}\n\n/** Fold one tool-call fragment into the accumulator. */\nfunction absorbToolCall(\n open: Map<number, OpenToolCall>,\n fragment: WireToolCallDelta,\n nextOrder: () => number,\n): void {\n const key = typeof fragment.index === 'number' ? fragment.index : 0\n let entry = open.get(key)\n if (entry === undefined) {\n entry = { order: nextOrder(), id: undefined, name: undefined, args: '' }\n open.set(key, entry)\n }\n // `id` and `name` arrive once. A later fragment repeating them is harmless;\n // a later fragment CLEARING them would not be, so only truthy values land.\n if (typeof fragment.id === 'string' && fragment.id.length > 0) entry.id = fragment.id\n const name = fragment.function?.name\n if (typeof name === 'string' && name.length > 0) entry.name = name\n const args = fragment.function?.arguments\n // Concatenation only. The joined string is the model's own bytes, replayed\n // verbatim on the next turn, so no reformatting happens anywhere on this path.\n if (typeof args === 'string') entry.args += args\n}\n\n/**\n * Build the authoritative tool-call blocks, parsing `arguments` right here.\n *\n * This is the single moment the accumulated string is allowed to be parsed, and\n * a failure is a protocol error. The alternative — emitting the call with empty\n * arguments — would hand the agent loop a call the model never made.\n */\nfunction toolCallBlocks(\n open: Map<number, OpenToolCall>,\n displayName: string,\n): {\n index: number\n id: string\n name: string\n arguments: string\n block: ContentBlock\n}[] {\n return [...open.entries()]\n .sort(([left], [right]) => left - right)\n .map(([wireIndex, entry]) => {\n if (entry.id === undefined || entry.name === undefined) {\n throw malformed(\n displayName,\n `tool call at index ${wireIndex} never carried an id and a name`,\n )\n }\n const args = entry.args.length > 0 ? entry.args : EMPTY_ARGUMENTS\n try {\n JSON.parse(args)\n } catch (error: unknown) {\n throw malformed(\n displayName,\n `tool call \"${entry.name}\" produced arguments that are not valid JSON`,\n error,\n )\n }\n return {\n index: entry.order,\n id: entry.id,\n name: entry.name,\n arguments: args,\n block: {\n type: 'tool-call',\n id: ToolCallId(entry.id),\n name: entry.name,\n arguments: args,\n } satisfies ContentBlock,\n }\n })\n}\n\n/**\n * Translate one Chat Completions SSE stream.\n *\n * Owns termination, and refuses to invent one: the stream is complete only when\n * a `finish_reason` other than `null` has been seen. Events after that point are\n * still read, because the `usage` chunk arrives there — with `choices: []`,\n * which is normal traffic rather than a malformed event.\n * @param events - decoded SSE events.\n * @param displayName - provider name, used in diagnostics.\n * @returns the chunk stream.\n */\nexport async function* translateChatCompletionsStream(\n events: AsyncIterable<ProtocolSseEvent>,\n displayName: string,\n): AsyncGenerator<ProtocolStreamChunk> {\n const toolCalls = new Map<number, OpenToolCall>()\n let nextIndex = 0\n const nextOrder = (): number => nextIndex++\n let text: OpenTextBlock | undefined\n let reasoning: OpenTextBlock | undefined\n let followedChoice: number | undefined\n let finish: WireFinishReason | undefined\n let usage: UsageCounters | undefined\n\n for await (const raw of events) {\n const payload = raw.data.trim()\n if (payload.length === 0) continue\n // Read past the sentinel rather than terminating on it. It says the BODY\n // ended, which is a different claim from \"the response completed\".\n if (payload === DONE_SENTINEL) continue\n\n let chunk: WireStreamChunk\n try {\n chunk = JSON.parse(payload) as WireStreamChunk\n } catch (error: unknown) {\n throw malformed(displayName, 'the event data is not JSON', error)\n }\n if (recordOrUndefined(chunk) === undefined) {\n throw malformed(displayName, 'the event data is not an object')\n }\n\n if (chunk.error !== undefined && chunk.error !== null) {\n throw inlineError(chunk.error, displayName)\n }\n\n // Last non-null report wins; the usage-bearing chunk normally arrives after\n // the terminal finish, and earlier chunks carry an explicit `null`.\n if (chunk.usage !== undefined && chunk.usage !== null) {\n usage = mapUsage(chunk.usage) ?? usage\n }\n\n const choices: readonly WireStreamChoice[] = Array.isArray(chunk.choices) ? chunk.choices : []\n for (const choice of choices) {\n if (recordOrUndefined(choice) === undefined) continue\n const index = typeof choice.index === 'number' ? choice.index : 0\n // One choice is followed for the whole stream — the first one seen. The\n // SDK never asks for more than one, and interleaving two into a single\n // block stream would silently splice two different answers together.\n followedChoice ??= index\n if (index !== followedChoice) continue\n // Everything after the terminal finish is read for `usage` only; a late\n // delta cannot be appended to a block that has already been closed.\n if (finish !== undefined) continue\n\n const delta = recordOrUndefined(choice.delta) === undefined ? undefined : choice.delta\n\n const reasoningText = delta?.reasoning_content\n if (typeof reasoningText === 'string' && reasoningText.length > 0) {\n if (reasoning === undefined) {\n reasoning = { index: nextOrder(), text: '' }\n yield { type: 'block-start', index: reasoning.index, blockType: 'reasoning' }\n }\n reasoning.text += reasoningText\n yield { type: 'reasoning-delta', index: reasoning.index, text: reasoningText }\n }\n\n // `refusal` is model-authored prose explaining why it declined, so it\n // belongs in the text block; the finish reason is what marks it a refusal.\n const content = typeof delta?.content === 'string' && delta.content.length > 0\n ? delta.content\n : typeof delta?.refusal === 'string' && delta.refusal.length > 0\n ? delta.refusal\n : undefined\n if (content !== undefined) {\n if (text === undefined) {\n text = { index: nextOrder(), text: '' }\n yield { type: 'block-start', index: text.index, blockType: 'text' }\n }\n text.text += content\n yield { type: 'text-delta', index: text.index, text: content }\n }\n\n if (Array.isArray(delta?.tool_calls)) {\n for (const fragment of delta.tool_calls) {\n if (recordOrUndefined(fragment) === undefined) continue\n absorbToolCall(toolCalls, fragment, nextOrder)\n }\n }\n\n const reason = choice.finish_reason\n if (reason === undefined || reason === null) continue\n\n // Terminal finish. Close what is open, in first-seen order, then keep\n // draining the stream so the trailing `usage` chunk is still read.\n finish = reason\n if (reasoning !== undefined) {\n yield {\n type: 'block-end',\n index: reasoning.index,\n block: { type: 'reasoning', text: reasoning.text },\n }\n }\n if (text !== undefined) {\n yield { type: 'block-end', index: text.index, block: { type: 'text', text: text.text } }\n }\n if (releasesToolCalls(reason)) {\n for (const call of toolCallBlocks(toolCalls, displayName)) {\n yield { type: 'block-start', index: call.index, blockType: 'tool-call' }\n yield {\n type: 'tool-call-delta',\n index: call.index,\n id: ToolCallId(call.id),\n name: call.name,\n argumentsDelta: call.arguments,\n }\n yield { type: 'block-end', index: call.index, block: call.block }\n }\n }\n // Any other finish reason with fragments still in the accumulator means\n // the call never completed — `length` truncated it, `stop` contradicts it\n // — and an incomplete tool call must not escape this function.\n toolCalls.clear()\n }\n }\n\n if (finish === undefined) {\n throw new ModelError(\n `${displayName} stream ended without a finish reason`,\n MODEL_ERROR_CODES.STREAM_CLOSED,\n )\n }\n\n if (usage !== undefined) yield { type: 'usage', usage }\n yield { type: 'finish', reason: finishReasonOf(finish) }\n}\n","/**\n * The OpenAI Chat Completions wire shapes and the dialect describing how one\n * endpoint differs from another.\n *\n * \"OpenAI-compatible\" is a family, not a single API: gateways, Azure\n * deployments, editor-subscription endpoints, and self-hosted proxies all speak\n * this protocol with small, well-known divergences — which field caps the\n * output length, whether\n * `parallel_tool_calls` is accepted at all, whether the system prompt travels\n * as `system` or as `developer`. Those divergences are expressed here as DATA\n * ({@link ChatCompletionsDialect}) so the serializer and the translator stay\n * single-implementation.\n *\n * These types never leave this folder except through `ChatCompletionsDialect`.\n *\n * @module ai-agent-sdk/protocols/openai-chat-completions/wire\n */\n\n// ---------------------------------------------------------------------------\n// Request\n// ---------------------------------------------------------------------------\n\n/** Detail level requested for an input image. */\nexport type WireImageDetail = 'auto' | 'low' | 'high'\n\n/** One part of a multimodal message's content. */\nexport type WireContentPart =\n | { type: 'text'; text: string }\n | { type: 'image_url'; image_url: { url: string; detail?: WireImageDetail } }\n\n/**\n * A tool call as the assistant turn carries it.\n *\n * `arguments` is a JSON-encoded STRING, not an object. When replaying a prior\n * turn the string is sent back BYTE-FOR-BYTE as received: a\n * `JSON.parse`/`JSON.stringify` round-trip reorders keys and renormalizes\n * numbers, and some models use that exact string as context.\n */\nexport interface WireToolCall {\n id: string\n type: 'function'\n function: {\n name: string\n /** JSON-encoded STRING, verbatim. */\n arguments: string\n }\n}\n\n/**\n * One entry of the `messages` array.\n *\n * Note the shape of the conversation: unlike the Responses API's flat item\n * list, this is a list of MESSAGES. A tool result is its own message with\n * `role: 'tool'` correlated by `tool_call_id`, and an assistant turn that spoke\n * and called two tools stays a single message carrying both `content` and\n * `tool_calls`.\n */\nexport type WireMessage =\n | {\n /** `developer` is the newer spelling; which one to use is a dialect knob. */\n role: 'system' | 'developer'\n content: string\n }\n | {\n role: 'user'\n content: string | WireContentPart[]\n }\n | {\n role: 'assistant'\n /** Absent or null on a turn that only called tools. */\n content?: string | null\n tool_calls?: WireToolCall[]\n }\n | {\n role: 'tool'\n /** Correlates with the `id` of the assistant's tool call. */\n tool_call_id: string\n content: string\n }\n\n/** A function tool, nested under a `function` key rather than flat. */\nexport interface WireFunctionTool {\n type: 'function'\n function: {\n name: string\n description?: string\n parameters?: Record<string, unknown>\n strict?: boolean\n }\n}\n\nexport type WireTool = WireFunctionTool\n\n/** How the model must choose among the offered tools. */\nexport type WireToolChoice =\n | 'auto'\n | 'none'\n | 'required'\n | { type: 'function'; function: { name: string } }\n\n/** Output format controls. */\nexport type WireResponseFormat =\n | { type: 'text' }\n | { type: 'json_object' }\n | {\n type: 'json_schema'\n json_schema: {\n name: string\n schema: Readonly<Record<string, unknown>>\n strict: true\n }\n }\n\n/** Streaming extras. */\nexport interface WireStreamOptions {\n include_usage?: boolean\n}\n\n/**\n * The request body.\n *\n * Every optional field here is gated by a {@link ChatCompletionsDialect} flag,\n * and a disabled flag means the key is ABSENT — never `null`, never a default\n * value. Some gateways reject unknown-but-null keys outright, and a default\n * value silently changes behaviour the caller never asked for.\n */\nexport interface WireRequest {\n model: string\n messages: WireMessage[]\n stream: boolean\n stream_options?: WireStreamOptions\n /** Present under whichever name `dialect.maxTokensField` names. */\n max_tokens?: number\n /** The reasoning-model spelling of the same cap. */\n max_completion_tokens?: number\n temperature?: number\n top_p?: number\n frequency_penalty?: number\n presence_penalty?: number\n stop?: string | string[]\n seed?: number\n reasoning_effort?: string\n tools?: WireTool[]\n tool_choice?: WireToolChoice\n parallel_tool_calls?: boolean\n response_format?: WireResponseFormat\n /** Stable key letting the endpoint reuse a cached prompt prefix. */\n prompt_cache_key?: string\n user?: string\n}\n\n// ---------------------------------------------------------------------------\n// Response and streaming\n// ---------------------------------------------------------------------------\n\n/** Cached-token breakdown of the prompt count. */\nexport interface WirePromptTokensDetails {\n /** Portion of `prompt_tokens` served from cache — a SUBSET, not an addition. */\n cached_tokens?: number\n}\n\n/** Reasoning breakdown of the completion count. */\nexport interface WireCompletionTokensDetails {\n reasoning_tokens?: number\n}\n\n/** Token accounting as Chat Completions reports it. */\nexport interface WireUsage {\n prompt_tokens?: number\n prompt_tokens_details?: WirePromptTokensDetails | null\n completion_tokens?: number\n completion_tokens_details?: WireCompletionTokensDetails | null\n total_tokens?: number\n}\n\n/**\n * Why a choice stopped.\n *\n * A value other than `null` is the TERMINAL FINISH of the stream. `[DONE]` is\n * not: a truncated stream ends without ever producing one of these, and it is\n * indistinguishable from a short answer unless the translator insists on\n * seeing it.\n */\nexport type WireFinishReason =\n | 'stop'\n | 'length'\n | 'tool_calls'\n | 'content_filter'\n | 'function_call'\n\n/**\n * A tool-call fragment inside a streaming delta.\n *\n * `index` is the correlation key, NOT `id`: `id` and `function.name` arrive\n * once, usually on the first fragment, while `function.arguments` arrives in\n * many fragments that carry only `index`.\n */\nexport interface WireToolCallDelta {\n index: number\n id?: string\n type?: 'function'\n function?: {\n name?: string\n /** One fragment of the JSON string. Concatenate; do not parse. */\n arguments?: string\n }\n}\n\n/** The incremental payload of one streamed choice. */\nexport interface WireChoiceDelta {\n role?: 'assistant'\n content?: string | null\n refusal?: string | null\n /** Non-standard but widely emitted by reasoning-capable endpoints. */\n reasoning_content?: string | null\n tool_calls?: WireToolCallDelta[]\n}\n\n/** One streamed choice. */\nexport interface WireStreamChoice {\n index?: number\n delta?: WireChoiceDelta\n finish_reason?: WireFinishReason | null\n}\n\n/**\n * One decoded `data:` payload of the stream.\n *\n * The usage-bearing final chunk has `choices: []`, so an empty `choices` array\n * is normal traffic rather than a malformed event.\n */\nexport interface WireStreamChunk {\n id?: string\n object?: string\n created?: number\n model?: string\n choices?: WireStreamChoice[]\n usage?: WireUsage | null\n /** Some gateways inline an error into the stream instead of failing the HTTP call. */\n error?: WireErrorBody | null\n}\n\n/** One choice of a non-streamed response. */\nexport interface WireChoice {\n index?: number\n message?: {\n role?: string\n content?: string | null\n refusal?: string | null\n tool_calls?: WireToolCall[]\n }\n finish_reason?: WireFinishReason | null\n}\n\n/** A non-streamed response body. */\nexport interface WireResponse {\n id?: string\n object?: string\n created?: number\n model?: string\n choices?: WireChoice[]\n usage?: WireUsage | null\n error?: WireErrorBody | null\n}\n\n/** The error payload of an HTTP error body, and of inline stream errors. */\nexport interface WireErrorBody {\n type?: string\n code?: string | number\n message?: string\n param?: string | null\n}\n\n/** An HTTP error response body, which nests the payload under `error`. */\nexport interface WireErrorResponse {\n error?: WireErrorBody | string | null\n}\n\n// ---------------------------------------------------------------------------\n// Dialect\n// ---------------------------------------------------------------------------\n\n/**\n * The set of differences between endpoints that speak Chat Completions.\n *\n * Expressed as data rather than as subclasses because the differences are all\n * \"send this field, under this name, or not at all\" — behaviour is identical.\n * Every flag maps one-to-one onto the presence of a wire field, and a disabled\n * flag means the field is absent from the body entirely.\n */\nexport interface ChatCompletionsDialect {\n /** Send `temperature`, `top_p`, `frequency_penalty`, `presence_penalty`. */\n readonly sampling: boolean\n /**\n * Name of the output-length field; `false` sends no field at all.\n *\n * A three-value enum rather than a boolean because this is exactly where\n * OpenAI-compatible endpoints split into two families: newer reasoning models\n * reject `max_tokens` and require `max_completion_tokens`, while older\n * gateways only understand `max_tokens`. A boolean would force each provider\n * to fork the translator; an enum keeps one.\n */\n readonly maxTokensField: 'max_tokens' | 'max_completion_tokens' | false\n /**\n * `response_format` support, three-state.\n *\n * `'json-schema'` sends the full schema with `strict: true`,\n * `'json-object'` sends only `{ type: 'json_object' }` for endpoints that\n * accept JSON mode but not schemas, and `false` sends nothing.\n */\n readonly structuredOutputs: 'json-schema' | 'json-object' | false\n /** Send `tools` + `tool_choice`. */\n readonly tools: boolean\n /** Send `parallel_tool_calls`. */\n readonly parallelToolCalls: boolean\n /** Send `stream_options: { include_usage: true }`. */\n readonly streamUsage: boolean\n /** Role the system prompt travels under in `messages[0]`. */\n readonly systemRole: 'system' | 'developer'\n /** Send `stop`. */\n readonly stop: boolean\n /** Send `seed`. */\n readonly seed: boolean\n /** Send `reasoning_effort`. */\n readonly reasoningEffort: boolean\n /** Prompt-cache key, sent when the endpoint accepts one. */\n readonly promptCacheKey?: string\n /**\n * Endpoint path appended to the base URL.\n *\n * Configurable because a gateway is free to mount the endpoint elsewhere, and\n * hard-coding the path would make such a gateway unreachable without forking\n * the protocol.\n */\n readonly path: string\n}\n\n/**\n * Conservative defaults.\n *\n * Conservative in one direction on purpose: a field an endpoint does not\n * understand is usually a hard HTTP 400, while a field left unsent merely\n * forgoes a feature. So `parallelToolCalls`, `seed` and `reasoningEffort` — the\n * three fields older gateways most often reject — stay OFF until a provider\n * opts in.\n *\n * Frozen because `defaultDialect` is snapshotted by the runtime and is shared\n * across every adapter built on this protocol; a mutable default is a\n * cross-provider side channel.\n */\nexport const DEFAULT_DIALECT: ChatCompletionsDialect = Object.freeze({\n sampling: true,\n maxTokensField: 'max_tokens',\n structuredOutputs: 'json-schema',\n tools: true,\n parallelToolCalls: false,\n streamUsage: true,\n systemRole: 'system',\n stop: true,\n seed: false,\n reasoningEffort: false,\n path: '/chat/completions',\n} as const satisfies ChatCompletionsDialect)\n","/**\n * The OpenAI Chat Completions protocol, as a reusable wire protocol.\n *\n * Spoken by `api.openai.com`, by Azure OpenAI deployments, by editor-subscription\n * gateways, and by most self-hosted proxies. None of them needs its own\n * translation code — they differ only in the dialect knobs, which travel as data\n * ({@link ChatCompletionsDialect}) rather than as forks of the serializer.\n *\n * Nothing here names a particular endpoint. Base URL, headers and dialect all\n * arrive as parameters, which is what keeps this package reusable by any\n * OpenAI-compatible provider.\n *\n * @module ai-agent-sdk/protocols/openai-chat-completions/protocol\n */\n\nimport type {\n ProtocolDefinition,\n ProtocolRequest,\n ProtocolSseEvent,\n ProtocolStreamChunk,\n} from './contract.ts'\nimport type { ModelTarget, ResolvedModelInfo } from '@alvin0/ai-agent-sdk-core/provider'\nimport { serializeChatCompletionsRequest } from './serialize.ts'\nimport { translateChatCompletionsStream } from './translate.ts'\nimport { DEFAULT_DIALECT, type ChatCompletionsDialect } from './wire.ts'\n\n/** Protocol id, usable as a stable string in configuration. */\nexport const OPENAI_CHAT_COMPLETIONS_PROTOCOL_ID = 'openai-chat-completions'\n\ninterface RuntimeProtocolRequest extends ProtocolRequest {\n readonly model: ResolvedModelInfo\n readonly connection: {\n readonly baseUrl: string\n readonly headers: Readonly<Record<string, string>>\n }\n}\n\n/** Marker-based runtime view, kept structurally independent from provider-http. */\nexport interface ChatCompletionsProtocolDefinition {\n readonly kind: 'http-wire-protocol'\n readonly apiVersion: 1\n readonly id: string\n readonly defaultDialect: ChatCompletionsDialect\n readonly exampleModel?: ModelTarget\n readonly endpointPath: (\n request: RuntimeProtocolRequest,\n dialect: ChatCompletionsDialect,\n ) => string\n readonly protocolHeaders?: (\n dialect: ChatCompletionsDialect,\n ) => Readonly<Record<string, string>>\n readonly serialize: (\n request: RuntimeProtocolRequest,\n dialect: ChatCompletionsDialect,\n ) => Readonly<Record<string, unknown>>\n readonly translate: (\n events: AsyncIterable<ProtocolSseEvent>,\n request: RuntimeProtocolRequest,\n displayName: string,\n ) => AsyncGenerator<ProtocolStreamChunk>\n}\n\n/** The OpenAI Chat Completions wire protocol. */\nexport const openAiChatCompletionsProtocol: ProtocolDefinition<ChatCompletionsDialect>\n & ChatCompletionsProtocolDefinition = Object.freeze({\n kind: 'http-wire-protocol' as const,\n apiVersion: 1 as const,\n id: OPENAI_CHAT_COMPLETIONS_PROTOCOL_ID,\n // Flat and finite, so the runtime's config snapshot survives it unchanged.\n defaultDialect: DEFAULT_DIALECT,\n // The path is a dialect knob: a gateway is free to mount the endpoint\n // elsewhere, and hard-coding it would make such a gateway unreachable.\n endpointPath: (_request: ProtocolRequest, dialect: ChatCompletionsDialect): string =>\n dialect.path,\n serialize(\n request: ProtocolRequest,\n dialect: ChatCompletionsDialect,\n ): Readonly<Record<string, unknown>> {\n return serializeChatCompletionsRequest(request, dialect) as unknown as Readonly<\n Record<string, unknown>\n >\n },\n // Params are annotated because `Object.freeze` erases the contextual typing the\n // `ProtocolDefinition` annotation would otherwise supply.\n translate: (\n events: AsyncIterable<ProtocolSseEvent>,\n _request: ProtocolRequest,\n displayName: string,\n ): AsyncGenerator<ProtocolStreamChunk> => translateChatCompletionsStream(events, displayName),\n})\n\nexport type { ChatCompletionsDialect }\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/errors.ts","../src/serialize.ts","../src/translate.ts","../src/wire.ts","../src/protocol.ts"],"sourcesContent":["/**\n * Wire failures of Chat Completions, mapped onto the SDK's shared error codes.\n *\n * Two constraints shape this module. First, the code set is NOT new: every code\n * produced here already exists in `@alvin0/ai-agent-sdk-core`\n * ({@link MODEL_ERROR_CODES}, {@link QUOTA_EXCEEDED_CODE},\n * {@link CONTEXT_WINDOW_EXCEEDED_CODE}), because a caller routing on `code`\n * must not have to learn a second vocabulary just because the request went to\n * an OpenAI-compatible endpoint instead of a native one (Requirement 10.6).\n * Second, the mapping is a REPRODUCTION of the shared HTTP-to-taxonomy table\n * rather than an import of it: this package's only dependency is core (DD-10),\n * so it cannot reach into `provider-http`. The table is therefore kept\n * deliberately identical — same status ordering, same wording classifiers, same\n * `detail` construction — and a cross-provider property test pins the two\n * together so a future edit to one that is not made to the other fails CI.\n *\n * `retry-after` and the provider request id are read whenever the endpoint\n * sends them, because they are the two facts a retry decision and a support\n * ticket respectively cannot be reconstructed without (Requirement 13.5).\n *\n * @module ai-agent-sdk/protocols/openai-chat-completions/errors\n */\n\nimport {\n CONTEXT_WINDOW_EXCEEDED_CODE,\n MODEL_ERROR_CODES,\n ModelError,\n ProviderRequestId,\n QUOTA_EXCEEDED_CODE,\n isContextWindowExceededError,\n isQuotaExceededError,\n} from '@alvin0/ai-agent-sdk-core'\nimport type { WireErrorBody } from './wire.ts'\n\n/** Largest slice of an unparseable body kept as classifier input. */\nconst MAX_DETAIL_LENGTH = 2_048\n\n/**\n * Wording that identifies a moderation rejection rather than a bad request.\n *\n * Narrow on purpose. A false positive would tell the caller their prompt was\n * filtered when in fact their JSON schema was wrong, and the two have opposite\n * fixes. Endpoints in this family are consistent about the token\n * `content_filter` / `content_policy`, so nothing looser is needed.\n */\nconst CONTENT_FILTER_WORDING = new RegExp(\n String.raw`\\bcontent[\\s_-]?(?:filter(?:ed|ing)?|policy(?:[\\s_-]?violation)?)\\b`\n + String.raw`|\\bresponsible[\\s_-]?ai[\\s_-]?policy\\b`,\n 'i',\n)\n\n/**\n * Recognize a moderation rejection in provider error text.\n *\n * Classified as `UNSUPPORTED_CONTENT` rather than as a new `CONTENT_FILTERED`\n * code: the existing code already says exactly this — the request carried\n * content the selected model will not accept — and it is already outside the\n * default retryable set, which is the behaviour a filtered prompt needs.\n * @param detail - provider error code/type/message text joined into one string.\n * @returns true when the wording names content filtering or a content policy.\n */\nexport function isContentFilteredError(detail: string): boolean {\n return CONTENT_FILTER_WORDING.test(detail)\n}\n\n/**\n * Map an HTTP status plus the provider's own error text onto a stable code.\n *\n * The status alone is not enough anywhere interesting: a 429 is either \"slow\n * down\" (retry) or \"your balance is gone\" (never retry), and a 400 is either a\n * prompt that overflows the context window (compact and retry), a moderation\n * rejection, or a malformed request. All three distinctions are invisible in\n * the status and each changes what the caller should do next, so the wording\n * classifiers run in a fixed order ahead of the plain status buckets.\n * @param status - status of a non-2xx response.\n * @param detail - provider error code/type/message joined; empty when the body was unusable.\n * @returns the normalized code, or `HTTP_{status}` when nothing classified it.\n */\nexport function chatCompletionsErrorCode(status: number, detail = ''): string {\n if (status === 401 || status === 403) return MODEL_ERROR_CODES.AUTH\n if (status === 413) return MODEL_ERROR_CODES.INVALID_REQUEST\n // Ahead of 429: an exhausted quota is usually delivered as 429 but never\n // clears on its own, so retrying it burns latency and money for nothing.\n if (isQuotaExceededError(detail)) return QUOTA_EXCEEDED_CODE\n if (status === 429) return MODEL_ERROR_CODES.RATE_LIMIT\n if (status === 400 || status === 422) {\n if (isContextWindowExceededError(detail)) return CONTEXT_WINDOW_EXCEEDED_CODE\n // Only inside the request-rejected statuses: the same wording in a 500 body\n // describes what the endpoint was doing, not why it refused the caller.\n if (isContentFilteredError(detail)) return MODEL_ERROR_CODES.UNSUPPORTED_CONTENT\n return MODEL_ERROR_CODES.INVALID_REQUEST\n }\n // A model this endpoint does not serve, or a path this gateway does not\n // mount. Both are the caller's mistake, so neither may land in the retryable\n // SERVER bucket where a 404 would otherwise be retried five times.\n if (status === 404) return MODEL_ERROR_CODES.INVALID_REQUEST\n if (status >= 500) return MODEL_ERROR_CODES.SERVER\n return `HTTP_${status}`\n}\n\n/**\n * Parse a `retry-after` header into milliseconds.\n *\n * Both defined forms appear in practice — delta-seconds and an HTTP date — so\n * both are read. A date already in the past yields `undefined` rather than a\n * negative delay, because a negative delay would be rejected downstream and\n * take the whole diagnostic with it.\n * @param value - the raw header value, or `null` when absent.\n * @returns a positive finite delay in milliseconds, or `undefined` when absent or unusable.\n */\nexport function chatCompletionsRetryAfterMs(value: string | null): number | undefined {\n if (value === null) return undefined\n const trimmed = value.trim()\n if (/^\\d+$/.test(trimmed)) {\n const delay = Number(trimmed) * 1_000\n return Number.isFinite(delay) && delay > 0 ? delay : undefined\n }\n const delay = Date.parse(trimmed) - Date.now()\n return Number.isFinite(delay) && delay > 0 ? delay : undefined\n}\n\n/** Header names this endpoint family uses for a request correlation id, in priority order. */\nconst REQUEST_ID_HEADERS = [\n 'request-id',\n 'x-request-id',\n 'x-requestid',\n 'cf-ray',\n] as const\n\n/**\n * Extract a provider request id for diagnostics.\n *\n * Nothing programmatic reads it, and it is still worth carrying: when an\n * endpoint misbehaves, this id is the only handle its operators have for\n * finding the request.\n * @param headers - the response headers.\n * @returns the first non-empty id found, or `undefined`.\n */\nexport function chatCompletionsRequestId(headers: Headers): ProviderRequestId | undefined {\n for (const name of REQUEST_ID_HEADERS) {\n const value = headers.get(name)\n if (value !== null && value.length > 0) return ProviderRequestId(value)\n }\n return undefined\n}\n\n/** An error body reduced to the two things the mapping needs. */\nexport interface ParsedChatCompletionsError {\n /** Best human-readable message found, or `undefined` to fall back to the status. */\n readonly message: string | undefined\n /** Provider `code`/`type`/`message` joined, as input to the wording classifiers. */\n readonly detail: string\n}\n\n/** Read a string property from an unknown value without trusting its shape. */\nfunction stringField(source: unknown, key: string): string | undefined {\n if (typeof source !== 'object' || source === null) return undefined\n const value = (source as Record<string, unknown>)[key]\n return typeof value === 'string' && value.length > 0 ? value : undefined\n}\n\n/**\n * Reduce an error body to a message and a classifier detail string.\n *\n * Tolerates every shape actually seen on this wire: the canonical\n * `{error: {code, type, message}}`, a bare `{type, message}` with no wrapper, a\n * `{detail: \"...\"}` in the FastAPI style some compatible endpoints use, an\n * `{error: \"...\"}` carrying a plain string, and a body that is not JSON at all\n * — which is what a gateway or load balancer sitting in front of the endpoint\n * returns. In the last case the status stays authoritative and the raw text,\n * truncated, is still the best classifier input available.\n *\n * A bare-string `error` contributes to the MESSAGE only, never to `detail`.\n * That asymmetry is deliberate: `detail` is what decides the code, and it is\n * held byte-identical to the shared provider mapping so the same\n * `(status, body)` pair cannot classify differently here than it does for an\n * existing provider.\n * @param raw - the response body as text.\n * @returns the message and the joined detail.\n */\nexport function parseChatCompletionsErrorBody(raw: string): ParsedChatCompletionsError {\n let parsed: unknown\n try {\n parsed = JSON.parse(raw) as unknown\n } catch {\n return { message: undefined, detail: raw.slice(0, MAX_DETAIL_LENGTH) }\n }\n const error = typeof parsed === 'object' && parsed !== null\n && 'error' in (parsed as Record<string, unknown>)\n ? (parsed as Record<string, unknown>).error\n : parsed\n const code = stringField(error, 'code')\n const type = stringField(error, 'type')\n const message = stringField(error, 'message')\n const detailField = stringField(error, 'detail') ?? stringField(parsed, 'detail')\n const parts = [code, type, message ?? detailField]\n .filter((part): part is string => part !== undefined)\n const bareError = typeof error === 'string' && error.length > 0 ? error : undefined\n return {\n message: message ?? detailField ?? bareError,\n detail: parts.join(' '),\n }\n}\n\n/** Everything known about a non-2xx Chat Completions response. */\nexport interface ChatCompletionsHttpFailure {\n /** Status of the response. */\n readonly status: number\n /** The response body as text; empty when it could not be read. */\n readonly body: string\n /** Response headers, read for `retry-after` and the request id. */\n readonly headers: Headers\n /** Name used in the fallback message, e.g. the provider display name. */\n readonly displayName: string\n /** Request URL, included in the fallback message for diagnosis. */\n readonly url?: string\n}\n\n/**\n * Turn a non-2xx response into a fully populated {@link ModelError}.\n *\n * The provider's own message wins when there is one, because it is invariably\n * more specific than anything this layer could synthesize; the synthesized\n * fallback exists for bodies that carry no message at all. `cause` keeps the\n * raw body so a diagnosis is possible even when the classifier found nothing.\n * @param failure - the status, body, and headers of the failed response.\n * @returns a ModelError carrying the code, status, retry delay, and request id.\n */\nexport function chatCompletionsHttpError(failure: ChatCompletionsHttpFailure): ModelError {\n const { message, detail } = parseChatCompletionsErrorBody(failure.body)\n const delay = chatCompletionsRetryAfterMs(failure.headers.get('retry-after'))\n const id = chatCompletionsRequestId(failure.headers)\n const location = failure.url === undefined ? '' : ` from ${failure.url}`\n return new ModelError(\n message ?? `${failure.displayName} error (HTTP ${failure.status})${location}`,\n chatCompletionsErrorCode(failure.status, detail),\n {\n cause: new Error(failure.body.length > 0 ? failure.body : `HTTP ${failure.status}`),\n status: failure.status,\n ...delay === undefined ? {} : { providerRetryAfterMs: delay },\n ...id === undefined ? {} : { requestId: id },\n },\n )\n}\n\n/**\n * Map an error the endpoint inlined into a 200 stream onto the same taxonomy.\n *\n * Some gateways answer a rejected request with HTTP 200 and an `error` object\n * inside a `data:` frame. There is no status to classify from, so the wording\n * classifiers carry the whole decision, and the residual case is\n * `MALFORMED_RESPONSE`: an error arriving where content was promised is a\n * broken response, not a server fault to be retried.\n * @param body - the `error` payload of a stream chunk.\n * @param displayName - name used when the payload carries no message.\n * @returns a ModelError with no `status`, since the HTTP call itself succeeded.\n */\nexport function chatCompletionsStreamError(\n body: WireErrorBody,\n displayName: string,\n): ModelError {\n const code = typeof body.code === 'string' ? body.code : undefined\n const detail = [code, body.type, body.message]\n .filter((part): part is string => typeof part === 'string' && part.length > 0)\n .join(' ')\n const classified = isQuotaExceededError(detail)\n ? QUOTA_EXCEEDED_CODE\n : isContextWindowExceededError(detail)\n ? CONTEXT_WINDOW_EXCEEDED_CODE\n : isContentFilteredError(detail)\n ? MODEL_ERROR_CODES.UNSUPPORTED_CONTENT\n : MODEL_ERROR_CODES.MALFORMED_RESPONSE\n return new ModelError(\n body.message ?? `${displayName} inlined an error into the stream`,\n classified,\n { cause: new Error(detail.length > 0 ? detail : 'stream error') },\n )\n}\n\n/**\n * Classify a failure raised before any status was seen.\n *\n * Kept apart from the status mapping because the three outcomes are decided by\n * WHO ended the request, not by what the endpoint said: the caller's signal\n * (`ABORTED`), a deadline (`TIMEOUT`), or the network (`TRANSPORT`). Web\n * platform semantics supply the distinction — `AbortSignal.timeout` rejects\n * with a `TimeoutError`, an explicit `abort()` with an `AbortError` — so no\n * message parsing is involved.\n * @param error - the thrown value.\n * @param fallbackMessage - message used when the thrown value carries none.\n * @returns a ModelError coded ABORTED, TIMEOUT, or TRANSPORT.\n */\nexport function chatCompletionsTransportError(\n error: unknown,\n fallbackMessage: string,\n): ModelError {\n const name = error instanceof Error ? error.name : ''\n const code = name === 'TimeoutError'\n ? MODEL_ERROR_CODES.TIMEOUT\n : name === 'AbortError'\n ? MODEL_ERROR_CODES.ABORTED\n : MODEL_ERROR_CODES.TRANSPORT\n const message = error instanceof Error && error.message.length > 0\n ? error.message\n : fallbackMessage\n return new ModelError(message, code, { cause: error })\n}\n","/**\n * Normalized request to Chat Completions wire JSON.\n *\n * Two things make this different from the Responses serializer. First, the wire\n * wants MESSAGES rather than a flat item list, so an assistant turn that spoke\n * and called two tools stays ONE message carrying both `content` and\n * `tool_calls`, while a tool result becomes its own `role: 'tool'` message\n * correlated by `tool_call_id`. Second, every optional field is gated by a\n * {@link ChatCompletionsDialect} flag, and a disabled flag means the key is\n * ABSENT — not `null`, not a default value. Some gateways reject an\n * unknown-but-null key outright, and a default silently changes behaviour\n * nobody asked for.\n *\n * @module ai-agent-sdk/protocols/openai-chat-completions/serialize\n */\n\nimport { MODEL_ERROR_CODES, ModelError, isNativeToolSchema } from '@alvin0/ai-agent-sdk-core'\nimport type {\n ContentBlock,\n ImageBlock,\n Message,\n ModelOutputFormat,\n ModelToolSchema,\n TextBlock,\n ToolChoice,\n ToolSchema,\n} from '@alvin0/ai-agent-sdk-core'\nimport type { ProtocolRequest } from './contract.ts'\nimport type {\n ChatCompletionsDialect,\n WireContentPart,\n WireImageDetail,\n WireMessage,\n WireRequest,\n WireResponseFormat,\n WireTool,\n WireToolCall,\n WireToolChoice,\n} from './wire.ts'\n\n/** Separator used whenever several text blocks collapse into one string. */\nconst TEXT_JOINER = '\\n'\n\nfunction textBlocks(blocks: readonly ContentBlock[]): TextBlock[] {\n return blocks.filter((block): block is TextBlock => block.type === 'text')\n}\n\nfunction joinedText(blocks: readonly ContentBlock[]): string {\n return textBlocks(blocks).map(block => block.text).join(TEXT_JOINER)\n}\n\n/** `original` has no wire spelling here, so it travels as no detail at all. */\nfunction imageDetail(block: ImageBlock): WireImageDetail | undefined {\n const detail = block.detail\n if (detail === 'auto' || detail === 'low' || detail === 'high') return detail\n return undefined\n}\n\n/**\n * Convert one image block to an `image_url` part.\n *\n * A provider-side uploaded file has no representation in this API — the part\n * only accepts a URL — so a `file` source yields nothing rather than a\n * fabricated URL that would 400 at best and fetch the wrong bytes at worst.\n */\nfunction imagePart(block: ImageBlock): WireContentPart | undefined {\n if (block.source.kind === 'file') return undefined\n const url = block.source.kind === 'url'\n ? block.source.url\n : `data:${block.source.mediaType};base64,${block.source.data}`\n const detail = imageDetail(block)\n return { type: 'image_url', image_url: { url, ...detail === undefined ? {} : { detail } } }\n}\n\nfunction contentPart(block: ContentBlock): WireContentPart | undefined {\n if (block.type === 'text') return { type: 'text', text: block.text }\n if (block.type === 'image') return imagePart(block)\n // `document` included: Chat Completions has no document part, and rendering a\n // PDF as text here would drop the page images the caller asked the model to read.\n return undefined\n}\n\nfunction toolCall(block: Extract<ContentBlock, { type: 'tool-call' }>): WireToolCall {\n return {\n id: block.id,\n type: 'function',\n function: {\n name: block.name,\n // VERBATIM. The string is replayed exactly as the model produced it: a\n // `JSON.parse`/`JSON.stringify` round-trip reorders keys and renormalizes\n // numbers, and some models read that exact string as their own context.\n // Only a truly empty value is substituted, because `arguments` must still\n // be valid JSON.\n arguments: block.arguments.length > 0 ? block.arguments : '{}',\n },\n }\n}\n\n/**\n * Expand one message into one or more wire messages, appended in order.\n *\n * The accumulator is FLUSHED before a block that has to become its own\n * top-level message, which is what keeps \"spoke, called a tool, got a result,\n * spoke again\" in the order the model produced it.\n */\nfunction appendMessage(message: Message, messages: WireMessage[]): void {\n const role = message.role === 'assistant' ? 'assistant' : 'user'\n let parts: WireContentPart[] = []\n let calls: WireToolCall[] = []\n\n const flush = (): void => {\n if (parts.length === 0 && calls.length === 0) return\n if (role === 'assistant') {\n const text = parts\n .flatMap(part => part.type === 'text' ? [part.text] : [])\n .join(TEXT_JOINER)\n messages.push({\n role: 'assistant',\n // Absent on a turn that only called tools; an empty string there reads\n // to the model as \"I said nothing out loud\", which is a different claim.\n ...text.length === 0 ? {} : { content: text },\n ...calls.length === 0 ? {} : { tool_calls: calls },\n })\n } else if (parts.length > 0) {\n const onlyText = parts.every(part => part.type === 'text')\n messages.push({\n role: 'user',\n content: onlyText\n ? parts.flatMap(part => part.type === 'text' ? [part.text] : []).join(TEXT_JOINER)\n : parts,\n })\n }\n parts = []\n calls = []\n }\n\n for (const block of message.content) {\n switch (block.type) {\n case 'text':\n case 'image':\n case 'document': {\n const part = contentPart(block)\n if (part !== undefined) parts.push(part)\n break\n }\n case 'tool-call': {\n calls.push(toolCall(block))\n break\n }\n case 'tool-result': {\n flush()\n messages.push({\n role: 'tool',\n tool_call_id: block.toolCallId,\n // The wire accepts a string only; non-text result blocks have no slot.\n content: joinedText(block.content),\n })\n break\n }\n default:\n // `reasoning`, `native-tool-call`, and any block added by declaration\n // merging. Skipping is correct: this API has no field to replay them\n // into, and inventing one would corrupt the turn.\n break\n }\n }\n flush()\n}\n\n/** The system prompt, plus any system-role messages, in order. */\nfunction systemTextOf(request: ProtocolRequest): string {\n const fromMessages = request.options.messages\n .filter(message => message.role === 'system')\n .map(message => joinedText(message.content))\n .filter(text => text.length > 0)\n const all = request.options.system === undefined\n ? fromMessages\n : [request.options.system, ...fromMessages]\n return all.join('\\n\\n')\n}\n\n/** Map a tool schema; `strict` is left off because caller schemas are not vetted. */\nfunction functionTool(tool: ToolSchema): WireTool {\n return {\n type: 'function',\n function: {\n name: tool.name,\n description: tool.description,\n parameters: { ...tool.parameters },\n },\n }\n}\n\nfunction toolOf(tool: ModelToolSchema): WireTool {\n if (!isNativeToolSchema(tool)) return functionTool(tool)\n // Chat Completions runs no tools of its own. Dropping the request silently\n // would hand back an answer produced without the search the caller required.\n throw new ModelError(\n `Chat Completions does not support the provider-native tool \"${tool.name}\"`,\n MODEL_ERROR_CODES.INVALID_REQUEST,\n )\n}\n\nfunction toolChoiceOf(choice: ToolChoice): WireToolChoice {\n if (typeof choice === 'string') return choice\n if (choice.type === 'native') {\n throw new ModelError(\n `Chat Completions cannot be forced to call the provider-native tool \"${choice.name}\"`,\n MODEL_ERROR_CODES.INVALID_REQUEST,\n )\n }\n return { type: 'function', function: { name: choice.name } }\n}\n\n/**\n * The reasoning-effort wire fields for one request, per `dialect.reasoningFormat`.\n *\n * The effort value itself is never translated — decision 1 sends whatever the\n * caller passed, verbatim, as a string. Only the FIELD(S) it travels on change\n * per format; see the `reasoningFormat` doc comment on {@link ChatCompletionsDialect}.\n */\nfunction reasoningFieldsOf(\n format: ChatCompletionsDialect['reasoningFormat'],\n effort: string | undefined,\n): Pick<WireRequest, 'reasoning_effort' | 'thinking'> {\n if (format === false || effort === undefined) return {}\n if (format === 'openai') return { reasoning_effort: effort }\n // 'deepseek': the endpoint reasons unless told not to, so `'off'` disables\n // thinking instead of merely omitting an effort the endpoint would ignore.\n if (effort === 'off') return { thinking: { type: 'disabled' } }\n return { thinking: { type: 'enabled' }, reasoning_effort: effort }\n}\n\n/**\n * Map the neutral output format onto `response_format`, per dialect state.\n *\n * A schema the endpoint cannot honour is an error rather than a downgrade to\n * free text: the caller is about to `JSON.parse` the answer.\n */\nfunction responseFormatOf(\n format: ModelOutputFormat | undefined,\n structuredOutputs: ChatCompletionsDialect['structuredOutputs'],\n): WireResponseFormat | undefined {\n if (format === undefined || structuredOutputs === false) {\n if (format !== undefined && format.type === 'json_schema' && structuredOutputs === false) {\n throw new ModelError(\n 'This Chat Completions endpoint does not support structured output',\n MODEL_ERROR_CODES.INVALID_REQUEST,\n )\n }\n return undefined\n }\n if (format.type === 'text') return { type: 'text' }\n // `json-object`: JSON mode without a schema. The endpoint guarantees valid\n // JSON but not this shape, which is still strictly better than free text.\n if (structuredOutputs === 'json-object') return { type: 'json_object' }\n return {\n type: 'json_schema',\n json_schema: { name: format.name, schema: format.schema, strict: true },\n }\n}\n\n/**\n * Build the Chat Completions request body.\n * @param request - the resolved request, model, and output cap.\n * @param dialect - which optional fields this endpoint accepts.\n * @returns the wire body, ready to serialize.\n */\nexport function serializeChatCompletionsRequest(\n request: ProtocolRequest,\n dialect: ChatCompletionsDialect,\n): WireRequest {\n const { options } = request\n const messages: WireMessage[] = []\n\n const system = systemTextOf(request)\n // The system prompt leads the array under whichever role this endpoint reads.\n if (system.length > 0) messages.push({ role: dialect.systemRole, content: system })\n\n for (const message of options.messages) {\n if (message.role === 'system') continue // already folded in above\n appendMessage(message, messages)\n }\n\n const tools = dialect.tools && options.tools !== undefined && options.tools.length > 0\n ? options.tools.map(toolOf)\n : undefined\n const responseFormat = responseFormatOf(options.outputFormat, dialect.structuredOutputs)\n\n return {\n model: options.model,\n messages,\n stream: true,\n ...dialect.streamUsage ? { stream_options: { include_usage: true } } : {},\n ...dialect.maxTokensField === false || request.maxTokens === undefined\n ? {}\n : { [dialect.maxTokensField]: request.maxTokens },\n ...dialect.sampling && options.temperature !== undefined\n ? { temperature: options.temperature }\n : {},\n ...dialect.sampling && options.topP !== undefined ? { top_p: options.topP } : {},\n // `frequency_penalty` and `presence_penalty` have no source in\n // `GenerateOptions`, so they are never sent. Same for `seed` and `user`:\n // `dialect.seed` exists so a provider that grows a source for it does not\n // have to re-thread the flag through the dialect first.\n ...dialect.stop && options.stop !== undefined && options.stop.length > 0\n ? { stop: [...options.stop] }\n : {},\n ...reasoningFieldsOf(dialect.reasoningFormat, options.reasoningEffort),\n ...tools === undefined ? {} : { tools },\n ...tools === undefined || options.toolChoice === undefined\n ? {}\n : { tool_choice: toolChoiceOf(options.toolChoice) },\n // Only meaningful alongside `tools`, and older gateways reject the key\n // outright, which is why the flag defaults off.\n ...tools !== undefined && dialect.parallelToolCalls ? { parallel_tool_calls: true } : {},\n ...responseFormat === undefined ? {} : { response_format: responseFormat },\n ...dialect.promptCacheKey === undefined\n ? {}\n : { prompt_cache_key: dialect.promptCacheKey },\n }\n}\n","/**\n * Chat Completions SSE events to the SDK's chunk protocol.\n *\n * Four rules drive everything below, and each one exists because the obvious\n * implementation is wrong.\n *\n * First, the tool-call accumulator is keyed on `index`, never on `id`. `id` and\n * `function.name` arrive ONCE, on the first fragment of that call, while\n * `function.arguments` arrives in many fragments that carry only `index`. So\n * `index` is the only correlation key present on every fragment.\n *\n * Second, `arguments` fragments are CONCATENATED as strings and never parsed\n * per fragment. A fragment can split in the middle of a JSON escape sequence or\n * in the middle of a multi-byte character, so an early parse fails on traffic\n * that is perfectly valid once joined.\n *\n * Third, a tool call is emitted only once `finish_reason === 'tool_calls'` has\n * arrived, and the accumulated `arguments` is parsed at exactly that moment. A\n * parse failure there is a protocol error, not an empty tool call: handing a\n * caller `{}` invents an argument list the model never produced.\n *\n * Fourth, `[DONE]` is NOT the terminal finish. Terminal finish is a chunk\n * carrying a `finish_reason` other than `null`. A stream that runs out of\n * events, or reaches `[DONE]`, or is cut mid-way without one is a failure —\n * because a truncated stream is byte-for-byte indistinguishable from a short\n * answer, and nothing above this layer can tell them apart if the translator\n * stays quiet.\n *\n * @module ai-agent-sdk/protocols/openai-chat-completions/translate\n */\n\nimport { MODEL_ERROR_CODES, ModelError, ToolCallId } from '@alvin0/ai-agent-sdk-core'\nimport type { ContentBlock, FinishReason, UsageCounters } from '@alvin0/ai-agent-sdk-core'\nimport type { ProtocolSseEvent, ProtocolStreamChunk } from './contract.ts'\nimport type {\n WireErrorBody,\n WireFinishReason,\n WireStreamChoice,\n WireStreamChunk,\n WireToolCallDelta,\n WireUsage,\n} from './wire.ts'\n\n/** The `data:` payload that ends the body; not a finish, and not a chunk. */\nconst DONE_SENTINEL = '[DONE]'\n\n/** Substituted when a zero-parameter tool sends no `arguments` at all. */\nconst EMPTY_ARGUMENTS = '{}'\n\n/** One in-flight tool call, correlated by the wire's `index`. */\ninterface OpenToolCall {\n /** Our own block index, assigned in first-seen order at emit time. */\n readonly order: number\n id: string | undefined\n name: string | undefined\n /** Fragments joined verbatim. Never parsed until the terminal finish. */\n args: string\n}\n\n/** One in-flight text or reasoning block. */\ninterface OpenTextBlock {\n readonly index: number\n text: string\n}\n\nfunction malformed(displayName: string, detail: string, cause?: unknown): ModelError {\n return new ModelError(\n `${displayName} sent a malformed stream event: ${detail}`,\n MODEL_ERROR_CODES.MALFORMED_RESPONSE,\n cause === undefined ? {} : { cause },\n )\n}\n\nfunction recordOrUndefined(value: unknown): Record<string, unknown> | undefined {\n return typeof value === 'object' && value !== null ? value as Record<string, unknown> : undefined\n}\n\n/**\n * Normalize usage, honouring the SDK's disjoint-count convention.\n *\n * This API reports `prompt_tokens` as the TOTAL input and\n * `prompt_tokens_details.cached_tokens` as a SUBSET of it, while the SDK's three\n * input figures are disjoint and sum to what is billed. So the cached portion is\n * subtracted back out here; skip that and every cost estimate double-counts\n * cache hits.\n *\n * Otherwise the counters travel RAW: malformed values are passed through rather\n * than dropped, and nothing here decides whether the report is complete. That\n * judgement belongs to the accounting boundary above, which is the only layer\n * that knows what the caller asked for.\n */\nfunction mapUsage(usage: WireUsage): UsageCounters | undefined {\n const source = usage as unknown as Record<string, unknown>\n const promptDetails = recordOrUndefined(source.prompt_tokens_details)\n const completionDetails = recordOrUndefined(source.completion_tokens_details)\n const promptTokens = source.prompt_tokens\n const outputTokens = source.completion_tokens\n const totalTokens = source.total_tokens\n const cacheRead = promptDetails?.cached_tokens\n const reasoning = completionDetails?.reasoning_tokens\n\n const present = [promptTokens, outputTokens, totalTokens, cacheRead, reasoning]\n .some(value => value !== undefined)\n if (!present) return undefined\n\n const counters: Record<string, unknown> = {\n ...outputTokens === undefined ? {} : { outputTokens },\n ...totalTokens === undefined ? {} : { totalTokens },\n // An omitted or zero cache figure is authoritative zero for this API; a\n // present-but-invalid one is retained for the accounting validator.\n ...cacheRead === undefined || cacheRead === 0 ? {} : { cacheReadTokens: cacheRead },\n ...reasoning === undefined || reasoning === 0 ? {} : { reasoningTokens: reasoning },\n }\n if (promptTokens !== undefined) {\n counters.inputTokens = typeof promptTokens === 'number'\n && (cacheRead === undefined || typeof cacheRead === 'number')\n ? promptTokens - (cacheRead ?? 0)\n : promptTokens\n }\n return counters as UsageCounters\n}\n\n/**\n * Map this API's finish reason onto ours.\n *\n * `content_filter` becomes a terminal `error` finish rather than a thrown\n * exception, so whatever text arrived before the filter tripped still reaches\n * the caller. `function_call` is the pre-`tools` spelling of `tool_calls` and\n * means the same thing.\n */\nfunction finishReasonOf(reason: WireFinishReason): FinishReason {\n switch (reason) {\n case 'tool_calls':\n case 'function_call':\n return { kind: 'tool-calls' }\n case 'length':\n return { kind: 'max-tokens' }\n case 'content_filter':\n return {\n kind: 'error',\n failure: {\n message: 'the endpoint filtered this response',\n code: MODEL_ERROR_CODES.INVALID_REQUEST,\n },\n }\n default:\n return { kind: 'stop' }\n }\n}\n\n/** Whether this finish reason is the one that releases accumulated tool calls. */\nfunction releasesToolCalls(reason: WireFinishReason): boolean {\n return reason === 'tool_calls' || reason === 'function_call'\n}\n\n/** Turn an inline stream error into a typed failure. */\nfunction inlineError(error: WireErrorBody, displayName: string): ModelError {\n const message = error.message ?? `${displayName} reported an error mid-stream`\n const code = error.code ?? error.type\n // An unrecognized inline error defaults to SERVER, which IS retryable: the\n // turn produced nothing usable, so repeating it is safe and often works.\n return new ModelError(\n code === undefined ? message : `${message} (${String(code)})`,\n MODEL_ERROR_CODES.SERVER,\n )\n}\n\n/** Fold one tool-call fragment into the accumulator. */\nfunction absorbToolCall(\n open: Map<number, OpenToolCall>,\n fragment: WireToolCallDelta,\n nextOrder: () => number,\n): void {\n const key = typeof fragment.index === 'number' ? fragment.index : 0\n let entry = open.get(key)\n if (entry === undefined) {\n entry = { order: nextOrder(), id: undefined, name: undefined, args: '' }\n open.set(key, entry)\n }\n // `id` and `name` arrive once. A later fragment repeating them is harmless;\n // a later fragment CLEARING them would not be, so only truthy values land.\n if (typeof fragment.id === 'string' && fragment.id.length > 0) entry.id = fragment.id\n const name = fragment.function?.name\n if (typeof name === 'string' && name.length > 0) entry.name = name\n const args = fragment.function?.arguments\n // Concatenation only. The joined string is the model's own bytes, replayed\n // verbatim on the next turn, so no reformatting happens anywhere on this path.\n if (typeof args === 'string') entry.args += args\n}\n\n/**\n * Build the authoritative tool-call blocks, parsing `arguments` right here.\n *\n * This is the single moment the accumulated string is allowed to be parsed, and\n * a failure is a protocol error. The alternative — emitting the call with empty\n * arguments — would hand the agent loop a call the model never made.\n */\nfunction toolCallBlocks(\n open: Map<number, OpenToolCall>,\n displayName: string,\n): {\n index: number\n id: string\n name: string\n arguments: string\n block: ContentBlock\n}[] {\n return [...open.entries()]\n .sort(([left], [right]) => left - right)\n .map(([wireIndex, entry]) => {\n if (entry.id === undefined || entry.name === undefined) {\n throw malformed(\n displayName,\n `tool call at index ${wireIndex} never carried an id and a name`,\n )\n }\n const args = entry.args.length > 0 ? entry.args : EMPTY_ARGUMENTS\n try {\n JSON.parse(args)\n } catch (error: unknown) {\n throw malformed(\n displayName,\n `tool call \"${entry.name}\" produced arguments that are not valid JSON`,\n error,\n )\n }\n return {\n index: entry.order,\n id: entry.id,\n name: entry.name,\n arguments: args,\n block: {\n type: 'tool-call',\n id: ToolCallId(entry.id),\n name: entry.name,\n arguments: args,\n } satisfies ContentBlock,\n }\n })\n}\n\n/**\n * Translate one Chat Completions SSE stream.\n *\n * Owns termination, and refuses to invent one: the stream is complete only when\n * a `finish_reason` other than `null` has been seen. Events after that point are\n * still read, because the `usage` chunk arrives there — with `choices: []`,\n * which is normal traffic rather than a malformed event.\n * @param events - decoded SSE events.\n * @param displayName - provider name, used in diagnostics.\n * @returns the chunk stream.\n */\nexport async function* translateChatCompletionsStream(\n events: AsyncIterable<ProtocolSseEvent>,\n displayName: string,\n): AsyncGenerator<ProtocolStreamChunk> {\n const toolCalls = new Map<number, OpenToolCall>()\n let nextIndex = 0\n const nextOrder = (): number => nextIndex++\n let text: OpenTextBlock | undefined\n let reasoning: OpenTextBlock | undefined\n let followedChoice: number | undefined\n let finish: WireFinishReason | undefined\n let usage: UsageCounters | undefined\n\n for await (const raw of events) {\n const payload = raw.data.trim()\n if (payload.length === 0) continue\n // Read past the sentinel rather than terminating on it. It says the BODY\n // ended, which is a different claim from \"the response completed\".\n if (payload === DONE_SENTINEL) continue\n\n let chunk: WireStreamChunk\n try {\n chunk = JSON.parse(payload) as WireStreamChunk\n } catch (error: unknown) {\n throw malformed(displayName, 'the event data is not JSON', error)\n }\n if (recordOrUndefined(chunk) === undefined) {\n throw malformed(displayName, 'the event data is not an object')\n }\n\n if (chunk.error !== undefined && chunk.error !== null) {\n throw inlineError(chunk.error, displayName)\n }\n\n // Last non-null report wins; the usage-bearing chunk normally arrives after\n // the terminal finish, and earlier chunks carry an explicit `null`.\n if (chunk.usage !== undefined && chunk.usage !== null) {\n usage = mapUsage(chunk.usage) ?? usage\n }\n\n const choices: readonly WireStreamChoice[] = Array.isArray(chunk.choices) ? chunk.choices : []\n for (const choice of choices) {\n if (recordOrUndefined(choice) === undefined) continue\n const index = typeof choice.index === 'number' ? choice.index : 0\n // One choice is followed for the whole stream — the first one seen. The\n // SDK never asks for more than one, and interleaving two into a single\n // block stream would silently splice two different answers together.\n followedChoice ??= index\n if (index !== followedChoice) continue\n // Everything after the terminal finish is read for `usage` only; a late\n // delta cannot be appended to a block that has already been closed.\n if (finish !== undefined) continue\n\n const delta = recordOrUndefined(choice.delta) === undefined ? undefined : choice.delta\n\n const reasoningText = delta?.reasoning_content\n if (typeof reasoningText === 'string' && reasoningText.length > 0) {\n if (reasoning === undefined) {\n reasoning = { index: nextOrder(), text: '' }\n yield { type: 'block-start', index: reasoning.index, blockType: 'reasoning' }\n }\n reasoning.text += reasoningText\n yield { type: 'reasoning-delta', index: reasoning.index, text: reasoningText }\n }\n\n // `refusal` is model-authored prose explaining why it declined, so it\n // belongs in the text block; the finish reason is what marks it a refusal.\n const content = typeof delta?.content === 'string' && delta.content.length > 0\n ? delta.content\n : typeof delta?.refusal === 'string' && delta.refusal.length > 0\n ? delta.refusal\n : undefined\n if (content !== undefined) {\n if (text === undefined) {\n text = { index: nextOrder(), text: '' }\n yield { type: 'block-start', index: text.index, blockType: 'text' }\n }\n text.text += content\n yield { type: 'text-delta', index: text.index, text: content }\n }\n\n if (Array.isArray(delta?.tool_calls)) {\n for (const fragment of delta.tool_calls) {\n if (recordOrUndefined(fragment) === undefined) continue\n absorbToolCall(toolCalls, fragment, nextOrder)\n }\n }\n\n const reason = choice.finish_reason\n if (reason === undefined || reason === null) continue\n\n // Terminal finish. Close what is open, in first-seen order, then keep\n // draining the stream so the trailing `usage` chunk is still read.\n finish = reason\n if (reasoning !== undefined) {\n yield {\n type: 'block-end',\n index: reasoning.index,\n block: { type: 'reasoning', text: reasoning.text },\n }\n }\n if (text !== undefined) {\n yield { type: 'block-end', index: text.index, block: { type: 'text', text: text.text } }\n }\n if (releasesToolCalls(reason)) {\n for (const call of toolCallBlocks(toolCalls, displayName)) {\n yield { type: 'block-start', index: call.index, blockType: 'tool-call' }\n yield {\n type: 'tool-call-delta',\n index: call.index,\n id: ToolCallId(call.id),\n name: call.name,\n argumentsDelta: call.arguments,\n }\n yield { type: 'block-end', index: call.index, block: call.block }\n }\n }\n // Any other finish reason with fragments still in the accumulator means\n // the call never completed — `length` truncated it, `stop` contradicts it\n // — and an incomplete tool call must not escape this function.\n toolCalls.clear()\n }\n }\n\n if (finish === undefined) {\n throw new ModelError(\n `${displayName} stream ended without a finish reason`,\n MODEL_ERROR_CODES.STREAM_CLOSED,\n )\n }\n\n if (usage !== undefined) yield { type: 'usage', usage }\n yield { type: 'finish', reason: finishReasonOf(finish) }\n}\n","/**\n * The OpenAI Chat Completions wire shapes and the dialect describing how one\n * endpoint differs from another.\n *\n * \"OpenAI-compatible\" is a family, not a single API: gateways, Azure\n * deployments, editor-subscription endpoints, and self-hosted proxies all speak\n * this protocol with small, well-known divergences — which field caps the\n * output length, whether\n * `parallel_tool_calls` is accepted at all, whether the system prompt travels\n * as `system` or as `developer`. Those divergences are expressed here as DATA\n * ({@link ChatCompletionsDialect}) so the serializer and the translator stay\n * single-implementation.\n *\n * These types never leave this folder except through `ChatCompletionsDialect`.\n *\n * @module ai-agent-sdk/protocols/openai-chat-completions/wire\n */\n\n// ---------------------------------------------------------------------------\n// Request\n// ---------------------------------------------------------------------------\n\n/** Detail level requested for an input image. */\nexport type WireImageDetail = 'auto' | 'low' | 'high'\n\n/** One part of a multimodal message's content. */\nexport type WireContentPart =\n | { type: 'text'; text: string }\n | { type: 'image_url'; image_url: { url: string; detail?: WireImageDetail } }\n\n/**\n * A tool call as the assistant turn carries it.\n *\n * `arguments` is a JSON-encoded STRING, not an object. When replaying a prior\n * turn the string is sent back BYTE-FOR-BYTE as received: a\n * `JSON.parse`/`JSON.stringify` round-trip reorders keys and renormalizes\n * numbers, and some models use that exact string as context.\n */\nexport interface WireToolCall {\n id: string\n type: 'function'\n function: {\n name: string\n /** JSON-encoded STRING, verbatim. */\n arguments: string\n }\n}\n\n/**\n * One entry of the `messages` array.\n *\n * Note the shape of the conversation: unlike the Responses API's flat item\n * list, this is a list of MESSAGES. A tool result is its own message with\n * `role: 'tool'` correlated by `tool_call_id`, and an assistant turn that spoke\n * and called two tools stays a single message carrying both `content` and\n * `tool_calls`.\n */\nexport type WireMessage =\n | {\n /** `developer` is the newer spelling; which one to use is a dialect knob. */\n role: 'system' | 'developer'\n content: string\n }\n | {\n role: 'user'\n content: string | WireContentPart[]\n }\n | {\n role: 'assistant'\n /** Absent or null on a turn that only called tools. */\n content?: string | null\n tool_calls?: WireToolCall[]\n }\n | {\n role: 'tool'\n /** Correlates with the `id` of the assistant's tool call. */\n tool_call_id: string\n content: string\n }\n\n/** A function tool, nested under a `function` key rather than flat. */\nexport interface WireFunctionTool {\n type: 'function'\n function: {\n name: string\n description?: string\n parameters?: Record<string, unknown>\n strict?: boolean\n }\n}\n\nexport type WireTool = WireFunctionTool\n\n/** How the model must choose among the offered tools. */\nexport type WireToolChoice =\n | 'auto'\n | 'none'\n | 'required'\n | { type: 'function'; function: { name: string } }\n\n/** Output format controls. */\nexport type WireResponseFormat =\n | { type: 'text' }\n | { type: 'json_object' }\n | {\n type: 'json_schema'\n json_schema: {\n name: string\n schema: Readonly<Record<string, unknown>>\n strict: true\n }\n }\n\n/** Streaming extras. */\nexport interface WireStreamOptions {\n include_usage?: boolean\n}\n\n/**\n * The request body.\n *\n * Every optional field here is gated by a {@link ChatCompletionsDialect} flag,\n * and a disabled flag means the key is ABSENT — never `null`, never a default\n * value. Some gateways reject unknown-but-null keys outright, and a default\n * value silently changes behaviour the caller never asked for.\n */\nexport interface WireRequest {\n model: string\n messages: WireMessage[]\n stream: boolean\n stream_options?: WireStreamOptions\n /** Present under whichever name `dialect.maxTokensField` names. */\n max_tokens?: number\n /** The reasoning-model spelling of the same cap. */\n max_completion_tokens?: number\n temperature?: number\n top_p?: number\n frequency_penalty?: number\n presence_penalty?: number\n stop?: string | string[]\n seed?: number\n reasoning_effort?: string\n /** Sent alongside `reasoning_effort` by `dialect.reasoningFormat: 'deepseek'`. */\n thinking?: { type: 'enabled' | 'disabled' }\n tools?: WireTool[]\n tool_choice?: WireToolChoice\n parallel_tool_calls?: boolean\n response_format?: WireResponseFormat\n /** Stable key letting the endpoint reuse a cached prompt prefix. */\n prompt_cache_key?: string\n user?: string\n}\n\n// ---------------------------------------------------------------------------\n// Response and streaming\n// ---------------------------------------------------------------------------\n\n/** Cached-token breakdown of the prompt count. */\nexport interface WirePromptTokensDetails {\n /** Portion of `prompt_tokens` served from cache — a SUBSET, not an addition. */\n cached_tokens?: number\n}\n\n/** Reasoning breakdown of the completion count. */\nexport interface WireCompletionTokensDetails {\n reasoning_tokens?: number\n}\n\n/** Token accounting as Chat Completions reports it. */\nexport interface WireUsage {\n prompt_tokens?: number\n prompt_tokens_details?: WirePromptTokensDetails | null\n completion_tokens?: number\n completion_tokens_details?: WireCompletionTokensDetails | null\n total_tokens?: number\n}\n\n/**\n * Why a choice stopped.\n *\n * A value other than `null` is the TERMINAL FINISH of the stream. `[DONE]` is\n * not: a truncated stream ends without ever producing one of these, and it is\n * indistinguishable from a short answer unless the translator insists on\n * seeing it.\n */\nexport type WireFinishReason =\n | 'stop'\n | 'length'\n | 'tool_calls'\n | 'content_filter'\n | 'function_call'\n\n/**\n * A tool-call fragment inside a streaming delta.\n *\n * `index` is the correlation key, NOT `id`: `id` and `function.name` arrive\n * once, usually on the first fragment, while `function.arguments` arrives in\n * many fragments that carry only `index`.\n */\nexport interface WireToolCallDelta {\n index: number\n id?: string\n type?: 'function'\n function?: {\n name?: string\n /** One fragment of the JSON string. Concatenate; do not parse. */\n arguments?: string\n }\n}\n\n/** The incremental payload of one streamed choice. */\nexport interface WireChoiceDelta {\n role?: 'assistant'\n content?: string | null\n refusal?: string | null\n /** Non-standard but widely emitted by reasoning-capable endpoints. */\n reasoning_content?: string | null\n tool_calls?: WireToolCallDelta[]\n}\n\n/** One streamed choice. */\nexport interface WireStreamChoice {\n index?: number\n delta?: WireChoiceDelta\n finish_reason?: WireFinishReason | null\n}\n\n/**\n * One decoded `data:` payload of the stream.\n *\n * The usage-bearing final chunk has `choices: []`, so an empty `choices` array\n * is normal traffic rather than a malformed event.\n */\nexport interface WireStreamChunk {\n id?: string\n object?: string\n created?: number\n model?: string\n choices?: WireStreamChoice[]\n usage?: WireUsage | null\n /** Some gateways inline an error into the stream instead of failing the HTTP call. */\n error?: WireErrorBody | null\n}\n\n/** One choice of a non-streamed response. */\nexport interface WireChoice {\n index?: number\n message?: {\n role?: string\n content?: string | null\n refusal?: string | null\n tool_calls?: WireToolCall[]\n }\n finish_reason?: WireFinishReason | null\n}\n\n/** A non-streamed response body. */\nexport interface WireResponse {\n id?: string\n object?: string\n created?: number\n model?: string\n choices?: WireChoice[]\n usage?: WireUsage | null\n error?: WireErrorBody | null\n}\n\n/** The error payload of an HTTP error body, and of inline stream errors. */\nexport interface WireErrorBody {\n type?: string\n code?: string | number\n message?: string\n param?: string | null\n}\n\n/** An HTTP error response body, which nests the payload under `error`. */\nexport interface WireErrorResponse {\n error?: WireErrorBody | string | null\n}\n\n// ---------------------------------------------------------------------------\n// Dialect\n// ---------------------------------------------------------------------------\n\n/**\n * The set of differences between endpoints that speak Chat Completions.\n *\n * Expressed as data rather than as subclasses because the differences are all\n * \"send this field, under this name, or not at all\" — behaviour is identical.\n * Every flag maps one-to-one onto the presence of a wire field, and a disabled\n * flag means the field is absent from the body entirely.\n */\nexport interface ChatCompletionsDialect {\n /** Send `temperature`, `top_p`, `frequency_penalty`, `presence_penalty`. */\n readonly sampling: boolean\n /**\n * Name of the output-length field; `false` sends no field at all.\n *\n * A three-value enum rather than a boolean because this is exactly where\n * OpenAI-compatible endpoints split into two families: newer reasoning models\n * reject `max_tokens` and require `max_completion_tokens`, while older\n * gateways only understand `max_tokens`. A boolean would force each provider\n * to fork the translator; an enum keeps one.\n */\n readonly maxTokensField: 'max_tokens' | 'max_completion_tokens' | false\n /**\n * `response_format` support, three-state.\n *\n * `'json-schema'` sends the full schema with `strict: true`,\n * `'json-object'` sends only `{ type: 'json_object' }` for endpoints that\n * accept JSON mode but not schemas, and `false` sends nothing.\n */\n readonly structuredOutputs: 'json-schema' | 'json-object' | false\n /** Send `tools` + `tool_choice`. */\n readonly tools: boolean\n /** Send `parallel_tool_calls`. */\n readonly parallelToolCalls: boolean\n /** Send `stream_options: { include_usage: true }`. */\n readonly streamUsage: boolean\n /** Role the system prompt travels under in `messages[0]`. */\n readonly systemRole: 'system' | 'developer'\n /** Send `stop`. */\n readonly stop: boolean\n /** Send `seed`. */\n readonly seed: boolean\n /**\n * How this endpoint wants to be told how hard to think, or `false` to send\n * nothing regardless of the effort the caller asked for.\n *\n * `'openai'` sends `reasoning_effort` alone (the value passed through\n * verbatim — see decision 1 in the redesign plan). `'deepseek'` matches an\n * endpoint that reasons unless told not to: every effort except `'off'`\n * sends `thinking: {type: 'enabled'}` beside `reasoning_effort`, and\n * `'off'` sends `thinking: {type: 'disabled'}` and omits `reasoning_effort`\n * entirely, mirroring `.temp/deepseek-harness`'s `thinkingFormat: 'deepseek'`\n * (battle-tested there, not guessed here).\n *\n * `'openrouter' | 'qwen'` are deliberately NOT implemented yet: this SDK\n * has no live credentials or verified wire capture for either endpoint's\n * reasoning field, and decision 7 (honest behaviour, no guessing) rules out\n * shipping an unverified shape under a name that promises otherwise.\n */\n readonly reasoningFormat: 'openai' | 'deepseek' | false\n /** Prompt-cache key, sent when the endpoint accepts one. */\n readonly promptCacheKey?: string\n /**\n * Endpoint path appended to the base URL.\n *\n * Configurable because a gateway is free to mount the endpoint elsewhere, and\n * hard-coding the path would make such a gateway unreachable without forking\n * the protocol.\n */\n readonly path: string\n}\n\n/**\n * Conservative defaults.\n *\n * Conservative in one direction on purpose: a field an endpoint does not\n * understand is usually a hard HTTP 400, while a field left unsent merely\n * forgoes a feature. So `parallelToolCalls`, `seed` and `reasoningFormat` — the\n * three fields older gateways most often reject — stay OFF until a provider\n * opts in.\n *\n * Frozen because `defaultDialect` is snapshotted by the runtime and is shared\n * across every adapter built on this protocol; a mutable default is a\n * cross-provider side channel.\n */\nexport const DEFAULT_DIALECT: ChatCompletionsDialect = Object.freeze({\n sampling: true,\n maxTokensField: 'max_tokens',\n structuredOutputs: 'json-schema',\n tools: true,\n parallelToolCalls: false,\n streamUsage: true,\n systemRole: 'system',\n stop: true,\n seed: false,\n reasoningFormat: false,\n path: '/chat/completions',\n} as const satisfies ChatCompletionsDialect)\n","/**\n * The OpenAI Chat Completions protocol, as a reusable wire protocol.\n *\n * Spoken by `api.openai.com`, by Azure OpenAI deployments, by editor-subscription\n * gateways, and by most self-hosted proxies. None of them needs its own\n * translation code — they differ only in the dialect knobs, which travel as data\n * ({@link ChatCompletionsDialect}) rather than as forks of the serializer.\n *\n * Nothing here names a particular endpoint. Base URL, headers and dialect all\n * arrive as parameters, which is what keeps this package reusable by any\n * OpenAI-compatible provider.\n *\n * @module ai-agent-sdk/protocols/openai-chat-completions/protocol\n */\n\nimport type {\n ProtocolDefinition,\n ProtocolRequest,\n ProtocolSseEvent,\n ProtocolStreamChunk,\n} from './contract.ts'\nimport type { ModelTarget, ResolvedModelInfo } from '@alvin0/ai-agent-sdk-core/provider'\nimport { serializeChatCompletionsRequest } from './serialize.ts'\nimport { translateChatCompletionsStream } from './translate.ts'\nimport { DEFAULT_DIALECT, type ChatCompletionsDialect } from './wire.ts'\n\n/** Protocol id, usable as a stable string in configuration. */\nexport const OPENAI_CHAT_COMPLETIONS_PROTOCOL_ID = 'openai-chat-completions'\n\ninterface RuntimeProtocolRequest extends ProtocolRequest {\n readonly model: ResolvedModelInfo\n readonly connection: {\n readonly baseUrl: string\n readonly headers: Readonly<Record<string, string>>\n }\n}\n\n/** Marker-based runtime view, kept structurally independent from provider-http. */\nexport interface ChatCompletionsProtocolDefinition {\n readonly kind: 'http-wire-protocol'\n readonly apiVersion: 1\n readonly id: string\n readonly defaultDialect: ChatCompletionsDialect\n readonly exampleModel?: ModelTarget\n readonly endpointPath: (\n request: RuntimeProtocolRequest,\n dialect: ChatCompletionsDialect,\n ) => string\n readonly protocolHeaders?: (\n dialect: ChatCompletionsDialect,\n ) => Readonly<Record<string, string>>\n readonly serialize: (\n request: RuntimeProtocolRequest,\n dialect: ChatCompletionsDialect,\n ) => Readonly<Record<string, unknown>>\n readonly translate: (\n events: AsyncIterable<ProtocolSseEvent>,\n request: RuntimeProtocolRequest,\n displayName: string,\n ) => AsyncGenerator<ProtocolStreamChunk>\n}\n\n/** The OpenAI Chat Completions wire protocol. */\nexport const openAiChatCompletionsProtocol: ProtocolDefinition<ChatCompletionsDialect>\n & ChatCompletionsProtocolDefinition = Object.freeze({\n kind: 'http-wire-protocol' as const,\n apiVersion: 1 as const,\n id: OPENAI_CHAT_COMPLETIONS_PROTOCOL_ID,\n // Flat and finite, so the runtime's config snapshot survives it unchanged.\n defaultDialect: DEFAULT_DIALECT,\n // The path is a dialect knob: a gateway is free to mount the endpoint\n // elsewhere, and hard-coding it would make such a gateway unreachable.\n endpointPath: (_request: ProtocolRequest, dialect: ChatCompletionsDialect): string =>\n dialect.path,\n serialize(\n request: ProtocolRequest,\n dialect: ChatCompletionsDialect,\n ): Readonly<Record<string, unknown>> {\n return serializeChatCompletionsRequest(request, dialect) as unknown as Readonly<\n Record<string, unknown>\n >\n },\n // Params are annotated because `Object.freeze` erases the contextual typing the\n // `ProtocolDefinition` annotation would otherwise supply.\n translate: (\n events: AsyncIterable<ProtocolSseEvent>,\n _request: ProtocolRequest,\n displayName: string,\n ): AsyncGenerator<ProtocolStreamChunk> => translateChatCompletionsStream(events, displayName),\n})\n\nexport type { ChatCompletionsDialect }\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"name": "alvin0 - chaulamdinhai",
|
|
5
5
|
"email": "chaulamdinhai@gmail.com"
|
|
6
6
|
},
|
|
7
|
-
"version": "0.1.
|
|
7
|
+
"version": "0.1.4",
|
|
8
8
|
"description": "Universal OpenAI Chat Completions wire schema, serializer, translator, and dialect for ai-agent-sdk",
|
|
9
9
|
"license": "MIT",
|
|
10
10
|
"repository": {
|
|
@@ -38,10 +38,10 @@
|
|
|
38
38
|
"provenance": true
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@alvin0/ai-agent-sdk-core": "^0.1.
|
|
41
|
+
"@alvin0/ai-agent-sdk-core": "^0.1.4"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@alvin0/ai-agent-sdk-core": "^0.1.
|
|
44
|
+
"@alvin0/ai-agent-sdk-core": "^0.1.4",
|
|
45
45
|
"@arethetypeswrong/cli": "0.18.5",
|
|
46
46
|
"playwright": "1.62.1",
|
|
47
47
|
"publint": "0.3.24",
|