@combycode/llm-sdk 2.3.0 → 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 (67) hide show
  1. package/CHANGELOG.md +373 -0
  2. package/MIGRATION.md +130 -0
  3. package/dist/bus/hook-bus.d.ts +13 -3
  4. package/dist/bus/hook-map.d.ts +24 -0
  5. package/dist/catalog/catalog.d.ts +8 -23
  6. package/dist/helpers/client-resolver.d.ts +21 -2
  7. package/dist/helpers/count-tokens.d.ts +1 -1
  8. package/dist/helpers/engine.d.ts +27 -5
  9. package/dist/index.browser.js +17131 -4291
  10. package/dist/index.d.ts +1 -1
  11. package/dist/index.js +17131 -4291
  12. package/dist/llm/client-config.d.ts +8 -0
  13. package/dist/llm/client.d.ts +2 -0
  14. package/dist/llm/providers/anthropic/batch.d.ts +13 -2
  15. package/dist/llm/providers/anthropic/constants.d.ts +8 -43
  16. package/dist/llm/providers/anthropic/files.d.ts +13 -2
  17. package/dist/llm/providers/anthropic/messages.d.ts +18 -1
  18. package/dist/llm/providers/google/batch.d.ts +14 -1
  19. package/dist/llm/providers/google/constants.d.ts +6 -15
  20. package/dist/llm/providers/google/embeddings.d.ts +8 -1
  21. package/dist/llm/providers/google/files.d.ts +19 -1
  22. package/dist/llm/providers/google/generate.d.ts +15 -1
  23. package/dist/llm/providers/google/interactions.d.ts +5 -1
  24. package/dist/llm/providers/google/media.d.ts +26 -3
  25. package/dist/llm/providers/openai/batch.d.ts +19 -2
  26. package/dist/llm/providers/openai/completions.d.ts +13 -1
  27. package/dist/llm/providers/openai/embeddings.d.ts +13 -1
  28. package/dist/llm/providers/openai/files.d.ts +13 -2
  29. package/dist/llm/providers/openai/media.d.ts +21 -1
  30. package/dist/llm/providers/openai/moderations.d.ts +11 -1
  31. package/dist/llm/providers/openai/realtime.d.ts +3 -0
  32. package/dist/llm/providers/openai/responses.d.ts +10 -2
  33. package/dist/llm/providers/openai/transcription.d.ts +7 -1
  34. package/dist/llm/providers/openrouter/completions.d.ts +5 -3
  35. package/dist/llm/providers/openrouter/embeddings.d.ts +3 -0
  36. package/dist/llm/providers/openrouter/media.d.ts +14 -4
  37. package/dist/llm/providers/openrouter/responses.d.ts +5 -3
  38. package/dist/llm/providers/xai/batch.d.ts +13 -2
  39. package/dist/llm/providers/xai/completions.d.ts +5 -3
  40. package/dist/llm/providers/xai/files.d.ts +13 -2
  41. package/dist/llm/providers/xai/media.d.ts +28 -10
  42. package/dist/llm/providers/xai/responses.d.ts +5 -3
  43. package/dist/llm/response-shape.d.ts +96 -0
  44. package/dist/llm/types/provider.d.ts +6 -0
  45. package/dist/llm/types/request.d.ts +5 -6
  46. package/dist/llm/wire-multipart.d.ts +26 -0
  47. package/dist/{wire/transforms.d.ts → llm/wire-transforms.d.ts} +11 -5
  48. package/dist/plugins/context-measurer/counter/count-api.d.ts +39 -5
  49. package/dist/plugins/context-measurer/counter/hybrid.d.ts +33 -0
  50. package/dist/plugins/context-measurer/counter/tiktoken.d.ts +7 -0
  51. package/dist/plugins/mcp/transport-http.d.ts +13 -7
  52. package/dist/plugins/mcp/wire-rules.d.ts +21 -0
  53. package/dist/plugins/retrieval/document-file.d.ts +15 -0
  54. package/dist/plugins/retrieval/hosted-google.d.ts +8 -3
  55. package/dist/plugins/retrieval/hosted-openai.d.ts +9 -1
  56. package/dist/plugins/retrieval/hosted-xai.d.ts +8 -2
  57. package/dist/wire/chat-specs.d.ts +32 -0
  58. package/dist/wire/inherit.d.ts +0 -4
  59. package/dist/wire/interpreter.d.ts +44 -1
  60. package/dist/wire/mcp-specs.d.ts +13 -0
  61. package/dist/wire/media-specs.d.ts +20 -0
  62. package/dist/wire/pins.d.ts +34 -0
  63. package/dist/wire/registry.d.ts +6 -4
  64. package/dist/wire/retrieval-specs.d.ts +14 -0
  65. package/dist/wire/service-specs.d.ts +18 -0
  66. package/dist/wire/utility-specs.d.ts +10 -0
  67. package/package.json +4 -1
