@combycode/llm-sdk 2.2.2 → 3.0.0

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.
Files changed (95) hide show
  1. package/CHANGELOG.md +555 -0
  2. package/MIGRATION.md +130 -0
  3. package/dist/agent/loop-internals.d.ts +4 -0
  4. package/dist/agent/loop.d.ts +35 -0
  5. package/dist/bus/hook-bus.d.ts +13 -3
  6. package/dist/bus/hook-map.d.ts +24 -0
  7. package/dist/{llm/providers → catalog}/builtin-tools.d.ts +1 -1
  8. package/dist/{plugins/model-catalog → catalog}/catalog.d.ts +19 -0
  9. package/dist/helpers/client-pool.d.ts +1 -1
  10. package/dist/helpers/client-resolver.d.ts +22 -3
  11. package/dist/helpers/count-tokens.d.ts +1 -1
  12. package/dist/helpers/engine.d.ts +39 -6
  13. package/dist/helpers/mcp.d.ts +6 -1
  14. package/dist/helpers/models.d.ts +1 -1
  15. package/dist/helpers/one-shot.d.ts +2 -2
  16. package/dist/helpers/select-model.d.ts +1 -1
  17. package/dist/index.browser.js +17611 -4247
  18. package/dist/index.d.ts +5 -5
  19. package/dist/index.js +17611 -4247
  20. package/dist/llm/client-config.d.ts +9 -1
  21. package/dist/llm/client-internal.d.ts +11 -0
  22. package/dist/llm/client.d.ts +6 -0
  23. package/dist/llm/providers/_shared/sse.d.ts +19 -0
  24. package/dist/llm/providers/anthropic/batch.d.ts +13 -2
  25. package/dist/llm/providers/anthropic/constants.d.ts +8 -43
  26. package/dist/llm/providers/anthropic/files.d.ts +13 -2
  27. package/dist/llm/providers/anthropic/messages.d.ts +18 -1
  28. package/dist/llm/providers/google/batch.d.ts +14 -1
  29. package/dist/llm/providers/google/constants.d.ts +6 -15
  30. package/dist/llm/providers/google/embeddings.d.ts +8 -1
  31. package/dist/llm/providers/google/files.d.ts +34 -1
  32. package/dist/llm/providers/google/generate.d.ts +15 -1
  33. package/dist/llm/providers/google/interactions.d.ts +5 -1
  34. package/dist/llm/providers/google/media.d.ts +45 -4
  35. package/dist/llm/providers/google/realtime.d.ts +15 -2
  36. package/dist/llm/providers/openai/batch.d.ts +19 -2
  37. package/dist/llm/providers/openai/completions.d.ts +13 -1
  38. package/dist/llm/providers/openai/embeddings.d.ts +13 -1
  39. package/dist/llm/providers/openai/files.d.ts +13 -2
  40. package/dist/llm/providers/openai/media.d.ts +31 -2
  41. package/dist/llm/providers/openai/moderations.d.ts +11 -1
  42. package/dist/llm/providers/openai/realtime.d.ts +18 -2
  43. package/dist/llm/providers/openai/responses.d.ts +10 -2
  44. package/dist/llm/providers/openai/transcription.d.ts +7 -1
  45. package/dist/llm/providers/openrouter/completions.d.ts +5 -3
  46. package/dist/llm/providers/openrouter/embeddings.d.ts +3 -0
  47. package/dist/llm/providers/openrouter/media.d.ts +14 -4
  48. package/dist/llm/providers/openrouter/responses.d.ts +5 -3
  49. package/dist/llm/providers/xai/batch.d.ts +13 -2
  50. package/dist/llm/providers/xai/completions.d.ts +5 -3
  51. package/dist/llm/providers/xai/files.d.ts +13 -2
  52. package/dist/llm/providers/xai/media.d.ts +28 -10
  53. package/dist/llm/providers/xai/responses.d.ts +5 -3
  54. package/dist/llm/response-shape.d.ts +96 -0
  55. package/dist/llm/server-state.d.ts +1 -1
  56. package/dist/llm/types/options.d.ts +2 -2
  57. package/dist/llm/types/provider.d.ts +6 -0
  58. package/dist/llm/types/request.d.ts +49 -1
  59. package/dist/llm/wire-multipart.d.ts +26 -0
  60. package/dist/llm/wire-transforms.d.ts +28 -0
  61. package/dist/plugins/context-measurer/counter/count-api.d.ts +40 -6
  62. package/dist/plugins/context-measurer/counter/heuristic.d.ts +1 -1
  63. package/dist/plugins/context-measurer/counter/hybrid.d.ts +34 -1
  64. package/dist/plugins/context-measurer/counter/tiktoken.d.ts +7 -0
  65. package/dist/plugins/context-measurer/measurer.d.ts +1 -1
  66. package/dist/plugins/cost-collector/collector.d.ts +1 -1
  67. package/dist/plugins/cost-collector/cost-collector-internal.d.ts +1 -1
  68. package/dist/plugins/cost-collector/cost-collector-types.d.ts +1 -1
  69. package/dist/plugins/files/registry.d.ts +1 -1
  70. package/dist/plugins/files/strategy.d.ts +1 -1
  71. package/dist/plugins/internal-tools/registry.d.ts +1 -1
  72. package/dist/plugins/internal-tools/runner/types.d.ts +1 -1
  73. package/dist/plugins/mcp/sampling.d.ts +23 -1
  74. package/dist/plugins/mcp/transport-http.d.ts +13 -7
  75. package/dist/plugins/mcp/wire-rules.d.ts +21 -0
  76. package/dist/plugins/media/output.d.ts +1 -1
  77. package/dist/plugins/retrieval/document-file.d.ts +15 -0
  78. package/dist/plugins/retrieval/hosted-google.d.ts +8 -3
  79. package/dist/plugins/retrieval/hosted-openai.d.ts +9 -1
  80. package/dist/plugins/retrieval/hosted-xai.d.ts +8 -2
  81. package/dist/plugins/telemetry/telemetry.d.ts +2 -133
  82. package/dist/plugins/telemetry/types.d.ts +139 -0
  83. package/dist/util/hash.d.ts +8 -0
  84. package/dist/{plugins/media → util}/source-image.d.ts +1 -1
  85. package/dist/wire/chat-specs.d.ts +32 -0
  86. package/dist/wire/inherit.d.ts +43 -0
  87. package/dist/wire/interpreter.d.ts +279 -0
  88. package/dist/wire/mcp-specs.d.ts +13 -0
  89. package/dist/wire/media-specs.d.ts +20 -0
  90. package/dist/wire/pins.d.ts +34 -0
  91. package/dist/wire/registry.d.ts +20 -0
  92. package/dist/wire/retrieval-specs.d.ts +14 -0
  93. package/dist/wire/service-specs.d.ts +18 -0
  94. package/dist/wire/utility-specs.d.ts +10 -0
  95. package/package.json +6 -3
