@combycode/llm-sdk 1.7.0 → 2.0.1

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 (50) hide show
  1. package/CHANGELOG.md +462 -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/agent/types.d.ts +8 -2
  10. package/dist/helpers/mcp.d.ts +24 -2
  11. package/dist/helpers/provenance-types.d.ts +63 -0
  12. package/dist/helpers/provenance.d.ts +12 -0
  13. package/dist/helpers/transcribe.d.ts +35 -6
  14. package/dist/index.browser.js +2997 -708
  15. package/dist/index.d.ts +19 -7
  16. package/dist/index.js +2997 -708
  17. package/dist/llm/providers/anthropic/constants.d.ts +2 -0
  18. package/dist/llm/providers/openai/completions.d.ts +9 -0
  19. package/dist/llm/providers/openai/provenance.d.ts +26 -0
  20. package/dist/llm/providers/openai/responses.d.ts +4 -2
  21. package/dist/llm/providers/openai/transcription.d.ts +39 -2
  22. package/dist/llm/types/audio.d.ts +31 -0
  23. package/dist/llm/types/messages.d.ts +89 -1
  24. package/dist/llm/types/options.d.ts +15 -0
  25. package/dist/llm/types/request.d.ts +13 -1
  26. package/dist/llm/types/response.d.ts +31 -1
  27. package/dist/llm/types/stream.d.ts +14 -1
  28. package/dist/llm/types/tiers.d.ts +6 -6
  29. package/dist/network/queue-state-config.d.ts +5 -0
  30. package/dist/network/queue-state.d.ts +7 -0
  31. package/dist/network/types.d.ts +23 -0
  32. package/dist/plugins/context-guard/strategies/anchored.d.ts +47 -0
  33. package/dist/plugins/context-measurer/counter/hybrid.d.ts +4 -1
  34. package/dist/plugins/context-measurer/counter/tiktoken.d.ts +8 -1
  35. package/dist/plugins/mcp/base-transport.d.ts +16 -0
  36. package/dist/plugins/mcp/client.d.ts +144 -7
  37. package/dist/plugins/mcp/input-required.d.ts +35 -0
  38. package/dist/plugins/mcp/jsonrpc.d.ts +7 -0
  39. package/dist/plugins/mcp/oauth.d.ts +21 -1
  40. package/dist/plugins/mcp/protocol-version.d.ts +61 -0
  41. package/dist/plugins/mcp/result-cache.d.ts +31 -0
  42. package/dist/plugins/mcp/subscriptions.d.ts +69 -0
  43. package/dist/plugins/mcp/transport-http.d.ts +31 -0
  44. package/dist/plugins/mcp/transport-stdio.d.ts +2 -0
  45. package/dist/plugins/mcp/transport-ws.d.ts +11 -1
  46. package/dist/plugins/mcp/transport.d.ts +11 -0
  47. package/dist/plugins/mcp/types.d.ts +54 -2
  48. package/dist/plugins/telemetry/telemetry.d.ts +12 -0
  49. package/dist/util/http.d.ts +8 -0
  50. package/package.json +9 -6
