@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.
- package/CHANGELOG.md +555 -0
- package/MIGRATION.md +130 -0
- package/dist/agent/loop-internals.d.ts +4 -0
- package/dist/agent/loop.d.ts +35 -0
- package/dist/bus/hook-bus.d.ts +13 -3
- package/dist/bus/hook-map.d.ts +24 -0
- package/dist/{llm/providers → catalog}/builtin-tools.d.ts +1 -1
- package/dist/{plugins/model-catalog → catalog}/catalog.d.ts +19 -0
- package/dist/helpers/client-pool.d.ts +1 -1
- package/dist/helpers/client-resolver.d.ts +22 -3
- package/dist/helpers/count-tokens.d.ts +1 -1
- package/dist/helpers/engine.d.ts +39 -6
- package/dist/helpers/mcp.d.ts +6 -1
- package/dist/helpers/models.d.ts +1 -1
- package/dist/helpers/one-shot.d.ts +2 -2
- package/dist/helpers/select-model.d.ts +1 -1
- package/dist/index.browser.js +17611 -4247
- package/dist/index.d.ts +5 -5
- package/dist/index.js +17611 -4247
- package/dist/llm/client-config.d.ts +9 -1
- package/dist/llm/client-internal.d.ts +11 -0
- package/dist/llm/client.d.ts +6 -0
- package/dist/llm/providers/_shared/sse.d.ts +19 -0
- package/dist/llm/providers/anthropic/batch.d.ts +13 -2
- package/dist/llm/providers/anthropic/constants.d.ts +8 -43
- package/dist/llm/providers/anthropic/files.d.ts +13 -2
- package/dist/llm/providers/anthropic/messages.d.ts +18 -1
- package/dist/llm/providers/google/batch.d.ts +14 -1
- package/dist/llm/providers/google/constants.d.ts +6 -15
- package/dist/llm/providers/google/embeddings.d.ts +8 -1
- package/dist/llm/providers/google/files.d.ts +34 -1
- package/dist/llm/providers/google/generate.d.ts +15 -1
- package/dist/llm/providers/google/interactions.d.ts +5 -1
- package/dist/llm/providers/google/media.d.ts +45 -4
- package/dist/llm/providers/google/realtime.d.ts +15 -2
- package/dist/llm/providers/openai/batch.d.ts +19 -2
- package/dist/llm/providers/openai/completions.d.ts +13 -1
- package/dist/llm/providers/openai/embeddings.d.ts +13 -1
- package/dist/llm/providers/openai/files.d.ts +13 -2
- package/dist/llm/providers/openai/media.d.ts +31 -2
- package/dist/llm/providers/openai/moderations.d.ts +11 -1
- package/dist/llm/providers/openai/realtime.d.ts +18 -2
- package/dist/llm/providers/openai/responses.d.ts +10 -2
- package/dist/llm/providers/openai/transcription.d.ts +7 -1
- package/dist/llm/providers/openrouter/completions.d.ts +5 -3
- package/dist/llm/providers/openrouter/embeddings.d.ts +3 -0
- package/dist/llm/providers/openrouter/media.d.ts +14 -4
- package/dist/llm/providers/openrouter/responses.d.ts +5 -3
- package/dist/llm/providers/xai/batch.d.ts +13 -2
- package/dist/llm/providers/xai/completions.d.ts +5 -3
- package/dist/llm/providers/xai/files.d.ts +13 -2
- package/dist/llm/providers/xai/media.d.ts +28 -10
- package/dist/llm/providers/xai/responses.d.ts +5 -3
- package/dist/llm/response-shape.d.ts +96 -0
- package/dist/llm/server-state.d.ts +1 -1
- package/dist/llm/types/options.d.ts +2 -2
- package/dist/llm/types/provider.d.ts +6 -0
- package/dist/llm/types/request.d.ts +49 -1
- package/dist/llm/wire-multipart.d.ts +26 -0
- package/dist/llm/wire-transforms.d.ts +28 -0
- package/dist/plugins/context-measurer/counter/count-api.d.ts +40 -6
- package/dist/plugins/context-measurer/counter/heuristic.d.ts +1 -1
- package/dist/plugins/context-measurer/counter/hybrid.d.ts +34 -1
- package/dist/plugins/context-measurer/counter/tiktoken.d.ts +7 -0
- package/dist/plugins/context-measurer/measurer.d.ts +1 -1
- package/dist/plugins/cost-collector/collector.d.ts +1 -1
- package/dist/plugins/cost-collector/cost-collector-internal.d.ts +1 -1
- package/dist/plugins/cost-collector/cost-collector-types.d.ts +1 -1
- package/dist/plugins/files/registry.d.ts +1 -1
- package/dist/plugins/files/strategy.d.ts +1 -1
- package/dist/plugins/internal-tools/registry.d.ts +1 -1
- package/dist/plugins/internal-tools/runner/types.d.ts +1 -1
- package/dist/plugins/mcp/sampling.d.ts +23 -1
- package/dist/plugins/mcp/transport-http.d.ts +13 -7
- package/dist/plugins/mcp/wire-rules.d.ts +21 -0
- package/dist/plugins/media/output.d.ts +1 -1
- package/dist/plugins/retrieval/document-file.d.ts +15 -0
- package/dist/plugins/retrieval/hosted-google.d.ts +8 -3
- package/dist/plugins/retrieval/hosted-openai.d.ts +9 -1
- package/dist/plugins/retrieval/hosted-xai.d.ts +8 -2
- package/dist/plugins/telemetry/telemetry.d.ts +2 -133
- package/dist/plugins/telemetry/types.d.ts +139 -0
- package/dist/util/hash.d.ts +8 -0
- package/dist/{plugins/media → util}/source-image.d.ts +1 -1
- package/dist/wire/chat-specs.d.ts +32 -0
- package/dist/wire/inherit.d.ts +43 -0
- package/dist/wire/interpreter.d.ts +279 -0
- package/dist/wire/mcp-specs.d.ts +13 -0
- package/dist/wire/media-specs.d.ts +20 -0
- package/dist/wire/pins.d.ts +34 -0
- package/dist/wire/registry.d.ts +20 -0
- package/dist/wire/retrieval-specs.d.ts +14 -0
- package/dist/wire/service-specs.d.ts +18 -0
- package/dist/wire/utility-specs.d.ts +10 -0
- package/package.json +6 -3
package/MIGRATION.md
CHANGED
|
@@ -1,3 +1,133 @@
|
|
|
1
|
+
# Migrating to 3.0.0
|
|
2
|
+
|
|
3
|
+
**Almost certainly no source changes.** One type member was removed, and it was one the SDK
|
|
4
|
+
set for its own adapters to read — not something an application was ever expected to write.
|
|
5
|
+
|
|
6
|
+
## `ModelInfo.wire`, `NormalizedRequest.wire` and the `ModelWire` type are gone
|
|
7
|
+
|
|
8
|
+
`wire` carried per-model traits (`{ thinking, topK }`) that told an adapter which shape a model
|
|
9
|
+
takes. `ModelInfo.wireSpec` — which names the wire spec that builds the request — now carries the
|
|
10
|
+
same knowledge, and it carries it exactly once.
|
|
11
|
+
|
|
12
|
+
That duplication was the point of removing it. Two representations of one fact drift apart, and
|
|
13
|
+
this library has already shipped two bugs from precisely that: 2.2.1 sent Anthropic the retired
|
|
14
|
+
`thinking` shape, and 2.2.2 sent Gemini tool schemas on the wrong field. Keeping `wire` alongside
|
|
15
|
+
`wireSpec` would have been the same mistake with better tests.
|
|
16
|
+
|
|
17
|
+
**What to do:** nothing, unless you read `.wire` off a catalog entry. If you did:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
// before
|
|
21
|
+
const shape = catalog.get('anthropic', model)?.wire?.thinking; // 'adaptive' | 'budgeted'
|
|
22
|
+
|
|
23
|
+
// after — the pin names the spec, and the spec defines the shape
|
|
24
|
+
const spec = catalog.get('anthropic', model)?.wireSpec; // 'anthropic/messages@4.7'
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
If you were SETTING `wire` on a custom catalog entry to steer an adapter, set `wireSpec` instead:
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
catalog.set('anthropic', 'my-model', { pricing: {}, wireSpec: 'anthropic/messages@4.1' });
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Behaviour is unchanged.** Every catalogued model produces the byte-identical request it produced
|
|
34
|
+
in 2.3.0 — checked on every CI run against a corpus frozen from the 2.3.0 tag: 290 subjects across
|
|
35
|
+
23 request shapes, on both the pinned and the id-derived route.
|
|
36
|
+
|
|
37
|
+
## The catalog is loaded by default
|
|
38
|
+
|
|
39
|
+
`createEngine()` and `LLMClient` now start with the bundled provider catalogs instead of an empty
|
|
40
|
+
one. **Almost certainly no source change** — and if you were already passing `catalog: 'defaults'`,
|
|
41
|
+
that still works and now says the same thing twice.
|
|
42
|
+
|
|
43
|
+
What changes if you were NOT passing it: prices become known, token counts can use the exact
|
|
44
|
+
strategies, and requests are built from the model's wire-spec pin rather than from a rule over its
|
|
45
|
+
id. All three were falling back before, silently.
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
// before — an empty catalog unless you asked
|
|
49
|
+
const engine = createEngine({ apiKeys }); // no pricing, no pins
|
|
50
|
+
const engine = createEngine({ apiKeys, catalog: 'defaults' }); // the data
|
|
51
|
+
|
|
52
|
+
// after — the data, unless you opt out
|
|
53
|
+
const engine = createEngine({ apiKeys }); // the data
|
|
54
|
+
const engine = createEngine({ apiKeys, catalog: false }); // no entries, on purpose
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
If you relied on an empty catalog — to be certain no bundled price was used, say — pass
|
|
58
|
+
`catalog: false` (or `'empty'`).
|
|
59
|
+
|
|
60
|
+
## The token-count APIs need the engine's fetch
|
|
61
|
+
|
|
62
|
+
`AnthropicCountApi` and `GoogleCountApi` used to default their second argument to
|
|
63
|
+
`globalThis.fetch`. That default is gone: the fetch is required, and it is an `EngineFetch` — the
|
|
64
|
+
same request-object fetch every other adapter takes — rather than a WHATWG `(url, init)` one.
|
|
65
|
+
|
|
66
|
+
**Almost certainly no source change.** `countTokens()` and `HybridTokenCounter` build these for you,
|
|
67
|
+
and `countTokens()` passes `engine.fetch`. You only touch this if you construct one directly:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
// before — went around the NetworkEngine entirely
|
|
71
|
+
const api = new AnthropicCountApi(apiKey);
|
|
72
|
+
|
|
73
|
+
// after
|
|
74
|
+
const api = new AnthropicCountApi(apiKey, engine.fetch);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
If you build a `HybridTokenCounter` yourself and want the exact count APIs, pass `fetch`:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
new HybridTokenCounter({ catalog, countApiKeys, fetch: engine.fetch });
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Without it the exact strategies are unavailable and counting falls back to the heuristic, with a
|
|
84
|
+
warning — rather than quietly calling the provider outside the queue, the rate limiter, the retry
|
|
85
|
+
policy and the telemetry, which is what the old default did.
|
|
86
|
+
|
|
87
|
+
## `hooks.onAny` receives one event instead of `(name, ctx)`
|
|
88
|
+
|
|
89
|
+
Only affects code that subscribes to the WHOLE event stream. `hooks.on('onCompletion', h)` — the
|
|
90
|
+
named subscription — is unchanged.
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
// before
|
|
94
|
+
hooks.onAny((name, ctx) => {
|
|
95
|
+
if (name === 'onCompletion') {
|
|
96
|
+
const c = ctx as { response?: { usage?: { inputTokens?: number } } };
|
|
97
|
+
record(c.response?.usage?.inputTokens ?? 0);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// after — `event.type` narrows `event.ctx`, so the cast is gone
|
|
102
|
+
hooks.onAny((event) => {
|
|
103
|
+
if (event.type === 'onCompletion') {
|
|
104
|
+
record(event.ctx.response.usage?.inputTokens ?? 0);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The old shape forced every subscriber to cast, and a cast keeps compiling after the field it names
|
|
110
|
+
is renamed — which for a usage or cost field is a metric that silently reads zero. `HookEvent` is
|
|
111
|
+
derived from `HookMap`, so a hook added later becomes a variant your `switch` is told about.
|
|
112
|
+
|
|
113
|
+
## Google requests carry the API key in a header, not the URL
|
|
114
|
+
|
|
115
|
+
Behaviour, not signature: no source change, and no key of yours moves. Twelve Google endpoints —
|
|
116
|
+
files, batch, media, embeddings, count — used to append `?key=…` to the URL; they now send
|
|
117
|
+
`x-goog-api-key`. Nothing to update unless something in your infrastructure reads the key OUT of
|
|
118
|
+
the URL: an allowlist matching on the query string, a log scrubber written against `key=`, or a
|
|
119
|
+
proxy that routes on it. Those stop seeing it, which was the point — a URL travels through logs,
|
|
120
|
+
proxies and error reports that a header does not.
|
|
121
|
+
|
|
122
|
+
## Nothing else was removed
|
|
123
|
+
|
|
124
|
+
The band helpers that went with it — `anthropicThinkingShape`, `anthropicAcceptsTopK`,
|
|
125
|
+
`googleUsesThinkingBudget`, and the thinking-budget tables — were never exported from the package
|
|
126
|
+
entry point, and the `exports` map has always blocked deep imports, so no application could reach
|
|
127
|
+
them. They now live as data in `src/wire/pins/`, which is what the Python and Rust ports read.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
1
131
|
# Migrating to 2.0.0
|
|
2
132
|
|
|
3
133
|
**Most codebases need no source changes.** The point of this library is that provider churn is our
|
|
@@ -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.
|
package/dist/agent/loop.d.ts
CHANGED
|
@@ -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
|
package/dist/bus/hook-bus.d.ts
CHANGED
|
@@ -12,9 +12,19 @@
|
|
|
12
12
|
* swallow — emitters need to know if a critical handler (like ContextGuard
|
|
13
13
|
* abort) failed. Plugins that should never break the request must catch
|
|
14
14
|
* their own errors. */
|
|
15
|
-
import type { HookHandler, HookMap, HookName } from './hook-map';
|
|
16
|
-
/** Catch-all handler: receives
|
|
17
|
-
|
|
15
|
+
import type { HookEvent, HookHandler, HookMap, HookName } from './hook-map';
|
|
16
|
+
/** Catch-all handler: receives EVERY emit as one discriminated union.
|
|
17
|
+
*
|
|
18
|
+
* It used to receive `(name, ctx: unknown)`, which pushed the type back onto the
|
|
19
|
+
* subscriber — and the SDK's own telemetry adapter, the only consumer, answered
|
|
20
|
+
* the way anyone would: `ctx as Record<string, unknown>`, then a cast per field —
|
|
21
|
+
* 26 of them in one method.
|
|
22
|
+
* Renaming a context field left those reads compiling and silently `undefined`,
|
|
23
|
+
* which for the token and cost fields means a metric that quietly goes to zero.
|
|
24
|
+
*
|
|
25
|
+
* With `HookEvent`, `e.type` narrows `e.ctx` and those casts stop being possible
|
|
26
|
+
* to write. */
|
|
27
|
+
export type AnyHookHandler = (event: HookEvent) => void | Promise<void>;
|
|
18
28
|
export declare class HookBus {
|
|
19
29
|
private handlers;
|
|
20
30
|
private anyHandlers;
|
package/dist/bus/hook-map.d.ts
CHANGED
|
@@ -621,3 +621,27 @@ export interface HookMap {
|
|
|
621
621
|
}
|
|
622
622
|
export type HookName = keyof HookMap;
|
|
623
623
|
export type HookHandler<K extends HookName> = (ctx: HookMap[K]) => void | Promise<void>;
|
|
624
|
+
/** One event, as a value. `HookMap` types a SUBSCRIPTION (`on('onCompletion', h)`
|
|
625
|
+
* knows its own ctx); this types the STREAM, where the name is not known until
|
|
626
|
+
* runtime and the payload has to travel with it.
|
|
627
|
+
*
|
|
628
|
+
* Derived from `HookMap` rather than written out, so the 51 variants cannot drift
|
|
629
|
+
* from the 51 hooks: adding an entry above adds a variant here, and a consumer
|
|
630
|
+
* switching exhaustively over `type` stops compiling until it handles the new one.
|
|
631
|
+
*
|
|
632
|
+
* hooks.onAny((e) => {
|
|
633
|
+
* if (e.type === 'onCompletion') e.ctx.response.usage; // narrowed
|
|
634
|
+
* });
|
|
635
|
+
*
|
|
636
|
+
* The payload stays nested under `ctx` instead of being spread onto the event.
|
|
637
|
+
* Spreading would collide with the contexts that already carry their own `type`
|
|
638
|
+
* field, and would copy an object on every emit — including the per-chunk ones. */
|
|
639
|
+
export type HookEventOf<K extends HookName> = {
|
|
640
|
+
readonly type: K;
|
|
641
|
+
readonly ctx: HookMap[K];
|
|
642
|
+
};
|
|
643
|
+
/** The SDK's whole event stream as a single discriminated union — the portable
|
|
644
|
+
* shape: a Rust enum, a Python tagged union, a TS union, all from one catalog. */
|
|
645
|
+
export type HookEvent = {
|
|
646
|
+
[K in HookName]: HookEventOf<K>;
|
|
647
|
+
}[HookName];
|
|
@@ -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[]>;
|
|
@@ -94,6 +94,17 @@ export interface ModelInfo {
|
|
|
94
94
|
/** Other callable ids that resolve to this model (dated snapshots, the bare
|
|
95
95
|
* callable form). Indexed for lookup + accepted as model strings. */
|
|
96
96
|
aliases?: string[];
|
|
97
|
+
/** Which wire spec builds this model's requests, e.g. `anthropic/messages@4.7`.
|
|
98
|
+
*
|
|
99
|
+
* The specs live in `src/wire/specs` and are the artifact the Python and Rust
|
|
100
|
+
* ports consume, so the pin is what lets all three agree on a model without
|
|
101
|
+
* each re-deriving it from the model id.
|
|
102
|
+
*
|
|
103
|
+
* Today the pin is carried and validated but the hand-written adapters still
|
|
104
|
+
* build requests, reading `wire` above. Driving them from the pinned spec is
|
|
105
|
+
* the 3.0.0 step, at which point `wire` goes away — the spec already encodes
|
|
106
|
+
* the same knowledge, which is why a test asserts the two agree. */
|
|
107
|
+
wireSpec?: string;
|
|
97
108
|
/** Model role/modality: chat | code | image | video | tts | stt | embedding | … */
|
|
98
109
|
type?: string;
|
|
99
110
|
/** Content kinds the model ACCEPTS as input: text | image | audio | video |
|
|
@@ -156,6 +167,14 @@ export declare class ModelCatalog {
|
|
|
156
167
|
isStateModelBound(provider: string, model: string): boolean;
|
|
157
168
|
list(provider?: string): ModelInfo[];
|
|
158
169
|
load(data: Record<string, unknown>): void;
|
|
170
|
+
/** A catalog with every bundled provider entry already loaded.
|
|
171
|
+
*
|
|
172
|
+
* This is what the engine and the client build when nobody says otherwise. A
|
|
173
|
+
* fresh instance each time rather than a shared one: the catalog is mutable
|
|
174
|
+
* (`set()` is public and examples use it), so sharing would let one engine's
|
|
175
|
+
* edit reach another's request. Indexing all 427 entries costs about a
|
|
176
|
+
* millisecond, against a network call. */
|
|
177
|
+
static withProviderDefaults(): ModelCatalog;
|
|
159
178
|
/** Load every provider's `catalog.json` shipped with the SDK. Synchronous —
|
|
160
179
|
* the JSON files are bundled via static import so no I/O at runtime. */
|
|
161
180
|
loadProviderDefaults(): void;
|
|
@@ -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 '../
|
|
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 '../
|
|
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. */
|
|
@@ -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
|
-
*
|
|
50
|
-
*
|
|
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
|
|
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;
|
package/dist/helpers/engine.d.ts
CHANGED
|
@@ -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 '../
|
|
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,18 @@ 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
|
+
/** Whether clients built from this engine check response shapes. Read by
|
|
69
|
+
* `createLLM`; see `checkResponseShapes` on the options. */
|
|
70
|
+
checkResponseShapes: boolean;
|
|
71
|
+
/** Build an LLMClient bound to this engine.
|
|
72
|
+
*
|
|
73
|
+
* Exists so lower layers can obtain a client without importing the helpers
|
|
74
|
+
* layer: `plugins/internal-tools` needs one for LLM-backed tools, and
|
|
75
|
+
* importing `createLLM` directly made `plugins` depend on `helpers` while
|
|
76
|
+
* `helpers` already depended on `plugins` — a cycle that a Rust crate split
|
|
77
|
+
* cannot express. The engine is something those plugins already hold, so it
|
|
78
|
+
* is the natural place to hand the capability down. */
|
|
79
|
+
createClient(options: Omit<CreateLLMOptions, 'engine'>): LLMClient;
|
|
66
80
|
/** Tear down all owned plugins. */
|
|
67
81
|
destroy(): void;
|
|
68
82
|
}
|
|
@@ -89,12 +103,31 @@ export interface EngineConfig {
|
|
|
89
103
|
/** Custom low-level fetch transport — forwarded to the NetworkEngine's queue
|
|
90
104
|
* (so retry/rate-limit/hooks still apply). Defaults to globalThis.fetch. */
|
|
91
105
|
fetch?: FetchFn;
|
|
92
|
-
/**
|
|
93
|
-
*
|
|
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
|
|
94
127
|
* - existing ModelCatalog instance → use as-is
|
|
95
|
-
* - `{ entries: {...} }` →
|
|
96
|
-
* -
|
|
97
|
-
catalog?: ModelCatalog |
|
|
128
|
+
* - `{ entries: {...} }` → the given entries only
|
|
129
|
+
* - `false` / 'empty' → no entries. Everything falls back; say so on purpose. */
|
|
130
|
+
catalog?: ModelCatalog | boolean | 'defaults' | 'empty' | {
|
|
98
131
|
entries: Record<string, unknown>;
|
|
99
132
|
};
|
|
100
133
|
/** Per-provider API keys. Helpers consult this when no apiKey is passed
|
package/dist/helpers/mcp.d.ts
CHANGED
|
@@ -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 {
|
package/dist/helpers/models.d.ts
CHANGED
|
@@ -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 '../
|
|
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?:
|
|
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 '../
|
|
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 {
|