@@ -1,6 +1,6 @@
1
1
  /** OpenAI file adapter — POST /v1/files.
2
2
  * All HTTP flows through the injected EngineFetch (NetworkEngine queue). */
3
- import type { EngineFetch } from '../../../network/types';
3
+ import type { EngineFetch, HttpRequest } from '../../../network/types';
4
4
  import type { FileAttachment } from '../../../plugins/files/attachment';
5
5
  import type { FileProviderAdapter, FileUploadResult, RemoteFileInfo } from '../../../plugins/files/provider-adapter';
6
6
  export interface OpenAIFileAdapterConfig {
@@ -15,7 +15,18 @@ export declare class OpenAIFileAdapter implements FileProviderAdapter {
15
15
  private readonly apiKey;
16
16
  private readonly baseURL;
17
17
  constructor(config: OpenAIFileAdapterConfig);
18
- private bearer;
18
+ /** File rules need no adapter handles. */
19
+ private readonly wireRegistry;
20
+ /** Build one file request from its spec, then add the engine metadata.
21
+ *
22
+ * A multipart spec describes the FIELDS but not the bytes, so an upload passes
23
+ * its attachment in and the descriptor is filled here. `bodyKind: none` arrives
24
+ * as `noBody`; the engine wants the field simply absent. */
25
+ private fromSpec;
26
+ buildUploadRequest(file: FileAttachment, data: Uint8Array): Promise<HttpRequest>;
27
+ buildDeleteRequest(remoteId: string): Promise<HttpRequest>;
28
+ buildGetInfoRequest(remoteId: string): Promise<HttpRequest>;
29
+ buildListRequest(): Promise<HttpRequest>;
19
30
  upload(file: FileAttachment, fetch: EngineFetch): Promise<FileUploadResult>;
20
31
  delete(remoteId: string, fetch: EngineFetch): Promise<void>;
21
32
  getInfo(remoteId: string, fetch: EngineFetch): Promise<RemoteFileInfo | null>;
@@ -1,7 +1,7 @@
1
1
  /** OpenAI media adapter — image generation (/v1/images/generations) and TTS
2
2
  * (/v1/audio/speech). All HTTP calls flow through an injected EngineFetch
3
3
  * so they share the NetworkEngine queue, rate-limits, retry, and hooks. */
4
- import type { EngineFetch } from '../../../network/types';
4
+ import type { EngineFetch, HttpRequest } from '../../../network/types';
5
5
  import type { AudioGenRequest, ImageEditRequest, ImageGenRequest, MediaCapabilities, MediaProviderAdapter, RawMediaResult, VideoGenRequest, VideoStatus } from '../../../plugins/media/types';
6
6
  export interface OpenAIMediaAdapterConfig {
7
7
  apiKey: string;
@@ -13,7 +13,36 @@ export declare class OpenAIMediaAdapter implements MediaProviderAdapter {
13
13
  private readonly baseURL;
14
14
  constructor(config: OpenAIMediaAdapterConfig);
15
15
  capabilities(): MediaCapabilities;
16
- private authHeaders;
16
+ /** Named code the specs cannot express as data — image-source normalisation. */
17
+ private readonly wireRegistry;
18
+ /** Build one media request from its spec, then add the engine metadata.
19
+ *
20
+ * `provider`, `model` and `responseType` are NOT wire: the NetworkEngine routes
21
+ * and decodes with them and no provider ever sees them, so the specs do not
22
+ * model them and this layer keeps supplying them. */
23
+ private fromSpec;
24
+ /** Text-to-image. */
25
+ /** Text-to-image.
26
+ *
27
+ * The spec forks by family, and the fork is real: gpt-image-1 always returns
28
+ * b64_json and REJECTS `response_format`, while dall-e-3 / dall-e-2 still
29
+ * require it. */
30
+ buildGenerateImageRequest(req: ImageGenRequest, model?: string): HttpRequest;
31
+ /** Image-to-image edit. Generation's field set minus `style`, plus the source
32
+ * image and an optional mask. */
33
+ /** Image-to-image edit. */
34
+ buildEditImageRequest(req: ImageEditRequest, model?: string): HttpRequest;
35
+ /** TTS. `responseType` is arraybuffer because the response is audio bytes. */
36
+ /** TTS. `responseType` is arraybuffer because the response is audio bytes —
37
+ * transport decoding, which is the engine's business rather than the wire's. */
38
+ buildAudioRequest(req: AudioGenRequest, model: string): HttpRequest;
39
+ /** Sora video submission. `seconds` goes on the wire as a string. */
40
+ /** Sora video submission. */
41
+ buildVideoRequest(req: VideoGenRequest, model?: string): HttpRequest;
42
+ /** Poll a Sora job. */
43
+ buildVideoStatusRequest(videoId: string): HttpRequest;
44
+ /** Download the finished video: bytes, hence arraybuffer. */
45
+ buildVideoContentRequest(videoId: string): HttpRequest;
17
46
  generateImage(req: ImageGenRequest, fetch: EngineFetch): Promise<RawMediaResult[]>;
18
47
  /** Parse `/v1/images/{generations,edits}` response → RawMediaResult[], with
19
48
  * the request-level usage attached to the first item (billed once). */
@@ -2,7 +2,7 @@
2
2
  * Supports text and image+text content-part input as described in
3
3
  * https://platform.openai.com/docs/api-reference/moderations/create
4
4
  * All HTTP flows through the injected EngineFetch. */
5
- import type { EngineFetch } from '../../../network/types';
5
+ import type { EngineFetch, HttpRequest } from '../../../network/types';
6
6
  import type { ModerationContentPart, ModerationResult } from '../../../helpers/moderate-types';
7
7
  export interface OpenAIModerationAdapterConfig {
8
8
  apiKey: string;
@@ -15,5 +15,15 @@ export declare class OpenAIModerationAdapter {
15
15
  private readonly apiKey;
16
16
  private readonly baseURL;
17
17
  constructor(config: OpenAIModerationAdapterConfig);
18
+ /** Named code these specs need. */
19
+ private readonly wireRegistry;
20
+ /** Build one request from its spec, then add the engine metadata.
21
+ *
22
+ * `bodyKind: none` arrives as `noBody` and `raw` as `rawBody`; the engine wants
23
+ * the body field absent in the first case and the caller's bytes in the second. */
24
+ private fromSpec;
25
+ /** Report-only classification. `input` reaches the wire untouched: string,
26
+ * array of strings, or content parts are all accepted. */
27
+ buildModerateRequest(input: string | string[] | ModerationContentPart | ModerationContentPart[], model: string): HttpRequest;
18
28
  moderate(input: string | string[] | ModerationContentPart | ModerationContentPart[], model: string, fetch: EngineFetch): Promise<ModerationResult[]>;
19
29
  }
@@ -10,8 +10,8 @@
10
10
  * response.output_audio.delta → { audio, base64 delta }
11
11
  * response.done → turn complete
12
12
  * error → error */
13
- import type { EngineConnect } from '../../../network/types';
14
- import type { RealtimeProviderAdapter, RealtimeSession, RealtimeSessionConfig } from '../../realtime/types';
13
+ import type { EngineConnect, WsRequest } from '../../../network/types';
14
+ import type { RealtimeInput, RealtimeProviderAdapter, RealtimeSession, RealtimeSessionConfig } from '../../realtime/types';
15
15
  export interface OpenAIRealtimeAdapterConfig {
16
16
  apiKey: string;
17
17
  baseURL?: string;
@@ -20,5 +20,21 @@ export declare class OpenAIRealtimeAdapter implements RealtimeProviderAdapter {
20
20
  private readonly apiKey;
21
21
  private readonly baseURL;
22
22
  constructor(config: OpenAIRealtimeAdapterConfig);
23
+ /** The WebSocket descriptor. Separated from `connect` so it can be asserted
24
+ * without opening a socket. Note the auth: OpenAI carries the key in a
25
+ * SUBPROTOCOL, not a header or query param, because browsers cannot set
26
+ * WebSocket headers. */
27
+ /** The WebSocket descriptor. Note the auth: OpenAI carries the key in a
28
+ * SUBPROTOCOL, not a header or query param, because browsers cannot set
29
+ * WebSocket headers — which is why the spec models `protocols` at all. */
30
+ buildConnectRequest(config: RealtimeSessionConfig): WsRequest;
23
31
  connect(config: RealtimeSessionConfig, connect: EngineConnect): RealtimeSession;
24
32
  }
33
+ /** The handshake frame, sent once the socket opens. */
34
+ export declare function buildOpenAISessionUpdate(config: Pick<RealtimeSessionConfig, 'modalities' | 'instructions' | 'voice'>): Record<string, unknown>;
35
+ /** The frames for one turn. `response.create` is what asks the model to reply,
36
+ * so `turnComplete: false` withholds it and the turn stays open — where Gemini
37
+ * carries the same meaning as a field on its single frame. */
38
+ export declare function buildOpenAITurnFrames(input: RealtimeInput, opts?: {
39
+ turnComplete?: boolean;
40
+ }): Array<Record<string, unknown>>;
@@ -3,7 +3,8 @@
3
3
  * Modern API: input (not messages), instructions (not system role),
4
4
  * output items (not choices), function_call/function_call_output for tools. */
5
5
  import type { SSEEvent } from '../../../network/types';
6
- import type { AssistantPhase } from '../../types/messages';
6
+ import type { AssistantPhase, Message } from '../../types/messages';
7
+ import type { Registry } from '../../../wire/interpreter';
7
8
  import type { ProviderAdapter, ProviderHttpRequest } from '../../types/provider';
8
9
  import type { NormalizedRequest } from '../../types/request';
9
10
  import { type CompletionResponse, type FileOutput, type Usage } from '../../types/response';
@@ -20,10 +21,17 @@ export declare class OpenAIResponsesAdapter implements ProviderAdapter {
20
21
  authHeaders(): Record<string, string>;
21
22
  baseURL(): string;
22
23
  completionPath(): string;
24
+ /** Named code the spec cannot express as data — message/input assembly. Carries
25
+ * `this`, so a subclass drives the same rules with its own overrides. */
26
+ protected readonly wireRegistry: Registry;
27
+ /** Which flavor overlay patches the shared spec. Subclasses for
28
+ * OpenAI-compatible backends override this and nothing else. */
29
+ protected readonly wireFlavor: string;
23
30
  buildRequest(req: NormalizedRequest): ProviderHttpRequest;
24
31
  /** Convert a universal Message to Responses API input items.
25
32
  * `toolNames` is threaded across messages so a tool result can name its originating call. */
26
- private buildInputItems;
33
+ /** Reached through the wire registry while building the request. */
34
+ buildInputItems(msg: Message, toolNames?: Map<string, string>): unknown[];
27
35
  enableStreaming(providerReq: ProviderHttpRequest): void;
28
36
  parseResponse(raw: unknown, latencyMs: number): CompletionResponse;
29
37
  parseStreamEvent(event: SSEEvent, phaseByItem?: Map<string, AssistantPhase>): StreamEvent[];
@@ -15,7 +15,7 @@
15
15
  * No model returns everything: speaker labels and word timings live on different
16
16
  * models. `keywords` / `languages` are `gpt-transcribe`-only and 400 elsewhere. */
17
17
  import type { TranscriptLanguage, TranscriptSegment, TranscriptWord } from '../../types/audio';
18
- import type { EngineFetch } from '../../../network/types';
18
+ import type { EngineFetch, HttpRequest } from '../../../network/types';
19
19
  export interface OpenAITranscriptionAdapterConfig {
20
20
  apiKey: string;
21
21
  baseURL?: string;
@@ -52,6 +52,12 @@ export declare class OpenAITranscriptionAdapter {
52
52
  private readonly apiKey;
53
53
  private readonly baseURL;
54
54
  constructor(config: OpenAITranscriptionAdapterConfig);
55
+ /** Named code these specs need. */
56
+ private readonly wireRegistry;
57
+ /** The multipart spec names the FIELDS; the audio bytes come from the request.
58
+ * `wordTimestamps` with `diarization` is rejected before this point: they select
59
+ * different response formats and no model serves both. */
60
+ buildTranscribeRequest(req: TranscriptionRequest): HttpRequest;
55
61
  transcribe(req: TranscriptionRequest, fetch: EngineFetch): Promise<OpenAITranscriptionResult>;
56
62
  }
57
63
  export declare function parseTranscription(body: unknown): OpenAITranscriptionResult;
@@ -1,7 +1,6 @@
1
1
  /** OpenRouter provider adapter — OpenAI-compatible with extensions. */
2
2
  import type { SSEEvent } from '../../../network/types';
3
- import type { ProviderAdapter, ProviderHttpRequest } from '../../types/provider';
4
- import type { NormalizedRequest } from '../../types/request';
3
+ import type { ProviderAdapter } from '../../types/provider';
5
4
  import type { CompletionResponse } from '../../types/response';
6
5
  import type { StreamEvent } from '../../types/stream';
7
6
  import { OpenAIAdapter } from '../openai/completions';
@@ -14,7 +13,10 @@ export declare class OpenRouterAdapter extends OpenAIAdapter {
14
13
  constructor(config: OpenRouterAdapterConfig);
15
14
  baseURL(): string;
16
15
  completionPath(): string;
17
- buildRequest(req: NormalizedRequest): ProviderHttpRequest;
16
+ /** Everything this class used to do to `super.buildRequest()` — the max_tokens
17
+ * rename, the reasoning strip, the tier remap, the routing passthrough — is the
18
+ * `openrouter` overlay in the shared spec. Naming the flavor IS the override now. */
19
+ protected readonly wireFlavor: string;
18
20
  parseResponse(raw: unknown, latencyMs: number): CompletionResponse;
19
21
  /** Stateful — emit a single `web_search` builtin-tool pair the first time
20
22
  * `url_citation` annotations appear in the stream (the `:online` search signal). */
@@ -4,4 +4,7 @@ export declare class OpenRouterEmbeddingAdapter extends OpenAIEmbeddingAdapter {
4
4
  readonly name: string;
5
5
  constructor(config: OpenAIEmbeddingAdapterConfig);
6
6
  protected embeddingsPath(): string;
7
+ /** The whole OpenRouter delta is the URL, so the spec that carries it is the
8
+ * whole override. */
9
+ protected specId(): string;
7
10
  }
@@ -2,7 +2,7 @@
2
2
  * image (and audio) generation go through `POST /api/v1/chat/completions` with
3
3
  * a `modalities` field; output comes back on `message.images[]` /
4
4
  * `message.audio`. Cost is the provider-reported `usage.cost`. */
5
- import type { EngineFetch } from '../../../network/types';
5
+ import type { EngineFetch, HttpRequest } from '../../../network/types';
6
6
  import type { AudioGenRequest, ImageEditRequest, ImageGenRequest, MediaCapabilities, MediaProviderAdapter, RawMediaResult } from '../../../plugins/media/types';
7
7
  export interface OpenRouterMediaAdapterConfig {
8
8
  apiKey: string;
@@ -14,12 +14,22 @@ export declare class OpenRouterMediaAdapter implements MediaProviderAdapter {
14
14
  private readonly baseURL;
15
15
  constructor(config: OpenRouterMediaAdapterConfig);
16
16
  capabilities(): MediaCapabilities;
17
- private authHeaders;
17
+ /** Named code the specs cannot express as data — the data-URL and image_config. */
18
+ private readonly wireRegistry;
19
+ /** Build one request from its spec, then add the engine metadata. */
20
+ private fromSpec;
21
+ /** Text-to-image. OpenRouter has no image endpoint: images come back from
22
+ * chat/completions with `modalities: ['image','text']`. */
23
+ buildImageRequest(req: ImageGenRequest, model?: string): HttpRequest;
24
+ /** Image-to-image: the same call with the source image as a second content part. */
25
+ buildEditImageRequest(req: ImageEditRequest, model?: string): HttpRequest;
26
+ /** Audio out, again through chat/completions. */
27
+ buildAudioRequest(req: AudioGenRequest, model?: string): HttpRequest;
28
+ /** Exposed for the wire registry: `image_config` from normalised params. */
29
+ imageConfig(params: ImageGenRequest['params']): Record<string, unknown>;
18
30
  generateImage(req: ImageGenRequest, fetch: EngineFetch): Promise<RawMediaResult[]>;
19
31
  editImage(req: ImageEditRequest, fetch: EngineFetch): Promise<RawMediaResult[]>;
20
32
  generateAudio(req: AudioGenRequest, fetch: EngineFetch): Promise<RawMediaResult>;
21
- /** image_config from normalized params (aspect_ratio / image_size / strength). */
22
- private imageConfig;
23
33
  private chat;
24
34
  private chatImage;
25
35
  }
@@ -1,8 +1,7 @@
1
1
  /** OpenRouter Responses API adapter.
2
2
  * Drop-in replacement for OpenAI Responses API at openrouter.ai/api/v1/responses.
3
3
  * Stateless: no previous_response_id support (beta limitation). */
4
- import type { ProviderAdapter, ProviderHttpRequest } from '../../types/provider';
5
- import type { NormalizedRequest } from '../../types/request';
4
+ import type { ProviderAdapter } from '../../types/provider';
6
5
  import { OpenAIResponsesAdapter } from '../openai/responses';
7
6
  export interface OpenRouterResponsesAdapterConfig {
8
7
  apiKey: string;
@@ -13,5 +12,8 @@ export declare class OpenRouterResponsesAdapter extends OpenAIResponsesAdapter {
13
12
  constructor(config: OpenRouterResponsesAdapterConfig);
14
13
  baseURL(): string;
15
14
  completionPath(): string;
16
- buildRequest(req: NormalizedRequest): ProviderHttpRequest;
15
+ /** Everything this class used to do to `super.buildRequest()` — the max_tokens
16
+ * rename, the reasoning strip, the tier remap, the routing passthrough — is the
17
+ * `openrouter` overlay in the shared spec. Naming the flavor IS the override now. */
18
+ protected readonly wireFlavor: string;
17
19
  }
@@ -1,6 +1,6 @@
1
1
  /** xAI batch adapter — create batch, add requests, poll, get results.
2
2
  * All HTTP flows through the injected EngineFetch (NetworkEngine queue). */
3
- import type { EngineFetch } from '../../../network/types';
3
+ import type { EngineFetch, HttpRequest } from '../../../network/types';
4
4
  import type { BatchProviderAdapter, BatchRequest, BatchResult, BatchStatus } from '../../../plugins/batch/types';
5
5
  export interface XAIBatchAdapterConfig {
6
6
  apiKey: string;
@@ -11,7 +11,18 @@ export declare class XAIBatchAdapter implements BatchProviderAdapter {
11
11
  private readonly apiKey;
12
12
  private readonly baseURL;
13
13
  constructor(config: XAIBatchAdapterConfig);
14
- private bearer;
14
+ /** Batch rules need no adapter handles: the request list is mapped by the spec. */
15
+ private readonly wireRegistry;
16
+ /** Build one batch request from its spec, then add the engine metadata.
17
+ *
18
+ * `bodyKind: none` in a spec means no body at all: the interpreter reports that
19
+ * as `noBody`, and the engine wants the field simply absent. */
20
+ private fromSpec;
21
+ buildCreateRequest(requests: BatchRequest[]): HttpRequest;
22
+ buildAddRequestsRequest(batchId: string, requests: BatchRequest[]): HttpRequest;
23
+ buildStatusRequest(batchId: string): HttpRequest;
24
+ buildCancelRequest(batchId: string): HttpRequest;
25
+ buildResultsRequest(batchId: string): HttpRequest;
15
26
  submit(requests: BatchRequest[], fetch: EngineFetch): Promise<string>;
16
27
  getStatus(batchId: string, fetch: EngineFetch): Promise<BatchStatus>;
17
28
  getResults(batchId: string, fetch: EngineFetch): Promise<BatchResult[]>;
@@ -5,8 +5,7 @@
5
5
  * - Returns reasoning_content in message (plain text, unlike OpenAI which hides it)
6
6
  */
7
7
  import type { SSEEvent } from '../../../network/types';
8
- import type { ProviderAdapter, ProviderHttpRequest } from '../../types/provider';
9
- import type { NormalizedRequest } from '../../types/request';
8
+ import type { ProviderAdapter } from '../../types/provider';
10
9
  import type { CompletionResponse } from '../../types/response';
11
10
  import type { StreamEvent } from '../../types/stream';
12
11
  import { OpenAIAdapter, type OpenAIStreamState } from '../openai/completions';
@@ -18,7 +17,10 @@ export declare class XAIAdapter extends OpenAIAdapter {
18
17
  readonly name: ProviderAdapter['name'];
19
18
  constructor(config: XAIAdapterConfig);
20
19
  baseURL(): string;
21
- buildRequest(req: NormalizedRequest): ProviderHttpRequest;
20
+ /** Everything this class used to do to `super.buildRequest()` — the max_tokens
21
+ * rename, the reasoning strip, the tier remap, the routing passthrough — is the
22
+ * `xai` overlay in the shared spec. Naming the flavor IS the override now. */
23
+ protected readonly wireFlavor: string;
22
24
  parseResponse(raw: unknown, latencyMs: number): CompletionResponse;
23
25
  parseStreamEvent(event: SSEEvent, state?: OpenAIStreamState): StreamEvent[];
24
26
  }
@@ -1,6 +1,6 @@
1
1
  /** xAI file adapter — POST /v1/files (purpose=assistants).
2
2
  * All HTTP flows through the injected EngineFetch (NetworkEngine queue). */
3
- import type { EngineFetch } from '../../../network/types';
3
+ import type { EngineFetch, HttpRequest } from '../../../network/types';
4
4
  import type { FileAttachment } from '../../../plugins/files/attachment';
5
5
  import type { FileProviderAdapter, FileUploadResult, RemoteFileInfo } from '../../../plugins/files/provider-adapter';
6
6
  export interface XAIFileAdapterConfig {
@@ -15,7 +15,18 @@ export declare class XAIFileAdapter implements FileProviderAdapter {
15
15
  private readonly apiKey;
16
16
  private readonly baseURL;
17
17
  constructor(config: XAIFileAdapterConfig);
18
- private bearer;
18
+ /** File rules need no adapter handles. */
19
+ private readonly wireRegistry;
20
+ /** Build one file request from its spec, then add the engine metadata.
21
+ *
22
+ * A multipart spec describes the FIELDS but not the bytes, so an upload passes
23
+ * its attachment in and the descriptor is filled here. `bodyKind: none` arrives
24
+ * as `noBody`; the engine wants the field simply absent. */
25
+ private fromSpec;
26
+ buildUploadRequest(file: FileAttachment, data: Uint8Array): Promise<HttpRequest>;
27
+ buildDeleteRequest(remoteId: string): Promise<HttpRequest>;
28
+ buildGetInfoRequest(remoteId: string): Promise<HttpRequest>;
29
+ buildListRequest(): Promise<HttpRequest>;
19
30
  upload(file: FileAttachment, fetch: EngineFetch): Promise<FileUploadResult>;
20
31
  delete(remoteId: string, fetch: EngineFetch): Promise<void>;
21
32
  getInfo(remoteId: string, fetch: EngineFetch): Promise<RemoteFileInfo | null>;
@@ -1,5 +1,5 @@
1
1
  /** xAI media adapter — images, TTS, video. All HTTP through EngineFetch. */
2
- import type { EngineFetch } from '../../../network/types';
2
+ import type { EngineFetch, HttpRequest } from '../../../network/types';
3
3
  import type { AudioGenRequest, ImageEditRequest, ImageGenRequest, MediaCapabilities, MediaProviderAdapter, RawMediaResult, VideoGenRequest, VideoStatus } from '../../../plugins/media/types';
4
4
  export interface XAIMediaAdapterConfig {
5
5
  apiKey: string;
@@ -11,7 +11,33 @@ export declare class XAIMediaAdapter implements MediaProviderAdapter {
11
11
  private readonly baseURL;
12
12
  constructor(config: XAIMediaAdapterConfig);
13
13
  capabilities(): MediaCapabilities;
14
- private authHeaders;
14
+ /** Named code the specs cannot express as data — image/video source refs. */
15
+ private readonly wireRegistry;
16
+ /** Build one request from its spec, then add the engine metadata.
17
+ *
18
+ * `provider`, `model` and `responseType` are routing and decoding concerns that
19
+ * never reach xAI, so the specs do not model them. */
20
+ private fromSpec;
21
+ /** Poll a video job. All three xAI video endpoints return a request_id that is
22
+ * polled here. */
23
+ buildVideoStatusRequest(operationId: string): HttpRequest;
24
+ buildVideoCancelRequest(operationId: string): HttpRequest;
25
+ /** Fetch bytes from a URL xAI put in its own response - a generated image or a
26
+ * finished video. The URL is an input because xAI chose it; the auth headers
27
+ * are still ours, which is why it is a spec and not a bare fetch. */
28
+ buildDownloadRequest(downloadUrl: string, model?: string): HttpRequest;
29
+ /** Text-to-image. */
30
+ buildImageRequest(req: ImageGenRequest, model?: string): HttpRequest;
31
+ /** Image-to-image edit — JSON with a data-URL or file_id, no multipart, no mask. */
32
+ buildEditImageRequest(req: ImageEditRequest, model?: string): HttpRequest;
33
+ /** TTS. arraybuffer because the response is audio bytes. */
34
+ buildAudioRequest(req: AudioGenRequest, model?: string): HttpRequest;
35
+ /** Video submission, routed by input and mode:
36
+ * no sourceVideo -> /v1/videos/generations
37
+ * sourceVideo + videoMode extend -> /v1/videos/extensions (duration only)
38
+ * sourceVideo + videoMode edit -> /v1/videos/edits (prompt + video)
39
+ * Three endpoints with three different field sets, so three specs. */
40
+ buildVideoRequest(req: VideoGenRequest, model?: string): HttpRequest;
15
41
  generateImage(req: ImageGenRequest, fetch: EngineFetch): Promise<RawMediaResult[]>;
16
42
  /** Image-to-image edit via `/v1/images/edits` (JSON; base64 data-URL or
17
43
  * file_id, no multipart, no mask). */
@@ -21,14 +47,6 @@ export declare class XAIMediaAdapter implements MediaProviderAdapter {
21
47
  private parseImages;
22
48
  generateAudio(req: AudioGenRequest, fetch: EngineFetch): Promise<RawMediaResult>;
23
49
  submitVideo(req: VideoGenRequest, fetch: EngineFetch): Promise<string>;
24
- /** Route a video request to the right xAI endpoint by input + mode:
25
- * - no `sourceVideo` → `/v1/videos/generations` (text/image-to-video)
26
- * - `sourceVideo` + `videoMode:'extend'` (default) → `/v1/videos/extensions`
27
- * — continues from the last frame; takes `duration`, NOT aspect/resolution.
28
- * - `sourceVideo` + `videoMode:'edit'` → `/v1/videos/edits` — prompt + video
29
- * only (no duration/aspect/resolution).
30
- * All three return a `request_id` polled via the same status endpoint. */
31
- private buildVideoSubmit;
32
50
  getVideoStatus(operationId: string, fetch: EngineFetch): Promise<VideoStatus>;
33
51
  downloadVideo(operationId: string, fetch: EngineFetch): Promise<RawMediaResult>;
34
52
  cancelVideo(operationId: string, fetch: EngineFetch): Promise<void>;
@@ -5,8 +5,7 @@
5
5
  * - Reasoning automatic for reasoning models (no effort param needed)
6
6
  * - Encrypted reasoning via include: ["reasoning.encrypted_content"]
7
7
  */
8
- import type { ProviderAdapter, ProviderHttpRequest } from '../../types/provider';
9
- import type { NormalizedRequest } from '../../types/request';
8
+ import type { ProviderAdapter } from '../../types/provider';
10
9
  import type { FileOutput } from '../../types/response';
11
10
  import { OpenAIResponsesAdapter } from '../openai/responses';
12
11
  export interface XAIResponsesAdapterConfig {
@@ -17,7 +16,10 @@ export declare class XAIResponsesAdapter extends OpenAIResponsesAdapter {
17
16
  readonly name: ProviderAdapter['name'];
18
17
  constructor(config: XAIResponsesAdapterConfig);
19
18
  baseURL(): string;
20
- buildRequest(req: NormalizedRequest): ProviderHttpRequest;
19
+ /** Everything this class used to do to `super.buildRequest()` — the max_tokens
20
+ * rename, the reasoning strip, the tier remap, the routing passthrough — is the
21
+ * `xai` overlay in the shared spec. Naming the flavor IS the override now. */
22
+ protected readonly wireFlavor: string;
21
23
  /** xAI embeds code-execution files inline in the `logs` payload — extend the base
22
24
  * extraction (which handles OpenAI-style annotations / image URLs) with the xAI shape. */
23
25
  protected filesFromOutputItem(item: Record<string, unknown>): FileOutput[];
@@ -0,0 +1,96 @@
1
+ /** Runtime shape check — tell me when a provider's response stops looking like the
2
+ * one we learned to read.
3
+ *
4
+ * Parsing is the half of the library with the least warning before a failure. A
5
+ * request that goes wrong comes back as a 400. A RESPONSE that goes wrong comes
6
+ * back as a 200 with a field we do not read: the parse succeeds, the number is
7
+ * `undefined`, and the first sign is a cost dashboard that quietly reports zero
8
+ * or a tool call that never arrives. `google/generate` shipped for months
9
+ * discarding a `responseId` that had been there all along, under a comment saying
10
+ * it did not exist.
11
+ *
12
+ * So this compares the body against a description of the shape we understand:
13
+ *
14
+ * unknown path a field we have never seen. Either the provider added it —
15
+ * possibly the one carrying something we now want — or we are
16
+ * talking to something that is not the API we think.
17
+ * missing path a field present in EVERY recording is absent from this one.
18
+ * That is the shape of a rename, and a rename is what silently
19
+ * turns a token count into `undefined`.
20
+ * unknown value a discriminator (`type`, `role`, `finish_reason`, …) carries
21
+ * a value we do not branch on. A new content-block type is the
22
+ * most expensive kind of drift there is, because the content
23
+ * is simply dropped and nothing errors.
24
+ *
25
+ * OFF by default, and it never changes what is parsed: it only emits `onWarning`.
26
+ * A check that could alter a response would be a new way to break one.
27
+ *
28
+ * The descriptions in `response-shapes.json` are DERIVED, not hand-written —
29
+ * `scripts/derive-response-shapes.ts` builds them from the recorded corpus, and a
30
+ * test asserts every recorded body produces no unknown paths. That is what keeps
31
+ * the description honest: it cannot drift from real responses without a test going
32
+ * red, and when a provider does add a field, re-recording surfaces it as a
33
+ * decision rather than as a silent difference.
34
+ */
35
+ import type { HookBus } from '../bus/hook-bus';
36
+ import type { SSEEvent } from '../network/types';
37
+ export interface ShapeDecl {
38
+ /** Every path seen across the recordings for this target. */
39
+ known: string[];
40
+ /** Paths present in EVERY recording — absence is worth a warning. */
41
+ expected: string[];
42
+ /** Discriminator path → the values we have actually seen. */
43
+ values?: Record<string, string[]>;
44
+ }
45
+ export interface ShapeSet {
46
+ /** The non-streaming body. */
47
+ response?: ShapeDecl;
48
+ /** One declaration PER SSE event type, keyed by the SSE `event:` name when the
49
+ * provider sends one and by the payload's own `type` otherwise.
50
+ *
51
+ * Pooling every event type into one declaration was the first attempt, and it
52
+ * cost the missing-field check entirely: `message_start` and
53
+ * `content_block_delta` share almost no fields, so the intersection across a
54
+ * whole stream came to a single path and nothing could ever be reported
55
+ * absent. Per type, "this event always carries usage" becomes a statement
56
+ * worth making. */
57
+ stream?: Record<string, ShapeDecl>;
58
+ }
59
+ /** How an SSE event names its own kind. The SSE `event:` line wins — Anthropic
60
+ * routes on it, and its payload `type` merely repeats it — then the payload's
61
+ * `type`, then a single bucket for providers that discriminate on neither
62
+ * (chat-completions chunks are all one shape). */
63
+ export declare function streamEventKey(event: SSEEvent, data: unknown): string;
64
+ export type ShapeBook = Record<string, ShapeSet>;
65
+ export interface ShapeFindings {
66
+ unknown: string[];
67
+ missing: string[];
68
+ /** `[path, value]` for a discriminator carrying something new. */
69
+ unknownValues: Array<[string, string]>;
70
+ }
71
+ /** Collect the paths of a value. Array indices collapse to `[]` — otherwise a
72
+ * three-element list would describe a different shape than a four-element one. */
73
+ export declare function pathsOf(value: unknown, prefix?: string, out?: Set<string>, depth?: number): Set<string>;
74
+ /** Collect `path → value` for the discriminator keys only. */
75
+ export declare function discriminatorsOf(value: unknown, prefix?: string, out?: Array<[string, string]>, depth?: number): Array<[string, string]>;
76
+ /** Compare one body against a declaration. Pure — the caller decides what to do. */
77
+ export declare function checkShape(body: unknown, decl: ShapeDecl): ShapeFindings;
78
+ /** Per-client checker. Holds what it has already reported, because the alternative
79
+ * is the same warning on every request for the rest of the process — which is how
80
+ * a diagnostic gets muted by the person reading it. */
81
+ export declare class ResponseShapeChecker {
82
+ private readonly hooks;
83
+ private readonly provider;
84
+ private readonly api;
85
+ private readonly book;
86
+ private readonly reported;
87
+ constructor(hooks: HookBus, provider: string, api: string, book: ShapeBook);
88
+ /** The declaration for this client, or undefined when nothing was recorded for
89
+ * it — in which case the check stays silent rather than calling every field
90
+ * unknown. */
91
+ private get set();
92
+ checkResponse(body: unknown): void;
93
+ checkStreamEvent(event: SSEEvent): void;
94
+ private report;
95
+ private warn;
96
+ }
@@ -13,7 +13,7 @@
13
13
  * - it's within the retention TTL (catalog duration),
14
14
  * - the model matches, OR the provider is not model-bound (catalog).
15
15
  * Otherwise we fall back to resending full history (always correct). */
16
- import type { ModelCatalog } from '../plugins/model-catalog/catalog';
16
+ import type { ModelCatalog } from '../catalog/catalog';
17
17
  import type { Message } from './types/messages';
18
18
  import type { ProviderName } from './types/provider';
19
19
  export interface ServerStateDecision {
@@ -3,7 +3,7 @@ import type { ConversationHistory } from '../../agent/history';
3
3
  import type { RequestContext } from '../../types/request-context';
4
4
  import type { ModerationRequest } from '../moderation/types';
5
5
  import type { AudioOptions } from './audio';
6
- import type { CacheConfig, ThinkingConfig } from './request';
6
+ import type { CacheConfig, ProviderOptions, ThinkingConfig } from './request';
7
7
  import type { ServiceTier } from './tiers';
8
8
  import type { Tool, ToolChoice } from './tools';
9
9
  export interface ExecuteOptions {
@@ -65,7 +65,7 @@ export interface ExecuteOptions {
65
65
  * OpenAI runs it natively; other providers are emulated via OpenAI's
66
66
  * moderations endpoint. See ModerationRequest. */
67
67
  moderation?: ModerationRequest;
68
- providerOptions?: Record<string, unknown>;
68
+ providerOptions?: ProviderOptions;
69
69
  previousResponseId?: string;
70
70
  /** Server-state optimization: when the prior assistant turn carries a usable
71
71
  * server id (same provider, within TTL, model ok), send the id + only the new
@@ -6,6 +6,12 @@ import type { NormalizedRequest } from './request';
6
6
  import type { CompletionResponse } from './response';
7
7
  import type { StreamEvent } from './stream';
8
8
  export type ProviderName = 'anthropic' | 'openai' | 'google' | 'xai' | 'openrouter';
9
+ /** The same five names at runtime. A `"vendor/model"` prefix can only be read as
10
+ * a provider if it IS one — OpenRouter's own ids are all `vendor/model`, so
11
+ * without this check `openai/gpt-5.4-nano` on OpenRouter parses as the provider
12
+ * `openai`, and `qwen/qwen3` parses as a provider named `qwen`. */
13
+ export declare const PROVIDER_NAMES: readonly ["anthropic", "openai", "google", "xai", "openrouter"];
14
+ export declare function isProviderName(value: string): value is ProviderName;
9
15
  export type ApiType = 'completions' | 'responses' | 'messages' | 'interactions' | 'generate';
10
16
  export interface ProviderConfig {
11
17
  provider: ProviderName;