@@ -62,13 +62,19 @@ export declare class HttpTransport extends BaseJsonRpcTransport implements McpTr
62
62
  * two. The eventual JSON-RPC response has no pending entry to settle — its only meaning is "the
63
63
  * stream ended", which is reported through `onEnd`. */
64
64
  sendLongLivedRequest(method: string, params?: unknown, onEnd?: (error?: unknown) => void): Promise<number>;
65
- private headers;
66
- /** Modern-era routing headers: `Mcp-Method` on every request, plus `Mcp-Name` carrying the
67
- * method's subject (tool name / prompt name / resource URI) so a gateway can route and
68
- * authorize without parsing the body. No-op on the handshake wire. */
69
- private routingHeaders;
70
- /** Base headers + any OAuth bearer + per-call extras. */
71
- private authedHeaders;
65
+ /** The shared registry plus the two rules only MCP has. */
66
+ private readonly wireRegistry;
67
+ /** Build one MCP request from its spec.
68
+ *
69
+ * Everything that varies — the negotiated era, the session, the declared
70
+ * protocol version, a resolved bearer, the resumption cursor is passed IN, so
71
+ * the spec decides which headers those facts produce. That decision used to be
72
+ * spread across three private helpers and the order in which their results were
73
+ * spread into an object literal.
74
+ *
75
+ * `provider` / `model` route and queue the call inside the NetworkEngine and are
76
+ * not part of the wire, so they wrap the spec's output. */
77
+ private request0;
72
78
  private post;
73
79
  }
74
80
  /** Extract the JSON-RPC response matching `id` from a JSON or SSE body. */
