@combycode/llm-sdk 2.3.0 → 3.1.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 (68) hide show
  1. package/CHANGELOG.md +429 -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 +23 -26
  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/helpers/select-model.d.ts +35 -0
  10. package/dist/index.browser.js +17394 -4392
  11. package/dist/index.d.ts +3 -3
  12. package/dist/index.js +17394 -4392
  13. package/dist/llm/client-config.d.ts +8 -0
  14. package/dist/llm/client.d.ts +5 -0
  15. package/dist/llm/providers/anthropic/batch.d.ts +13 -2
  16. package/dist/llm/providers/anthropic/constants.d.ts +8 -43
  17. package/dist/llm/providers/anthropic/files.d.ts +13 -2
  18. package/dist/llm/providers/anthropic/messages.d.ts +18 -1
  19. package/dist/llm/providers/google/batch.d.ts +14 -1
  20. package/dist/llm/providers/google/constants.d.ts +6 -15
  21. package/dist/llm/providers/google/embeddings.d.ts +8 -1
  22. package/dist/llm/providers/google/files.d.ts +19 -1
  23. package/dist/llm/providers/google/generate.d.ts +15 -1
  24. package/dist/llm/providers/google/interactions.d.ts +5 -1
  25. package/dist/llm/providers/google/media.d.ts +26 -3
  26. package/dist/llm/providers/openai/batch.d.ts +19 -2
  27. package/dist/llm/providers/openai/completions.d.ts +13 -1
  28. package/dist/llm/providers/openai/embeddings.d.ts +13 -1
  29. package/dist/llm/providers/openai/files.d.ts +13 -2
  30. package/dist/llm/providers/openai/media.d.ts +21 -1
  31. package/dist/llm/providers/openai/moderations.d.ts +11 -1
  32. package/dist/llm/providers/openai/realtime.d.ts +3 -0
  33. package/dist/llm/providers/openai/responses.d.ts +10 -2
  34. package/dist/llm/providers/openai/transcription.d.ts +7 -1
  35. package/dist/llm/providers/openrouter/completions.d.ts +5 -3
  36. package/dist/llm/providers/openrouter/embeddings.d.ts +3 -0
  37. package/dist/llm/providers/openrouter/media.d.ts +14 -4
  38. package/dist/llm/providers/openrouter/responses.d.ts +5 -3
  39. package/dist/llm/providers/xai/batch.d.ts +13 -2
  40. package/dist/llm/providers/xai/completions.d.ts +5 -3
  41. package/dist/llm/providers/xai/files.d.ts +13 -2
  42. package/dist/llm/providers/xai/media.d.ts +28 -10
  43. package/dist/llm/providers/xai/responses.d.ts +5 -3
  44. package/dist/llm/response-shape.d.ts +96 -0
  45. package/dist/llm/types/provider.d.ts +11 -0
  46. package/dist/llm/types/request.d.ts +5 -6
  47. package/dist/llm/wire-multipart.d.ts +26 -0
  48. package/dist/{wire/transforms.d.ts → llm/wire-transforms.d.ts} +11 -5
  49. package/dist/plugins/context-measurer/counter/count-api.d.ts +39 -5
  50. package/dist/plugins/context-measurer/counter/hybrid.d.ts +33 -0
  51. package/dist/plugins/context-measurer/counter/tiktoken.d.ts +7 -0
  52. package/dist/plugins/mcp/transport-http.d.ts +13 -7
  53. package/dist/plugins/mcp/wire-rules.d.ts +21 -0
  54. package/dist/plugins/retrieval/document-file.d.ts +15 -0
  55. package/dist/plugins/retrieval/hosted-google.d.ts +8 -3
  56. package/dist/plugins/retrieval/hosted-openai.d.ts +9 -1
  57. package/dist/plugins/retrieval/hosted-xai.d.ts +8 -2
  58. package/dist/wire/chat-specs.d.ts +32 -0
  59. package/dist/wire/inherit.d.ts +0 -4
  60. package/dist/wire/interpreter.d.ts +84 -1
  61. package/dist/wire/mcp-specs.d.ts +13 -0
  62. package/dist/wire/media-specs.d.ts +20 -0
  63. package/dist/wire/pins.d.ts +34 -0
  64. package/dist/wire/registry.d.ts +6 -4
  65. package/dist/wire/retrieval-specs.d.ts +14 -0
  66. package/dist/wire/service-specs.d.ts +18 -0
  67. package/dist/wire/utility-specs.d.ts +10 -0
  68. package/package.json +4 -1
