@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
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
* An in-memory store backs the sandbox sidebar; `toOtlpTraces()` shapes spans
|
|
11
11
|
* into OTLP-compatible JSON for a real OTel exporter to forward. */
|
|
12
12
|
import type { HookBus } from '../../bus/hook-bus';
|
|
13
|
-
|
|
13
|
+
export type { Span, SpanKind, TelemetryAdapterOptions, TelemetryEvent, TelemetryMetrics, TelemetryResource, TraceEvent, TraceEventType, TraceFilter, TraceHandler, } from './types';
|
|
14
|
+
import type { Span, TelemetryAdapterOptions, TelemetryEvent, TelemetryMetrics, TelemetryResource, TraceFilter, TraceHandler } from './types';
|
|
14
15
|
/** Derive a conformant hex id from one of our readable ids. `bytes` is 16 for a trace
|
|
15
16
|
* id, 8 for a span id.
|
|
16
17
|
*
|
|
@@ -22,81 +23,6 @@ export declare function toOtlpId(input: string, bytes: 8 | 16): string;
|
|
|
22
23
|
* `intValue` carrying a STRING, which is how OTLP/JSON encodes 64-bit integers; send
|
|
23
24
|
* them as plain strings instead and no backend can sum them. */
|
|
24
25
|
export declare function toOtlpValue(value: unknown): Record<string, unknown>;
|
|
25
|
-
export type SpanKind = 'llm' | 'http' | 'media' | 'agent' | 'tool' | 'mcp' | 'other';
|
|
26
|
-
export interface Span {
|
|
27
|
-
traceId: string;
|
|
28
|
-
spanId: string;
|
|
29
|
-
/** The span this one runs under. Without it every span is a sibling and a backend
|
|
30
|
-
* draws a flat list instead of a tree — so a run reads as "9 things happened", not
|
|
31
|
-
* "a turn, which called a tool, which asked a second model".
|
|
32
|
-
*
|
|
33
|
-
* Resolved in this order: the innermost container span still open on this trace
|
|
34
|
-
* (`agent.run` / `tool.call`), else the app's span from a supplied `traceparent`,
|
|
35
|
-
* else none — this span is the root. */
|
|
36
|
-
parentSpanId?: string;
|
|
37
|
-
name: string;
|
|
38
|
-
kind: SpanKind;
|
|
39
|
-
startTime: number;
|
|
40
|
-
endTime?: number;
|
|
41
|
-
durationMs?: number;
|
|
42
|
-
status: 'unset' | 'ok' | 'error';
|
|
43
|
-
attributes: Record<string, unknown>;
|
|
44
|
-
}
|
|
45
|
-
/** What kind of work an event describes. `message` is conversation content, which is not
|
|
46
|
-
* a span — it is the thing you want in a debug store and NOT in your metrics backend,
|
|
47
|
-
* which is exactly why it filters separately. */
|
|
48
|
-
export type TraceEventType = 'agent' | 'tool' | 'llm' | 'http' | 'mcp' | 'media' | 'message' | 'other';
|
|
49
|
-
/** One piece of work, carrying enough of the tree that a consumer can push it straight
|
|
50
|
-
* into their own tracer without reconstructing anything. */
|
|
51
|
-
export interface TraceEvent {
|
|
52
|
-
type: TraceEventType;
|
|
53
|
-
/** The app's trace when it supplied a `traceparent`, else ours. */
|
|
54
|
-
traceId: string;
|
|
55
|
-
spanId: string;
|
|
56
|
-
/** Already resolved past anything this subscriber filtered out — see `survivingParent`. */
|
|
57
|
-
parentSpanId?: string;
|
|
58
|
-
/** The conventional name (`chat gpt-5.4-nano`, `execute_tool search`). */
|
|
59
|
-
name: string;
|
|
60
|
-
startTime: number;
|
|
61
|
-
endTime?: number;
|
|
62
|
-
durationMs?: number;
|
|
63
|
-
status: 'unset' | 'ok' | 'error';
|
|
64
|
-
attributes: Record<string, unknown>;
|
|
65
|
-
}
|
|
66
|
-
/** Declarative on purpose, rather than a predicate: knowing the types up front lets a
|
|
67
|
-
* filtered-out event cost nothing, where a predicate would force us to build the payload
|
|
68
|
-
* just to let the caller throw it away. */
|
|
69
|
-
export interface TraceFilter {
|
|
70
|
-
types?: readonly TraceEventType[];
|
|
71
|
-
}
|
|
72
|
-
export type TraceHandler = (event: TraceEvent) => void;
|
|
73
|
-
export interface TelemetryEvent {
|
|
74
|
-
seq: number;
|
|
75
|
-
time: number;
|
|
76
|
-
name: HookName;
|
|
77
|
-
category: string;
|
|
78
|
-
traceId?: string;
|
|
79
|
-
ctx: unknown;
|
|
80
|
-
}
|
|
81
|
-
export interface TelemetryMetrics {
|
|
82
|
-
requests: number;
|
|
83
|
-
errors: number;
|
|
84
|
-
retries: number;
|
|
85
|
-
rateLimitHits: number;
|
|
86
|
-
completions: number;
|
|
87
|
-
mediaGenerated: number;
|
|
88
|
-
costUsd: number;
|
|
89
|
-
inputTokens: number;
|
|
90
|
-
outputTokens: number;
|
|
91
|
-
inFlight: number;
|
|
92
|
-
queueDepth: number;
|
|
93
|
-
latency: {
|
|
94
|
-
count: number;
|
|
95
|
-
min: number;
|
|
96
|
-
max: number;
|
|
97
|
-
avg: number;
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
26
|
/** Parse a W3C `traceparent`: `00-<32 hex trace>-<16 hex span>-<flags>`.
|
|
101
27
|
* Returns null for anything malformed or for the all-zero ids the spec forbids —
|
|
102
28
|
* a bad header must not silently reroute telemetry into a garbage trace. */
|
|
@@ -104,63 +30,6 @@ export declare function parseTraceparent(value: string | undefined): {
|
|
|
104
30
|
traceId: string;
|
|
105
31
|
spanId: string;
|
|
106
32
|
} | null;
|
|
107
|
-
/** OpenTelemetry Resource — identifies the SERVICE producing this telemetry, so
|
|
108
|
-
* a shared backend can separate streams from different apps and attribute cost
|
|
109
|
-
* per service (`sum by service.name`). Stamped on every span/metric/log. */
|
|
110
|
-
export interface TelemetryResource {
|
|
111
|
-
/** Primary grouping key, e.g. "billing-api". OTel default: "unknown_service". */
|
|
112
|
-
serviceName: string;
|
|
113
|
-
/** Optional namespace/group, e.g. "prod" or a team. */
|
|
114
|
-
serviceNamespace?: string;
|
|
115
|
-
/** Unique instance (pod/host/process); a good default is the engine sessionId. */
|
|
116
|
-
serviceInstanceId?: string;
|
|
117
|
-
serviceVersion?: string;
|
|
118
|
-
/** Arbitrary resource attributes (deployment.environment, cloud.region, …). */
|
|
119
|
-
attributes?: Record<string, string>;
|
|
120
|
-
}
|
|
121
|
-
export interface TelemetryAdapterOptions {
|
|
122
|
-
/** Cap on retained events (ring buffer). Default 2000. */
|
|
123
|
-
maxEvents?: number;
|
|
124
|
-
/** Service identity stamped on all exported telemetry. */
|
|
125
|
-
resource?: TelemetryResource;
|
|
126
|
-
/** Whether provider error TEXT may be stored in telemetry. Default `true`
|
|
127
|
-
* (unchanged behaviour, and the same default as the OpenAI Agents SDK's
|
|
128
|
-
* `trace_include_sensitive_data`).
|
|
129
|
-
*
|
|
130
|
-
* A provider's `error.message` / `error.raw` can echo request content back —
|
|
131
|
-
* a moderation refusal quotes the prompt, a validation error names the offending
|
|
132
|
-
* field and value. URLs and headers are always redacted regardless; this switch
|
|
133
|
-
* governs the free-text payload. Set `false` when telemetry leaves your trust
|
|
134
|
-
* boundary (a shared collector, a vendor APM) and the message is replaced by a
|
|
135
|
-
* fixed `[redacted]` string while name/code/status are kept for triage. */
|
|
136
|
-
includeSensitiveData?: boolean;
|
|
137
|
-
/** Which event types to hand to `onTrace`. Omitted → everything.
|
|
138
|
-
*
|
|
139
|
-
* Filtering SPLICES the tree rather than punching holes in it: drop `http` and the
|
|
140
|
-
* spans under it re-parent to the nearest surviving ancestor. Dropping without that
|
|
141
|
-
* leaves orphans, and a backend draws an orphan as a second root — worse than not
|
|
142
|
-
* filtering at all. */
|
|
143
|
-
types?: readonly TraceEventType[];
|
|
144
|
-
/** Whether conversation content rides along on `message` events. Default `'none'`:
|
|
145
|
-
* prompts and completions are the debugging gold AND the PII, so sending them is a
|
|
146
|
-
* decision to make on purpose rather than inherit. `'full'` adds the Opt-In
|
|
147
|
-
* `gen_ai.input.messages` / `gen_ai.output.messages` attributes; `'none'` still
|
|
148
|
-
* reports the shape (counts and sizes), which is enough to spot a runaway prompt. */
|
|
149
|
-
content?: 'none' | 'full';
|
|
150
|
-
/** Fraction of TRACES to emit, 0..1. Default 1.
|
|
151
|
-
*
|
|
152
|
-
* Per trace, never per span: sampling spans independently shreds every tree it touches
|
|
153
|
-
* — a tool call with no run, a model call with no tool. The decision is a hash of the
|
|
154
|
-
* trace id, so it is stable across processes and two services sharing a trace agree
|
|
155
|
-
* without coordinating.
|
|
156
|
-
*
|
|
157
|
-
* This is HEAD sampling: the choice is made when the trace first appears, before we
|
|
158
|
-
* know whether it ends in an error. Keeping all errors needs tail sampling, which
|
|
159
|
-
* needs buffering; do that in your collector, which is built for it. */
|
|
160
|
-
sample?: number;
|
|
161
|
-
/** Convenience for the common case of a single sink — same as calling `onTrace`. */
|
|
162
|
-
onTrace?: TraceHandler;
|
|
163
|
-
}
|
|
164
33
|
export declare class TelemetryAdapter {
|
|
165
34
|
readonly events: TelemetryEvent[];
|
|
166
35
|
readonly spans: Span[];
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/** Telemetry types.
|
|
2
|
+
*
|
|
3
|
+
* Split out of `telemetry.ts` so the adapter file is implementation and this one
|
|
4
|
+
* is contract — matching how the rest of the codebase is laid out, and making
|
|
5
|
+
* the public surface of the plugin readable without paging through 1,200 lines.
|
|
6
|
+
*/
|
|
7
|
+
import type { HookName } from '../../bus/hook-map';
|
|
8
|
+
export type SpanKind = 'llm' | 'http' | 'media' | 'agent' | 'tool' | 'mcp' | 'other';
|
|
9
|
+
export interface Span {
|
|
10
|
+
traceId: string;
|
|
11
|
+
spanId: string;
|
|
12
|
+
/** The span this one runs under. Without it every span is a sibling and a backend
|
|
13
|
+
* draws a flat list instead of a tree — so a run reads as "9 things happened", not
|
|
14
|
+
* "a turn, which called a tool, which asked a second model".
|
|
15
|
+
*
|
|
16
|
+
* Resolved in this order: the innermost container span still open on this trace
|
|
17
|
+
* (`agent.run` / `tool.call`), else the app's span from a supplied `traceparent`,
|
|
18
|
+
* else none — this span is the root. */
|
|
19
|
+
parentSpanId?: string;
|
|
20
|
+
name: string;
|
|
21
|
+
kind: SpanKind;
|
|
22
|
+
startTime: number;
|
|
23
|
+
endTime?: number;
|
|
24
|
+
durationMs?: number;
|
|
25
|
+
status: 'unset' | 'ok' | 'error';
|
|
26
|
+
attributes: Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
/** What kind of work an event describes. `message` is conversation content, which is not
|
|
29
|
+
* a span — it is the thing you want in a debug store and NOT in your metrics backend,
|
|
30
|
+
* which is exactly why it filters separately. */
|
|
31
|
+
export type TraceEventType = 'agent' | 'tool' | 'llm' | 'http' | 'mcp' | 'media' | 'message' | 'other';
|
|
32
|
+
/** One piece of work, carrying enough of the tree that a consumer can push it straight
|
|
33
|
+
* into their own tracer without reconstructing anything. */
|
|
34
|
+
export interface TraceEvent {
|
|
35
|
+
type: TraceEventType;
|
|
36
|
+
/** The app's trace when it supplied a `traceparent`, else ours. */
|
|
37
|
+
traceId: string;
|
|
38
|
+
spanId: string;
|
|
39
|
+
/** Already resolved past anything this subscriber filtered out — see `survivingParent`. */
|
|
40
|
+
parentSpanId?: string;
|
|
41
|
+
/** The conventional name (`chat gpt-5.4-nano`, `execute_tool search`). */
|
|
42
|
+
name: string;
|
|
43
|
+
startTime: number;
|
|
44
|
+
endTime?: number;
|
|
45
|
+
durationMs?: number;
|
|
46
|
+
status: 'unset' | 'ok' | 'error';
|
|
47
|
+
attributes: Record<string, unknown>;
|
|
48
|
+
}
|
|
49
|
+
/** Declarative on purpose, rather than a predicate: knowing the types up front lets a
|
|
50
|
+
* filtered-out event cost nothing, where a predicate would force us to build the payload
|
|
51
|
+
* just to let the caller throw it away. */
|
|
52
|
+
export interface TraceFilter {
|
|
53
|
+
types?: readonly TraceEventType[];
|
|
54
|
+
}
|
|
55
|
+
export type TraceHandler = (event: TraceEvent) => void;
|
|
56
|
+
export interface TelemetryEvent {
|
|
57
|
+
seq: number;
|
|
58
|
+
time: number;
|
|
59
|
+
name: HookName;
|
|
60
|
+
category: string;
|
|
61
|
+
traceId?: string;
|
|
62
|
+
ctx: unknown;
|
|
63
|
+
}
|
|
64
|
+
export interface TelemetryMetrics {
|
|
65
|
+
requests: number;
|
|
66
|
+
errors: number;
|
|
67
|
+
retries: number;
|
|
68
|
+
rateLimitHits: number;
|
|
69
|
+
completions: number;
|
|
70
|
+
mediaGenerated: number;
|
|
71
|
+
costUsd: number;
|
|
72
|
+
inputTokens: number;
|
|
73
|
+
outputTokens: number;
|
|
74
|
+
inFlight: number;
|
|
75
|
+
queueDepth: number;
|
|
76
|
+
latency: {
|
|
77
|
+
count: number;
|
|
78
|
+
min: number;
|
|
79
|
+
max: number;
|
|
80
|
+
avg: number;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/** OpenTelemetry Resource — identifies the SERVICE producing this telemetry, so
|
|
84
|
+
* a shared backend can separate streams from different apps and attribute cost
|
|
85
|
+
* per service (`sum by service.name`). Stamped on every span/metric/log. */
|
|
86
|
+
export interface TelemetryResource {
|
|
87
|
+
/** Primary grouping key, e.g. "billing-api". OTel default: "unknown_service". */
|
|
88
|
+
serviceName: string;
|
|
89
|
+
/** Optional namespace/group, e.g. "prod" or a team. */
|
|
90
|
+
serviceNamespace?: string;
|
|
91
|
+
/** Unique instance (pod/host/process); a good default is the engine sessionId. */
|
|
92
|
+
serviceInstanceId?: string;
|
|
93
|
+
serviceVersion?: string;
|
|
94
|
+
/** Arbitrary resource attributes (deployment.environment, cloud.region, …). */
|
|
95
|
+
attributes?: Record<string, string>;
|
|
96
|
+
}
|
|
97
|
+
export interface TelemetryAdapterOptions {
|
|
98
|
+
/** Cap on retained events (ring buffer). Default 2000. */
|
|
99
|
+
maxEvents?: number;
|
|
100
|
+
/** Service identity stamped on all exported telemetry. */
|
|
101
|
+
resource?: TelemetryResource;
|
|
102
|
+
/** Whether provider error TEXT may be stored in telemetry. Default `true`
|
|
103
|
+
* (unchanged behaviour, and the same default as the OpenAI Agents SDK's
|
|
104
|
+
* `trace_include_sensitive_data`).
|
|
105
|
+
*
|
|
106
|
+
* A provider's `error.message` / `error.raw` can echo request content back —
|
|
107
|
+
* a moderation refusal quotes the prompt, a validation error names the offending
|
|
108
|
+
* field and value. URLs and headers are always redacted regardless; this switch
|
|
109
|
+
* governs the free-text payload. Set `false` when telemetry leaves your trust
|
|
110
|
+
* boundary (a shared collector, a vendor APM) and the message is replaced by a
|
|
111
|
+
* fixed `[redacted]` string while name/code/status are kept for triage. */
|
|
112
|
+
includeSensitiveData?: boolean;
|
|
113
|
+
/** Which event types to hand to `onTrace`. Omitted → everything.
|
|
114
|
+
*
|
|
115
|
+
* Filtering SPLICES the tree rather than punching holes in it: drop `http` and the
|
|
116
|
+
* spans under it re-parent to the nearest surviving ancestor. Dropping without that
|
|
117
|
+
* leaves orphans, and a backend draws an orphan as a second root — worse than not
|
|
118
|
+
* filtering at all. */
|
|
119
|
+
types?: readonly TraceEventType[];
|
|
120
|
+
/** Whether conversation content rides along on `message` events. Default `'none'`:
|
|
121
|
+
* prompts and completions are the debugging gold AND the PII, so sending them is a
|
|
122
|
+
* decision to make on purpose rather than inherit. `'full'` adds the Opt-In
|
|
123
|
+
* `gen_ai.input.messages` / `gen_ai.output.messages` attributes; `'none'` still
|
|
124
|
+
* reports the shape (counts and sizes), which is enough to spot a runaway prompt. */
|
|
125
|
+
content?: 'none' | 'full';
|
|
126
|
+
/** Fraction of TRACES to emit, 0..1. Default 1.
|
|
127
|
+
*
|
|
128
|
+
* Per trace, never per span: sampling spans independently shreds every tree it touches
|
|
129
|
+
* — a tool call with no run, a model call with no tool. The decision is a hash of the
|
|
130
|
+
* trace id, so it is stable across processes and two services sharing a trace agree
|
|
131
|
+
* without coordinating.
|
|
132
|
+
*
|
|
133
|
+
* This is HEAD sampling: the choice is made when the trace first appears, before we
|
|
134
|
+
* know whether it ends in an error. Keeping all errors needs tail sampling, which
|
|
135
|
+
* needs buffering; do that in your collector, which is built for it. */
|
|
136
|
+
sample?: number;
|
|
137
|
+
/** Convenience for the common case of a single sink — same as calling `onTrace`. */
|
|
138
|
+
onTrace?: TraceHandler;
|
|
139
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** FNV-1a, 32-bit. Deterministic, synchronous and dependency-free — used where a
|
|
2
|
+
* stable short id has to be derived from content rather than from a clock.
|
|
3
|
+
*
|
|
4
|
+
* Not cryptographic. Collisions are acceptable for naming and bucketing; do not
|
|
5
|
+
* use it for integrity or security. */
|
|
6
|
+
export declare function fnv1a32(input: string): number;
|
|
7
|
+
/** Same value as 8 lowercase hex characters. */
|
|
8
|
+
export declare function fnv1a32Hex(input: string): string;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* - OpenAI: `{ image_url }` (data-URL) or `{ file_id }`
|
|
6
6
|
* - xAI: `{ url }` (data-URL) or `{ file_id }`
|
|
7
7
|
* - Google: `inline_data {mime_type,data}` or `file_data {file_uri}` */
|
|
8
|
-
import type { DataSource } from '
|
|
8
|
+
import type { DataSource } from '../llm/types/messages';
|
|
9
9
|
export interface NormalizedImageRef {
|
|
10
10
|
/** Raw base64 (no `data:` prefix), when inline. */
|
|
11
11
|
base64?: string;
|
|
@@ -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[];
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** Spec inheritance: a spec version extends the previous one and overrides only
|
|
2
|
+
* what changed.
|
|
3
|
+
*
|
|
4
|
+
* This replaces the `variants` section. Instead of asking "which shape does this
|
|
5
|
+
* model id take?" at request time — which needed version arithmetic, and which
|
|
6
|
+
* is currently WRONG for `claude-opus-4-20250514` (the date suffix parses as the
|
|
7
|
+
* minor version, so a 4.0 model resolves to the 4.6+ shape) — the catalog pins a
|
|
8
|
+
* model to a spec id, and the spec chain carries the differences as deltas.
|
|
9
|
+
*
|
|
10
|
+
* Merge rules, in one sentence each:
|
|
11
|
+
* - `fields` are keyed by `to` — child replaces, `remove:true` deletes, new ones append
|
|
12
|
+
* - `blocks` are keyed by `name` — same, with `before`/`after` for explicit placement
|
|
13
|
+
* - `tables` merge per table, per key
|
|
14
|
+
* - `overlays`/`envelope.headers` are keyed and replaced
|
|
15
|
+
* - everything scalar: child wins
|
|
16
|
+
*
|
|
17
|
+
* Block ORDER is load-bearing (proved by the mutation suite), so appended blocks
|
|
18
|
+
* land at the end unless the delta says otherwise.
|
|
19
|
+
*/
|
|
20
|
+
import type { WireSpec } from './interpreter';
|
|
21
|
+
export interface Deltas {
|
|
22
|
+
extends?: string;
|
|
23
|
+
/** Rules to delete from the inherited spec. */
|
|
24
|
+
removeFields?: string[];
|
|
25
|
+
removeBlocks?: string[];
|
|
26
|
+
/** Explicit placement for an appended block. */
|
|
27
|
+
placeBlocks?: Record<string, {
|
|
28
|
+
before?: string;
|
|
29
|
+
after?: string;
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
32
|
+
export type SpecDelta = Partial<WireSpec> & Deltas & {
|
|
33
|
+
id: string;
|
|
34
|
+
};
|
|
35
|
+
export declare function applyDelta(base: WireSpec, delta: SpecDelta): WireSpec;
|
|
36
|
+
/** Resolve a spec id to its fully flattened form by walking `extends`. */
|
|
37
|
+
export declare function resolveSpec(id: string, byId: Map<string, SpecDelta>, seen?: Set<string>): WireSpec;
|
|
38
|
+
/** Catalog pin: model id -> spec id. `default` is what an unknown model gets. */
|
|
39
|
+
export interface PinTable {
|
|
40
|
+
default: string;
|
|
41
|
+
models: Record<string, string>;
|
|
42
|
+
}
|
|
43
|
+
export declare function specForModel(model: string, pins: PinTable): string;
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/** Wire-spec interpreter — prototype for report 037 / 3.0.0.
|
|
2
|
+
*
|
|
3
|
+
* Turns a declarative wire spec + a NormalizedRequest into the same
|
|
4
|
+
* ProviderHttpRequest the hand-written `buildRequest` produces today.
|
|
5
|
+
*
|
|
6
|
+
* The point is NOT to eliminate code. It is to move the per-provider and
|
|
7
|
+
* per-MODEL knowledge — field names, shapes, enum values, which variant a
|
|
8
|
+
* model takes — out of imperative code and into reviewable data that the
|
|
9
|
+
* update pipeline can diff and that all three language ports can share.
|
|
10
|
+
*
|
|
11
|
+
* Structural work (turning unified messages into provider content parts) stays
|
|
12
|
+
* as named code in the registry. See `transforms.ts`.
|
|
13
|
+
*/
|
|
14
|
+
export type Json = unknown;
|
|
15
|
+
/** A condition evaluated against the request + resolved model variants. */
|
|
16
|
+
export type Cond = {
|
|
17
|
+
defined: string;
|
|
18
|
+
} | {
|
|
19
|
+
truthy: string;
|
|
20
|
+
} | {
|
|
21
|
+
eq: [string, Json];
|
|
22
|
+
} | {
|
|
23
|
+
ne: [string, Json];
|
|
24
|
+
} | {
|
|
25
|
+
variant: string;
|
|
26
|
+
} | {
|
|
27
|
+
flavor: string | string[];
|
|
28
|
+
} | {
|
|
29
|
+
pred: string;
|
|
30
|
+
}
|
|
31
|
+
/** Truthiness of a field on the current $map item. */
|
|
32
|
+
| {
|
|
33
|
+
itemTruthy: string;
|
|
34
|
+
}
|
|
35
|
+
/** The current $map item equals this value (items are scalars here). */
|
|
36
|
+
| {
|
|
37
|
+
itemEq: Json;
|
|
38
|
+
} | {
|
|
39
|
+
isLast: true;
|
|
40
|
+
} | {
|
|
41
|
+
isFunctionTool: true;
|
|
42
|
+
} | {
|
|
43
|
+
builtin: string;
|
|
44
|
+
}
|
|
45
|
+
/** `req.tools` contains a builtin of this type. */
|
|
46
|
+
| {
|
|
47
|
+
hasTool: string;
|
|
48
|
+
}
|
|
49
|
+
/** `req.tools` contains at least one function tool. */
|
|
50
|
+
| {
|
|
51
|
+
hasFunctionTool: true;
|
|
52
|
+
}
|
|
53
|
+
/** Array at `path` contains `value`. */
|
|
54
|
+
| {
|
|
55
|
+
includes: [string, Json];
|
|
56
|
+
} | {
|
|
57
|
+
not: Cond;
|
|
58
|
+
} | {
|
|
59
|
+
all: Cond[];
|
|
60
|
+
} | {
|
|
61
|
+
any: Cond[];
|
|
62
|
+
};
|
|
63
|
+
export interface FieldRule {
|
|
64
|
+
/** Dotted path into NormalizedRequest. */
|
|
65
|
+
from: string;
|
|
66
|
+
/** Dotted path into the body. */
|
|
67
|
+
to: string;
|
|
68
|
+
/** Default when the source is absent. Emits the field even if unset. */
|
|
69
|
+
default?: Json;
|
|
70
|
+
/** Extra gate on top of the presence check. */
|
|
71
|
+
when?: Cond;
|
|
72
|
+
/** Presence test: `defined` (!== undefined) or `truthy`. Default `defined`. */
|
|
73
|
+
presence?: 'defined' | 'truthy';
|
|
74
|
+
/** Named value table to map the source value through. */
|
|
75
|
+
table?: string;
|
|
76
|
+
/** Value used when the table has no entry (rather than dropping the field). */
|
|
77
|
+
tableDefault?: Json;
|
|
78
|
+
/** Named transform applied to the source value. */
|
|
79
|
+
call?: string;
|
|
80
|
+
}
|
|
81
|
+
export interface BlockRule {
|
|
82
|
+
/** Documentation handle; also used in diff output. */
|
|
83
|
+
name: string;
|
|
84
|
+
when?: Cond;
|
|
85
|
+
/** Dotted target path. Omit to merge the result into the body root. */
|
|
86
|
+
to?: string;
|
|
87
|
+
/** Deep-merge into whatever is already at `to` (output_config case). */
|
|
88
|
+
merge?: boolean;
|
|
89
|
+
/** Value template. */
|
|
90
|
+
template?: Json;
|
|
91
|
+
/** Named builder invoked with (req, ctx) instead of a template. */
|
|
92
|
+
call?: string;
|
|
93
|
+
/** Named cross-field effects run after the block is written. */
|
|
94
|
+
effects?: string[];
|
|
95
|
+
}
|
|
96
|
+
export interface WireSpec {
|
|
97
|
+
id: string;
|
|
98
|
+
provider: string;
|
|
99
|
+
api: string;
|
|
100
|
+
/** Adapter flavor, for specs shared by several providers (openai|xai|openrouter). */
|
|
101
|
+
flavors?: string[];
|
|
102
|
+
envelope?: {
|
|
103
|
+
path?: Json;
|
|
104
|
+
/** Full URL template. Non-chat adapters (media, files, batch) address an
|
|
105
|
+
* absolute URL rather than a path under a shared base. */
|
|
106
|
+
url?: Json;
|
|
107
|
+
method?: string;
|
|
108
|
+
/** `json` (default), `multipart`, or `none` for GET/DELETE with no body.
|
|
109
|
+
* Multipart matters: a FormData body JSON-stringifies to `{}`, so comparing
|
|
110
|
+
* it as JSON would pass vacuously no matter what the fields are. */
|
|
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. */
|
|
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?: {
|
|
137
|
+
name: string;
|
|
138
|
+
value: Json;
|
|
139
|
+
when?: Cond;
|
|
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';
|
|
148
|
+
};
|
|
149
|
+
/** Model-id → variant flags. The migration target is a catalog pin; the
|
|
150
|
+
* `idMatch` form is what today's regex helpers do, expressed as data.
|
|
151
|
+
* `fn` is the escape hatch for rules a pattern cannot express (version
|
|
152
|
+
* arithmetic) — every use of it is a finding, not a feature. */
|
|
153
|
+
variants?: {
|
|
154
|
+
flag: string;
|
|
155
|
+
idMatch?: string;
|
|
156
|
+
fn?: string;
|
|
157
|
+
unless?: string;
|
|
158
|
+
note?: string;
|
|
159
|
+
}[];
|
|
160
|
+
/** Value tables referenced by `table:` and `$table`. */
|
|
161
|
+
tables?: Record<string, Record<string, Json>>;
|
|
162
|
+
/** Fields we deliberately never send, with the reason. */
|
|
163
|
+
unsupported?: {
|
|
164
|
+
from: string;
|
|
165
|
+
reason: string;
|
|
166
|
+
}[];
|
|
167
|
+
fields?: FieldRule[];
|
|
168
|
+
blocks?: BlockRule[];
|
|
169
|
+
/** Multipart form fields, in order, when `envelope.bodyKind` is 'multipart'. */
|
|
170
|
+
multipart?: {
|
|
171
|
+
name: string;
|
|
172
|
+
value?: Json;
|
|
173
|
+
file?: boolean;
|
|
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;
|
|
180
|
+
}[];
|
|
181
|
+
/** Non-HTTP surfaces. A realtime session is not one request: it is a
|
|
182
|
+
* connection descriptor plus a sequence of outbound frames, so those are
|
|
183
|
+
* named operations rather than a single envelope+body. */
|
|
184
|
+
operations?: Record<string, OperationRule>;
|
|
185
|
+
/** Per-flavor patches applied after the blocks. This mirrors what the xai and
|
|
186
|
+
* openrouter adapters already do today: call the base builder, then patch the
|
|
187
|
+
* result. Expressing it as ops keeps the shared spec authoritative. */
|
|
188
|
+
overlays?: Record<string, {
|
|
189
|
+
ops: OverlayOp[];
|
|
190
|
+
}>;
|
|
191
|
+
}
|
|
192
|
+
export interface OverlayOp {
|
|
193
|
+
op: 'rename' | 'delete' | 'set' | 'mergeFrom' | 'call';
|
|
194
|
+
/** rename/delete: body path. mergeFrom: request path. */
|
|
195
|
+
from?: string;
|
|
196
|
+
/** rename/set: body path. */
|
|
197
|
+
to?: string;
|
|
198
|
+
value?: Json;
|
|
199
|
+
call?: string;
|
|
200
|
+
when?: Cond;
|
|
201
|
+
}
|
|
202
|
+
export interface Ctx {
|
|
203
|
+
req: any;
|
|
204
|
+
spec: WireSpec;
|
|
205
|
+
flavor: string;
|
|
206
|
+
/** Adapter-level configuration (baseURL, apiKey, ...) referenced by `$config`.
|
|
207
|
+
* Keeps the URL declarative rather than pushing it into a named transform. */
|
|
208
|
+
config: Record<string, unknown>;
|
|
209
|
+
variants: Set<string>;
|
|
210
|
+
body: Record<string, unknown>;
|
|
211
|
+
/** Per-item scope while inside a $map. */
|
|
212
|
+
item?: {
|
|
213
|
+
value: any;
|
|
214
|
+
index: number;
|
|
215
|
+
isLast: boolean;
|
|
216
|
+
};
|
|
217
|
+
/** Collected multipart fields, when the spec declares a multipart body. */
|
|
218
|
+
multipart?: MultipartField[];
|
|
219
|
+
}
|
|
220
|
+
export interface MultipartField {
|
|
221
|
+
name: string;
|
|
222
|
+
kind: 'file' | 'value';
|
|
223
|
+
value?: Json;
|
|
224
|
+
}
|
|
225
|
+
export type Transform = (value: any, ctx: Ctx) => Json;
|
|
226
|
+
export type Builder = (ctx: Ctx) => Json;
|
|
227
|
+
export type Predicate = (ctx: Ctx) => boolean;
|
|
228
|
+
export type Effect = (ctx: Ctx) => void;
|
|
229
|
+
export interface Registry {
|
|
230
|
+
transforms: Record<string, Transform>;
|
|
231
|
+
builders: Record<string, Builder>;
|
|
232
|
+
predicates: Record<string, Predicate>;
|
|
233
|
+
effects: Record<string, Effect>;
|
|
234
|
+
}
|
|
235
|
+
export declare function getPath(root: any, path: string): any;
|
|
236
|
+
export declare function evalCond(cond: Cond | undefined, ctx: Ctx, reg: Registry): boolean;
|
|
237
|
+
export declare function resolveVariants(spec: WireSpec, model: string, reg: Registry): Set<string>;
|
|
238
|
+
export interface BuiltRequest {
|
|
239
|
+
body: Record<string, unknown>;
|
|
240
|
+
headers?: Record<string, string>;
|
|
241
|
+
path?: string;
|
|
242
|
+
url?: string;
|
|
243
|
+
method?: string;
|
|
244
|
+
/** The body is caller-supplied bytes (bodyKind 'raw'). */
|
|
245
|
+
rawBody?: boolean;
|
|
246
|
+
/** Present instead of a JSON body when bodyKind is 'multipart'. */
|
|
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;
|
|
253
|
+
/** True when the spec declares the request carries no body at all. */
|
|
254
|
+
noBody?: boolean;
|
|
255
|
+
}
|
|
256
|
+
export declare function buildFromSpec(spec: WireSpec, req: any, reg: Registry, flavor?: string,
|
|
257
|
+
/** Coverage hook: called with every rule that actually fired. */
|
|
258
|
+
onUse?: (kind: 'field' | 'block' | 'header' | 'variant' | 'overlay', name: string) => void,
|
|
259
|
+
/** Adapter config exposed to `$config`. */
|
|
260
|
+
config?: Record<string, unknown>): BuiltRequest;
|
|
261
|
+
export interface OperationRule {
|
|
262
|
+
/** Connection descriptor (realtime `connect`). */
|
|
263
|
+
url?: Json;
|
|
264
|
+
protocols?: Json;
|
|
265
|
+
/** Outbound frames, in order. A frame whose template evaluates away is skipped. */
|
|
266
|
+
frames?: {
|
|
267
|
+
name?: string;
|
|
268
|
+
when?: Cond;
|
|
269
|
+
template: Json;
|
|
270
|
+
}[];
|
|
271
|
+
}
|
|
272
|
+
export interface BuiltConnection {
|
|
273
|
+
url: string;
|
|
274
|
+
protocols?: string[];
|
|
275
|
+
}
|
|
276
|
+
/** Build the connection descriptor for an operation (realtime `connect`). */
|
|
277
|
+
export declare function buildConnection(spec: WireSpec, operation: string, input: any, reg: Registry, config?: Record<string, unknown>): BuiltConnection;
|
|
278
|
+
/** Build the outbound frames for an operation (realtime `open` / `send`). */
|
|
279
|
+
export declare function buildFrames(spec: WireSpec, operation: string, input: any, reg: Registry, config?: Record<string, unknown>): Json[];
|