@alvin0/ai-agent-sdk-protocol-openai-chat-completions 0.1.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 alvin0 (chaulamdinhai) <chaulamdinhai@gmail.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,18 @@
1
+ # @alvin0/ai-agent-sdk-protocol-openai-chat-completions
2
+
3
+ Runtime: **Universal** (Edge/Worker, browser, Deno, Bun, and Node).
4
+
5
+ ```sh
6
+ pnpm add @alvin0/ai-agent-sdk-core @alvin0/ai-agent-sdk-protocol-openai-chat-completions
7
+ ```
8
+
9
+ Universal OpenAI Chat Completions wire schema, request serializer, stream translator, and dialect. It owns no endpoint, credentials, fetch implementation, filesystem access, or Node.js APIs, and it knows nothing about any specific provider that happens to speak this wire format.
10
+
11
+ ```ts
12
+ import { openAiChatCompletionsProtocol } from '@alvin0/ai-agent-sdk-protocol-openai-chat-completions'
13
+ ```
14
+
15
+ Composition: `provider-author.protocol`. Lifecycle: `inert-value`; select it in
16
+ `createRuntimeHttpProvider()` without any startup or cleanup obligation.
17
+
18
+ The only runtime dependency is `@alvin0/ai-agent-sdk-core`.
@@ -0,0 +1,530 @@
1
+ import { GenerateOptions, ModelError, ProviderRequestId, StreamChunk, UsageCounters } from "@alvin0/ai-agent-sdk-core";
2
+ import { ModelTarget, ResolvedModelInfo } from "@alvin0/ai-agent-sdk-core/provider";
3
+ //#region src/contract.d.ts
4
+ /**
5
+ * The request fields a pure wire protocol is allowed to inspect.
6
+ *
7
+ * `model` is present at every decision point — `endpointPath`, `serialize` and
8
+ * `translate` all receive this shape — which is what makes model-keyed routing
9
+ * possible for a composite protocol built on top of this one.
10
+ */
11
+ interface ProtocolRequest {
12
+ readonly options: GenerateOptions;
13
+ readonly model: ResolvedModelInfo;
14
+ readonly maxTokens: number;
15
+ }
16
+ /** One decoded SSE event, expressed without depending on an HTTP transport. */
17
+ interface ProtocolSseEvent {
18
+ readonly event: string | undefined;
19
+ readonly data: string;
20
+ }
21
+ /** Protocol-internal chunks may carry a partial untrusted usage report. */
22
+ type ProtocolStreamChunk = Exclude<StreamChunk, {
23
+ readonly type: 'usage';
24
+ }> | {
25
+ readonly type: 'usage';
26
+ readonly usage: UsageCounters;
27
+ };
28
+ /** Structural protocol contract implemented without importing provider-http. */
29
+ interface ProtocolDefinition<Dialect> {
30
+ readonly id: string;
31
+ readonly defaultDialect: Dialect;
32
+ endpointPath(request: ProtocolRequest, dialect: Dialect): string;
33
+ protocolHeaders?(dialect: Dialect): Record<string, string>;
34
+ serialize(request: ProtocolRequest, dialect: Dialect): unknown | Promise<unknown>;
35
+ translate(events: AsyncIterable<ProtocolSseEvent>, request: ProtocolRequest, displayName: string): AsyncGenerator<ProtocolStreamChunk>;
36
+ }
37
+ //#endregion
38
+ //#region src/wire.d.ts
39
+ /**
40
+ * The OpenAI Chat Completions wire shapes and the dialect describing how one
41
+ * endpoint differs from another.
42
+ *
43
+ * "OpenAI-compatible" is a family, not a single API: gateways, Azure
44
+ * deployments, editor-subscription endpoints, and self-hosted proxies all speak
45
+ * this protocol with small, well-known divergences — which field caps the
46
+ * output length, whether
47
+ * `parallel_tool_calls` is accepted at all, whether the system prompt travels
48
+ * as `system` or as `developer`. Those divergences are expressed here as DATA
49
+ * ({@link ChatCompletionsDialect}) so the serializer and the translator stay
50
+ * single-implementation.
51
+ *
52
+ * These types never leave this folder except through `ChatCompletionsDialect`.
53
+ *
54
+ * @module ai-agent-sdk/protocols/openai-chat-completions/wire
55
+ */
56
+ /** Detail level requested for an input image. */
57
+ type WireImageDetail = 'auto' | 'low' | 'high';
58
+ /** One part of a multimodal message's content. */
59
+ type WireContentPart = {
60
+ type: 'text';
61
+ text: string;
62
+ } | {
63
+ type: 'image_url';
64
+ image_url: {
65
+ url: string;
66
+ detail?: WireImageDetail;
67
+ };
68
+ };
69
+ /**
70
+ * A tool call as the assistant turn carries it.
71
+ *
72
+ * `arguments` is a JSON-encoded STRING, not an object. When replaying a prior
73
+ * turn the string is sent back BYTE-FOR-BYTE as received: a
74
+ * `JSON.parse`/`JSON.stringify` round-trip reorders keys and renormalizes
75
+ * numbers, and some models use that exact string as context.
76
+ */
77
+ interface WireToolCall {
78
+ id: string;
79
+ type: 'function';
80
+ function: {
81
+ name: string;
82
+ /** JSON-encoded STRING, verbatim. */
83
+ arguments: string;
84
+ };
85
+ }
86
+ /**
87
+ * One entry of the `messages` array.
88
+ *
89
+ * Note the shape of the conversation: unlike the Responses API's flat item
90
+ * list, this is a list of MESSAGES. A tool result is its own message with
91
+ * `role: 'tool'` correlated by `tool_call_id`, and an assistant turn that spoke
92
+ * and called two tools stays a single message carrying both `content` and
93
+ * `tool_calls`.
94
+ */
95
+ type WireMessage = {
96
+ /** `developer` is the newer spelling; which one to use is a dialect knob. */
97
+ role: 'system' | 'developer';
98
+ content: string;
99
+ } | {
100
+ role: 'user';
101
+ content: string | WireContentPart[];
102
+ } | {
103
+ role: 'assistant';
104
+ /** Absent or null on a turn that only called tools. */
105
+ content?: string | null;
106
+ tool_calls?: WireToolCall[];
107
+ } | {
108
+ role: 'tool';
109
+ /** Correlates with the `id` of the assistant's tool call. */
110
+ tool_call_id: string;
111
+ content: string;
112
+ };
113
+ /** A function tool, nested under a `function` key rather than flat. */
114
+ interface WireFunctionTool {
115
+ type: 'function';
116
+ function: {
117
+ name: string;
118
+ description?: string;
119
+ parameters?: Record<string, unknown>;
120
+ strict?: boolean;
121
+ };
122
+ }
123
+ type WireTool = WireFunctionTool;
124
+ /** How the model must choose among the offered tools. */
125
+ type WireToolChoice = 'auto' | 'none' | 'required' | {
126
+ type: 'function';
127
+ function: {
128
+ name: string;
129
+ };
130
+ };
131
+ /** Output format controls. */
132
+ type WireResponseFormat = {
133
+ type: 'text';
134
+ } | {
135
+ type: 'json_object';
136
+ } | {
137
+ type: 'json_schema';
138
+ json_schema: {
139
+ name: string;
140
+ schema: Readonly<Record<string, unknown>>;
141
+ strict: true;
142
+ };
143
+ };
144
+ /** Streaming extras. */
145
+ interface WireStreamOptions {
146
+ include_usage?: boolean;
147
+ }
148
+ /**
149
+ * The request body.
150
+ *
151
+ * Every optional field here is gated by a {@link ChatCompletionsDialect} flag,
152
+ * and a disabled flag means the key is ABSENT — never `null`, never a default
153
+ * value. Some gateways reject unknown-but-null keys outright, and a default
154
+ * value silently changes behaviour the caller never asked for.
155
+ */
156
+ interface WireRequest {
157
+ model: string;
158
+ messages: WireMessage[];
159
+ stream: boolean;
160
+ stream_options?: WireStreamOptions;
161
+ /** Present under whichever name `dialect.maxTokensField` names. */
162
+ max_tokens?: number;
163
+ /** The reasoning-model spelling of the same cap. */
164
+ max_completion_tokens?: number;
165
+ temperature?: number;
166
+ top_p?: number;
167
+ frequency_penalty?: number;
168
+ presence_penalty?: number;
169
+ stop?: string | string[];
170
+ seed?: number;
171
+ reasoning_effort?: string;
172
+ tools?: WireTool[];
173
+ tool_choice?: WireToolChoice;
174
+ parallel_tool_calls?: boolean;
175
+ response_format?: WireResponseFormat;
176
+ /** Stable key letting the endpoint reuse a cached prompt prefix. */
177
+ prompt_cache_key?: string;
178
+ user?: string;
179
+ }
180
+ /** Cached-token breakdown of the prompt count. */
181
+ interface WirePromptTokensDetails {
182
+ /** Portion of `prompt_tokens` served from cache — a SUBSET, not an addition. */
183
+ cached_tokens?: number;
184
+ }
185
+ /** Reasoning breakdown of the completion count. */
186
+ interface WireCompletionTokensDetails {
187
+ reasoning_tokens?: number;
188
+ }
189
+ /** Token accounting as Chat Completions reports it. */
190
+ interface WireUsage {
191
+ prompt_tokens?: number;
192
+ prompt_tokens_details?: WirePromptTokensDetails | null;
193
+ completion_tokens?: number;
194
+ completion_tokens_details?: WireCompletionTokensDetails | null;
195
+ total_tokens?: number;
196
+ }
197
+ /**
198
+ * Why a choice stopped.
199
+ *
200
+ * A value other than `null` is the TERMINAL FINISH of the stream. `[DONE]` is
201
+ * not: a truncated stream ends without ever producing one of these, and it is
202
+ * indistinguishable from a short answer unless the translator insists on
203
+ * seeing it.
204
+ */
205
+ type WireFinishReason = 'stop' | 'length' | 'tool_calls' | 'content_filter' | 'function_call';
206
+ /**
207
+ * A tool-call fragment inside a streaming delta.
208
+ *
209
+ * `index` is the correlation key, NOT `id`: `id` and `function.name` arrive
210
+ * once, usually on the first fragment, while `function.arguments` arrives in
211
+ * many fragments that carry only `index`.
212
+ */
213
+ interface WireToolCallDelta {
214
+ index: number;
215
+ id?: string;
216
+ type?: 'function';
217
+ function?: {
218
+ name?: string;
219
+ /** One fragment of the JSON string. Concatenate; do not parse. */
220
+ arguments?: string;
221
+ };
222
+ }
223
+ /** The incremental payload of one streamed choice. */
224
+ interface WireChoiceDelta {
225
+ role?: 'assistant';
226
+ content?: string | null;
227
+ refusal?: string | null;
228
+ /** Non-standard but widely emitted by reasoning-capable endpoints. */
229
+ reasoning_content?: string | null;
230
+ tool_calls?: WireToolCallDelta[];
231
+ }
232
+ /** One streamed choice. */
233
+ interface WireStreamChoice {
234
+ index?: number;
235
+ delta?: WireChoiceDelta;
236
+ finish_reason?: WireFinishReason | null;
237
+ }
238
+ /**
239
+ * One decoded `data:` payload of the stream.
240
+ *
241
+ * The usage-bearing final chunk has `choices: []`, so an empty `choices` array
242
+ * is normal traffic rather than a malformed event.
243
+ */
244
+ interface WireStreamChunk {
245
+ id?: string;
246
+ object?: string;
247
+ created?: number;
248
+ model?: string;
249
+ choices?: WireStreamChoice[];
250
+ usage?: WireUsage | null;
251
+ /** Some gateways inline an error into the stream instead of failing the HTTP call. */
252
+ error?: WireErrorBody | null;
253
+ }
254
+ /** One choice of a non-streamed response. */
255
+ interface WireChoice {
256
+ index?: number;
257
+ message?: {
258
+ role?: string;
259
+ content?: string | null;
260
+ refusal?: string | null;
261
+ tool_calls?: WireToolCall[];
262
+ };
263
+ finish_reason?: WireFinishReason | null;
264
+ }
265
+ /** A non-streamed response body. */
266
+ interface WireResponse {
267
+ id?: string;
268
+ object?: string;
269
+ created?: number;
270
+ model?: string;
271
+ choices?: WireChoice[];
272
+ usage?: WireUsage | null;
273
+ error?: WireErrorBody | null;
274
+ }
275
+ /** The error payload of an HTTP error body, and of inline stream errors. */
276
+ interface WireErrorBody {
277
+ type?: string;
278
+ code?: string | number;
279
+ message?: string;
280
+ param?: string | null;
281
+ }
282
+ /** An HTTP error response body, which nests the payload under `error`. */
283
+ interface WireErrorResponse {
284
+ error?: WireErrorBody | string | null;
285
+ }
286
+ /**
287
+ * The set of differences between endpoints that speak Chat Completions.
288
+ *
289
+ * Expressed as data rather than as subclasses because the differences are all
290
+ * "send this field, under this name, or not at all" — behaviour is identical.
291
+ * Every flag maps one-to-one onto the presence of a wire field, and a disabled
292
+ * flag means the field is absent from the body entirely.
293
+ */
294
+ interface ChatCompletionsDialect {
295
+ /** Send `temperature`, `top_p`, `frequency_penalty`, `presence_penalty`. */
296
+ readonly sampling: boolean;
297
+ /**
298
+ * Name of the output-length field; `false` sends no field at all.
299
+ *
300
+ * A three-value enum rather than a boolean because this is exactly where
301
+ * OpenAI-compatible endpoints split into two families: newer reasoning models
302
+ * reject `max_tokens` and require `max_completion_tokens`, while older
303
+ * gateways only understand `max_tokens`. A boolean would force each provider
304
+ * to fork the translator; an enum keeps one.
305
+ */
306
+ readonly maxTokensField: 'max_tokens' | 'max_completion_tokens' | false;
307
+ /**
308
+ * `response_format` support, three-state.
309
+ *
310
+ * `'json-schema'` sends the full schema with `strict: true`,
311
+ * `'json-object'` sends only `{ type: 'json_object' }` for endpoints that
312
+ * accept JSON mode but not schemas, and `false` sends nothing.
313
+ */
314
+ readonly structuredOutputs: 'json-schema' | 'json-object' | false;
315
+ /** Send `tools` + `tool_choice`. */
316
+ readonly tools: boolean;
317
+ /** Send `parallel_tool_calls`. */
318
+ readonly parallelToolCalls: boolean;
319
+ /** Send `stream_options: { include_usage: true }`. */
320
+ readonly streamUsage: boolean;
321
+ /** Role the system prompt travels under in `messages[0]`. */
322
+ readonly systemRole: 'system' | 'developer';
323
+ /** Send `stop`. */
324
+ readonly stop: boolean;
325
+ /** Send `seed`. */
326
+ readonly seed: boolean;
327
+ /** Send `reasoning_effort`. */
328
+ readonly reasoningEffort: boolean;
329
+ /** Prompt-cache key, sent when the endpoint accepts one. */
330
+ readonly promptCacheKey?: string;
331
+ /**
332
+ * Endpoint path appended to the base URL.
333
+ *
334
+ * Configurable because a gateway is free to mount the endpoint elsewhere, and
335
+ * hard-coding the path would make such a gateway unreachable without forking
336
+ * the protocol.
337
+ */
338
+ readonly path: string;
339
+ }
340
+ /**
341
+ * Conservative defaults.
342
+ *
343
+ * Conservative in one direction on purpose: a field an endpoint does not
344
+ * understand is usually a hard HTTP 400, while a field left unsent merely
345
+ * forgoes a feature. So `parallelToolCalls`, `seed` and `reasoningEffort` — the
346
+ * three fields older gateways most often reject — stay OFF until a provider
347
+ * opts in.
348
+ *
349
+ * Frozen because `defaultDialect` is snapshotted by the runtime and is shared
350
+ * across every adapter built on this protocol; a mutable default is a
351
+ * cross-provider side channel.
352
+ */
353
+ declare const DEFAULT_DIALECT: ChatCompletionsDialect;
354
+ //#endregion
355
+ //#region src/errors.d.ts
356
+ /**
357
+ * Recognize a moderation rejection in provider error text.
358
+ *
359
+ * Classified as `UNSUPPORTED_CONTENT` rather than as a new `CONTENT_FILTERED`
360
+ * code: the existing code already says exactly this — the request carried
361
+ * content the selected model will not accept — and it is already outside the
362
+ * default retryable set, which is the behaviour a filtered prompt needs.
363
+ * @param detail - provider error code/type/message text joined into one string.
364
+ * @returns true when the wording names content filtering or a content policy.
365
+ */
366
+ declare function isContentFilteredError(detail: string): boolean;
367
+ /**
368
+ * Map an HTTP status plus the provider's own error text onto a stable code.
369
+ *
370
+ * The status alone is not enough anywhere interesting: a 429 is either "slow
371
+ * down" (retry) or "your balance is gone" (never retry), and a 400 is either a
372
+ * prompt that overflows the context window (compact and retry), a moderation
373
+ * rejection, or a malformed request. All three distinctions are invisible in
374
+ * the status and each changes what the caller should do next, so the wording
375
+ * classifiers run in a fixed order ahead of the plain status buckets.
376
+ * @param status - status of a non-2xx response.
377
+ * @param detail - provider error code/type/message joined; empty when the body was unusable.
378
+ * @returns the normalized code, or `HTTP_{status}` when nothing classified it.
379
+ */
380
+ declare function chatCompletionsErrorCode(status: number, detail?: string): string;
381
+ /**
382
+ * Parse a `retry-after` header into milliseconds.
383
+ *
384
+ * Both defined forms appear in practice — delta-seconds and an HTTP date — so
385
+ * both are read. A date already in the past yields `undefined` rather than a
386
+ * negative delay, because a negative delay would be rejected downstream and
387
+ * take the whole diagnostic with it.
388
+ * @param value - the raw header value, or `null` when absent.
389
+ * @returns a positive finite delay in milliseconds, or `undefined` when absent or unusable.
390
+ */
391
+ declare function chatCompletionsRetryAfterMs(value: string | null): number | undefined;
392
+ /**
393
+ * Extract a provider request id for diagnostics.
394
+ *
395
+ * Nothing programmatic reads it, and it is still worth carrying: when an
396
+ * endpoint misbehaves, this id is the only handle its operators have for
397
+ * finding the request.
398
+ * @param headers - the response headers.
399
+ * @returns the first non-empty id found, or `undefined`.
400
+ */
401
+ declare function chatCompletionsRequestId(headers: Headers): ProviderRequestId | undefined;
402
+ /** An error body reduced to the two things the mapping needs. */
403
+ interface ParsedChatCompletionsError {
404
+ /** Best human-readable message found, or `undefined` to fall back to the status. */
405
+ readonly message: string | undefined;
406
+ /** Provider `code`/`type`/`message` joined, as input to the wording classifiers. */
407
+ readonly detail: string;
408
+ }
409
+ /**
410
+ * Reduce an error body to a message and a classifier detail string.
411
+ *
412
+ * Tolerates every shape actually seen on this wire: the canonical
413
+ * `{error: {code, type, message}}`, a bare `{type, message}` with no wrapper, a
414
+ * `{detail: "..."}` in the FastAPI style some compatible endpoints use, an
415
+ * `{error: "..."}` carrying a plain string, and a body that is not JSON at all
416
+ * — which is what a gateway or load balancer sitting in front of the endpoint
417
+ * returns. In the last case the status stays authoritative and the raw text,
418
+ * truncated, is still the best classifier input available.
419
+ *
420
+ * A bare-string `error` contributes to the MESSAGE only, never to `detail`.
421
+ * That asymmetry is deliberate: `detail` is what decides the code, and it is
422
+ * held byte-identical to the shared provider mapping so the same
423
+ * `(status, body)` pair cannot classify differently here than it does for an
424
+ * existing provider.
425
+ * @param raw - the response body as text.
426
+ * @returns the message and the joined detail.
427
+ */
428
+ declare function parseChatCompletionsErrorBody(raw: string): ParsedChatCompletionsError;
429
+ /** Everything known about a non-2xx Chat Completions response. */
430
+ interface ChatCompletionsHttpFailure {
431
+ /** Status of the response. */
432
+ readonly status: number;
433
+ /** The response body as text; empty when it could not be read. */
434
+ readonly body: string;
435
+ /** Response headers, read for `retry-after` and the request id. */
436
+ readonly headers: Headers;
437
+ /** Name used in the fallback message, e.g. the provider display name. */
438
+ readonly displayName: string;
439
+ /** Request URL, included in the fallback message for diagnosis. */
440
+ readonly url?: string;
441
+ }
442
+ /**
443
+ * Turn a non-2xx response into a fully populated {@link ModelError}.
444
+ *
445
+ * The provider's own message wins when there is one, because it is invariably
446
+ * more specific than anything this layer could synthesize; the synthesized
447
+ * fallback exists for bodies that carry no message at all. `cause` keeps the
448
+ * raw body so a diagnosis is possible even when the classifier found nothing.
449
+ * @param failure - the status, body, and headers of the failed response.
450
+ * @returns a ModelError carrying the code, status, retry delay, and request id.
451
+ */
452
+ declare function chatCompletionsHttpError(failure: ChatCompletionsHttpFailure): ModelError;
453
+ /**
454
+ * Map an error the endpoint inlined into a 200 stream onto the same taxonomy.
455
+ *
456
+ * Some gateways answer a rejected request with HTTP 200 and an `error` object
457
+ * inside a `data:` frame. There is no status to classify from, so the wording
458
+ * classifiers carry the whole decision, and the residual case is
459
+ * `MALFORMED_RESPONSE`: an error arriving where content was promised is a
460
+ * broken response, not a server fault to be retried.
461
+ * @param body - the `error` payload of a stream chunk.
462
+ * @param displayName - name used when the payload carries no message.
463
+ * @returns a ModelError with no `status`, since the HTTP call itself succeeded.
464
+ */
465
+ declare function chatCompletionsStreamError(body: WireErrorBody, displayName: string): ModelError;
466
+ /**
467
+ * Classify a failure raised before any status was seen.
468
+ *
469
+ * Kept apart from the status mapping because the three outcomes are decided by
470
+ * WHO ended the request, not by what the endpoint said: the caller's signal
471
+ * (`ABORTED`), a deadline (`TIMEOUT`), or the network (`TRANSPORT`). Web
472
+ * platform semantics supply the distinction — `AbortSignal.timeout` rejects
473
+ * with a `TimeoutError`, an explicit `abort()` with an `AbortError` — so no
474
+ * message parsing is involved.
475
+ * @param error - the thrown value.
476
+ * @param fallbackMessage - message used when the thrown value carries none.
477
+ * @returns a ModelError coded ABORTED, TIMEOUT, or TRANSPORT.
478
+ */
479
+ declare function chatCompletionsTransportError(error: unknown, fallbackMessage: string): ModelError;
480
+ //#endregion
481
+ //#region src/protocol.d.ts
482
+ /** Protocol id, usable as a stable string in configuration. */
483
+ declare const OPENAI_CHAT_COMPLETIONS_PROTOCOL_ID = "openai-chat-completions";
484
+ interface RuntimeProtocolRequest extends ProtocolRequest {
485
+ readonly model: ResolvedModelInfo;
486
+ readonly connection: {
487
+ readonly baseUrl: string;
488
+ readonly headers: Readonly<Record<string, string>>;
489
+ };
490
+ }
491
+ /** Marker-based runtime view, kept structurally independent from provider-http. */
492
+ interface ChatCompletionsProtocolDefinition {
493
+ readonly kind: 'http-wire-protocol';
494
+ readonly apiVersion: 1;
495
+ readonly id: string;
496
+ readonly defaultDialect: ChatCompletionsDialect;
497
+ readonly exampleModel?: ModelTarget;
498
+ readonly endpointPath: (request: RuntimeProtocolRequest, dialect: ChatCompletionsDialect) => string;
499
+ readonly protocolHeaders?: (dialect: ChatCompletionsDialect) => Readonly<Record<string, string>>;
500
+ readonly serialize: (request: RuntimeProtocolRequest, dialect: ChatCompletionsDialect) => Readonly<Record<string, unknown>>;
501
+ readonly translate: (events: AsyncIterable<ProtocolSseEvent>, request: RuntimeProtocolRequest, displayName: string) => AsyncGenerator<ProtocolStreamChunk>;
502
+ }
503
+ /** The OpenAI Chat Completions wire protocol. */
504
+ declare const openAiChatCompletionsProtocol: ProtocolDefinition<ChatCompletionsDialect> & ChatCompletionsProtocolDefinition;
505
+ //#endregion
506
+ //#region src/serialize.d.ts
507
+ /**
508
+ * Build the Chat Completions request body.
509
+ * @param request - the resolved request, model, and output cap.
510
+ * @param dialect - which optional fields this endpoint accepts.
511
+ * @returns the wire body, ready to serialize.
512
+ */
513
+ declare function serializeChatCompletionsRequest(request: ProtocolRequest, dialect: ChatCompletionsDialect): WireRequest;
514
+ //#endregion
515
+ //#region src/translate.d.ts
516
+ /**
517
+ * Translate one Chat Completions SSE stream.
518
+ *
519
+ * Owns termination, and refuses to invent one: the stream is complete only when
520
+ * a `finish_reason` other than `null` has been seen. Events after that point are
521
+ * still read, because the `usage` chunk arrives there — with `choices: []`,
522
+ * which is normal traffic rather than a malformed event.
523
+ * @param events - decoded SSE events.
524
+ * @param displayName - provider name, used in diagnostics.
525
+ * @returns the chunk stream.
526
+ */
527
+ declare function translateChatCompletionsStream(events: AsyncIterable<ProtocolSseEvent>, displayName: string): AsyncGenerator<ProtocolStreamChunk>;
528
+ //#endregion
529
+ export { type ChatCompletionsDialect, type ChatCompletionsHttpFailure, type ChatCompletionsProtocolDefinition, type DEFAULT_DIALECT, OPENAI_CHAT_COMPLETIONS_PROTOCOL_ID, type ParsedChatCompletionsError, type ProtocolDefinition, type ProtocolRequest, type ProtocolSseEvent, type ProtocolStreamChunk, type WireChoice, type WireChoiceDelta, type WireCompletionTokensDetails, type WireContentPart, type WireErrorBody, type WireErrorResponse, type WireFinishReason, type WireFunctionTool, type WireImageDetail, type WireMessage, type WirePromptTokensDetails, type WireRequest, type WireResponse, type WireResponseFormat, type WireStreamChoice, type WireStreamChunk, type WireStreamOptions, type WireTool, type WireToolCall, type WireToolCallDelta, type WireToolChoice, type WireUsage, chatCompletionsErrorCode, chatCompletionsHttpError, chatCompletionsRequestId, chatCompletionsRetryAfterMs, chatCompletionsStreamError, chatCompletionsTransportError, isContentFilteredError, openAiChatCompletionsProtocol, parseChatCompletionsErrorBody, serializeChatCompletionsRequest, translateChatCompletionsStream };
530
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +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;WACP;;;UAIM;WACN;WACA;;;KAIC,sBACR,QAAQ;WAAwB;;WACrB;WAAwB,OAAO;;;UAG7B,mBAAmB;WACzB;WACA,gBAAgB;EACzB,aAAa,SAAS,iBAAiB,SAAS;EAChD,iBAAiB,SAAS,UAAU;EACpC,UAAU,SAAS,iBAAiB,SAAS,oBAAoB;EACjE,UACE,QAAQ,cAAc,mBACtB,SAAS,iBACT,sBACC,eAAe;;;;;;;;;;;;;;;;;;;;;;KC5BR;;KAGA;EACN;EAAc;;EACd;EAAmB;IAAa;IAAa,SAAS;;;;;;;;;;;UAU3C;EACf;EACA;EACA;IACE;;IAEA;;;;;;;;;;;;KAaQ;;EAGR;EACA;;EAGA;EACA,kBAAkB;;EAGlB;;EAEA;EACA,aAAa;;EAGb;;EAEA;EACA;;;UAIa;EACf;EACA;IACE;IACA;IACA,aAAa;IACb;;;KAIQ,WAAW;;KAGX;EAIN;EAAkB;IAAY;;;;KAGxB;EACN;;EACA;;EAEF;EACA;IACE;IACA,QAAQ,SAAS;IACjB;;;;UAKW;EACf;;;;;;;;;;UAWe;EACf;EACA,UAAU;EACV;EACA,iBAAiB;;EAEjB;;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,QAAQ;EACR,cAAc;EACd;EACA,kBAAkB;;EAElB;EACA;;;UAQe;;EAEf;;;UAIe;EACf;;;UAIe;EACf;EACA,wBAAwB;EACxB;EACA,4BAA4B;EAC5B;;;;;;;;;;KAWU;;;;;;;;UAcK;EACf;EACA;EACA;EACA;IACE;;IAEA;;;;UAKa;EACf;EACA;EACA;;EAEA;EACA,aAAa;;;UAIE;EACf;EACA,QAAQ;EACR,gBAAgB;;;;;;;;UASD;EACf;EACA;EACA;EACA;EACA,UAAU;EACV,QAAQ;;EAER,QAAQ;;;UAIO;EACf;EACA;IACE;IACA;IACA;IACA,aAAa;;EAEf,gBAAgB;;;UAID;EACf;EACA;EACA;EACA;EACA,UAAU;EACV,QAAQ;EACR,QAAQ;;;UAIO;EACf;EACA;EACA;EACA;;;UAIe;EACf,QAAQ;;;;;;;;;;UAeO;;WAEN;;;;;;;;;;WAUA;;;;;;;;WAQA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;;;;;;;WAQA;;;;;;;;;;;;;;;cAgBE,iBAAiB;;;;;;;;;;;;;iBCjSd,uBAAuB;;;;;;;;;;;;;;iBAiBvB,yBAAyB,gBAAgB;;;;;;;;;;;iBAgCzC,4BAA4B;;;;;;;;;;iBA4B5B,yBAAyB,SAAS,UAAU;;UAS3C;;WAEN;;WAEA;;;;;;;;;;;;;;;;;;;;;iBA6BK,8BAA8B,cAAc;;UAyB3C;;WAEN;;WAEA;;WAEA,SAAS;;WAET;;WAEA;;;;;;;;;;;;iBAaK,yBAAyB,SAAS,6BAA6B;;;;;;;;;;;;;iBA6B/D,2BACd,MAAM,eACN,sBACC;;;;;;;;;;;;;;iBAgCa,8BACd,gBACA,0BACC;;;;cC5QU;UAEH,+BAA+B;WAC9B,OAAO;WACP;aACE;aACA,SAAS,SAAS;;;;UAKd;WACN;WACA;WACA;WACA,gBAAgB;WAChB,eAAe;WACf,eACP,SAAS,wBACT,SAAS;WAEF,mBACP,SAAS,2BACN,SAAS;WACL,YACP,SAAS,wBACT,SAAS,2BACN,SAAS;WACL,YACP,QAAQ,cAAc,mBACtB,SAAS,wBACT,wBACG,eAAe;;;cAIT,+BAA+B,mBAAmB,0BAC3D;;;;;;;;;iBCyLY,gCACd,SAAS,iBACT,SAAS,yBACR;;;;;;;;;;;;;;iBCAoB,+BACrB,QAAQ,cAAc,mBACtB,sBACC,eAAe"}