@combycode/llm-sdk 2.2.2 → 2.3.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 (49) hide show
  1. package/CHANGELOG.md +182 -0
  2. package/dist/agent/loop-internals.d.ts +4 -0
  3. package/dist/agent/loop.d.ts +35 -0
  4. package/dist/{llm/providers → catalog}/builtin-tools.d.ts +1 -1
  5. package/dist/{plugins/model-catalog → catalog}/catalog.d.ts +34 -0
  6. package/dist/helpers/client-pool.d.ts +1 -1
  7. package/dist/helpers/client-resolver.d.ts +1 -1
  8. package/dist/helpers/engine.d.ts +12 -1
  9. package/dist/helpers/mcp.d.ts +6 -1
  10. package/dist/helpers/models.d.ts +1 -1
  11. package/dist/helpers/one-shot.d.ts +2 -2
  12. package/dist/helpers/select-model.d.ts +1 -1
  13. package/dist/index.browser.js +1414 -890
  14. package/dist/index.d.ts +4 -4
  15. package/dist/index.js +1414 -890
  16. package/dist/llm/client-config.d.ts +1 -1
  17. package/dist/llm/client-internal.d.ts +11 -0
  18. package/dist/llm/client.d.ts +4 -0
  19. package/dist/llm/providers/_shared/sse.d.ts +19 -0
  20. package/dist/llm/providers/google/files.d.ts +15 -0
  21. package/dist/llm/providers/google/media.d.ts +22 -4
  22. package/dist/llm/providers/google/realtime.d.ts +15 -2
  23. package/dist/llm/providers/openai/media.d.ts +10 -1
  24. package/dist/llm/providers/openai/realtime.d.ts +15 -2
  25. package/dist/llm/server-state.d.ts +1 -1
  26. package/dist/llm/types/options.d.ts +2 -2
  27. package/dist/llm/types/request.d.ts +50 -1
  28. package/dist/plugins/context-measurer/counter/count-api.d.ts +1 -1
  29. package/dist/plugins/context-measurer/counter/heuristic.d.ts +1 -1
  30. package/dist/plugins/context-measurer/counter/hybrid.d.ts +1 -1
  31. package/dist/plugins/context-measurer/measurer.d.ts +1 -1
  32. package/dist/plugins/cost-collector/collector.d.ts +1 -1
  33. package/dist/plugins/cost-collector/cost-collector-internal.d.ts +1 -1
  34. package/dist/plugins/cost-collector/cost-collector-types.d.ts +1 -1
  35. package/dist/plugins/files/registry.d.ts +1 -1
  36. package/dist/plugins/files/strategy.d.ts +1 -1
  37. package/dist/plugins/internal-tools/registry.d.ts +1 -1
  38. package/dist/plugins/internal-tools/runner/types.d.ts +1 -1
  39. package/dist/plugins/mcp/sampling.d.ts +23 -1
  40. package/dist/plugins/media/output.d.ts +1 -1
  41. package/dist/plugins/telemetry/telemetry.d.ts +2 -133
  42. package/dist/plugins/telemetry/types.d.ts +139 -0
  43. package/dist/util/hash.d.ts +8 -0
  44. package/dist/{plugins/media → util}/source-image.d.ts +1 -1
  45. package/dist/wire/inherit.d.ts +47 -0
  46. package/dist/wire/interpreter.d.ts +236 -0
  47. package/dist/wire/registry.d.ts +18 -0
  48. package/dist/wire/transforms.d.ts +22 -0
  49. package/package.json +3 -3
