@combycode/llm-sdk 1.6.1 → 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 (58) hide show
  1. package/CHANGELOG.md +518 -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/bus/hook-map.d.ts +11 -0
  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 +3134 -731
  15. package/dist/index.d.ts +19 -7
  16. package/dist/index.js +3134 -731
  17. package/dist/llm/moderation/native.d.ts +5 -4
  18. package/dist/llm/providers/anthropic/constants.d.ts +2 -0
  19. package/dist/llm/providers/google/constants.d.ts +17 -2
  20. package/dist/llm/providers/openai/completions.d.ts +18 -2
  21. package/dist/llm/providers/openai/provenance.d.ts +26 -0
  22. package/dist/llm/providers/openai/responses.d.ts +4 -2
  23. package/dist/llm/providers/openai/transcription.d.ts +39 -2
  24. package/dist/llm/providers/xai/completions.d.ts +2 -2
  25. package/dist/llm/providers/xai/media.d.ts +8 -0
  26. package/dist/llm/types/audio.d.ts +31 -0
  27. package/dist/llm/types/messages.d.ts +89 -1
  28. package/dist/llm/types/options.d.ts +15 -0
  29. package/dist/llm/types/request.d.ts +21 -1
  30. package/dist/llm/types/response.d.ts +31 -1
  31. package/dist/llm/types/stream.d.ts +14 -1
  32. package/dist/llm/types/tiers.d.ts +6 -6
  33. package/dist/llm/types/tools.d.ts +10 -1
  34. package/dist/network/queue-state-config.d.ts +5 -0
  35. package/dist/network/queue-state.d.ts +7 -0
  36. package/dist/network/types.d.ts +23 -0
  37. package/dist/plugins/context-guard/strategies/anchored.d.ts +47 -0
  38. package/dist/plugins/context-measurer/counter/hybrid.d.ts +4 -1
  39. package/dist/plugins/context-measurer/counter/tiktoken.d.ts +8 -1
  40. package/dist/plugins/mcp/base-transport.d.ts +16 -0
  41. package/dist/plugins/mcp/client.d.ts +144 -7
  42. package/dist/plugins/mcp/input-required.d.ts +35 -0
  43. package/dist/plugins/mcp/jsonrpc.d.ts +7 -0
  44. package/dist/plugins/mcp/oauth.d.ts +21 -1
  45. package/dist/plugins/mcp/protocol-version.d.ts +61 -0
  46. package/dist/plugins/mcp/result-cache.d.ts +31 -0
  47. package/dist/plugins/mcp/subscriptions.d.ts +69 -0
  48. package/dist/plugins/mcp/transport-http.d.ts +31 -0
  49. package/dist/plugins/mcp/transport-stdio.d.ts +2 -0
  50. package/dist/plugins/mcp/transport-ws.d.ts +11 -1
  51. package/dist/plugins/mcp/transport.d.ts +11 -0
  52. package/dist/plugins/mcp/types.d.ts +54 -2
  53. package/dist/plugins/media/source-image.d.ts +9 -0
  54. package/dist/plugins/media/types.d.ts +21 -0
  55. package/dist/plugins/model-catalog/catalog.d.ts +3 -0
  56. package/dist/plugins/telemetry/telemetry.d.ts +12 -0
  57. package/dist/util/http.d.ts +8 -0
  58. package/package.json +9 -6