@@ -36,6 +36,8 @@ export declare class AgentLoop {
36
36
  private _temperature?;
37
37
  private _thinking?;
38
38
  private _cache?;
39
+ private _collisionPolicy;
40
+ private readonly _reflectRetry;
39
41
  private _parallelToolCalls;
40
42
  private _toolTimeout;
41
43
  private _maxSteps;
@@ -71,6 +73,11 @@ export declare class AgentLoop {
71
73
  get lastReport(): AgentRunReport | null;
72
74
  get metadata(): Record<string, unknown>;
73
75
  addTool(tool: AgentTool): void;
76
+ /** Register a tool, surfacing a name collision instead of letting the last write win silently.
77
+ *
78
+ * The `warn` path still overwrites — that is the pre-existing behaviour and changing it would
79
+ * break apps that depend on a deliberate override — but it now says which tool lost. */
80
+ private registerTool;
74
81
  removeTool(name: string): void;
75
82
  toolNames(): string[];
76
83
  stop(): void;
@@ -0,0 +1,56 @@
1
+ /** Reflect-and-retry — self-healing recovery from a recoverable MODEL failure.
2
+ *
3
+ * Some turns fail in a way the model itself can fix: it produced malformed tool arguments, named a
4
+ * tool that does not exist, or was cut off mid-call. Surfacing that straight to the caller wastes a
5
+ * turn that a single corrective nudge would have salvaged. This injects structured guidance —
6
+ * naming the attempt number and telling the model not to repeat the same call — and lets the loop
7
+ * try again within a bounded budget.
8
+ *
9
+ * Ported from google-adk `ReflectAndRetryModelPlugin` (adk 2.6), including its default trigger
10
+ * (`MALFORMED_FUNCTION_CALL`) and its raise-vs-give-up switch.
11
+ *
12
+ * Deliberately NOT a network retry: `NetworkEngine` already retries transport failures. This is for
13
+ * a request that SUCCEEDED and came back unusable, which no amount of resending would fix. */
14
+ import type { FinishReason } from '../llm/types/response';
15
+ export interface ReflectAndRetryConfig {
16
+ /** Consecutive recoverable failures to tolerate before giving up. Default 3. */
17
+ maxRetries?: number;
18
+ /** Which finish reasons count as recoverable. Default `['malformed_tool_call']`.
19
+ *
20
+ * Adding `'content_filter'` is possible but rarely wise: a refusal is usually a decision, not a
21
+ * mistake, and retrying it burns budget to be refused again. */
22
+ onFinishReasons?: FinishReason[];
23
+ /** When the budget is exhausted: `true` (default) throws, `false` returns the last response as-is
24
+ * so the caller can decide. Upstream calls this `throw_exception_if_retry_exceeded`. */
25
+ throwIfExceeded?: boolean;
26
+ }
27
+ export declare const DEFAULT_REFLECT_RETRY_REASONS: FinishReason[];
28
+ export declare class ReflectAndRetryPolicy {
29
+ readonly maxRetries: number;
30
+ readonly throwIfExceeded: boolean;
31
+ private readonly reasons;
32
+ /** Consecutive failures for the CURRENT run. Reset by any successful turn, so an agent that
33
+ * recovers and then fails again much later gets a fresh budget rather than inheriting one. */
34
+ private consecutive;
35
+ constructor(config?: ReflectAndRetryConfig);
36
+ /** Is this finish reason one we try to recover from? */
37
+ handles(finishReason: FinishReason): boolean;
38
+ /** Record a successful turn — the failure streak is broken. */
39
+ recordSuccess(): void;
40
+ /** Record a recoverable failure and report what to do next.
41
+ *
42
+ * `attempt` counts from 1 so the guidance can say "attempt 1 of 3" the way a human would. */
43
+ recordFailure(): {
44
+ retry: boolean;
45
+ attempt: number;
46
+ exhausted: boolean;
47
+ };
48
+ get consecutiveFailures(): number;
49
+ /** Reset between runs so one run's failures never spend another run's budget. */
50
+ reset(): void;
51
+ }
52
+ /** The corrective message fed back to the model.
53
+ *
54
+ * Names the attempt number and explicitly forbids repeating the identical call — without that, a
55
+ * model tends to re-emit the same malformed arguments and burn the whole budget on one mistake. */
56
+ export declare function reflectionGuidance(finishReason: FinishReason, attempt: number, maxRetries: number, detail?: string): string;
@@ -1,3 +1,6 @@
1
1
  /** Registry key for an AgentTool: the function name, else the builtin type. */
2
2
  import type { AgentTool } from './types';
3
3
  export declare function toolKey(tool: AgentTool): string;
4
+ /** A short label for a tool, for collision diagnostics. Names the KIND as well as the key, because
5
+ * a function tool shadowing a builtin (or the reverse) is the case that reads as impossible. */
6
+ export declare function describeTool(tool: AgentTool): string;
@@ -1,7 +1,7 @@
1
1
  /** Agent-layer shared types — TokenCounter contract used by ContextRegistry,
2
2
  * ConversationHistory, and the ContextMeasurer plugin.
3
3
  * Also defines AgentTool (executable tool) and run-report types. */
4
- import type { ContentPart, Message } from '../llm/types/messages';
4
+ import type { AssistantPhase, ContentPart, Message } from '../llm/types/messages';
5
5
  import type { Tool } from '../llm/types/tools';
6
6
  import type { Usage } from '../llm/types/response';
7
7
  import type { HistorySnapshot } from './history-types';
@@ -103,9 +103,15 @@ export interface AgentRunReport {
103
103
  export type AgentStreamEvent = {
104
104
  type: 'step_start';
105
105
  step: number;
106
- } | {
106
+ }
107
+ /** `phase` mirrors the raw stream event: `'commentary'` is the model narrating, anything
108
+ * else (usually absent) is the answer. Passed through so a consumer can tell them apart
109
+ * LIVE — `finalAnswerText()` only cleans a finished message and cannot touch deltas.
110
+ * Absent on every provider that reports no phase, exactly as before. */
111
+ | {
107
112
  type: 'text';
108
113
  text: string;
114
+ phase?: AssistantPhase;
109
115
  } | {
110
116
  type: 'thinking';
111
117
  text: string;
@@ -40,8 +40,22 @@ export interface ConnectMcpOptions {
40
40
  roots?: McpRoot[] | (() => McpRoot[] | Promise<McpRoot[]>);
41
41
  /** Validate tool `structuredContent` against the tool's `outputSchema`. */
42
42
  validateOutput?: boolean;
43
- /** Send a `ping` every N ms to keep the connection alive (0 = off). */
43
+ /** Send a `ping` every N ms to keep the connection alive (0 = off).
44
+ * Ignored on a 2026-07-28 session, where `ping` no longer exists. */
44
45
  keepAliveMs?: number;
46
+ /** How to negotiate the MCP protocol revision.
47
+ *
48
+ * - `'auto'` (default) — probe `server/discover` first; fall back to the `initialize` handshake
49
+ * on anything that is not positive evidence of a 2026-07-28 server.
50
+ * - `'legacy'` — skip the probe entirely. Use for a server that mishandles unknown methods
51
+ * (the spec says answer with an error; not every implementation does).
52
+ * - a version string such as `'2026-07-28'` — adopt it directly, no probe. */
53
+ protocolMode?: 'auto' | 'legacy' | (string & {});
54
+ /** Honour the server's `ttlMs` / `cacheScope` hints on list and read results (2026-07-28).
55
+ * Off by default; a server that sends no hints caches nothing either way. */
56
+ cacheResults?: boolean;
57
+ /** Cap on `input_required` (MRTR) retry rounds before giving up. Default 10. */
58
+ inputRequiredMaxRounds?: number;
45
59
  /** OAuth provider for servers that require authorization (HTTP only). On a
46
60
  * required interactive grant, `connectMcp` throws `McpUnauthorizedError`
47
61
  * after the provider redirects; finish with `finishMcpAuth`, then reconnect. */
@@ -69,11 +83,19 @@ export declare function connectMcp(config: McpServerConfig, opts?: ConnectMcpOpt
69
83
  /** Finish an interactive OAuth grant: exchange the callback `code` for tokens
70
84
  * (saved via the provider). The `state` from the authorization callback MUST
71
85
  * be provided and is validated against the persisted value (CSRF guard).
72
- * Call after catching `McpUnauthorizedError`, then `connectMcp` again. */
86
+ * Call after catching `McpUnauthorizedError`, then `connectMcp` again.
87
+ *
88
+ * **Pass `opts.iss` if the callback URL carried one** (RFC 9207). It is validated against the
89
+ * authorization server's issuer before the code is redeemed, which is what stops a malicious
90
+ * server handing you a code minted elsewhere and having you replay the user's credentials against
91
+ * it. Optional so existing callers keep working, but a server that advertises
92
+ * `authorization_response_iss_parameter_supported` will make a missing `iss` an error — as it
93
+ * should, since an attacker could otherwise strip the parameter to skip the check. */
73
94
  export declare function finishMcpAuth(serverUrl: string, code: string, state: string, opts: {
74
95
  auth: McpAuthProvider;
75
96
  engine?: EngineHandle;
76
97
  security?: SsrfGuardOptions;
98
+ iss?: string;
77
99
  }): Promise<void>;
78
100
  export declare function mcpToolset(configs: McpServerConfig[], opts?: ConnectMcpOptions): Promise<{
79
101
  tools: AgentTool[];
@@ -0,0 +1,63 @@
1
+ /** Types for `checkProvenance()` — detecting provider provenance signals in a file. */
2
+ import type { EngineHandle } from './engine';
3
+ /** Which provenance scheme a signal came from.
4
+ *
5
+ * - `c2pa` — a signed Content Credentials manifest (who made it, with what, when).
6
+ * - `synthid` — Google's imperceptible watermark; survives some edits a manifest does not.
7
+ *
8
+ * Open union (CONSTITUTION.md R1): more schemes will appear, and a new one must not break a
9
+ * consumer's `switch`. */
10
+ export type ProvenanceSignalKind = 'c2pa' | 'synthid' | (string & {});
11
+ /** How much a detected C2PA manifest can be believed. `trusted` is the only state that means the
12
+ * signature verified against a known issuer. Open by R1. */
13
+ export type ProvenanceValidationState = 'trusted' | 'valid' | 'invalid' | 'not_present' | (string & {});
14
+ export interface ProvenanceSignal {
15
+ kind: ProvenanceSignalKind;
16
+ detected: boolean;
17
+ validationState?: ProvenanceValidationState;
18
+ /** Who signed the manifest (C2PA only). */
19
+ issuer?: string;
20
+ /** The model named in the manifest, when it records one. */
21
+ model?: string;
22
+ /** When the content was generated, per the manifest. */
23
+ generatedAt?: string;
24
+ }
25
+ export interface ProvenanceCheckResult {
26
+ /** Any signal detected at all.
27
+ *
28
+ * **`false` is not proof a human made the file.** Provenance signals are strippable — a
29
+ * re-encode, a crop, or a screenshot usually removes them — so absence is absence of evidence,
30
+ * not evidence of absence. */
31
+ detected: boolean;
32
+ /** A signal was detected AND its manifest validated against a trusted issuer. This is the only
33
+ * positive statement the check supports. */
34
+ trusted: boolean;
35
+ /** Every signal reported, including the ones that came back `detected: false` — an image is
36
+ * checked for both C2PA and SynthID, audio for SynthID only. */
37
+ signals: ProvenanceSignal[];
38
+ createdAt?: number;
39
+ }
40
+ export interface CheckProvenanceOptions {
41
+ /** File bytes to check. */
42
+ file: Uint8Array;
43
+ /** Filename with an extension — the API uses it to pick a decoder. */
44
+ filename: string;
45
+ /** MIME type of the file (e.g. `image/png`, `audio/wav`). */
46
+ mimeType: string;
47
+ provider?: string;
48
+ apiKey?: string;
49
+ engine?: EngineHandle;
50
+ }
51
+ /** The raw wire shape, normalised by `parseProvenanceResponse`. */
52
+ export interface ProvenanceRawResponse {
53
+ object?: string;
54
+ created_at?: number;
55
+ results?: Array<{
56
+ type?: string;
57
+ outcome?: string;
58
+ validation_state?: string;
59
+ issuer?: string;
60
+ model?: string;
61
+ generated_at?: string;
62
+ }>;
63
+ }
@@ -0,0 +1,12 @@
1
+ /** checkProvenance() — does this file carry provider provenance signals?
2
+ *
3
+ * const v = await checkProvenance({ file: bytes, filename: 'photo.png', mimeType: 'image/png' });
4
+ * if (v.trusted) { … } // a manifest that actually validated
5
+ *
6
+ * Same shape as `moderate()`: bytes in, structured verdict out, HTTP through engine.fetch.
7
+ *
8
+ * **Read `detected: false` carefully.** Provenance signals are strippable — a re-encode, a crop or
9
+ * a screenshot usually removes them — so a negative result is absence of evidence, not evidence
10
+ * that a human made the file. Only `trusted` is a positive statement. */
11
+ import type { CheckProvenanceOptions, ProvenanceCheckResult } from './provenance-types';
12
+ export declare function checkProvenance(opts: CheckProvenanceOptions): Promise<ProvenanceCheckResult>;
@@ -5,7 +5,7 @@
5
5
  * (gpt-4o-transcribe / whisper). Routed to OpenAITranscriptionAdapter.
6
6
  * - generateContent providers (google, …): transcription is just a normal
7
7
  * completion with the audio attached, so reuse complete(). */
8
- import type { AudioInput } from '../llm/types/audio';
8
+ import type { AudioInput, TranscriptLanguage, TranscriptSegment, TranscriptWord } from '../llm/types/audio';
9
9
  import type { ProviderName } from '../llm/types/provider';
10
10
  import type { EngineHandle } from './engine';
11
11
  export interface TranscribeOptions {
@@ -15,19 +15,48 @@ export interface TranscribeOptions {
15
15
  /** Audio source: a file path, raw bytes, or an AudioInput (with explicit
16
16
  * mimeType for raw/stream audio). */
17
17
  audio: string | Uint8Array | AudioInput;
18
- /** Optional language hint (BCP-47, e.g. "en"). */
18
+ /** Optional language hint (BCP-47, e.g. "en") — the language the audio IS in.
19
+ * Supported by `whisper-1` and `gpt-4o-transcribe`. Use `languages` instead for
20
+ * `gpt-transcribe`, which rejects this field. */
19
21
  language?: string;
22
+ /** Candidate languages for the audio (ISO-639-1), when the language is not known
23
+ * up front. Narrowing the candidates changes what the model reports detecting.
24
+ * OpenAI: `gpt-transcribe` only — other models reject it with a 400. */
25
+ languages?: string[];
26
+ /** Words or phrases that steer spelling — product names, people, jargon. Verified
27
+ * to work: an invented name transcribed as "Zalbrequist" without keywords comes
28
+ * back as "Zylberquist" with it.
29
+ * OpenAI: `gpt-transcribe` only — other models reject it with a 400. */
30
+ keywords?: string[];
31
+ /** Ask for word-level timings (and segments).
32
+ * OpenAI: `whisper-1` only — other models reject it with a 400. */
33
+ wordTimestamps?: boolean;
34
+ /** Ask for speaker-labelled segments.
35
+ * OpenAI: `gpt-4o-transcribe-diarize` only — other models reject it with a 400.
36
+ * Cannot be combined with `wordTimestamps`: no model returns both. */
37
+ diarization?: boolean;
20
38
  /** Prompt used for generateContent-style providers (ignored by openai). */
21
39
  prompt?: string;
22
- /** Caller-supplied audio duration in seconds. Used to price OpenAI
23
- * transcription calls (the API does not return duration). When omitted,
24
- * the helper tries to parse duration from a WAV header; other formats
25
- * emit an honest zero with a note. */
40
+ /** Caller-supplied audio duration in seconds, used to price the call.
41
+ * Takes precedence when set. Otherwise the provider's own reported duration is
42
+ * used when it returns one, then a WAV-header estimate; if none of the three is
43
+ * available the cost hook emits an honest zero with a note. */
26
44
  audioDurationSeconds?: number;
27
45
  engine?: EngineHandle;
28
46
  }
47
+ /** `text` is the only guaranteed field. Everything else appears when the chosen
48
+ * model returns it, so a consumer written against `text` keeps working forever
49
+ * (CONSTITUTION.md R3). */
29
50
  export interface TranscribeResult {
30
51
  text: string;
52
+ /** Languages the provider reports detecting. */
53
+ languages?: TranscriptLanguage[];
54
+ /** Timed segments. `speaker` is set only when diarization ran. */
55
+ segments?: TranscriptSegment[];
56
+ /** Word-level timings. */
57
+ words?: TranscriptWord[];
58
+ /** Audio duration in seconds as the provider measured it. */
59
+ durationSeconds?: number;
31
60
  }
32
61
  export declare function transcribe(opts: TranscribeOptions): Promise<TranscribeResult>;
33
62
  /** WAV PCM duration from raw bytes — pure arithmetic, cross-env.