@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,7 +1,7 @@
1
1
  /** LLMClient configuration types. */
2
2
  import type { HookBus } from '../bus/hook-bus';
3
3
  import type { EngineFetch, EngineFetchStream } from '../network/types';
4
- import type { ModelCatalog } from '../plugins/model-catalog/catalog';
4
+ import type { ModelCatalog } from '../catalog/catalog';
5
5
  import type { RequestContext } from '../types/request-context';
6
6
  import type { ApiType, ProviderAdapter, ProviderName } from './types/provider';
7
7
  import type { NormalizedRequest } from './types/request';
@@ -34,4 +34,12 @@ export interface LLMClientConfig {
34
34
  * createLLM supplies `engine.catalog`. An empty catalog still yields correct
35
35
  * provider-level defaults, so this is optional. */
36
36
  catalog?: ModelCatalog;
37
+ /** Warn when a provider's response stops looking like the one we learned to
38
+ * read — a field we have never seen, a field that was always there and is now
39
+ * absent, or a discriminator carrying a value nothing branches on.
40
+ *
41
+ * OFF by default and never changes what is parsed: it only emits `onWarning`.
42
+ * `createEngine({ checkResponseShapes: true })` turns it on for every client
43
+ * the engine builds. See `src/llm/response-shape.ts`. */
44
+ checkResponseShapes?: boolean;
37
45
  }