@@ -1,6 +1,16 @@
1
1
  /** WebSocket MCP transport — JSON-RPC messages as text frames over a duplex
2
2
  * socket (naturally bidirectional). Uses the engine's `connect` so it shares
3
- * the engine's WebSocket factory + hooks. Cross-env (browser + Node/Bun). */
3
+ * the engine's WebSocket factory + hooks. Cross-env (browser + Node/Bun).
4
+ *
5
+ * **NON-STANDARD, and deliberately kept.** The MCP SDK removed its own WebSocket transport in
6
+ * `mcp` 2.0.0, reasoning that it "was never part of the MCP specification". Ours stays: it is
7
+ * public API we shipped, and deleting an exported transport on an upstream style call would break
8
+ * consumers for no protocol reason (CONSTITUTION.md R7 — an upstream deletion is not our
9
+ * deletion). Treat it as a supported extra rather than a spec transport: the server has to opt
10
+ * into JSON-RPC over a socket, and the standard transports remain stdio and Streamable HTTP.
11
+ *
12
+ * Practical upside of keeping it: being duplex, it supports `subscriptions/listen` today —
13
+ * which Streamable HTTP does not yet. */
4
14
  import type { EngineConnect } from '../../network/types';
5
15
  import type { McpTransport } from './transport';
6
16
  import { BaseJsonRpcTransport } from './base-transport';