@@ -0,0 +1,21 @@
1
+ /** The two MCP rules a spec cannot express as data, and where they live.
2
+ *
3
+ * Every other named spec rule sits in `src/llm/wire-transforms.ts`, but these
4
+ * cannot: `llm -> plugins` is a forbidden edge (the layer test names it), and MCP
5
+ * is a plugin. So the transport composes its own registry from the shared one
6
+ * rather than the shared one reaching down into MCP.
7
+ *
8
+ * Both are genuinely code rather than data:
9
+ *
10
+ * `mcpModern` — era is set only AFTER discovery succeeds, so a request has to be
11
+ * judged by the version it DECLARES as well. Keying on era alone left the
12
+ * `server/discover` probe itself half-modern, which a modern server rejects.
13
+ *
14
+ * `mcpNameHeader` — the subject lives under a different param per method (`name`
15
+ * for tools/call and prompts/get, `uri` for resources/read), so this is a lookup
16
+ * followed by a read at the key that lookup returned. A template can express a
17
+ * fixed path, not a computed one.
18
+ */
19
+ import type { Registry } from '../../wire/interpreter';
20
+ /** Add the MCP rules to a base registry, leaving the base untouched. */
21
+ export declare function mcpWireRegistry(base: Registry): Registry;
@@ -0,0 +1,15 @@
1
+ /** The upload part for a document: its bytes, and a name for them.
2
+ *
3
+ * All three hosted backends upload a document the same way — the text as a
4
+ * `text/plain` file — and all three had their own copy of this, which is how they
5
+ * came to disagree about nothing yet still had to be fixed three times.
6
+ *
7
+ * The fallback name is derived from the CONTENT. It used to be a random UUID,
8
+ * which made the request unreproducible: it could not be asserted in a test,
9
+ * frozen in a fixture, or matched against a log, and a retried upload arrived
10
+ * under a different name every time. A content hash keeps a retry idempotent
11
+ * while still separating two different documents.
12
+ */
13
+ import type { MultipartFile } from '../../llm/wire-multipart';
14
+ import type { DocumentSource } from './types';
15
+ export declare function documentFile(source: DocumentSource): MultipartFile;
@@ -38,9 +38,14 @@ export declare class HostedGoogleRetrievalBackend implements RetrievalBackend {
38
38
  private readonly fetch;
39
39
  private readonly baseURL;
40
40
  constructor(config: HostedGoogleRetrievalConfig);
41
- private authHeaders;
42
- /** Auth headers for multipart/form-data file upload (no content-type override). */
43
- private authHeadersNoContentType;
41
+ /** File-search rules need no adapter handles. */
42
+ private readonly wireRegistry;
43
+ /** Build one file-search request from its spec, then add the engine metadata.
44
+ *
45
+ * `provider` / `model` / `responseType` route and queue the call inside the
46
+ * NetworkEngine; they are not part of the wire, so they wrap the spec's output
47
+ * rather than being described by it. */
48
+ private request;
44
49
  createCorpus(opts: CreateCorpusOptions): Promise<CorpusRef>;
45
50
  addDocument(corpus: CorpusRef, source: DocumentSource, opts?: AddDocumentOptions): Promise<DocumentRef>;
46
51
  /** Poll a long-running Operation until done: true.
@@ -29,7 +29,15 @@ export declare class HostedOpenAIRetrievalBackend implements RetrievalBackend {
29
29
  private readonly fetch;
30
30
  private readonly baseURL;
31
31
  constructor(config: HostedOpenAIRetrievalConfig);
32
- private bearer;
32
+ /** Vector-store rules need no adapter handles. */
33
+ private readonly wireRegistry;
34
+ /** Build one vector-store request from its spec, then add the engine metadata.
35
+ *
36
+ * `provider` / `model` / `responseType` are how the NetworkEngine routes and
37
+ * queues the call — they are not part of the wire, so they are wrapped around
38
+ * the spec's output rather than described by it. A multipart spec names the
39
+ * fields but not the bytes, so an upload passes its file in here. */
40
+ private request;
33
41
  createCorpus(opts: CreateCorpusOptions): Promise<CorpusRef>;
34
42
  addDocument(corpus: CorpusRef, source: DocumentSource, opts?: AddDocumentOptions): Promise<DocumentRef>;
35
43
  indexStatus(corpus: CorpusRef): Promise<IndexStatus>;
@@ -41,8 +41,14 @@ export declare class HostedXaiRetrievalBackend implements RetrievalBackend {
41
41
  private readonly baseURL;
42
42
  private readonly managementBaseURL;
43
43
  constructor(config: HostedXaiRetrievalConfig);
44
- private stdBearer;
45
- private mgmtBearer;
44
+ /** Collection rules need no adapter handles. */
45
+ private readonly wireRegistry;
46
+ /** Build one collections request from its spec, then add the engine metadata.
47
+ *
48
+ * BOTH planes are handed to every spec: which host and which key a call uses is
49
+ * a property of the ENDPOINT, so the spec decides it rather than the caller
50
+ * picking a bearer helper and hoping it matches the URL it typed. */
51
+ private request;
46
52
  createCorpus(opts: CreateCorpusOptions): Promise<CorpusRef>;
47
53
  addDocument(corpus: CorpusRef, source: DocumentSource, opts?: AddDocumentOptions): Promise<DocumentRef>;
48
54
  indexStatus(corpus: CorpusRef): Promise<IndexStatus>;
@@ -0,0 +1,32 @@
1
+ /** The wire specs the RUNTIME loads: chat only, resolved and memoised.
2
+ *
3
+ * Deliberately not `registry.ts`. That index imports all 71 specs — every media,
4
+ * realtime, files and batch spec included — so an adapter importing it would pull
5
+ * the whole set into every bundle whether or not anything reads them. Measured on
6
+ * the 2.3.0 build: interpreter + inherit + transforms is 18 KB minified, the nine
7
+ * chat specs add 15 KB, and all 71 specs add 41 KB. Chat-only is +2.7% on the
8
+ * bundle; everything is +4.8% for specs nothing executes yet.
9
+ *
10
+ * `registry.ts` stays as the complete index for the tests and the ports. This is
11
+ * the runtime's subset, and it grows a family at a time as each adapter is
12
+ * migrated — so what ships is always what runs.
13
+ *
14
+ * Chains are resolved once per id and cached: resolution walks `extends` and
15
+ * merges deltas, which is pure setup work and has no business happening per
16
+ * request.
17
+ */
18
+ import type { WireSpec } from './interpreter';
19
+ /** The spec for `id`, with its inheritance chain already applied.
20
+ *
21
+ * Throws on an unknown id rather than falling back to something plausible: a
22
+ * silently-substituted spec is a wrong request sent confidently, which is the
23
+ * exact failure the specs exist to end. Callers pick the fallback themselves —
24
+ * see each adapter's DEFAULT_SPEC. */
25
+ export declare function chatSpec(id: string): WireSpec;
26
+ /** Whether a spec id is one the runtime can build. Lets an adapter fall back to
27
+ * its default instead of throwing when a catalog pin names a spec from a family
28
+ * that is not migrated yet. */
29
+ export declare const isChatSpec: (id: string | undefined) => id is string;
30
+ /** Ids the runtime carries — asserted by the tests so this list and the shipped
31
+ * spec files cannot drift apart unnoticed. */
32
+ export declare const CHAT_SPEC_IDS: readonly string[];
@@ -32,10 +32,6 @@ export interface Deltas {
32
32
  export type SpecDelta = Partial<WireSpec> & Deltas & {
33
33
  id: string;
34
34
  };
35
- /** Apply one delta to a resolved spec. This is the whole of composition, and it
36
- * is deliberately shared: a CHAIN picks the delta sequence by walking parents, a
37
- * MATRIX picks it by selecting features. Behind the resolver they are the same
38
- * operation — see `compose.ts`. */
39
35
  export declare function applyDelta(base: WireSpec, delta: SpecDelta): WireSpec;
40
36
  /** Resolve a spec id to its fully flattened form by walking `extends`. */
41
37
  export declare function resolveSpec(id: string, byId: Map<string, SpecDelta>, seen?: Set<string>): WireSpec;
@@ -108,12 +108,43 @@ export interface WireSpec {
108
108
  /** `json` (default), `multipart`, or `none` for GET/DELETE with no body.
109
109
  * Multipart matters: a FormData body JSON-stringifies to `{}`, so comparing
110
110
  * it as JSON would pass vacuously no matter what the fields are. */
111
- bodyKind?: 'json' | 'multipart' | 'none';
111
+ /** `json` (default), `multipart`, `none` for a bodyless GET/DELETE, or
112
+ * `raw` when the body is caller-supplied BYTES the spec cannot describe —
113
+ * a file being streamed to an upload session. The spec still owns the URL,
114
+ * method and headers; only the payload comes from outside. */
115
+ bodyKind?: 'json' | 'multipart' | 'none' | 'raw' | 'form';
116
+ /** Headers in declaration order. An entry with `spread` merges an evaluated
117
+ * OBJECT of headers instead of setting one, which is what a caller-supplied
118
+ * header map or a resolved auth bundle is. Order is the whole point: it is
119
+ * what decides whether a configured `accept` overrides the default one or the
120
+ * other way round, and that was previously a property of which spread came
121
+ * later in a hand-written object literal. */
112
122
  headers?: {
123
+ name?: string;
124
+ value?: Json;
125
+ when?: Cond;
126
+ spread?: Json;
127
+ }[];
128
+ /** Query parameters, appended to `url` (or `path`) in declaration order.
129
+ *
130
+ * Splicing them into the URL with `$join` works only while every parameter is
131
+ * present: `$join` propagates an omitted part, so one absent `pageToken` takes
132
+ * the whole URL with it. Declaring them separately lets a parameter drop out
133
+ * on its own, and puts the encoding in ONE place — the hand-written adapters
134
+ * disagreed about whether to call `encodeURIComponent`, which is how a page
135
+ * token with a `+` in it silently paged from the wrong place. */
136
+ query?: {
113
137
  name: string;
114
138
  value: Json;
115
139
  when?: Cond;
116
140
  }[];
141
+ /** How query values are escaped. `component` (default) percent-escapes
142
+ * everything, including a space as `%20`. `form` uses the
143
+ * application/x-www-form-urlencoded rules, where a space is `+` — which is
144
+ * what RFC 6749 prescribes for an OAuth authorization request, and what its
145
+ * servers are used to receiving. Both decode to the same string; they are not
146
+ * the same bytes, and a signature over the request would notice. */
147
+ queryEncoding?: 'component' | 'form';
117
148
  };
118
149
  /** Model-id → variant flags. The migration target is a catalog pin; the
119
150
  * `idMatch` form is what today's regex helpers do, expressed as data.
@@ -141,6 +172,11 @@ export interface WireSpec {
141
172
  value?: Json;
142
173
  file?: boolean;
143
174
  when?: Cond;
175
+ /** Emit ONE field per array element instead of a single array-valued
176
+ * field. Real forms use repeated keys for lists — OpenAI's transcription
177
+ * takes `languages[]` once per language — and a single field holding an
178
+ * array is a different request the server will not accept. */
179
+ repeat?: boolean;
144
180
  }[];
145
181
  /** Non-HTTP surfaces. A realtime session is not one request: it is a
146
182
  * connection descriptor plus a sequence of outbound frames, so those are
@@ -205,8 +241,15 @@ export interface BuiltRequest {
205
241
  path?: string;
206
242
  url?: string;
207
243
  method?: string;
244
+ /** The body is caller-supplied bytes (bodyKind 'raw'). */
245
+ rawBody?: boolean;
208
246
  /** Present instead of a JSON body when bodyKind is 'multipart'. */
209
247
  multipart?: MultipartField[];
248
+ /** The body is form-urlencoded: `body` holds the FIELDS, and the caller encodes
249
+ * them. Same split as multipart — the spec says what the form carries, the
250
+ * runtime does the encoding, and the frozen fixture stays readable as fields
251
+ * rather than as one percent-escaped string. */
252
+ formBody?: boolean;
210
253
  /** True when the spec declares the request carries no body at all. */
211
254
  noBody?: boolean;
212
255
  }
@@ -0,0 +1,13 @@
1
+ /** The MCP Streamable-HTTP spec family.
2
+ *
3
+ * A sibling of the provider families, loaded separately for the same reason: an
4
+ * application that never talks to an MCP server should not carry these, and the
5
+ * generated `registry.ts` imports everything.
6
+ *
7
+ * MCP is the case that shows the spec format is about NETWORKING rather than about
8
+ * LLM providers — a JSON-RPC envelope, era-dependent routing headers and a
9
+ * long-lived stream are described by the same six constructs a chat request uses.
10
+ */
11
+ import type { WireSpec } from './interpreter';
12
+ /** Resolve an MCP spec by id, flattening its `extends` chain. */
13
+ export declare function mcpSpec(id: string): WireSpec;
@@ -0,0 +1,20 @@
1
+ /** The media wire specs the RUNTIME loads, resolved and memoised.
2
+ *
3
+ * A sibling of `chat-specs.ts` and separate from it for the same reason that file
4
+ * is separate from `registry.ts`: the generated index imports all 71 specs, so any
5
+ * adapter reaching for it pulls in every family whether or not anything runs them.
6
+ * The runtime loads one family at a time, as each is migrated, so what ships is
7
+ * what executes.
8
+ *
9
+ * Only the leaves are exported — the `*.base` specs exist to be inherited from and
10
+ * are never built directly, so naming one is a mistake worth catching.
11
+ */
12
+ import type { WireSpec } from './interpreter';
13
+ /** The media spec for `id`, with its inheritance chain applied.
14
+ *
15
+ * Throws on an unknown or abstract id rather than substituting something
16
+ * plausible: a quietly wrong endpoint is the failure mode the specs exist to end. */
17
+ export declare function mediaSpec(id: string): WireSpec;
18
+ /** Buildable media spec ids — asserted by the tests so this list and the shipped
19
+ * spec files cannot drift apart unnoticed. */
20
+ export declare const MEDIA_SPEC_IDS: readonly string[];
@@ -0,0 +1,34 @@
1
+ /** Which spec builds a model's request when the catalog has no pin for it.
2
+ *
3
+ * Every catalogued model carries an explicit `wireSpec`, so this only decides for
4
+ * the models the catalog does not know: one released after this build, or an
5
+ * engine running without a catalog at all. That case is not an edge — it is how
6
+ * the SDK works on the day a provider ships something new — so it keeps a real
7
+ * answer rather than a guess.
8
+ *
9
+ * It is DATA, not code, for the same reason the specs are: the Python and Rust
10
+ * ports read this file instead of each re-implementing version arithmetic and
11
+ * drifting from it. Two versions of that arithmetic is exactly how the 2.2.1
12
+ * regression happened.
13
+ *
14
+ * Rules are ordered and the first match wins; `default` answers everything else.
15
+ */
16
+ export interface PinRule {
17
+ /** Anchored regular expression, matched against the model id. */
18
+ match: string;
19
+ spec: string;
20
+ /** Why this band exists. Read by humans, not by the resolver. */
21
+ why?: string;
22
+ }
23
+ export interface ModelPins {
24
+ id: string;
25
+ rules?: PinRule[];
26
+ default: string;
27
+ }
28
+ /** The spec id for `model`, from an ordered rule table.
29
+ *
30
+ * The id is lower-cased and stripped of a `provider/` prefix first, because a
31
+ * caller may legitimately pass either form and a band must not depend on which. */
32
+ export declare function pinFor(model: string, pins: ModelPins): string;
33
+ export declare const ANTHROPIC_MESSAGE_PINS: ModelPins;
34
+ export declare const GOOGLE_GENERATE_PINS: ModelPins;
@@ -5,11 +5,13 @@
5
5
  * is consumed by this SDK and by the Python and Rust ports, and a provider
6
6
  * change is one reviewable diff rather than three code changes.
7
7
  *
8
- * Generated indexregenerate rather than hand-edit when adding a spec.
8
+ * GENERATED by scripts/gen-wire-registry.ts do not hand-edit. Add a spec by
9
+ * dropping the JSON into src/wire/specs/ and running `bun run gen:registry`.
9
10
  *
10
- * Specs are not yet wired into the adapters: they currently serve as the
11
- * differential oracle that proves the hand-written adapters and this data agree
12
- * (see tests/unit/wire). Making them authoritative is the 3.0.0 step.
11
+ * This index is what the chain tests and the coverage audit enumerate. The
12
+ * ADAPTERS do not read it: each family has its own loader (chat-specs,
13
+ * media-specs, service-specs, retrieval-specs) so a chat call does not drag
14
+ * every batch and media spec into the bundle.
13
15
  */
14
16
  import type { SpecDelta } from './inherit';
15
17
  /** All shipped specs, keyed by `provider/api@version` id. */
@@ -0,0 +1,14 @@
1
+ /** The hosted-retrieval spec family: vector stores, file search stores, collections.
2
+ *
3
+ * A sibling of `chat-specs.ts`, `media-specs.ts` and `service-specs.ts`, split for
4
+ * the same reason: the generated `registry.ts` imports all 118 specs, so anything
5
+ * reaching for it drags every family into the bundle. An application that never
6
+ * builds a corpus should not carry these.
7
+ *
8
+ * Only leaves are buildable. The base specs exist to be inherited from — they
9
+ * carry auth and content-type and produce no endpoint of their own — so naming
10
+ * one is a mistake to catch, not a request to send.
11
+ */
12
+ import type { WireSpec } from './interpreter';
13
+ /** Resolve a retrieval spec by id, flattening its `extends` chain. */
14
+ export declare function retrievalSpec(id: string): WireSpec;
@@ -0,0 +1,18 @@
1
+ /** The remaining runtime spec families: embeddings, realtime, batch, files, and
2
+ * the media adapters that had no builder seam until now.
3
+ *
4
+ * A sibling of `chat-specs.ts` and `media-specs.ts`, split for the same reason:
5
+ * the generated `registry.ts` imports all 71 specs, so an adapter reaching for it
6
+ * drags every family into the bundle whether or not anything executes them. Each
7
+ * file here loads exactly what its adapters run.
8
+ *
9
+ * Only leaves are buildable. The `*.base` specs exist to be inherited from and
10
+ * produce no endpoint of their own, so naming one is a mistake to catch rather
11
+ * than a request to send.
12
+ */
13
+ import type { WireSpec } from './interpreter';
14
+ /** The service spec for `id`, with its inheritance chain applied. Throws on an
15
+ * unknown or abstract id rather than substituting something plausible. */
16
+ export declare function serviceSpec(id: string): WireSpec;
17
+ /** Buildable ids, asserted by the tests so this list cannot drift from the files. */
18
+ export declare const SERVICE_SPEC_IDS: readonly string[];
@@ -0,0 +1,10 @@
1
+ /** The utility spec family: exact token counting, live model listing, file
2
+ * content retrieval, and the provenance check.
3
+ *
4
+ * Four small surfaces that share nothing except being requests. They are loaded
5
+ * together because each is a handful of specs and no consumer reaches one without
6
+ * reaching the layer that owns it.
7
+ */
8
+ import type { WireSpec } from './interpreter';
9
+ /** Resolve a utility spec by id, flattening its `extends` chain. */
10
+ export declare function utilitySpec(id: string): WireSpec;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@combycode/llm-sdk",
3
- "version": "2.3.0",
3
+ "version": "3.0.0",
4
4
  "description": "Unified, pluggable AI SDK for accessing the LLMs of every major provider (Anthropic, OpenAI, Google, xAI, OpenRouter) through one API. Cross-environment: Node, Bun, and the browser.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -43,8 +43,11 @@
43
43
  "format": "biome format --write src tests",
44
44
  "check": "biome check src tests",
45
45
  "check:fix": "biome check --write src tests",
46
+ "gen:registry": "bun run scripts/gen-wire-registry.ts",
46
47
  "gate": "bun run scripts/gate.ts",
47
48
  "gate:selftest": "node ../../quality-gate/selftest.mjs",
49
+ "record:responses": "bun run scripts/record-responses.ts",
50
+ "derive:shapes": "bun run scripts/derive-response-shapes.ts",
48
51
  "gate:snapshot": "bun run scripts/gate.ts --only api-snapshot --update"
49
52
  },
50
53
  "devDependencies": {