@@ -34,6 +34,17 @@ export declare function extractSystem(messages: Message[]): {
34
34
  /** Strip leading/trailing markdown fences and JSON.parse. Exported so AgentLoop
35
35
  * + helper can share the same parsing rules. */
36
36
  export declare function parseStructured<T>(text: string): T;
37
+ /** The routing names `buildContext` needs from a client.
38
+ *
39
+ * These were read with `as unknown as { queueName: string }` casts straight into
40
+ * LLMClient's privates — which compiles, and silently returns `undefined` the
41
+ * day a field is renamed. LLMClient now exposes them deliberately as
42
+ * `client.routing`, so a rename is a type error instead. */
43
+ export interface ClientRouting {
44
+ readonly queueName: string;
45
+ readonly configName: string;
46
+ readonly cacheName: string;
47
+ }
37
48
  export declare function buildContext(client: LLMClient, options: ExecuteOptions): RequestContext;
38
49
  export declare function resolveApi(provider: ProviderName, api?: ApiType | 'auto'): ApiType;
39
50
  export declare function resolveAdapter(config: LLMClientConfig, api: ApiType): ProviderAdapter;
@@ -25,6 +25,7 @@ import type { ApiType, ProviderName } from './types/provider';
25
25
  import type { CompletionResponse, FileOutput } from './types/response';
26
26
  import type { StreamEvent } from './types/stream';
27
27
  import type { LLMClientConfig } from './client-config';
28
+ import { type ClientRouting } from './client-internal';
28
29
  export declare class LLMClient {
29
30
  readonly id: string;
30
31
  /** Trace session id (from the engine, or self-minted for a standalone client). */
@@ -37,6 +38,8 @@ export declare class LLMClient {
37
38
  readonly mode: 'foreground' | 'background';
38
39
  readonly batchable: boolean;
39
40
  private readonly adapter;
41
+ /** Present only when the caller asked for the shape check. */
42
+ private readonly shapeChecker?;
40
43
  private readonly apiKey;
41
44
  private readonly fetchFn;
42
45
  private readonly fetchStreamFn;
@@ -44,6 +47,9 @@ export declare class LLMClient {
44
47
  private readonly queueName;
45
48
  private readonly configName;
46
49
  private readonly cacheName;
50
+ /** The routing names this client was configured with, exposed so context
51
+ * building does not have to cast into the privates above. */
52
+ readonly routing: ClientRouting;
47
53
  private readonly cacheKeyFn?;
48
54
  private readonly catalog;
49
55
  constructor(config: LLMClientConfig);
@@ -0,0 +1,19 @@
1
+ /** The one piece of SSE handling every provider genuinely shares.
2
+ *
3
+ * Report 035 proposed a shared "SSE -> StreamEvent parser" because the parsing
4
+ * looked duplicated ~5x. Measured, it is not: the six parse bodies (76-149
5
+ * lines each) share ZERO runs of three or more identical lines, because they
6
+ * decode different wire schemas — Anthropic's `content_block_*` events,
7
+ * OpenAI chat's `choices[].delta`, OpenAI Responses' typed events, and Google's
8
+ * `candidates[].content.parts`. A "shared" parser would be a switch on provider
9
+ * wearing a common signature.
10
+ *
11
+ * What IS shared is exactly this line, repeated six times. Naming it gives the
12
+ * three language ports one primitive to agree on instead of six independent
13
+ * decisions, and one place to harden if malformed frames ever need handling
14
+ * (today a bad frame throws out of the parser, which is the existing
15
+ * behaviour and deliberately unchanged here).
16
+ */
17
+ import type { SSEEvent } from '../../../network/types';
18
+ /** Decode an SSE frame's `data` payload as a JSON object. */
19
+ export declare function sseJson(event: SSEEvent): Record<string, unknown>;
@@ -1,6 +1,6 @@
1
1
  /** Anthropic batch adapter — POST /v1/messages/batches with inline requests.
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 AnthropicBatchAdapterConfig {
6
6
  apiKey: string;
@@ -11,7 +11,18 @@ export declare class AnthropicBatchAdapter implements BatchProviderAdapter {
11
11
  private readonly apiKey;
12
12
  private readonly baseURL;
13
13
  constructor(config: AnthropicBatchAdapterConfig);
14
- private authHeaders;
14
+ /** Batch rules need no adapter handles: the requests are mapped by the spec. */
15
+ private readonly wireRegistry;
16
+ /** Build one batch request from its spec, then add the engine metadata.
17
+ * Every batch call is routed under the `batch` model name for queueing. */
18
+ private fromSpec;
19
+ buildSubmitRequest(requests: BatchRequest[]): HttpRequest;
20
+ buildStatusRequest(batchId: string): HttpRequest;
21
+ /** Results stream back as JSONL, so this one decodes as TEXT. Forcing `json`
22
+ * here would have broken every batch read — caught by the frozen corpus, not
23
+ * by any type. */
24
+ buildResultsRequest(batchId: string): HttpRequest;
25
+ buildCancelRequest(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[]>;
@@ -1,45 +1,10 @@
1
- /** Anthropic provider constants. */
1
+ /** Anthropic provider constants.
2
+ *
3
+ * The thinking-shape and top_k band helpers that used to live here are gone: that
4
+ * knowledge is now `src/wire/pins/anthropic.messages.json`, and the token budgets
5
+ * are a `$table` inside the chain specs. Both were version arithmetic in
6
+ * TypeScript, which the Python and Rust ports would have had to re-implement and
7
+ * keep in step - and one copy drifting is exactly how 2.2.1 shipped the wrong
8
+ * thinking shape. */
2
9
  /** The Anthropic API version header sent on every request. */
3
10
  export declare const ANTHROPIC_API_VERSION = "2023-06-01";
4
- /**
5
- * Token budgets for Anthropic extended thinking by effort level.
6
- * Used to set budget_tokens in the `thinking` request param.
7
- */
8
- export declare const ANTHROPIC_THINKING_BUDGETS: Record<string, number>;
9
- /**
10
- * Budget applied when no effort level is specified or when the level is
11
- * unrecognised.
12
- */
13
- export declare const DEFAULT_ANTHROPIC_THINKING_BUDGET = 2048;
14
- /**
15
- * The version at which `thinking: {type:'adaptive'}` takes over from
16
- * `{type:'enabled', budget_tokens}`.
17
- *
18
- * There is no shape that works everywhere, and the direction reversed under us. This
19
- * adapter used to send the budgeted form to every model, on the reasoning that it was the
20
- * universally accepted one — true when it was written. Anthropic then REMOVED
21
- * `budget_tokens` on 4.7 and later: Sonnet 5, Opus 5/4.8/4.7 and Fable 5 reject it with a
22
- * 400 ("thinking.type.enabled is not supported for this model"). Meanwhile the older half
23
- * — Haiku 4.5, Sonnet 4.5, Opus 4.x — has no `adaptive` at all and still requires the
24
- * budget. So the shape must be chosen per model.
25
- *
26
- * 4.6 is the boundary: it accepts both and prefers `adaptive`, everything above requires
27
- * `adaptive`, everything below requires the budget.
28
- */
29
- export declare const ANTHROPIC_ADAPTIVE_THINKING_MIN: {
30
- readonly major: 4;
31
- readonly minor: 6;
32
- };
33
- /**
34
- * Pick the `thinking` shape for a model id.
35
- *
36
- * Parsed from the id rather than read from the catalog on purpose: the catalog is optional
37
- * (an engine can run with none), `buildRequest` has no access to it, and its per-model
38
- * `reasoning` block does not currently distinguish the two shapes anyway.
39
- *
40
- * An unrecognised id gets `adaptive`, because `budget_tokens` is the shape being retired —
41
- * an id we do not recognise is far likelier to be newer than us than older.
42
- */
43
- export declare function anthropicThinkingShape(model: string): 'adaptive' | 'budgeted';
44
- /** True when this Anthropic model still accepts `top_k` (see ANTHROPIC_TOP_K_MODELS). */
45
- export declare function anthropicAcceptsTopK(model: string): boolean;
@@ -1,6 +1,6 @@
1
1
  /** Anthropic file adapter — POST /v1/files (beta).
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 AnthropicFileAdapterConfig {
@@ -15,7 +15,18 @@ export declare class AnthropicFileAdapter implements FileProviderAdapter {
15
15
  private readonly apiKey;
16
16
  private readonly baseURL;
17
17
  constructor(config: AnthropicFileAdapterConfig);
18
- private authHeaders;
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): HttpRequest;
27
+ buildDeleteRequest(remoteId: string): HttpRequest;
28
+ buildGetInfoRequest(remoteId: string): HttpRequest;
29
+ buildListRequest(): 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>;
@@ -4,6 +4,7 @@
4
4
  * of CompletionRequest (same shape, renamed for v2 to reflect it's the
5
5
  * internal normalized form LLMClient hands to the adapter). */
6
6
  import type { SSEEvent } from '../../../network/types';
7
+ import type { ContentPart } from '../../types/messages';
7
8
  import type { ProviderAdapter, ProviderHttpRequest } from '../../types/provider';
8
9
  import type { NormalizedRequest } from '../../types/request';
9
10
  import { type CompletionResponse } from '../../types/response';
@@ -19,10 +20,26 @@ export declare class AnthropicAdapter implements ProviderAdapter {
19
20
  constructor(config: AnthropicAdapterConfig);
20
21
  authHeaders(): Record<string, string>;
21
22
  baseURL(): string;
23
+ /** Named code the spec cannot express as data — message and content assembly.
24
+ * Built once, carrying only this adapter, since only Anthropic rules run. */
25
+ private readonly wireRegistry;
22
26
  completionPath(): string;
27
+ /** The spec that builds this model's request.
28
+ *
29
+ * The catalog pin decides when there is one. Without it — an engine running
30
+ * with no catalog, or a model released after this build — the band comes from
31
+ * the pin TABLE, which is data (`src/wire/pins/`) rather than version
32
+ * arithmetic in TypeScript, so the Python and Rust ports derive the same node
33
+ * from the same file instead of each re-implementing it. */
34
+ private specIdFor;
23
35
  buildRequest(req: NormalizedRequest): ProviderHttpRequest;
24
36
  enableStreaming(providerReq: ProviderHttpRequest, _req: NormalizedRequest): void;
25
- private buildMessage;
37
+ /** Reached through the wire registry while building this adapter's own request. */
38
+ buildMessage(msg: {
39
+ role: string;
40
+ content: string | ContentPart[];
41
+ cache?: boolean;
42
+ }, _req: NormalizedRequest, forceCache?: boolean): Record<string, unknown>;
26
43
  private buildContentPart;
27
44
  parseResponse(raw: unknown, latencyMs: number): CompletionResponse;
28
45
  parseStreamEvent(event: SSEEvent): StreamEvent[];
@@ -1,6 +1,6 @@
1
1
  /** Google batch adapter — inline batchGenerateContent.
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 GoogleBatchAdapterConfig {
6
6
  apiKey: string;
@@ -13,6 +13,19 @@ export declare class GoogleBatchAdapter implements BatchProviderAdapter {
13
13
  private readonly model;
14
14
  private readonly baseURL;
15
15
  constructor(config: GoogleBatchAdapterConfig);
16
+ /** Batch rules need no adapter handles: the request list is mapped by the spec. */
17
+ private readonly wireRegistry;
18
+ /** Build one batch request from its spec, then add the engine metadata.
19
+ *
20
+ * `bodyKind: none` in a spec means no body at all: the interpreter reports that
21
+ * as `noBody`, and the engine wants the field simply absent. */
22
+ private fromSpec;
23
+ buildSubmitRequest(requests: BatchRequest[]): HttpRequest;
24
+ buildStatusRequest(batchId: string): HttpRequest;
25
+ buildCancelRequest(batchId: string): HttpRequest;
26
+ /** Google returns results inline on the batch resource, so this is the same
27
+ * wire as getStatus - two operations that happen to share one request. */
28
+ buildResultsRequest(batchId: string): HttpRequest;
16
29
  submit(requests: BatchRequest[], fetch: EngineFetch): Promise<string>;
17
30
  getStatus(batchId: string, fetch: EngineFetch): Promise<BatchStatus>;
18
31
  getResults(batchId: string, fetch: EngineFetch): Promise<BatchResult[]>;
@@ -1,18 +1,9 @@
1
- /** Google provider constants. */
2
- /**
3
- * Map from unified thinking effort levels to Gemini `thinkingLevel` enum strings.
4
- * `thinkingLevel` is the Gemini 3.x thinking control (LOW/HIGH).
5
- */
6
- export declare const GOOGLE_THINKING_LEVELS: Record<string, string>;
7
- /**
8
- * Map from unified thinking effort to a Gemini `thinkingBudget` (token count).
9
- * Gemini **2.5** models only accept a token budget — they 400 on `thinkingLevel`
10
- * ("Thinking level is not supported for this model", live-verified 2026-07-16).
11
- * Values sit inside the 2.5 range (flash/flash-lite cap ~24576, pro ~32768).
12
- */
13
- export declare const GOOGLE_THINKING_BUDGETS: Record<string, number>;
14
- /** Gemini 2.5 series uses `thinkingBudget`; 3.x+ uses `thinkingLevel`. */
15
- export declare function googleUsesThinkingBudget(model: string): boolean;
1
+ /** Google provider constants.
2
+ *
3
+ * The thinking-control tables and the 2.5-vs-3.x band test have moved into data:
4
+ * the effort maps are `$table`s in the chain specs, and the band is
5
+ * `src/wire/pins/google.generate.json`. Only the Interactions map remains here,
6
+ * because its lowercase enum is read by code the specs do not own. */
16
7
  /**
17
8
  * Effort → Interactions `thinking_level`. The Interactions API uses **lowercase**
18
9
  * values (`minimal`/`low`/`medium`/`high`) — distinct from generateContent's
@@ -1,6 +1,6 @@
1
1
  /** Google embeddings adapter — POST /v1beta/models/{model}:embedContent.
2
2
  * One call per input text (batch via a simple loop). */
3
- import type { EngineFetch } from '../../../network/types';
3
+ import type { EngineFetch, HttpRequest } from '../../../network/types';
4
4
  import type { EmbedRequest, EmbedResult, EmbeddingProviderAdapter } from '../../../plugins/embeddings/types';
5
5
  export interface GoogleEmbeddingAdapterConfig {
6
6
  apiKey: string;
@@ -11,5 +11,12 @@ export declare class GoogleEmbeddingAdapter implements EmbeddingProviderAdapter
11
11
  private readonly apiKey;
12
12
  private readonly baseURL;
13
13
  constructor(config: GoogleEmbeddingAdapterConfig);
14
+ /** One request, for ONE input text.
15
+ *
16
+ * Google embeds a single text per call, so `embed` loops and this builds one
17
+ * iteration. The spec's input context is `{ model, text }` accordingly. */
18
+ buildEmbedRequest(req: EmbedRequest, text: string): HttpRequest;
19
+ /** Named code the spec cannot express as data — the models/ path prefix. */
20
+ private readonly wireRegistry;
14
21
  embed(req: EmbedRequest, fetch: EngineFetch): Promise<EmbedResult>;
15
22
  }
@@ -1,8 +1,23 @@
1
1
  /** Google file adapter — resumable upload to Files API. 48h auto-delete.
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
+ /** Reduce any form of Google file id to the bare name the REST path wants.
7
+ *
8
+ * Three forms reach this, and only two used to work:
9
+ *
10
+ * https://.../v1beta/files/abc the `uri` this adapter hands back from
11
+ * upload() and list() — matched on `/files/`
12
+ * abc a bare name — passed through
13
+ * files/abc Google's CANONICAL resource name, the `name`
14
+ * field its own API returns
15
+ *
16
+ * The third fell through the `/files/` test (no leading slash) and produced
17
+ * `/v1beta/files/files/abc` — a 404. It never broke this library's own
18
+ * round-trip, because upload() and list() return the `uri`; it broke the
19
+ * moment a caller passed the id Google itself gave them. */
20
+ export declare function googleFileName(remoteId: string): string;
6
21
  export interface GoogleFileAdapterConfig {
7
22
  apiKey: string;
8
23
  baseURL?: string;
@@ -15,6 +30,24 @@ export declare class GoogleFileAdapter implements FileProviderAdapter {
15
30
  private readonly apiKey;
16
31
  private readonly baseURL;
17
32
  constructor(config: GoogleFileAdapterConfig);
33
+ /** File rules need no adapter handles. */
34
+ private readonly wireRegistry;
35
+ /** Build one file request from its spec, then add the engine metadata.
36
+ *
37
+ * A multipart spec describes the FIELDS but not the bytes, so an upload passes
38
+ * its attachment in and the descriptor is filled here. `bodyKind: none` arrives
39
+ * as `noBody`; the engine wants the field simply absent. */
40
+ private fromSpec;
41
+ /** Step ONE of the resumable upload. The second call goes to a URL the server
42
+ * returns in a response header, so no spec can describe it — it stays here. */
43
+ buildStartUploadRequest(file: FileAttachment, byteLength: number): HttpRequest;
44
+ /** Step TWO of the resumable upload. The URL came back in a response header, so
45
+ * it is an INPUT to the spec rather than something the spec can build - the same
46
+ * way batchId is. */
47
+ buildFinishUploadRequest(uploadUrl: string, file: FileAttachment, data: Uint8Array): HttpRequest;
48
+ buildDeleteRequest(remoteId: string): HttpRequest;
49
+ buildGetInfoRequest(remoteId: string): HttpRequest;
50
+ buildListRequest(): HttpRequest;
18
51
  upload(file: FileAttachment, fetch: EngineFetch): Promise<FileUploadResult>;
19
52
  delete(remoteId: string, fetch: EngineFetch): Promise<void>;
20
53
  getInfo(remoteId: string, fetch: EngineFetch): Promise<RemoteFileInfo | null>;
@@ -1,5 +1,6 @@
1
1
  /** Google Gemini provider adapter (generateContent API). */
2
2
  import type { SSEEvent } from '../../../network/types';
3
+ import type { ContentPart } from '../../types/messages';
3
4
  import type { ProviderAdapter, ProviderHttpRequest } from '../../types/provider';
4
5
  import type { NormalizedRequest } from '../../types/request';
5
6
  import { type CompletionResponse } from '../../types/response';
@@ -16,11 +17,24 @@ export declare class GoogleAdapter implements ProviderAdapter {
16
17
  authHeaders(): Record<string, string>;
17
18
  baseURL(): string;
18
19
  completionPath(): string;
20
+ /** Named code the spec cannot express as data — content assembly. */
21
+ private readonly wireRegistry;
22
+ /** The spec that builds this model's request.
23
+ *
24
+ * Two nodes, keyed on the one thing that differs on the wire: 2.5 takes a token
25
+ * `thinkingBudget` and 400s on `thinkingLevel`, 3.x takes the level. Catalog pin
26
+ * first, then the pin TABLE — data rather than a regex in TypeScript, so the
27
+ * ports read the same rule. */
28
+ private specIdFor;
19
29
  buildRequest(req: NormalizedRequest): ProviderHttpRequest;
20
30
  enableStreaming(providerReq: ProviderHttpRequest, req: NormalizedRequest): void;
21
31
  /** Map tool call IDs to function names (Google needs name in functionResponse) */
22
32
  private toolCallNames;
23
- private buildContent;
33
+ /** Reached through the wire registry while building this adapter's own request. */
34
+ buildContent(msg: {
35
+ role: string;
36
+ content: string | ContentPart[];
37
+ }): Record<string, unknown>;
24
38
  parseResponse(raw: unknown, latencyMs: number): CompletionResponse;
25
39
  parseStreamEvent(event: SSEEvent): StreamEvent[];
26
40
  /** Stateful — Google splits the code-execution marker (`executableCode` /
@@ -3,6 +3,7 @@
3
3
  * Modern API: input, system_instruction, outputs (plural), function_result,
4
4
  * previous_interaction_id for stateful, 72h retention. */
5
5
  import type { SSEEvent } from '../../../network/types';
6
+ import type { Message } from '../../types/messages';
6
7
  import type { ProviderAdapter, ProviderHttpRequest } from '../../types/provider';
7
8
  import type { NormalizedRequest } from '../../types/request';
8
9
  import { type CompletionResponse } from '../../types/response';
@@ -19,8 +20,11 @@ export declare class GoogleInteractionsAdapter implements ProviderAdapter {
19
20
  authHeaders(): Record<string, string>;
20
21
  baseURL(): string;
21
22
  completionPath(): string;
23
+ /** Named code the spec cannot express as data — input-item assembly. */
24
+ private readonly wireRegistry;
22
25
  buildRequest(req: NormalizedRequest): ProviderHttpRequest;
23
- private buildInputItems;
26
+ /** Reached through the wire registry while building the request. */
27
+ buildInputItems(msg: Message): unknown[];
24
28
  /** Track tool call IDs → names for function_result */
25
29
  private toolCallNames;
26
30
  enableStreaming(providerReq: ProviderHttpRequest): void;
@@ -1,6 +1,6 @@
1
1
  /** Google media adapter — Imagen (:predict) + Veo (:predictLongRunning).
2
2
  * All HTTP calls go through an injected EngineFetch (NetworkEngine queue). */
3
- import type { EngineFetch } from '../../../network/types';
3
+ import type { EngineFetch, HttpRequest } from '../../../network/types';
4
4
  import type { AudioGenRequest, ImageEditRequest, ImageGenRequest, MediaCapabilities, MediaProviderAdapter, RawMediaResult, VideoGenRequest, VideoStatus } from '../../../plugins/media/types';
5
5
  export interface GoogleMediaAdapterConfig {
6
6
  apiKey: string;
@@ -12,14 +12,55 @@ export declare class GoogleMediaAdapter implements MediaProviderAdapter {
12
12
  private readonly baseURL;
13
13
  constructor(config: GoogleMediaAdapterConfig);
14
14
  capabilities(): MediaCapabilities;
15
+ /** Named code the specs cannot express as data — image-source normalisation. */
16
+ private readonly wireRegistry;
17
+ /** Build one media request from its spec, then add the engine metadata.
18
+ *
19
+ * `provider`, `model` and `responseType` are engine concerns, not wire: nothing
20
+ * a provider sees, so the specs do not model them. Every Google media response
21
+ * is JSON, including Veo's operation handle and the base64 inline data. */
22
+ /** Build one media request from its spec, then add the engine metadata.
23
+ *
24
+ * `bodyKind: none` arrives as `noBody`; the engine wants the field absent. */
25
+ private fromSpec;
26
+ /** Veo returns a long-running operation; these poll and cancel it. */
27
+ buildOperationStatusRequest(operationId: string): HttpRequest;
28
+ buildOperationCancelRequest(operationId: string): HttpRequest;
29
+ /** Google appends the key to the download URI too, which is why fetching the
30
+ * generated bytes is a spec rather than a bare GET. */
31
+ buildDownloadRequest(downloadUrl: string): HttpRequest;
32
+ /** Imagen image generation: the Vertex-style `:predict` envelope. */
33
+ /** Imagen image generation: the Vertex-style `:predict` envelope. */
34
+ buildImagenRequest(req: ImageGenRequest, model?: string): HttpRequest;
35
+ /** The inline-media path shared by gemini image generation, editing and TTS. */
36
+ buildGenerateContentRequest(model: string, text: string, generationConfig: Record<string, unknown>, extraParts?: Array<Record<string, unknown>>): HttpRequest;
37
+ /** Veo video submission — a long-running operation, hence the endpoint. */
38
+ /** Veo video submission — a long-running operation, hence the endpoint. */
39
+ buildVideoRequest(req: VideoGenRequest, model?: string): HttpRequest;
40
+ /** The complete image request, whichever of the two Google image paths applies:
41
+ * Imagen models use `:predict`, gemini-* models generate inline via
42
+ * `:generateContent` steered by responseModalities. */
43
+ /** The complete image request, whichever of the two Google image paths applies.
44
+ *
45
+ * Imagen models use `:predict`; gemini-* models generate inline via
46
+ * `:generateContent` steered by responseModalities. Different endpoint, body and
47
+ * response — the fork is a genuine wire difference, not a preference. */
48
+ buildImageRequest(req: ImageGenRequest, model?: string): HttpRequest;
49
+ /** Gemini TTS: the same inline path with an AUDIO modality and a speechConfig. */
50
+ /** Gemini TTS: the inline path with an AUDIO modality and a speechConfig. */
51
+ buildAudioRequest(req: AudioGenRequest, model?: string): HttpRequest;
52
+ /** Image-to-image edit: image generation plus the source image as a second part. */
53
+ /** Image-to-image edit: image generation plus the source image as a second part. */
54
+ buildEditImageRequest(req: ImageEditRequest, model?: string): HttpRequest;
15
55
  generateImage(req: ImageGenRequest, fetch: EngineFetch): Promise<RawMediaResult[]>;
16
56
  generateAudio(req: AudioGenRequest, fetch: EngineFetch): Promise<RawMediaResult>;
17
57
  /** Image-to-image edit: gemini generateContent with the source image as an
18
58
  * extra inline/file part next to the instruction. */
19
59
  editImage(req: ImageEditRequest, fetch: EngineFetch): Promise<RawMediaResult[]>;
20
- /** Shared inline-media path: POST :generateContent and collect inlineData
21
- * parts + the reported token usage (token-priced media). */
22
- private generateContentMedia;
60
+ /** Collect inlineData parts + reported usage from a `:generateContent`
61
+ * response. The request half is `buildGenerateContentRequest`; keeping the two
62
+ * apart is what lets a request be asserted without performing it. */
63
+ private parseGenerateContent;
23
64
  submitVideo(req: VideoGenRequest, fetch: EngineFetch): Promise<string>;
24
65
  getVideoStatus(operationId: string, fetch: EngineFetch): Promise<VideoStatus>;
25
66
  downloadVideo(operationId: string, fetch: EngineFetch): Promise<RawMediaResult>;
@@ -13,8 +13,8 @@
13
13
  *
14
14
  * Gemini Live models are audio-native: with responseModalities ['AUDIO'] the
15
15
  * parts carry inlineData audio, not text. */
16
- import type { EngineConnect } from '../../../network/types';
17
- import type { RealtimeProviderAdapter, RealtimeSession, RealtimeSessionConfig } from '../../realtime/types';
16
+ import type { EngineConnect, WsRequest } from '../../../network/types';
17
+ import type { RealtimeInput, RealtimeProviderAdapter, RealtimeSession, RealtimeSessionConfig } from '../../realtime/types';
18
18
  export interface GoogleRealtimeAdapterConfig {
19
19
  apiKey: string;
20
20
  baseURL?: string;
@@ -23,5 +23,18 @@ export declare class GoogleRealtimeAdapter implements RealtimeProviderAdapter {
23
23
  private readonly apiKey;
24
24
  private readonly base;
25
25
  constructor(config: GoogleRealtimeAdapterConfig);
26
+ /** The WebSocket descriptor. Separated from `connect` so it can be asserted
27
+ * without opening a socket. Gemini authenticates with a query-string key and
28
+ * does NOT name the model in the URL — that goes in the setup frame. */
29
+ buildConnectRequest(config: RealtimeSessionConfig): WsRequest;
26
30
  connect(config: RealtimeSessionConfig, connect: EngineConnect): RealtimeSession;
27
31
  }
32
+ /** The handshake frame. Pure: a function of the session config, so it can be
33
+ * asserted without opening a socket. Gemini Live names the model HERE rather
34
+ * than in the URL, which is the opposite of OpenAI. */
35
+ export declare function buildGoogleSetupFrame(config: RealtimeSessionConfig): Record<string, unknown>;
36
+ /** The frames for one turn. Gemini carries turn completion as a FIELD, where
37
+ * OpenAI signals it by sending a second frame. */
38
+ export declare function buildGoogleTurnFrames(input: RealtimeInput, opts?: {
39
+ turnComplete?: boolean;
40
+ }): Array<Record<string, unknown>>;
@@ -1,6 +1,6 @@
1
1
  /** OpenAI batch adapter — upload JSONL file, create batch, poll, download 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 OpenAIBatchAdapterConfig {
6
6
  apiKey: string;
@@ -11,7 +11,24 @@ export declare class OpenAIBatchAdapter implements BatchProviderAdapter {
11
11
  private readonly apiKey;
12
12
  private readonly baseURL;
13
13
  constructor(config: OpenAIBatchAdapterConfig);
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
+ /** The FIRST call of submit: the requests go up as a JSONL file. The spec names
22
+ * the multipart fields; the bytes are the serialised batch. */
23
+ buildUploadJsonlRequest(jsonl: string): HttpRequest;
24
+ /** The SECOND call of submit: the JSONL is uploaded first, then the batch is
25
+ * created referencing that file id. */
26
+ buildCreateRequest(fileId: string): HttpRequest;
27
+ buildStatusRequest(batchId: string): HttpRequest;
28
+ buildCancelRequest(batchId: string): HttpRequest;
29
+ /** The SECOND call of the results flow: the output file is JSONL, so it decodes
30
+ * as text. The first call is buildStatusRequest, which yields the file id. */
31
+ buildResultsFileRequest(fileId: string): HttpRequest;
15
32
  submit(requests: BatchRequest[], fetch: EngineFetch): Promise<string>;
16
33
  getStatus(batchId: string, fetch: EngineFetch): Promise<BatchStatus>;
17
34
  getResults(batchId: string, fetch: EngineFetch): Promise<BatchResult[]>;
@@ -1,5 +1,7 @@
1
1
  /** OpenAI provider adapter (Chat Completions API). */
2
2
  import type { SSEEvent } from '../../../network/types';
3
+ import type { Registry } from '../../../wire/interpreter';
4
+ import type { ContentPart } from '../../types/messages';
3
5
  import type { ProviderAdapter, ProviderHttpRequest } from '../../types/provider';
4
6
  import type { NormalizedRequest } from '../../types/request';
5
7
  import { type CompletionResponse } from '../../types/response';
@@ -22,6 +24,12 @@ export declare class OpenAIAdapter implements ProviderAdapter {
22
24
  authHeaders(): Record<string, string>;
23
25
  baseURL(): string;
24
26
  completionPath(): string;
27
+ /** Named code the spec cannot express as data — message/input assembly. Carries
28
+ * `this`, so a subclass drives the same rules with its own overrides. */
29
+ protected readonly wireRegistry: Registry;
30
+ /** Which flavor overlay patches the shared spec. Subclasses for
31
+ * OpenAI-compatible backends override this and nothing else. */
32
+ protected readonly wireFlavor: string;
25
33
  buildRequest(req: NormalizedRequest): ProviderHttpRequest;
26
34
  /** One universal message can become SEVERAL chat-completions messages.
27
35
  *
@@ -31,7 +39,11 @@ export declare class OpenAIAdapter implements ProviderAdapter {
31
39
  * the rest unanswered and the provider rejected the whole request with
32
40
  * "No tool output found for function call <id>" — so parallel tools were broken on
33
41
  * every chat-completions backend. */
34
- private buildMessages;
42
+ /** Reached through the wire registry while building the request. */
43
+ buildMessages(msg: {
44
+ role: string;
45
+ content: string | ContentPart[];
46
+ }): Record<string, unknown>[];
35
47
  private buildMessage;
36
48
  enableStreaming(providerReq: ProviderHttpRequest, _req: NormalizedRequest): void;
37
49
  parseResponse(raw: unknown, latencyMs: number): CompletionResponse;
@@ -1,6 +1,7 @@
1
1
  /** OpenAI embeddings adapter — POST /v1/embeddings. Also the base for the
2
2
  * OpenAI-compatible OpenRouter adapter. */
3
- import type { EngineFetch } from '../../../network/types';
3
+ import type { EngineFetch, HttpRequest } from '../../../network/types';
4
+ import type { Registry } from '../../../wire/interpreter';
4
5
  import type { EmbedRequest, EmbedResult, EmbeddingProviderAdapter } from '../../../plugins/embeddings/types';
5
6
  export interface OpenAIEmbeddingAdapterConfig {
6
7
  apiKey: string;
@@ -11,6 +12,17 @@ export declare class OpenAIEmbeddingAdapter implements EmbeddingProviderAdapter
11
12
  protected readonly apiKey: string;
12
13
  protected readonly _baseURL: string;
13
14
  constructor(config: OpenAIEmbeddingAdapterConfig);
15
+ /** Named code the spec cannot express as data — the array coercion. */
16
+ protected readonly wireRegistry: Registry;
14
17
  protected embeddingsPath(): string;
18
+ /** Which spec builds the request. OpenRouter is the same wire on a different
19
+ * host and path, expressed as a one-line override of this spec. */
20
+ protected specId(): string;
21
+ /** The request, built and inspectable without performing it.
22
+ *
23
+ * `input` is always an array on the wire even when the caller passes one
24
+ * string, which is the sort of rule that belongs in data rather than in a
25
+ * ternary nobody re-reads. */
26
+ buildEmbedRequest(req: EmbedRequest): HttpRequest;
15
27
  embed(req: EmbedRequest, fetch: EngineFetch): Promise<EmbedResult>;
16
28
  }