@@ -73,27 +73,6 @@ export interface TokenizerInfo {
73
73
  countApiAvailable: boolean;
74
74
  tiktokenEncoding?: string;
75
75
  }
76
- /** Per-model WIRE traits: how to say a thing to this model, as opposed to
77
- * `capabilities` / `reasoning`, which say what the model can do.
78
- *
79
- * This is the gap that produced two shipped bugs. The catalog knew a model
80
- * supported reasoning; nothing knew which of two incompatible `thinking` shapes
81
- * it accepted, so adapters parsed the model id with regexes and got it wrong
82
- * twice (2.2.1, and the 4.0 date-suffix defect fixed in 039 A1).
83
- *
84
- * Adapters read these through `NormalizedRequest.wire`, which `LLMClient`
85
- * resolves from the catalog. When the catalog has no entry the adapters fall
86
- * back to parsing the id, so a catalog-less engine still works — the fallback
87
- * is what a future release removes once every model is pinned. */
88
- export interface ModelWire {
89
- /** Which `thinking` shape the wire accepts.
90
- * Anthropic: `adaptive` (4.6+) vs `budgeted` (pre-4.6, needs budget_tokens).
91
- * Google: `level` (3.x, thinkingLevel enum) vs `budget` (2.5, token count). */
92
- thinking?: 'adaptive' | 'budgeted' | 'budget' | 'level';
93
- /** Whether the model accepts a top-k sampling parameter. Anthropic retired it
94
- * after Opus 4.6 and 400s on models that no longer take it. */
95
- topK?: boolean;
96
- }
97
76
  export interface ModelInfo {
98
77
  provider: string;
99
78
  /** Catalog key — our canonical (normalised) slug, e.g. `claude-opus-4.8`. */
@@ -115,8 +94,6 @@ export interface ModelInfo {
115
94
  /** Other callable ids that resolve to this model (dated snapshots, the bare
116
95
  * callable form). Indexed for lookup + accepted as model strings. */
117
96
  aliases?: string[];
118
- /** Per-model wire traits — see ModelWire. */
119
- wire?: ModelWire;
120
97
  /** Which wire spec builds this model's requests, e.g. `anthropic/messages@4.7`.
121
98
  *
122
99
  * The specs live in `src/wire/specs` and are the artifact the Python and Rust
@@ -164,14 +141,26 @@ export declare class ModelCatalog {
164
141
  /** `provider/alias` → `provider/canonical-slug`. Lets get()/resolveModelId
165
142
  * accept any callable id (providerModelName, dated snapshot) AND the slug. */
166
143
  private aliasIndex;
144
+ /** `provider/normalized-id` → `provider/canonical-slug`. The last resort, so a
145
+ * user's spelling of a version never decides whether the model is found. */
146
+ private normIndex;
167
147
  private key;
148
+ private normKey;
168
149
  set(provider: string, model: string, info: Partial<Omit<ModelInfo, 'provider' | 'model'>> & {
169
150
  pricing: ModelPricing;
170
151
  }): void;
171
152
  get(provider: string, model: string): ModelInfo | null;
172
- /** The exact id to SEND to the provider for a given model string. Translates
173
- * our slug → providerModelName; passes an already-callable id (alias) through
174
- * verbatim (respects an explicit choice); unknown model verbatim passthrough. */
153
+ /** The exact id to SEND to the provider for a given model string.
154
+ *
155
+ * Three cases, and the difference between them is what the caller ASKED for:
156
+ * - our slug → translate to providerModelName (the pinned snapshot)
157
+ * - an id the provider itself accepts (a listed alias, e.g. a dated snapshot
158
+ * or anthropic's undated name) → verbatim, because it is a deliberate choice
159
+ * and rewriting it would pin a caller who asked to float
160
+ * - a spelling variant that is NOT callable (`gemini-2-5-flash`) → the
161
+ * canonical entry's providerModelName, since forwarding it verbatim only
162
+ * produces a 404 with the user's typo in it
163
+ * - unknown → verbatim, so a model we have never heard of still works */
175
164
  resolveModelId(provider: string, model: string): string;
176
165
  getPricing(provider: string, model: string): ModelPricing | null;
177
166
  getPreferredApi(provider: string, model: string): ApiType | null;
@@ -190,6 +179,14 @@ export declare class ModelCatalog {
190
179
  isStateModelBound(provider: string, model: string): boolean;
191
180
  list(provider?: string): ModelInfo[];
192
181
  load(data: Record<string, unknown>): void;
182
+ /** A catalog with every bundled provider entry already loaded.
183
+ *
184
+ * This is what the engine and the client build when nobody says otherwise. A
185
+ * fresh instance each time rather than a shared one: the catalog is mutable
186
+ * (`set()` is public and examples use it), so sharing would let one engine's
187
+ * edit reach another's request. Indexing all 427 entries costs about a
188
+ * millisecond, against a network call. */
189
+ static withProviderDefaults(): ModelCatalog;
193
190
  /** Load every provider's `catalog.json` shipped with the SDK. Synchronous —
194
191
  * the JSON files are bundled via static import so no I/O at runtime. */
195
192
  loadProviderDefaults(): void;
@@ -46,8 +46,27 @@ export declare class ClientResolver {
46
46
  export declare function parseModelId(modelId: string): [ProviderName, string];
47
47
  export declare function isNamespacedModelId(modelId: string): boolean;
48
48
  /** Resolve a model + optional provider to a concrete { provider, model }.
49
- * A namespaced id ("provider/model") yields its own provider; a bare model
50
- * requires an explicit `provider`. `label` names the caller in the error. */
49
+ *
50
+ * An EXPLICIT provider always wins. It used to lose to the model's prefix, and
51
+ * that is not a preference — every OpenRouter model id is `vendor/model`, so
52
+ * `createLLM({ provider: 'openrouter', model: 'openai/gpt-5.4-nano' })` resolved
53
+ * to the provider `openai` and sent the **OpenRouter key to api.openai.com**.
54
+ * Ids whose vendor is not one of our five (`qwen/qwen3`) fared differently and no
55
+ * better: the prefix was cast to a ProviderName and failed later as "no default
56
+ * adapter for provider 'qwen'".
57
+ *
58
+ * With a provider given, a leading `<provider>/` on the model is redundant and is
59
+ * stripped — `openrouter` + `openrouter/openai/gpt-5.4-nano` is the catalog's own
60
+ * slug form and means the OpenRouter model `openai/gpt-5.4-nano`.
61
+ *
62
+ * Without one, the `provider/model` prefix is still the documented sugar, and it
63
+ * stays permissive about the prefix on purpose: the pricing paths (`estimate`,
64
+ * `estimator`) resolve models that are catalogued under a provider nobody can
65
+ * CALL — a private deployment, a test fixture — and rejecting those would break
66
+ * costing a model you never send. A prefix that is not callable fails where it
67
+ * matters, in the adapter factory, naming the provider it could not build.
68
+ *
69
+ * `label` names the caller in the error. */
51
70
  export declare function resolveModel(model: string, provider: ProviderName | undefined, label: string): {
52
71
  provider: ProviderName;
53
72
  model: string;
@@ -17,7 +17,7 @@ export interface CountTokensOptions {
17
17
  provider?: ProviderName;
18
18
  /** Text or messages to count. */
19
19
  input: string | Message[];
20
- /** Key for the exact count-API path (Anthropic/Google). Falls back to engine.apiKeys. */
20
+ /** Key for the exact count-API path (Anthropic, Google, xAI). Falls back to engine.apiKeys. */
21
21
  apiKey?: string;
22
22
  /** Use the precise counter where available (default true). false = sync estimate. */
23
23
  exact?: boolean;
@@ -65,6 +65,9 @@ export interface EngineHandle {
65
65
  * createMediaOutput, complete) read these to wire LLM clients without
66
66
  * the caller passing apiKey explicitly. */
67
67
  apiKeys: Partial<Record<ProviderName, string>>;
68
+ /** Whether clients built from this engine check response shapes. Read by
69
+ * `createLLM`; see `checkResponseShapes` on the options. */
70
+ checkResponseShapes: boolean;
68
71
  /** Build an LLMClient bound to this engine.
69
72
  *
70
73
  * Exists so lower layers can obtain a client without importing the helpers
@@ -100,12 +103,31 @@ export interface EngineConfig {
100
103
  /** Custom low-level fetch transport — forwarded to the NetworkEngine's queue
101
104
  * (so retry/rate-limit/hooks still apply). Defaults to globalThis.fetch. */
102
105
  fetch?: FetchFn;
103
- /** Catalog wiring. Pass:
104
- * - `true` / 'defaults' load every bundled provider catalog.json
106
+ /** Warn when a provider's response stops looking like the one we learned to
107
+ * read: a field never seen before, a field that was always present and is now
108
+ * absent, or a discriminator carrying a value nothing branches on.
109
+ *
110
+ * OFF by default. It never changes what is parsed — it only emits `onWarning`,
111
+ * so subscribe with `hooks.on('onWarning', …)` and look for codes starting
112
+ * `response_shape_`. Each distinct finding is reported ONCE per client.
113
+ *
114
+ * Worth turning on in staging and in your test suite: response drift is the
115
+ * failure this library gives you the least warning about, because a renamed
116
+ * field still parses — into `undefined`. */
117
+ checkResponseShapes?: boolean;
118
+ /** Catalog wiring. **Defaults to the bundled provider catalogs.**
119
+ *
120
+ * The catalog is what the adapters read per model: which wire spec builds the
121
+ * request, what the model costs, which tokenizer counts it. Starting empty
122
+ * meant every one of those silently fell back — the id-derived spec, an
123
+ * unknown price, an estimated token count — and nothing said so. The data is
124
+ * statically imported either way, so leaving it unloaded saved no bytes.
125
+ *
126
+ * - undefined (default) / `true` / 'defaults' → every bundled catalog.json
105
127
  * - existing ModelCatalog instance → use as-is
106
- * - `{ entries: {...} }` → build empty + load() the entries
107
- * - undefined empty catalog */
108
- catalog?: ModelCatalog | true | 'defaults' | {
128
+ * - `{ entries: {...} }` → the given entries only
129
+ * - `false` / 'empty' no entries. Everything falls back; say so on purpose. */
130
+ catalog?: ModelCatalog | boolean | 'defaults' | 'empty' | {
109
131
  entries: Record<string, unknown>;
110
132
  };
111
133
  /** Per-provider API keys. Helpers consult this when no apiKey is passed
@@ -34,6 +34,41 @@ declare const DEFAULT_THRESHOLDS: {
34
34
  'context.small': number;
35
35
  'context.large': number;
36
36
  };
37
+ /** One filter a UI can offer, and what it accepts. */
38
+ export interface FilterFacet {
39
+ /** The DSL key, e.g. `price`. Write it as `key:value`. */
40
+ key: string;
41
+ /** Group heading for a picker. */
42
+ category: 'what it is' | 'cost' | 'thinking' | 'inputs' | 'hosted tools' | 'availability';
43
+ /** Short human label. */
44
+ label: string;
45
+ /** The values this key accepts. Empty when the key is a bare flag (`vision`),
46
+ * which the parser reads as `key:yes`. */
47
+ values: string[];
48
+ /** True when the key also accepts `> N` / `< N`, so a picker can offer a number. */
49
+ numeric: boolean;
50
+ /** True when a bare `key` (no value) is meaningful — the parser expands it to
51
+ * `key:yes`. */
52
+ bare: boolean;
53
+ }
54
+ /** Every clause the query parser understands, as data.
55
+ *
56
+ * Exported because the alternative is a UI hand-listing the same tags: a second
57
+ * copy of this vocabulary, drifting from the parser the first time either moves,
58
+ * and drifting invisibly because a wrong tag reads as "no models matched" rather
59
+ * than as an error. Here the picker and the parser cannot disagree — both come
60
+ * from `KNOWN_KEYS`, `CAP_KEYS` and `BUILTIN_TOOL_KEYS` above.
61
+ *
62
+ * `type`, `provider`, `status` and `tier` take their values from the CATALOG
63
+ * when one is passed, because those are open sets: a provider ships a new model
64
+ * type and a hard-coded list is wrong that day. Without a catalog they come back
65
+ * empty rather than guessed — an empty list is honest, a stale list is not. */
66
+ export declare function filterFacets(catalog?: {
67
+ list(): ModelInfo[];
68
+ }): FilterFacet[];
69
+ /** The shorthand tags the parser expands before matching (`cheap` → `price:low`).
70
+ * A picker can show these as one-click presets. */
71
+ export declare function filterAliases(): Record<string, string>;
37
72
  /** All matching models, ranked cheapest-first (tiebreak: newest version). */
38
73
  export declare function selectModels(query: string | string[], opts?: SelectOptions): ModelInfo[];
39
74
  /** The single best match as a `provider/slug` string (feedable to complete), or null. */