@combycode/llm-sdk 1.7.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +430 -1
  2. package/MIGRATION.md +93 -0
  3. package/README.md +17 -2
  4. package/dist/agent/loop-config.d.ts +22 -0
  5. package/dist/agent/loop-step-state.d.ts +2 -0
  6. package/dist/agent/loop.d.ts +7 -0
  7. package/dist/agent/reflect-retry.d.ts +56 -0
  8. package/dist/agent/tool-key.d.ts +3 -0
  9. package/dist/helpers/mcp.d.ts +24 -2
  10. package/dist/helpers/provenance-types.d.ts +63 -0
  11. package/dist/helpers/provenance.d.ts +12 -0
  12. package/dist/helpers/transcribe.d.ts +35 -6
  13. package/dist/index.browser.js +2996 -707
  14. package/dist/index.d.ts +19 -7
  15. package/dist/index.js +2996 -707
  16. package/dist/llm/providers/anthropic/constants.d.ts +2 -0
  17. package/dist/llm/providers/openai/completions.d.ts +9 -0
  18. package/dist/llm/providers/openai/provenance.d.ts +26 -0
  19. package/dist/llm/providers/openai/responses.d.ts +4 -2
  20. package/dist/llm/providers/openai/transcription.d.ts +39 -2
  21. package/dist/llm/types/audio.d.ts +31 -0
  22. package/dist/llm/types/messages.d.ts +89 -1
  23. package/dist/llm/types/options.d.ts +15 -0
  24. package/dist/llm/types/request.d.ts +13 -1
  25. package/dist/llm/types/response.d.ts +31 -1
  26. package/dist/llm/types/stream.d.ts +14 -1
  27. package/dist/llm/types/tiers.d.ts +6 -6
  28. package/dist/network/queue-state-config.d.ts +5 -0
  29. package/dist/network/queue-state.d.ts +7 -0
  30. package/dist/network/types.d.ts +23 -0
  31. package/dist/plugins/context-guard/strategies/anchored.d.ts +47 -0
  32. package/dist/plugins/context-measurer/counter/hybrid.d.ts +4 -1
  33. package/dist/plugins/context-measurer/counter/tiktoken.d.ts +8 -1
  34. package/dist/plugins/mcp/base-transport.d.ts +16 -0
  35. package/dist/plugins/mcp/client.d.ts +144 -7
  36. package/dist/plugins/mcp/input-required.d.ts +35 -0
  37. package/dist/plugins/mcp/jsonrpc.d.ts +7 -0
  38. package/dist/plugins/mcp/oauth.d.ts +21 -1
  39. package/dist/plugins/mcp/protocol-version.d.ts +61 -0
  40. package/dist/plugins/mcp/result-cache.d.ts +31 -0
  41. package/dist/plugins/mcp/subscriptions.d.ts +69 -0
  42. package/dist/plugins/mcp/transport-http.d.ts +31 -0
  43. package/dist/plugins/mcp/transport-stdio.d.ts +2 -0
  44. package/dist/plugins/mcp/transport-ws.d.ts +11 -1
  45. package/dist/plugins/mcp/transport.d.ts +11 -0
  46. package/dist/plugins/mcp/types.d.ts +54 -2
  47. package/dist/plugins/telemetry/telemetry.d.ts +12 -0
  48. package/dist/util/http.d.ts +8 -0
  49. package/package.json +9 -6
@@ -11,3 +11,5 @@ export declare const ANTHROPIC_THINKING_BUDGETS: Record<string, number>;
11
11
  * unrecognised.
12
12
  */
13
13
  export declare const DEFAULT_ANTHROPIC_THINKING_BUDGET = 2048;