package/CHANGELOG.md CHANGED
@@ -4,6 +4,188 @@ All notable changes to `@combycode/llm-sdk` are documented here. The format foll
4
4
  [Keep a Changelog](https://keepachangelog.com/) and the project adheres to
5
5
  [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [2.3.0] — 2026-08-23
8
+
9
+ ### Added
10
+
11
+ - **Every catalogued chat model is pinned to a wire spec.** `ModelInfo.wireSpec` names the spec
12
+ that builds that model's requests — `anthropic/messages@4.7`, `google/generate@2.5`,
13
+ `openai/responses`, and so on. All 289 chat models across five providers carry one.
14
+
15
+ The pin is what lets this SDK and the Python and Rust ports agree on a model without each
16
+ re-deriving its wire shape from the model id — the derivation that produced the 2.2.1 and 2.2.2
17
+ bugs.
18
+
19
+ Carried and validated, not yet authoritative: the hand-written adapters still build requests
20
+ from `wire` traits. Because that is two representations of one fact, and two representations
21
+ drift, a test drives the PINNED SPEC and asserts the request it produces matches what the traits
22
+ say the model takes. When the adapters become spec-driven in 3.0.0, `wire` goes away and that
23
+ test is what makes the swap safe.
24
+
25
+ - **Wire specs ship in the repo** (`src/wire/`). 71 JSON specs describe how to talk to each
26
+ provider API — field names, enum values, defaults, versioned tool-type strings, which shape a
27
+ model version takes — covering every adapter the SDK has: chat, interactions, media, realtime,
28
+ embeddings, files and batch.
29
+
30
+ They exist so the Python and Rust ports consume one artifact instead of re-deriving the same
31
+ knowledge three times, and so a provider change is one reviewable diff rather than three code
32
+ changes. This is the knowledge that, living in regexes, produced the 2.2.1 and 2.2.2 bugs.
33
+
34
+ **Oracle, not yet authority:** the adapters remain hand-written, and a test
35
+ (`tests/unit/wire`) proves the specs and the adapters agree on every CI run — data that is
36
+ never executed rots. Making the specs authoritative is the 3.0.0 step.
37
+
38
+ They are not exported from `index.ts` and are tree-shaken out of `dist`, so they add **no bytes**
39
+ to the published package (verified: package size unchanged).
40
+
41
+ - **Every catalogued chat model is checked against its pinned spec on every run.** The previous
42
+ pin test drove Anthropic and Google models only — 26 of 289. The other 263, 224 of them
43
+ OpenRouter, were covered by nothing stronger than "the pin names a spec that resolves", which a
44
+ typo satisfies. `tests/unit/wire/every-model-reproduces-its-adapter.test.ts` now builds 17
45
+ request shapes for every chat model, through the spec the CATALOG pins it to, and requires the
46
+ payload to equal the one the real adapter produces — ~4,900 comparisons, under a second.
47
+
48
+ The adapter is chosen from `preferredApi`, never from the pin. Choosing it from the pin is
49
+ circular and silently so: mis-pin an OpenRouter model to `openai/responses` and the adapter
50
+ moves with it, both sides agree, and the sweep stays green on a broken pin. It did exactly that
51
+ until a deliberate corruption caught it. Mis-pinning any single model in any of the five
52
+ providers now fails, as does a provider losing its pins entirely.
53
+
54
+ - **A consumer example for traces** (`telemetry-traces`, in the examples corpus): subscribing with
55
+ `onTrace`, filtering by span type at the subscription rather than in the handler, head sampling,
56
+ keeping prompt content out, joining an inbound `traceparent`, naming a run with
57
+ `label`/`source`/`attributes`, and reading `client.routing`. The trace feed had no example at
58
+ all, which the quality gate's example-first check was reporting.
59
+
60
+ - **Request builders on the media and realtime adapters.** These adapters used to assemble each
61
+ request *inside* the method that also fetched and parsed it, so the only way to see what the SDK
62
+ would send was to intercept the network. Construction is now separated:
63
+
64
+ - `GoogleMediaAdapter`: `buildImageRequest`, `buildEditImageRequest`, `buildAudioRequest`,
65
+ `buildVideoRequest`, plus the lower-level `buildImagenRequest` / `buildGenerateContentRequest`.
66
+ - `OpenAIMediaAdapter`: `buildGenerateImageRequest`, `buildEditImageRequest`, `buildAudioRequest`,
67
+ `buildVideoRequest`.
68
+ - Realtime: `buildConnectRequest` on both adapters, and free functions
69
+ `buildOpenAISessionUpdate` / `buildOpenAITurnFrames` and `buildGoogleSetupFrame` /
70
+ `buildGoogleTurnFrames` for the handshake and per-turn frames.
71
+
72
+ The public methods now call these, so the two cannot drift — and a test
73
+ (`media-request-builders.test.ts`) asserts that what a builder returns is byte-identical to what
74
+ its method actually sends.
75
+
76
+ Additive: no existing signature changed.
77
+
78
+ - **The catalog now knows how to TALK to a model, not just what it can do.** `ModelInfo` gains a
79
+ `wire` block carrying per-model wire traits — which `thinking` shape the model accepts, whether
80
+ it takes `top_k` — and `LLMClient` resolves it onto every request as `NormalizedRequest.wire`.
81
+ Adapters read that instead of parsing the model id.
82
+
83
+ This is the gap behind two shipped bugs. The catalog already recorded that a model supported
84
+ reasoning; nothing recorded which of two incompatible `thinking` shapes it accepted, so adapters
85
+ matched on the id and got it wrong twice — in 2.2.1, and again in the 4.0 date-suffix defect
86
+ fixed this release. Wire knowledge is now reviewable data that can be diffed and generated,
87
+ rather than a regex nobody re-reads.
88
+
89
+ The bundled catalog carries `wire` for all 14 Anthropic and 12 Google chat models, generated
90
+ from the existing rules so behaviour is unchanged on day one. When the catalog is silent —
91
+ an engine running without one, or an uncatalogued model — adapters fall back to parsing the id
92
+ exactly as before, so nothing breaks. Removing that fallback is a later step, once every model
93
+ is pinned.
94
+
95
+ - `EngineHandle.createClient(options)` — build an `LLMClient` bound to that engine. Added so
96
+ `plugins/internal-tools` can obtain a client without importing `createLLM` from the helpers
97
+ layer. Additive on a handle callers receive rather than implement.
98
+
99
+ - `util/hash` — FNV-1a 32-bit, deterministic and dependency-free, for deriving stable short ids
100
+ from content instead of from a clock.
101
+
102
+ ### Changed
103
+
104
+ - **The module graph is now a DAG.** Two dependency cycles between top-level layers were closed:
105
+ `llm <-> plugins` and `helpers <-> plugins`. They were harmless in TypeScript and are not
106
+ harmless in Rust, where crates cannot express a cycle, so they blocked the port.
107
+
108
+ Shared code moved DOWN rather than sideways: the model catalog and its bundled data now live in
109
+ `src/catalog/` instead of `src/plugins/model-catalog/` + `src/llm/providers/*/catalog.json`, and
110
+ image-source normalisation moved from `src/plugins/media/source-image` to `src/util/source-image`.
111
+ Where a lower layer genuinely needs a capability from a higher one it is now passed down instead
112
+ of imported up.
113
+
114
+ **No public API changed** — the package has a single root export and every moved symbol is
115
+ re-exported from the same place as before. A new test (`tests/unit/architecture/layers.test.ts`)
116
+ fails if any cycle returns.
117
+
118
+ - **Internal cleanups carried over from the 1.0 backlog.**
119
+ - `buildContext` read `LLMClient`'s private `queueName` / `configName` / `cacheName` through
120
+ `as unknown as` casts, which compile happily and would silently yield `undefined` the day a
121
+ field is renamed. The client now exposes them deliberately as `client.routing`, so a rename is
122
+ a type error.
123
+ - Telemetry's ten exported types moved from the 1,225-line `telemetry.ts` to
124
+ `plugins/telemetry/types.ts`, matching the rest of the codebase. Re-exported from the old path,
125
+ so no import — public or internal — changed.
126
+ - `sseJson()` in `providers/_shared` replaces the one line every provider's stream parser
127
+ repeated verbatim.
128
+
129
+ - **`providerOptions` is typed.** It was `Record<string, unknown>` — the one untyped hole in the
130
+ request, and so the one place a typo produced silence rather than an error:
131
+ `promtCacheOptions` type-checked and was simply never sent.
132
+
133
+ The new `ProviderOptions` interface documents every key an adapter actually reads
134
+ (`userProfileId`; `moderationPolicy`, `promptCacheOptions`, `reasoningMode`;
135
+ `responseModalities`, `speechConfig`, `imageConfig`, `translationConfig`, `cachedContent`;
136
+ `openrouter`), derived from the read sites rather than invented. Two `as` casts at those sites
137
+ became unnecessary and were removed.
138
+
139
+ **Not breaking:** the index signature stays, so an unmodelled key is still accepted — providers
140
+ ship parameters before the SDK models them, and refusing those would make the escape hatch
141
+ useless. What changed is that the keys we do know are checked and discoverable.
142
+
143
+ - **`AgentLoop.complete()` and `stream()` no longer duplicate their scaffolding.** The two are the
144
+ same loop with different plumbing, and they had drifted into near-duplicates — 167 identical
145
+ lines across ~560. Five shared pieces are now extracted: `recordRunError`, `resolveFinalText`,
146
+ `buildFinalResponse`, `settleRun`, and `buildStepOptions`.
147
+
148
+ This is not tidying. The `ctx` block in `buildStepOptions` is what stops one conversation
149
+ arriving at a collector as several unrelated traces; duplicated, a fix to one path would have
150
+ left the other silently splitting. The same applies to the run's final-text rules and its
151
+ error reporting.
152
+
153
+ `complete()` 303 -> 234 lines, `stream()` 261 -> 209, identical shared lines 167 -> ~118.
154
+ Behaviour unchanged: all 1,953 tests pass untouched.
155
+
156
+ ### Fixed
157
+
158
+ - **Anthropic 4.0 models were handed the 4.6+ `thinking` shape.** `anthropicThinkingShape()`
159
+ parsed a model id with `/^claude-[a-z]+-(\d+)(?:[-.](\d+))?/`, so an id carrying a release date
160
+ but no minor version read the date AS the minor: `claude-opus-4-20250514` became major 4 /
161
+ minor 20250514, cleared the `>= 4.6` test, and was sent `thinking: {type:'adaptive'}` — the
162
+ opposite of what the file's own `ANTHROPIC_ADAPTIVE_THINKING_MIN = {major:4, minor:6}` says.
163
+
164
+ This never shipped a failure: `claude-opus-4-20250514` and `claude-sonnet-4-20250514` are the
165
+ only affected ids and both are `active: false` in the catalog (deprecated 2026-05-14). It is
166
+ nonetheless the 2.2.1 regression in mirror image, so it is fixed rather than left latent. The
167
+ minor is now bounded to one or two digits and must not be followed by another digit. Checked
168
+ against every id in the catalog plus aliases and future-shaped ids (`claude-opus-4-10`,
169
+ `claude-sonnet-6-1-20270101`): exactly those two change classification, the other 17 are
170
+ untouched.
171
+
172
+ - **xAI batch creation was not reproducible.** The create call named the batch
173
+ `` `batch_${Date.now()}` ``, which made it the only request in the provider surface that was not
174
+ a pure function of its input: it could not be asserted in a test or reproduced from a log, and a
175
+ retried create produced a second batch under a different name that nothing could deduplicate.
176
+ The name is now derived from the batch contents, so identical submissions produce identical
177
+ requests and differing ones still differ.
178
+
179
+ - **`google/files` rejected Google's own resource-name format.** `delete()` and `getInfo()`
180
+ normalised the file id only when it contained `/files/` *with* a leading slash — true of the
181
+ full `uri` this adapter returns from `upload()` and `list()`, and false of `files/abc`, the
182
+ canonical `name` the Google API itself returns. Passing that back produced
183
+ `/v1beta/files/files/abc` and a 404.
184
+
185
+ It never broke the library's own round-trip, which is why it survived: the only way to reach it
186
+ was to use the provider's own id format. All three forms — full uri, `files/abc`, and a bare
187
+ name — now normalise to the same request.
188
+
7
189
  ## [2.2.2] — 2026-08-17
8
190
 
9
191
  ### Fixed
@@ -20,6 +20,10 @@ export type RunTrace = TraceContext & {
20
20
  sessionId: string;
21
21
  requestId: string;
22
22
  };
23
+ /** Why a run ended. Written inline in both `complete()` and `stream()` and in
24
+ * `finalizeRun`'s argument list, which is three places to keep in step; named
25
+ * so adding a reason is one edit. */
26
+ export type RunEndReason = 'done' | 'stopped' | 'error' | 'guardrail' | 'max_steps';
23
27
  /** Create a fresh StepState for the start of a streaming step. */
24
28
  export declare function makeStepState(): StepState;
25
29
  /** Accumulate one SSE StreamEvent into StepState.
@@ -94,6 +94,41 @@ export declare class AgentLoop {
94
94
  removeTool(name: string): void;
95
95
  toolNames(): string[];
96
96
  stop(): void;
97
+ /** Record a thrown error onto the run: emit `onRunError` and return the parts
98
+ * the caller assigns. Both paths report `phase: 'llm_call'`. */
99
+ private recordRunError;
100
+ /** The run's final text.
101
+ *
102
+ * `finalAnswerText` strips `phase: 'commentary'` parts: a codex-family model
103
+ * narrates before it answers and `response.text` concatenates both, so an
104
+ * agent's final output used to include its own thinking-out-loud. Falls back
105
+ * to `.text` when the content carries no text parts, and is identical to
106
+ * `.text` for every model that reports no phase.
107
+ *
108
+ * The streaming path passes what it accumulated; the non-streaming path lets
109
+ * the last response decide. Both share the guardrail / max-steps overrides. */
110
+ private resolveFinalText;
111
+ /** Compose the run's CompletionResponse. `media` and `raw` are parameters
112
+ * because the two paths genuinely differ: the streaming path has already
113
+ * emitted media as events and never holds a raw provider payload. */
114
+ private buildFinalResponse;
115
+ /** Close out a run: write the report, then surface a failure.
116
+ *
117
+ * The order matters and is the reason this is one function rather than two
118
+ * calls at each site — a failed run must still emit its metrics and hooks
119
+ * before the error propagates, or a crash silently loses the run's telemetry.
120
+ * A failed run throws rather than returning empty text, matching the raw
121
+ * client. */
122
+ private settleRun;
123
+ /** Per-step options for the underlying LLM call: loop defaults, then the
124
+ * caller's overrides, then the run's trace.
125
+ *
126
+ * `complete()` and `stream()` duplicated this verbatim, differing only in the
127
+ * name of the local holding the composed system prompt. That is a bad place
128
+ * for a copy: the ctx block below is what stops one conversation arriving at
129
+ * the collector as several unrelated traces, and a fix applied to one path
130
+ * would have left the other silently splitting. */
131
+ private buildStepOptions;
97
132
  complete(input: string | ContentPart[] | Message[], options?: ExecuteOptions): Promise<CompletionResponse>;
98
133
  /** Run `complete` with a JSON Schema enforced via `structured`, then
99
134
  * JSON.parse the response text. Tool calls are still allowed within the
@@ -14,5 +14,5 @@
14
14
  * - web_fetch → anthropic (web_fetch_20260318), google (urlContext).
15
15
  * OpenAI has no separate fetch tool (its web_search does page-open); xAI /
16
16
  * openrouter expose none. */
17
- import type { ProviderName } from '../types/provider';
17
+ import type { ProviderName } from '../llm/types/provider';
18
18
  export declare const PROVIDER_BUILTIN_TOOLS: Record<ProviderName, readonly string[]>;
@@ -73,6 +73,27 @@ 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
+ }
76
97
  export interface ModelInfo {
77
98
  provider: string;
78
99
  /** Catalog key — our canonical (normalised) slug, e.g. `claude-opus-4.8`. */
@@ -94,6 +115,19 @@ export interface ModelInfo {
94
115
  /** Other callable ids that resolve to this model (dated snapshots, the bare
95
116
  * callable form). Indexed for lookup + accepted as model strings. */
96
117
  aliases?: string[];
118
+ /** Per-model wire traits — see ModelWire. */
119
+ wire?: ModelWire;
120
+ /** Which wire spec builds this model's requests, e.g. `anthropic/messages@4.7`.
121
+ *
122
+ * The specs live in `src/wire/specs` and are the artifact the Python and Rust
123
+ * ports consume, so the pin is what lets all three agree on a model without
124
+ * each re-deriving it from the model id.
125
+ *
126
+ * Today the pin is carried and validated but the hand-written adapters still
127
+ * build requests, reading `wire` above. Driving them from the pinned spec is
128
+ * the 3.0.0 step, at which point `wire` goes away — the spec already encodes
129
+ * the same knowledge, which is why a test asserts the two agree. */
130
+ wireSpec?: string;
97
131
  /** Model role/modality: chat | code | image | video | tts | stt | embedding | … */
98
132
  type?: string;
99
133
  /** Content kinds the model ACCEPTS as input: text | image | audio | video |
@@ -3,7 +3,7 @@
3
3
  * ClientResolver and (in the legacy SDK) by InternalToolRunner. */
4
4
  import { LLMClient } from '../llm/client';
5
5
  import type { LLMClientConfig } from '../llm/client-config';
6
- import type { ModelCatalog } from '../plugins/model-catalog/catalog';
6
+ import type { ModelCatalog } from '../catalog/catalog';
7
7
  export declare class ClientPool {
8
8
  private readonly catalog?;
9
9
  private clients;
@@ -9,7 +9,7 @@ import type { LLMClientConfig } from '../llm/client-config';
9
9
  import type { HookBus } from '../bus/hook-bus';
10
10
  import type { ProviderName } from '../llm/types/provider';
11
11
  import type { ServiceTier } from '../llm/types/tiers';
12
- import type { ModelCatalog } from '../plugins/model-catalog/catalog';
12
+ import type { ModelCatalog } from '../catalog/catalog';
13
13
  import type { EngineFetch, EngineFetchStream } from '../network/types';
14
14
  export interface ClientResolverConfig {
15
15
  /** provider → API key. Providers absent from this map can't be resolved. */
@@ -17,13 +17,15 @@
17
17
  * // llm.client uses engine.fetch + engine.hooks automatically. */
18
18
  import { AgentBus } from '../bus/agent-bus';
19
19
  import { HookBus } from '../bus/hook-bus';
20
+ import type { LLMClient } from '../llm/client';
21
+ import { type CreateLLMOptions } from './llm';
20
22
  import type { ProviderName } from '../llm/types/provider';
21
23
  import { NetworkEngine, type QueueSettings } from '../network/engine';
22
24
  import type { RetryPolicyOverride } from '../network/queue-state-config';
23
25
  import type { EngineConnect, EngineFetch, EngineFetchStream, FetchFn } from '../network/types';
24
26
  import { Cache } from '../plugins/cache/cache';
25
27
  import { CostCollector } from '../plugins/cost-collector/collector';
26
- import { ModelCatalog } from '../plugins/model-catalog/catalog';
28
+ import { ModelCatalog } from '../catalog/catalog';
27
29
  import { TelemetryAdapter, type TelemetryAdapterOptions } from '../plugins/telemetry/telemetry';
28
30
  import type { Persistence } from '../plugins/persistence/types';
29
31
  export interface EngineHandle {
@@ -63,6 +65,15 @@ export interface EngineHandle {
63
65
  * createMediaOutput, complete) read these to wire LLM clients without
64
66
  * the caller passing apiKey explicitly. */
65
67
  apiKeys: Partial<Record<ProviderName, string>>;
68
+ /** Build an LLMClient bound to this engine.
69
+ *
70
+ * Exists so lower layers can obtain a client without importing the helpers
71
+ * layer: `plugins/internal-tools` needs one for LLM-backed tools, and
72
+ * importing `createLLM` directly made `plugins` depend on `helpers` while
73
+ * `helpers` already depended on `plugins` — a cycle that a Rust crate split
74
+ * cannot express. The engine is something those plugins already hold, so it
75
+ * is the natural place to hand the capability down. */
76
+ createClient(options: Omit<CreateLLMOptions, 'engine'>): LLMClient;
66
77
  /** Tear down all owned plugins. */
67
78
  destroy(): void;
68
79
  }
@@ -11,7 +11,12 @@ import type { AgentTool } from '../agent/types';
11
11
  import { McpClient } from '../plugins/mcp/client';
12
12
  import { type McpAuthProvider } from '../plugins/mcp/oauth';
13
13
  import type { SsrfGuardOptions } from '../plugins/mcp/url-guard';
14
- import { type McpSamplingConfig } from '../plugins/mcp/sampling';
14
+ import { type McpSamplingConfig, type McpSamplingHandler } from '../plugins/mcp/sampling';
15
+ /** Build a sampling handler that fulfils an MCP server's `sampling/createMessage`
16
+ * with our own engine. Thin wiring: the MCP-shape mapping lives in
17
+ * `plugins/mcp/sampling`, which cannot import this layer without closing a
18
+ * dependency cycle, so the completion function is passed down instead. */
19
+ export declare function samplingHandler(config: McpSamplingConfig): McpSamplingHandler;
15
20
  import { type McpElicitRequestParams, type McpElicitResult, type McpInitializeResult, type McpRoot, type McpServerConfig } from '../plugins/mcp/types';
16
21
  import { type EngineHandle } from './engine';
17
22
  export interface ConnectMcpOptions {
@@ -9,7 +9,7 @@
9
9
  * `{ raw: true }` for bare id strings. Results are cached IN MEMORY for 24h
10
10
  * (override with `refresh: true`). */
11
11
  import type { ProviderName } from '../llm/types/provider';
12
- import type { ModelInfo } from '../plugins/model-catalog/catalog';
12
+ import type { ModelInfo } from '../catalog/catalog';
13
13
  import { type EngineHandle } from './engine';
14
14
  /** Curated local catalog (the main answer). */
15
15
  export declare function listModels(opts?: {
@@ -15,7 +15,7 @@
15
15
  * LLMClient.complete. Either way the helper destroys its created client
16
16
  * before returning so callers don't leak. */
17
17
  import type { AgentTool } from '../agent/types';
18
- import type { CacheConfig, ThinkingConfig } from '../llm/types/request';
18
+ import type { CacheConfig, ProviderOptions, ThinkingConfig } from '../llm/types/request';
19
19
  import type { LLMClientConfig } from '../llm/client-config';
20
20
  import type { AudioOptions } from '../llm/types/audio';
21
21
  import type { ContentPart, Message } from '../llm/types/messages';
@@ -84,7 +84,7 @@ export interface CompleteOptions {
84
84
  /** Optional engine to use. Falls back to coreRegistry default. */
85
85
  engine?: EngineHandle;
86
86
  /** Provider-specific request options (e.g. `{ openrouter: { models: [...] } }`). */
87
- providerOptions?: Record<string, unknown>;
87
+ providerOptions?: ProviderOptions;
88
88
  /** Extra LLMClient options. */
89
89
  client?: Partial<Omit<LLMClientConfig, 'provider' | 'model' | 'apiKey'>>;
90
90
  /** When set, `estimate()` runs BEFORE the request is sent. If the cost for
@@ -11,7 +11,7 @@
11
11
  * Availability-aware: only considers providers with a configured API key.
12
12
  * Ranks cheapest-first (tiebreak: newest version); select() returns the single
13
13
  * best, selectModels() the ranked list. Thresholds + custom tags are overridable. */
14
- import type { ModelInfo } from '../plugins/model-catalog/catalog';
14
+ import type { ModelInfo } from '../catalog/catalog';
15
15
  import type { ProviderName } from '../llm/types/provider';
16
16
  import { type EngineHandle } from './engine';
17
17
  export interface SelectPrefs {