@@ -21,6 +21,17 @@ export interface McpTransport {
21
21
  setHandlers(handlers: IncomingMcpHandlers): void;
22
22
  /** Record the negotiated protocol version (HTTP sets a header; stdio ignores). */
23
23
  setProtocolVersion?(version: string): void;
24
+ /** Record the negotiated ERA. At `'modern'` (2026-07-28+) the wire is stateless: no
25
+ * `Mcp-Session-Id`, and every request carries `Mcp-Method` / `Mcp-Name` routing headers so an
26
+ * intermediary can route without parsing the body. Optional — stdio has no headers to set. */
27
+ setEra?(era: 'handshake' | 'modern'): void;
28
+ /** Send a long-lived request (`subscriptions/listen`) and return its id, without arming the
29
+ * normal response timeout.
30
+ *
31
+ * `onEnd` fires when the stream finishes — cleanly (the server tore the subscription down) or
32
+ * with the error that killed it. A subscription that stops delivering is otherwise invisible to
33
+ * the caller. */
34
+ sendLongLivedRequest?(method: string, params?: unknown, onEnd?: (error?: unknown) => void): Promise<string | number>;
24
35
  /** Open the server->client channel (HTTP GET SSE stream). Stdio is already
25
36
  * duplex, so this is a no-op there. Call after the initialize handshake. */
26
37
  listen?(): Promise<void> | void;
@@ -52,7 +52,40 @@ export type McpContentBlock = {
52
52
  type: string;
53
53
  [k: string]: unknown;
54
54
  };
55
- export interface McpCallResult {
55
+ /** One server→client request embedded in an `input_required` result: a `sampling/createMessage`,
56
+ * `elicitation/create` or `roots/list`, in JSON-RPC request shape. Identical in content to what a
57
+ * handshake-era server pushes over the back-channel — only the delivery differs. */
58
+ export interface McpInputRequest {
59
+ method: string;
60
+ params?: unknown;
61
+ }
62
+ /** Client-side caching directives carried by 2026-07-28 list/read results (`CacheableResult`).
63
+ *
64
+ * Optional here because every pre-2026 server omits them, and a server that sends no hints must
65
+ * behave exactly as before (CONSTITUTION.md R3). */
66
+ export interface McpCacheHints {
67
+ /** How long (ms) the client MAY reuse this result. **`0` means immediately stale** — re-fetch
68
+ * every time — so it is NOT the same as "absent" and must not be coerced to a default. */
69
+ ttlMs?: number;
70
+ /** `'public'`: no user-specific data, any cache may serve it across authorization contexts.
71
+ * `'private'`: reusable only within the same authorization context. */
72
+ cacheScope?: 'private' | 'public';
73
+ }
74
+ /** The 2026-07-28 multi-round-trip fields (SEP-2322).
75
+ *
76
+ * Upstream models this as a separate `InputRequiredResult` type, making every result a union. We
77
+ * attach it as OPTIONAL fields on the existing results instead (CONSTITUTION.md R2): code reading
78
+ * `result.content` keeps compiling, and callers who never meet a modern server never see them. */
79
+ export interface McpInputRequiredFields {
80
+ /** `'complete'` | `'input_required'`. **Absent MUST be read as `'complete'`** — earlier revisions
81
+ * never send it. Open by R1: a future revision may add a third kind. */
82
+ resultType?: 'complete' | 'input_required' | (string & {});
83
+ /** Server-assigned key → the request to answer. Present when the server has questions. */
84
+ inputRequests?: Record<string, McpInputRequest>;
85
+ /** Opaque continuation token. Echoed back byte-exact and never inspected. */
86
+ requestState?: string;
87
+ }
88
+ export interface McpCallResult extends McpInputRequiredFields {
56
89
  content: McpContentBlock[];
57
90
  isError?: boolean;
58
91
  structuredContent?: Record<string, unknown>;
@@ -92,7 +125,7 @@ export interface McpPromptMessage {
92
125
  role: 'user' | 'assistant';
93
126
  content: McpContentBlock;
94
127
  }
95
- export interface McpGetPromptResult {
128
+ export interface McpGetPromptResult extends McpInputRequiredFields {
96
129
  description?: string;
97
130
  messages: McpPromptMessage[];
98
131
  }
@@ -180,6 +213,25 @@ export interface McpInitializeResult {
180
213
  serverInfo: McpServerInfo;
181
214
  instructions?: string;
182
215
  }
216
+ /** Result of the 2026-07-28 `server/discover` probe — the modern replacement for the `initialize`
217
+ * handshake. Shape verified against mcp-py 2.0.0 (`_v2026_07_28.DiscoverResult`).
218
+ *
219
+ * `McpClient.info` synthesises an `McpInitializeResult` from this, so a caller never has to branch
220
+ * on the era (CONSTITUTION.md R2). This type exposes the fields that have no handshake equivalent. */
221
+ export interface McpDiscoverResult {
222
+ capabilities: Record<string, unknown>;
223
+ /** Revisions the server speaks; the client picks one from this list. */
224
+ supportedVersions: string[];
225
+ /** How long (ms) the client MAY cache this result. `0` = treat as immediately stale. */
226
+ ttlMs?: number;
227
+ /** `public` = cacheable across authorization contexts; `private` = same context only. */
228
+ cacheScope?: 'private' | 'public';
229
+ instructions?: string;
230
+ /** Absent on servers implementing an earlier revision, which MUST be read as `'complete'`. */
231
+ resultType?: string;
232
+ /** Carries the display-only `io.modelcontextprotocol/serverInfo` stamp. */
233
+ _meta?: Record<string, unknown>;
234
+ }
183
235
  export interface McpHttpConfig {
184
236
  /** Streamable-HTTP MCP endpoint URL. Cross-env (browser needs server CORS). */
185
237
  url: string;
@@ -26,6 +26,15 @@ export declare function toDataUrl(ref: NormalizedImageRef): string;
26
26
  export declare function openaiImageRef(ref: NormalizedImageRef): Record<string, string>;
27
27
  /** xAI image-ref object (`/v1/images/edits` image, video image). */
28
28
  export declare function xaiImageRef(ref: NormalizedImageRef): Record<string, string>;
29
+ /** xAI video-ref object (`/v1/videos/extensions` + `/v1/videos/edits` `video`
30
+ * field) — `{ url }` (public URL or base64 data-URL) or `{ file_id }`. Kept
31
+ * separate from the image path so it doesn't run image mime-sniffing over
32
+ * video bytes; the video mime is taken from the DataSource as declared. */
33
+ export declare function xaiVideoRef(src: DataSource): {
34
+ url: string;
35
+ } | {
36
+ file_id: string;
37
+ };
29
38
  /** Google generateContent image part (inline base64 or Files-API file_uri). */
30
39
  export declare function googleImagePart(ref: NormalizedImageRef): Record<string, unknown>;
31
40
  /** Google Veo instance image (`:predictLongRunning` instances[].image).
@@ -18,6 +18,9 @@ export interface MediaMeta {
18
18
  durationMs?: number;
19
19
  sampleRate?: number;
20
20
  params?: Record<string, unknown>;
21
+ /** Provider-hosted URL (async video). Present when the bytes live remotely —
22
+ * the browser renders from this since a cross-origin byte-fetch is CORS-blocked. */
23
+ sourceUrl?: string;
21
24
  }
22
25
  export interface MediaStore {
23
26
  save(id: string, data: Uint8Array, meta: MediaMeta): Promise<void>;
@@ -76,13 +79,22 @@ export interface VideoGenRequest {
76
79
  provider: string;
77
80
  model?: string;
78
81
  prompt: string;
82
+ /** First-frame image → image-to-video. */
79
83
  sourceImage?: DataSource;
84
+ /** Input video → extend or edit an existing clip (see `params.videoMode`).
85
+ * The adapter routes to the provider's extension/edit endpoint instead of
86
+ * plain generation. */
87
+ sourceVideo?: DataSource;
80
88
  params?: {
81
89
  duration?: number;
82
90
  aspectRatio?: string;
83
91
  resolution?: string;
84
92
  /** OpenAI Sora literal pixel `size` (e.g. "720x1280"). */
85
93
  size?: string;
94
+ /** When `sourceVideo` is set, which operation to run. `extend` (default)
95
+ * continues the clip from its last frame; `edit` modifies it in place per
96
+ * the prompt. Ignored without `sourceVideo`. */
97
+ videoMode?: 'extend' | 'edit';
86
98
  };
87
99
  }
88
100
  export interface MediaResult {
@@ -94,6 +106,12 @@ export interface MediaResult {
94
106
  export interface RawMediaResult {
95
107
  data: Uint8Array;
96
108
  mimeType: string;
109
+ /** Provider-hosted URL for the asset, when it exists (async video). In the
110
+ * browser, cross-origin buckets (e.g. xAI vidgen) block a programmatic
111
+ * byte-fetch via CORS, so `data` may be empty and this URL is the only way
112
+ * to render (`<video src>` plays cross-origin without CORS) or to re-submit
113
+ * as a `sourceVideo`. */
114
+ sourceUrl?: string;
97
115
  width?: number;
98
116
  height?: number;
99
117
  durationMs?: number;
@@ -116,6 +134,9 @@ export interface MediaCapabilities {
116
134
  audioGeneration: boolean;
117
135
  videoGeneration: boolean;
118
136
  audioStreaming: boolean;
137
+ /** Provider can extend/edit an existing video (`sourceVideo` on the request).
138
+ * Undefined/false → passing `sourceVideo` throws. */
139
+ videoExtension?: boolean;
119
140
  }
120
141
  /** All MediaProviderAdapter HTTP calls now go through the NetworkEngine
121
142
  * queue (rate limits, retries, hooks, observability) instead of holding a
@@ -39,6 +39,9 @@ export interface ModelCapabilities {
39
39
  imageGeneration: boolean;
40
40
  audioGeneration: boolean;
41
41
  videoGeneration: boolean;
42
+ /** Model accepts an existing video as input to extend/edit it (xAI
43
+ * grok-imagine-video). Undefined/false → generation only. */
44
+ videoExtension?: boolean;
42
45
  }
43
46
  /** One generation parameter a media model accepts, with its allowed values.
44
47
  * Either an enum (`values` + `default`) or a numeric range (`min`/`max`).
@@ -69,6 +69,17 @@ export interface TelemetryAdapterOptions {
69
69
  maxEvents?: number;
70
70
  /** Service identity stamped on all exported telemetry. */
71
71
  resource?: TelemetryResource;
72
+ /** Whether provider error TEXT may be stored in telemetry. Default `true`
73
+ * (unchanged behaviour, and the same default as the OpenAI Agents SDK's
74
+ * `trace_include_sensitive_data`).
75
+ *
76
+ * A provider's `error.message` / `error.raw` can echo request content back —
77
+ * a moderation refusal quotes the prompt, a validation error names the offending
78
+ * field and value. URLs and headers are always redacted regardless; this switch
79
+ * governs the free-text payload. Set `false` when telemetry leaves your trust
80
+ * boundary (a shared collector, a vendor APM) and the message is replaced by a
81
+ * fixed `[redacted]` string while name/code/status are kept for triage. */
82
+ includeSensitiveData?: boolean;
72
83
  }
73
84
  export declare class TelemetryAdapter {
74
85
  readonly events: TelemetryEvent[];
@@ -80,6 +91,7 @@ export declare class TelemetryAdapter {
80
91
  private latSum;
81
92
  private readonly open;
82
93
  private readonly maxEvents;
94
+ private readonly includeSensitiveData;
83
95
  private readonly unsub;
84
96
  constructor(hooks: HookBus, opts?: TelemetryAdapterOptions);
85
97
  /** Stop tapping the bus. */
@@ -1,6 +1,14 @@
1
1
  /** Small HTTP header helpers shared across network + server layers. */
2
2
  /** Lowercase-keyed plain record from a WHATWG `Headers`. */
3
3
  export declare function headersToRecord(headers: Headers): Record<string, string>;
4
+ /** Case-insensitive header lookup. HTTP header names are case-insensitive
5
+ * (RFC 9110 §5.1) but a plain record is not, and the casing a server or fetch
6
+ * implementation actually sends varies — so read response headers through here
7
+ * instead of guessing casings at the call site. */
8
+ export declare function header(headers: Record<string, string>, name: string): string | undefined;
9
+ /** True when a request body cannot be replayed for a retry (the first attempt
10
+ * consumes it). FormData, strings and byte views are replayable; a stream is not. */
11
+ export declare function isStreamBody(body: unknown): boolean;
4
12
  /** Parse an integer header value, or null if absent / not a number. */
5
13
  export declare function parseIntHeader(headers: Record<string, string>, key: string): number | null;
6
14
  /** Combine multiple AbortSignals into one that aborts when any of them does. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@combycode/llm-sdk",
3
- "version": "1.6.1",
3
+ "version": "2.0.0",
4
4
  "description": "Unified, pluggable AI SDK for accessing the LLMs of every major provider (Anthropic, OpenAI, Google, xAI, OpenRouter) through one API. Cross-environment: Node, Bun, and the browser.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -23,7 +23,8 @@
23
23
  "dist",
24
24
  "README.md",
25
25
  "CHANGELOG.md",
26
- "LICENSE"
26
+ "LICENSE",
27
+ "MIGRATION.md"
27
28
  ],
28
29
  "scripts": {
29
30
  "build": "bun run clean && bun run build:js && bun run build:types",
@@ -43,25 +44,27 @@
43
44
  "check": "biome check src tests",
44
45
  "check:fix": "biome check --write src tests"
45
46
  },
46
- "optionalDependencies": {
47
- "tiktoken": "^1.0.22"
48
- },
49
47
  "devDependencies": {
50
48
  "@biomejs/biome": "^2.4.13",
51
49
  "@types/bun": "latest",
52
50
  "esbuild": "0.28.1",
51
+ "tiktoken": "^1.0.22",
53
52
  "typescript": "^5.6.0"
54
53
  },
55
54
  "peerDependencies": {
55
+ "tiktoken": "^1.0.22",
56
56
  "typescript": ">=5.0"
57
57
  },
58
58
  "peerDependenciesMeta": {
59
59
  "typescript": {
60
60
  "optional": true
61
+ },
62
+ "tiktoken": {
63
+ "optional": true
61
64
  }
62
65
  },
63
66
  "engines": {
64
- "node": ">=18",
67
+ "node": ">=22",
65
68
  "bun": ">=1.1.0"
66
69
  },
67
70
  "repository": {