14
+ /** True when this Anthropic model still accepts `top_k` (see ANTHROPIC_TOP_K_MODELS). */
15
+ export declare function anthropicAcceptsTopK(model: string): boolean;
@@ -23,6 +23,15 @@ export declare class OpenAIAdapter implements ProviderAdapter {
23
23
  baseURL(): string;
24
24
  completionPath(): string;
25
25
  buildRequest(req: NormalizedRequest): ProviderHttpRequest;
26
+ /** One universal message can become SEVERAL chat-completions messages.
27
+ *
28
+ * Parallel tool calls are the case that matters: the loop answers a round of calls
29
+ * with ONE tool message carrying a `tool_result` part per call, but this API wants a
30
+ * separate `{role:'tool'}` message per `tool_call_id`. Emitting only the first left
31
+ * the rest unanswered and the provider rejected the whole request with
32
+ * "No tool output found for function call <id>" — so parallel tools were broken on
33
+ * every chat-completions backend. */
34
+ private buildMessages;
26
35
  private buildMessage;
27
36
  enableStreaming(providerReq: ProviderHttpRequest, _req: NormalizedRequest): void;
28
37
  parseResponse(raw: unknown, latencyMs: number): CompletionResponse;
@@ -0,0 +1,26 @@
1
+ /** OpenAI content-provenance adapter — POST /v1/content_provenance_checks (openai-ts 7.x).
2
+ *
3
+ * Answers "does this file carry provider provenance signals?" — a C2PA manifest and/or a SynthID
4
+ * watermark. It is the only "was this AI-generated" primitive any tracked SDK ships.
5
+ *
6
+ * Note what it does NOT do: a `not_detected` result is not proof a human made the file. Provenance
7
+ * signals are strippable (a re-encode or a screenshot usually loses them), so absence is absence of
8
+ * evidence. Only `detected` with a `trusted` validation state is a positive statement.
9
+ *
10
+ * All HTTP flows through the injected EngineFetch. */
11
+ import type { EngineFetch } from '../../../network/types';
12
+ import type { ProvenanceCheckResult, ProvenanceRawResponse } from '../../../helpers/provenance-types';
13
+ export interface OpenAIProvenanceAdapterConfig {
14
+ apiKey: string;
15
+ baseURL?: string;
16
+ }
17
+ export declare const OPENAI_PROVENANCE_BASE_URL = "https://api.openai.com";
18
+ export declare const OPENAI_PROVENANCE_PATH = "/v1/content_provenance_checks";
19
+ export declare class OpenAIProvenanceAdapter {
20
+ private readonly apiKey;
21
+ private readonly baseURL;
22
+ constructor(config: OpenAIProvenanceAdapterConfig);
23
+ check(bytes: Uint8Array, filename: string, mimeType: string, fetch: EngineFetch): Promise<ProvenanceCheckResult>;
24
+ }
25
+ /** Normalise the wire response into the unified verdict. */
26
+ export declare function parseProvenanceResponse(raw: ProvenanceRawResponse | undefined): ProvenanceCheckResult;
@@ -3,6 +3,7 @@
3
3
  * Modern API: input (not messages), instructions (not system role),
4
4
  * output items (not choices), function_call/function_call_output for tools. */
5
5
  import type { SSEEvent } from '../../../network/types';
6
+ import type { AssistantPhase } from '../../types/messages';
6
7
  import type { ProviderAdapter, ProviderHttpRequest } from '../../types/provider';
7
8
  import type { NormalizedRequest } from '../../types/request';
8
9
  import { type CompletionResponse, type FileOutput, type Usage } from '../../types/response';
@@ -20,11 +21,12 @@ export declare class OpenAIResponsesAdapter implements ProviderAdapter {
20
21
  baseURL(): string;
21
22
  completionPath(): string;
22
23
  buildRequest(req: NormalizedRequest): ProviderHttpRequest;
23
- /** Convert a universal Message to Responses API input items */
24
+ /** Convert a universal Message to Responses API input items.
25
+ * `toolNames` is threaded across messages so a tool result can name its originating call. */
24
26
  private buildInputItems;
25
27
  enableStreaming(providerReq: ProviderHttpRequest): void;
26
28
  parseResponse(raw: unknown, latencyMs: number): CompletionResponse;
27
- parseStreamEvent(event: SSEEvent): StreamEvent[];
29
+ parseStreamEvent(event: SSEEvent, phaseByItem?: Map<string, AssistantPhase>): StreamEvent[];
28
30
  /** Stateless — each output item finalizes with all its file annotations in a
29
31
  * single response.output_item.done event. */
30
32
  createStreamParser(): (event: SSEEvent) => StreamEvent[];
@@ -1,6 +1,20 @@
1
1
  /** OpenAI transcription adapter — POST /v1/audio/transcriptions (multipart).
2
2
  * All HTTP flows through the injected EngineFetch (rawBody multipart, like the
3
- * batch file upload). gpt-4o-transcribe / whisper return `{ text }`. */
3
+ * batch file upload).
4
+ *
5
+ * Three response shapes come back from one endpoint, selected by `response_format`
6
+ * and constrained by the model (all verified on the wire, 2026-08-09):
7
+ *
8
+ * | model | `json` | `verbose_json` | `diarized_json` |
9
+ * |---------------------------|-------------------------------|---------------------|---------------------|
10
+ * | `gpt-transcribe` | text + detected `languages` | 400 | 400 |
11
+ * | `gpt-4o-transcribe` | text | 400 | 400 |
12
+ * | `whisper-1` | text | segments + words | 400 |
13
+ * | `gpt-4o-transcribe-diarize` | text | - | segments + speakers |
14
+ *
15
+ * No model returns everything: speaker labels and word timings live on different
16
+ * models. `keywords` / `languages` are `gpt-transcribe`-only and 400 elsewhere. */
17
+ import type { TranscriptLanguage, TranscriptSegment, TranscriptWord } from '../../types/audio';
4
18
  import type { EngineFetch } from '../../../network/types';
5
19
  export interface OpenAITranscriptionAdapterConfig {
6
20
  apiKey: string;
@@ -10,11 +24,34 @@ export interface TranscriptionRequest {
10
24
  bytes: Uint8Array;
11
25
  mimeType: string;
12
26
  model: string;
27
+ /** The language of the input audio (ISO-639-1). Improves accuracy and latency.
28
+ * Supported by `whisper-1` / `gpt-4o-transcribe`; NOT by `gpt-transcribe`,
29
+ * which uses `languages` instead. */
13
30
  language?: string;
31
+ /** Candidate languages for the input audio (ISO-639-1). `gpt-transcribe` only. */
32
+ languages?: string[];
33
+ /** Words or phrases that steer spelling of names and jargon. `gpt-transcribe` only. */
34
+ keywords?: string[];
35
+ /** Ask for segment + word timings (`response_format: verbose_json`). `whisper-1` only. */
36
+ wordTimestamps?: boolean;
37
+ /** Ask for speaker-labelled segments (`response_format: diarized_json`).
38
+ * `gpt-4o-transcribe-diarize` only. */
39
+ diarization?: boolean;
40
+ }
41
+ export interface OpenAITranscriptionResult {
42
+ text: string;
43
+ /** Languages the model reports detecting. Returned unconditionally by `gpt-transcribe`. */
44
+ languages?: TranscriptLanguage[];
45
+ segments?: TranscriptSegment[];
46
+ words?: TranscriptWord[];
47
+ /** Audio duration in seconds as the provider measured it — this is the quantity
48
+ * duration-billed models charge on, so it beats any local estimate. */
49
+ durationSeconds?: number;
14
50
  }
15
51
  export declare class OpenAITranscriptionAdapter {
16
52
  private readonly apiKey;
17
53
  private readonly baseURL;
18
54
  constructor(config: OpenAITranscriptionAdapterConfig);
19
- transcribe(req: TranscriptionRequest, fetch: EngineFetch): Promise<string>;
55
+ transcribe(req: TranscriptionRequest, fetch: EngineFetch): Promise<OpenAITranscriptionResult>;
20
56
  }
57
+ export declare function parseTranscription(body: unknown): OpenAITranscriptionResult;
@@ -16,3 +16,34 @@ export interface AudioInput {
16
16
  mimeType?: string;
17
17
  sampleRate?: number;
18
18
  }
19
+ /** A language a provider reports detecting in transcribed audio.
20
+ *
21
+ * An object rather than a bare `string` on purpose: providers already annotate
22
+ * detections and will annotate them further (confidence, spans). A `string[]`
23
+ * could only grow by becoming a different type, which is exactly the breaking
24
+ * change CONSTITUTION.md R3 forbids. */
25
+ export interface TranscriptLanguage {
26
+ /** Language code as the provider reported it (typically ISO-639-1, e.g. `en`). */
27
+ code: string;
28
+ }
29
+ /** One word with its timing, in seconds from the start of the audio. */
30
+ export interface TranscriptWord {
31
+ word: string;
32
+ start: number;
33
+ end: number;
34
+ }
35
+ /** A timed run of transcript text.
36
+ *
37
+ * `speaker` is present only when diarization ran; no provider surface currently
38
+ * returns speakers and word timings together (see `transcribe()`), so a segment
39
+ * carries whichever the chosen model produces. */
40
+ export interface TranscriptSegment {
41
+ /** Provider segment id, normalised to a string (whisper numbers them, the
42
+ * diarizing models use `seg_N`). */
43
+ id?: string;
44
+ start: number;
45
+ end: number;
46
+ text: string;
47
+ /** Speaker label, e.g. `A` / `B`, or a name from the provider's known-speaker list. */
48
+ speaker?: string;
49
+ }
@@ -18,11 +18,23 @@ export interface MessageOrigin {
18
18
  /** Opaque provider signatures to echo back (thought-signature, encrypted reasoning). */
19
19
  signatures?: unknown;
20
20
  }
21
- export type ContentPart = TextPart | ImagePart | DocumentPart | AudioPart | VideoPart | ToolCallPart | ToolResultPart | ImageOutputPart | AudioOutputPart | VideoOutputPart;
21
+ export type ContentPart = TextPart | ImagePart | DocumentPart | AudioPart | VideoPart | ToolCallPart | ToolResultPart | ProgramCallPart | ProgramResultPart | ImageOutputPart | AudioOutputPart | VideoOutputPart;
22
+ /** Whether a piece of assistant text is the ANSWER or narration on the way to it.
23
+ *
24
+ * `commentary` is the model thinking out loud for the user's benefit — distinct from reasoning,
25
+ * which is its own part. `final_answer` is the response proper. Codex-family models emit both, and
26
+ * without the distinction an agent loop treats narration as the result.
27
+ *
28
+ * Open union (CONSTITUTION.md R1): a provider adding a third phase must not break consumers, so
29
+ * write a `default` branch. */
30
+ export type AssistantPhase = 'commentary' | 'final_answer' | (string & {});
22
31
  export interface TextPart {
23
32
  type: 'text';
24
33
  text: string;
25
34
  cache?: boolean;
35
+ /** Set only by providers that report it (OpenAI Responses, `gpt-5.3-codex` and later). Absent
36
+ * everywhere else, which reads exactly as it did before: treat the text as the answer. */
37
+ phase?: AssistantPhase;
26
38
  }
27
39
  export interface ImagePart {
28
40
  type: 'image';
@@ -42,11 +54,30 @@ export interface VideoPart {
42
54
  type: 'video';
43
55
  source: DataSource;
44
56
  }
57
+ /** Who invoked a tool: the model itself, or code the model wrote.
58
+ *
59
+ * Open union (CONSTITUTION.md R1) — the provider enumerates the values it knows
60
+ * (`direct` and `program` today) and may add more, so write a `default` branch. */
61
+ export type ToolCallerType = 'direct' | 'program' | (string & {});
62
+ /** The execution context that invoked a tool.
63
+ *
64
+ * A single shape with an optional payload rather than
65
+ * `{type:'direct'} | {type:'program', callerId}` (R2): a new caller kind with its own
66
+ * fields then extends this instead of widening a union every consumer switches on. */
67
+ export interface ToolCaller {
68
+ type: ToolCallerType;
69
+ /** The id of the {@link ProgramCallPart} that made this call. Present when
70
+ * `type === 'program'`. */
71
+ callerId?: string;
72
+ }
45
73
  export interface ToolCallPart {
46
74
  type: 'tool_call';
47
75
  id: string;
48
76
  name: string;
49
77
  arguments: Record<string, unknown>;
78
+ /** Who invoked this tool. Absent means the ordinary case — the model called it
79
+ * directly — so existing consumers read exactly as before. */
80
+ caller?: ToolCaller;
50
81
  /** Provider-specific metadata (e.g. Google thought signatures). */
51
82
  _meta?: Record<string, unknown>;
52
83
  }
@@ -55,6 +86,53 @@ export interface ToolResultPart {
55
86
  id: string;
56
87
  content: string | ContentPart[];
57
88
  isError?: boolean;
89
+ /** The namespace of the tool that produced this result, when the provider tracks one (OpenAI
90
+ * Responses). Round-tripped so a namespaced tool's output is attributable on the next turn; the
91
+ * tool NAME is derived from the matching call rather than stored twice. */
92
+ namespace?: string;
93
+ /** Who invoked the call this result answers. Mirrors {@link ToolCallPart.caller}. */
94
+ caller?: ToolCaller;
95
+ }
96
+ /** Code the model wrote to orchestrate tool calls itself, instead of emitting them
97
+ * one at a time and waiting for each result (OpenAI Responses "programmatic tool
98
+ * calling", `gpt-5.6` family).
99
+ *
100
+ * The program runs on the provider's side and suspends at every `await`, so its
101
+ * tool calls still arrive as ordinary {@link ToolCallPart}s — each tagged with a
102
+ * `caller` pointing back at this part's `id`. When the program finishes, a
103
+ * {@link ProgramResultPart} carries what it returned.
104
+ *
105
+ * **This part must be preserved in history and sent back.** Dropping it does not
106
+ * merely lose an audit trail: the model re-emits the program and runs it again
107
+ * from the start (verified 2026-08-09). */
108
+ export interface ProgramCallPart {
109
+ type: 'program_call';
110
+ /** Call id shared with the matching {@link ProgramResultPart} and referenced by
111
+ * `ToolCaller.callerId` on every tool call the program made. */
112
+ id: string;
113
+ /** The source the model wrote — JavaScript for OpenAI. Readable, and worth showing
114
+ * to a user: it is the plan the model is executing. */
115
+ code: string;
116
+ /** Opaque provider token that must be round-tripped verbatim. */
117
+ fingerprint: string;
118
+ /** Provider-specific metadata: the raw item id, and the provider items this one is
119
+ * bound to (OpenAI rejects the program without its reasoning item). Echoed back
120
+ * by the adapter that produced it and ignored by every other provider. */
121
+ _meta?: Record<string, unknown>;
122
+ }
123
+ /** What a {@link ProgramCallPart} returned once it ran to completion. */
124
+ export interface ProgramResultPart {
125
+ type: 'program_result';
126
+ /** Matches the {@link ProgramCallPart} `id`. */
127
+ id: string;
128
+ /** The program's return value, as the provider serialised it. */
129
+ result: string;
130
+ /** Terminal state. Open union (R1): `incomplete` means the program stopped early —
131
+ * hitting a step limit or throwing — so the result is partial. */
132
+ status?: 'completed' | 'incomplete' | (string & {});
133
+ /** Provider-specific metadata: the raw item id, which OpenAI requires when this item
134
+ * is sent back as history ("Missing required parameter: 'input[n].id'"). */
135
+ _meta?: Record<string, unknown>;
58
136
  }
59
137
  export interface ImageOutputPart {
60
138
  type: 'image_output';
@@ -123,3 +201,13 @@ export interface Message {
123
201
  export declare function contentParts(content: Content): ContentPart[];
124
202
  /** Extract plain text from content. */
125
203
  export declare function contentText(content: Content): string;
204
+ /** The assistant's ANSWER, with commentary removed.
205
+ *
206
+ * Codex-family models narrate before answering and mark the narration `phase: 'commentary'`.
207
+ * Concatenating everything makes an agent's final output include its own thinking-out-loud.
208
+ *
209
+ * Excludes only what is explicitly `'commentary'` rather than keeping only `'final_answer'`: the
210
+ * phase vocabulary is open (R1), and a phase we do not recognise yet must never cause us to drop
211
+ * the answer. Text with no phase at all — every other model — is returned unchanged, so this is
212
+ * identical to `contentText` outside the codex family. */
213
+ export declare function finalAnswerText(content: Content): string;
@@ -20,6 +20,21 @@ export interface ExecuteOptions {
20
20
  maxTokens?: number;
21
21
  temperature?: number;
22
22
  topP?: number;
23
+ /** Restrict sampling to the k most likely tokens. Sent to Anthropic, Google
24
+ * (generateContent + Interactions), xAI chat and OpenRouter chat; dropped for OpenAI,
25
+ * which defines no top-k.
26
+ *
27
+ * ACCEPTED IS NOT HONOURED. A behavioural test on 2026-07-29 (top_k=1 must force greedy
28
+ * decoding) found only **Anthropic** actually applies it: six samples collapsed to a
29
+ * single output. Google (gemini-2.5-flash, 3.6-flash) and xAI (grok-4.20) returned 200
30
+ * but showed no greedy effect — accepted and inert on those models. It is still sent
31
+ * (harmless, and may apply elsewhere) but do not rely on it outside Anthropic. */
32
+ topK?: number;
33
+ /** Best-effort deterministic sampling: the same seed + params should return the same
34
+ * result. Honoured by OpenAI **chat-completions** (the Responses API rejects it), Google
35
+ * (generateContent + Interactions), xAI (chat + responses) and OpenRouter chat.
36
+ * Anthropic has no seed, so it is dropped there. Determinism is never guaranteed. */
37
+ seed?: number;
23
38
  /** Penalise tokens by prior presence ([-2, 2]). Honoured by OpenAI/xAI chat-completions,
24
39
  * OpenRouter, and Google (generateContent + Interactions); ignored by OpenAI/xAI Responses
25
40
  * and Anthropic, which don't accept it. */
@@ -18,6 +18,17 @@ export interface NormalizedRequest {
18
18
  maxTokens?: number;
19
19
  temperature?: number;
20
20
  topP?: number;
21
+ /** Restrict sampling to the k most likely tokens. Emitted only where the wire accepts it
22
+ * (live-verified 2026-07-28): Anthropic, Google generateContent AND Interactions, xAI chat,
23
+ * OpenRouter chat. OpenAI has no top-k on either surface, so it is dropped there rather
24
+ * than sent and rejected. */
25
+ topK?: number;
26
+ /** Best-effort deterministic sampling. Emitted only where the wire accepts it
27
+ * (live-verified 2026-07-28): OpenAI **chat-completions** — the Responses API rejects it
28
+ * (400 "Unknown parameter: 'seed'") — Google generateContent + Interactions, xAI chat AND
29
+ * responses, OpenRouter chat. Anthropic has no seed (400 "Extra inputs are not permitted"),
30
+ * so it is dropped there. */
31
+ seed?: number;
21
32
  /** Penalise tokens by prior presence (OpenAI/xAI chat-completions, OpenRouter, Google). */
22
33
  presencePenalty?: number;
23
34
  /** Penalise tokens by prior frequency (OpenAI/xAI chat-completions, OpenRouter, Google). */
@@ -47,7 +58,8 @@ export interface NormalizedRequest {
47
58
  * rendered back to it on later turns of a stateful conversation (chained via
48
59
  * `previousResponseId` / server-state). `all_turns` keeps continuity at higher
49
60
  * token cost; `current_turn` drops earlier reasoning; `auto` lets OpenAI decide.
50
- * Ignored by every other provider. */
61
+ * Omitted, the model picks: the gpt-5.6 family defaults to `all_turns`, earlier
62
+ * models to `current_turn`. Ignored by every other provider. */
51
63
  export type ReasoningContext = 'auto' | 'current_turn' | 'all_turns';
52
64
  /** How much of the model's reasoning is returned. `full` (default) returns it as
53
65
  * fully as the provider allows; `summary` a condensed form where the provider
@@ -24,6 +24,16 @@ export interface CompletionResponse {
24
24
  * Report-only: present for observability; it never blocks the call. Absent when
25
25
  * moderation was not requested. */
26
26
  moderation?: ModerationReport;
27
+ /** Why the turn failed, when `finishReason` is `'error'`. Some providers report a
28
+ * failure *inside* a 200 response instead of a transport error (OpenAI Responses
29
+ * `status: 'failed'` + `response.error`, Google Interactions `status: 'failed'`), so
30
+ * there is no exception to catch — without this the caller only sees an empty result.
31
+ * Absent unless the provider reported a failure. */
32
+ error?: {
33
+ /** Provider error code, e.g. OpenAI `data_residency_mismatch` (added 2026-07). */
34
+ code?: string;
35
+ message?: string;
36
+ };
27
37
  latencyMs: number;
28
38
  raw: unknown;
29
39
  }
@@ -66,7 +76,27 @@ export interface BuiltinToolCall {
66
76
  * actions, which carry a URL instead of a query). Absent for plain searches. */
67
77
  url?: string;
68
78
  }
69
- export type FinishReason = 'stop' | 'tool_use' | 'length' | 'content_filter' | 'error';
79
+ /** The finish reasons this SDK documents and maps deliberately. */
80
+ export type KnownFinishReason = 'stop' | 'tool_use' | 'length' | 'content_filter' | 'error' | 'pending'
81
+ /** The model tried to call a tool and produced something unusable — malformed arguments, a
82
+ * hallucinated tool name, a truncated call. Distinct from `error` (the request itself failed)
83
+ * and from `tool_use` (a call we can execute): this turn is *recoverable* by telling the model
84
+ * what went wrong and letting it try again — see `reflectAndRetry` on `AgentLoop`. */
85
+ | 'malformed_tool_call';
86
+ /** Why a turn ended.
87
+ *
88
+ * **This union is OPEN by design** (CONSTITUTION.md R1). Providers keep inventing terminal states —
89
+ * four of them did so in a single upstream cycle — and against a closed union every one of those is
90
+ * a breaking change for every consumer, including consumers of providers that changed nothing.
91
+ * Always write a `default` branch; use `KnownFinishReason` where you want the documented set alone.
92
+ *
93
+ * `pending` is NOT terminal: the provider accepted the request but has not produced a completion
94
+ * yet, so the response carries no content. It exists because several providers can return a
95
+ * non-terminal status on an otherwise successful call — Google Interactions `queued` (google 2.13)
96
+ * and OpenAI Responses `queued` / `in_progress` (background mode). Those used to fall through to
97
+ * `stop`, which claimed a clean finish for an empty response. Treat `pending` as "poll/retry",
98
+ * never as a result. */
99
+ export type FinishReason = KnownFinishReason | (string & {});
70
100
  export interface Usage {
71
101
  inputTokens: number;
72
102
  outputTokens: number;
@@ -1,13 +1,26 @@
1
1
  /** Universal streaming event types. */
2
2
  import type { ModerationEntry } from '../moderation/types';
3
+ import type { AssistantPhase } from './messages';
3
4
  import type { FileOutput, Usage } from './response';
4
5
  export type MediaStreamType = 'image' | 'audio' | 'video';
5
- export type StreamEvent = {
6
+ export type StreamEvent =
7
+ /** `itemId` identifies WHICH output item a delta belongs to, when the provider reports
8
+ * one (OpenAI Responses forwards `item_id`; chat-completions has no per-item concept,
9
+ * so it is absent there). A single turn can interleave deltas from several output
10
+ * items, so a consumer that reassembles them per item — rather than just concatenating
11
+ * into one string — needs this to keep them apart. Optional and additive: ignoring it
12
+ * gives exactly the previous behaviour. */
13
+ /** `phase` mirrors the buffered `TextPart.phase`: whether this delta is commentary or the answer
14
+ * proper. Reported only by models that distinguish them (codex family); absent elsewhere. */
15
+ {
6
16
  type: 'text';
7
17
  text: string;
18
+ itemId?: string;
19
+ phase?: AssistantPhase;
8
20
  } | {
9
21
  type: 'thinking';
10
22
  text: string;
23
+ itemId?: string;
11
24
  } | {
12
25
  type: 'tool_call_start';
13
26
  id: string;
@@ -1,11 +1,11 @@
1
- /** Unified service tier for a request. The four named values are the
2
- * cross-provider core; the open `(string & {})` lets callers pass any tier
3
- * (e.g. `'scale'`, or a future internal-optimization label) each adapter
4
- * decides whether it can honor it (pass through if the provider allows it,
5
- * else fall back to that provider's `auto`).
1
+ /** Unified service tier for a request. The named values are the cross-provider core; the open
2
+ * `(string & {})` lets callers pass any tier (e.g. `'scale'`, or a future internal-optimization
3
+ * label) each adapter decides whether it can honor it (pass through if the provider allows it,
4
+ * else fall back to that provider's `auto`). Open by design, per CONSTITUTION.md R1: a provider
5
+ * adding a tier must never break a consumer, so listing a value here only adds autocomplete.
6
6
  *
7
7
  * `batch` is intentionally NOT a value here — it's a separate API (the Batch
8
8
  * endpoint), not a per-request flag on a synchronous call.
9
9
  *
10
10
  * Tier mapping is provider-specific and lives ENTIRELY in the adapters. */
11
- export type ServiceTier = 'auto' | 'standard' | 'priority' | 'flex' | (string & {});
11
+ export type ServiceTier = 'auto' | 'standard' | 'priority' | 'flex' | 'fast' | (string & {});
@@ -9,6 +9,11 @@ export interface RetryConfig {
9
9
  totalTimeoutMs: number;
10
10
  attemptTimeoutMs: number;
11
11
  backoff: BackoffConfig;
12
+ /** Longest `Retry-After` we are willing to honour. A server asking us to wait longer than this
13
+ * does not get waited for: the request fails fast instead of parking in the queue, and the rate
14
+ * limiter is not paused for that long either. Without a cap, a single `Retry-After: 86400`
15
+ * silently holds a request — and the whole limiter — for a day. */
16
+ maxRetryAfterMs: number;
12
17
  perKind?: Partial<Record<ErrorKind, ErrorRetryConfig>>;
13
18
  }
14
19
  export interface BackoffConfig {
@@ -37,6 +37,13 @@ export declare class QueueState {
37
37
  private settleOnWorkerCrash;
38
38
  private executeWithRetry;
39
39
  private handleRetry;
40
+ /** The `Retry-After` we will actually honour: the server's value clamped to `maxRetryAfterMs`.
41
+ * Anything above the cap never reaches here as a retry (see `retryAfterTooLong`), so the clamp
42
+ * is a floor-level guard for the limiter-pause path and any future consumer. */
43
+ /** The effective retry policy for one request: the queue's, with any per-request override
44
+ * applied on top. `perKind` is never overridden — see `RequestRetryOverride`. */
45
+ private retryFor;
46
+ private honoredRetryAfterMs;
40
47
  private calculateBackoff;
41
48
  private executeOnce;
42
49
  private waitForCapacity;
@@ -26,6 +26,29 @@ export interface HttpRequest {
26
26
  /** Trace correlation — set by the caller (LLM client / media op) from the
27
27
  * RequestContext so every network event can echo `sessionId:requestId`. */
28
28
  trace?: TraceContext;
29
+ /** Per-request retry overrides, applied over the queue's policy for THIS request only.
30
+ *
31
+ * The queue's policy is shared by every call on it, so a one-off needing to be more (or less)
32
+ * patient — a long batch submit, a health check that should fail fast — previously had to accept
33
+ * the shared policy or get its own queue. Mirrors Google moving `retryOptions` from client-level
34
+ * to per-request `HttpOptions` (google-ts 2.15). */
35
+ retry?: RequestRetryOverride;
36
+ }
37
+ /** The retry knobs a single request may override.
38
+ *
39
+ * Deliberately a subset: `perKind` stays queue-level, because one request cannot sensibly redefine
40
+ * which error classes are retryable for the queue it shares with everyone else. */
41
+ export interface RequestRetryOverride {
42
+ maxRetries?: number;
43
+ totalTimeoutMs?: number;
44
+ attemptTimeoutMs?: number;
45
+ maxRetryAfterMs?: number;
46
+ backoff?: Partial<{
47
+ initialMs: number;
48
+ maxMs: number;
49
+ multiplier: number;
50
+ jitter: number;
51
+ }>;
29
52
  }
30
53
  /** Raw HTTP response (post-fetch, pre-provider-parse). */
31
54
  export interface HttpResponse {
@@ -0,0 +1,47 @@
1
+ /** AnchoredStrategy — one growing scratchpad instead of a chain of summaries.
2
+ *
3
+ * `LayeredStrategy` emits a NEW summary each time it compacts, so a long conversation accumulates
4
+ * summaries-of-summaries: the oldest facts get re-summarised repeatedly and drift further from
5
+ * what was actually said. This strategy keeps a SINGLE anchor entry at the head of history and
6
+ * merges each compaction into it — the anchor grows, but every fact is summarised from the raw
7
+ * text exactly once.
8
+ *
9
+ * The trade is real and worth stating: one anchor means one blast radius. A bad merge corrupts the
10
+ * whole record, where a chain of summaries only corrupts one link. Anchored suits long-running
11
+ * task/state tracking ("what have we established so far"); layered suits conversations where
12
+ * recency matters more than a durable state.
13
+ *
14
+ * Ported from google-adk-ts `AnchoredContextCompactor` (adk 1.5), including its refusal to split a
15
+ * tool call from its result. */
16
+ import type { HistoryEntry } from '../../../agent/history-types';
17
+ import type { ContextStrategy, ReactContext, StrategyDecision, TriggerLevel } from '../types';
18
+ export interface AnchoredStrategyConfig {
19
+ /** Raw entries to keep verbatim at the tail. */
20
+ keepRecent?: number;
21
+ /** Cap on the anchor's own length, so the thing that replaces history cannot become history. */
22
+ anchorMaxChars?: number;
23
+ triggers?: TriggerLevel[];
24
+ /** Above this usage ratio, report `decline` — compaction alone will not save the request. */
25
+ declineCeiling?: number;
26
+ }
27
+ /** Marks the anchor entry so it can be found again on the next compaction. Kept in the text rather
28
+ * than in metadata because the anchor has to survive an export/import round trip of history. */
29
+ export declare const ANCHOR_MARKER = "[context-anchor]";
30
+ /** Where the retained tail starts, refusing to split a tool call from its result.
31
+ *
32
+ * Cutting between them leaves a `tool_result` whose call is gone, which several providers reject
33
+ * outright and the rest silently misread. Walking the boundary backwards keeps the pair together.
34
+ * Direct port of adk's `calculateRetainStartIndex`. */
35
+ export declare function calculateRetainStartIndex(entries: readonly HistoryEntry[], keepRecent: number): number;
36
+ export declare class AnchoredStrategy implements ContextStrategy {
37
+ readonly name: "anchored";
38
+ readonly triggers: TriggerLevel[];
39
+ private readonly keepRecent;
40
+ private readonly anchorMaxChars;
41
+ private readonly declineCeiling;
42
+ constructor(config?: AnchoredStrategyConfig);
43
+ react(ctx: ReactContext): Promise<StrategyDecision>;
44
+ }
45
+ /** Fold a new summary into the anchor, bounded so the anchor cannot itself become the problem.
46
+ * When trimming is needed the NEWER text is kept — it already subsumes the older state. */
47
+ export declare function mergeAnchor(previous: string, addition: string, maxChars: number): string;
@@ -19,7 +19,10 @@ export interface HybridCounterConfig {
19
19
  */
20
20
  export declare class HybridTokenCounter implements TokenCounter {
21
21
  private heuristic;
22
- private tiktoken;
22
+ /** Built on first use, not in the constructor — most consumers never route to the tiktoken
23
+ * strategy, and the optional peer dependency should not be reached for merely constructing a
24
+ * counter. See CONSTITUTION.md standing decisions (2026-08-08). */
25
+ private _tiktoken?;
23
26
  private countApi;
24
27
  private readonly _config;
25
28
  constructor(config: HybridCounterConfig);
@@ -1,6 +1,13 @@
1
- /** Tiktoken adapter — exact tokenization for OpenAI models. */
1
+ /** Tiktoken adapter — exact tokenization for OpenAI models.
2
+ *
3
+ * `tiktoken` is an OPTIONAL PEER dependency: it is not installed unless the consumer asks for it,
4
+ * and nothing here runs until local token counting is actually used. See the standing decisions in
5
+ * CONSTITUTION.md (2026-08-08).
6
+ */
2
7
  import type { Message } from '../../../llm/types/messages';
3
8
  import type { TokenCountContext, TokenCounter, LearnInput } from '../../../agent/types';
9
+ /** Build the error thrown when the optional peer is missing. Exported for tests; not public API. */
10
+ export declare function tiktokenUnavailableError(cause: unknown): Error;
4
11
  export declare class TiktokenCounter implements TokenCounter {
5
12
  private encodings;
6
13
  estimate(text: string, ctx?: TokenCountContext): number;
@@ -39,11 +39,27 @@ export declare abstract class BaseJsonRpcTransport {
39
39
  protected nextId: number;
40
40
  protected handlers: IncomingMcpHandlers;
41
41
  protected readonly pending: Map<number, Pending>;
42
+ /** Long-lived requests (`subscriptions/listen`) awaiting their end-of-stream response. Kept apart
43
+ * from `pending` because these must NOT time out. */
44
+ protected readonly longLived: Map<number, ((error?: unknown) => void) | undefined>;
42
45
  setHandlers(handlers: IncomingMcpHandlers): void;
43
46
  /** Write a serialised JSON-RPC object back to the peer. */
44
47
  protected abstract sendMessage(obj: unknown): void | Promise<void>;
45
48
  /** Allocate the next monotonic request id. */
46
49
  protected allocateId(): number;
50
+ /** Send a request that is NOT expected to answer promptly, and return its id.
51
+ *
52
+ * `subscriptions/listen` (2026-07-28) is long-lived by design: the response arrives only when
53
+ * the server tears the subscription down, while notifications flow in the meantime. Routing it
54
+ * through `request()` would arm the normal timeout and kill a perfectly healthy subscription, so
55
+ * no pending entry is registered — the eventual response is dropped, its only meaning being
56
+ * "the stream ended".
57
+ *
58
+ * The caller correlates frames itself via the returned id. Duplex transports (stdio, WebSocket)
59
+ * support this; a request/response transport must override and reject. */
60
+ sendLongLivedRequest(method: string, params?: unknown, onEnd?: (error?: unknown) => void): Promise<number>;
61
+ /** Settle a long-lived request from its (late) response. Returns whether one was waiting. */
62
+ protected resolveLongLived(id: number | string, error?: unknown): boolean;
47
63
  /** Register a pending request and arm its timeout. */
48
64
  protected registerPending(id: number, resolve: (v: unknown) => void, reject: (e: unknown) => void, timeoutMs: number, method: string): void;
49
65
  /** Dispatch a parsed inbound message to the correct handler. */