@combycode/llm-sdk 3.0.0 → 3.2.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.
@@ -85,6 +85,9 @@ export declare class LLMClient {
85
85
  * files piped straight to a file / GridFS / HTTP response without buffering. */
86
86
  streamFile(file: FileOutput): Promise<FileStream>;
87
87
  /** Submit a request. Returns the parsed CompletionResponse. */
88
+ /** Anything the spec left out on purpose reaches the caller as a warning.
89
+ * Said once per request; the build already de-duplicates within one. */
90
+ private reportBuildNotes;
88
91
  complete(input: string | ContentPart[] | Message[], options?: ExecuteOptions): Promise<CompletionResponse>;
89
92
  /** Run `complete` with a JSON Schema enforced via `structured`. Strips any
90
93
  * leading/trailing markdown fences from the model reply, then JSON.parses
@@ -0,0 +1,25 @@
1
+ /** The sources an answer cited, read from four different wire shapes.
2
+ *
3
+ * `builtinToolCalls` already records what the model INVOKED — that it searched,
4
+ * and for what. This is the other half: what it ended up CITING. They are not
5
+ * the same list. A model can run three searches and cite one page, or open a
6
+ * page and cite nothing, and a caller rendering footnotes needs the second
7
+ * list, not the first.
8
+ *
9
+ * Until now this was only reachable by regexing `response.raw`, which is what
10
+ * the web-search example actually did. That is a per-consumer reimplementation
11
+ * of provider knowledge that belongs in the SDK.
12
+ *
13
+ * This function always returns an array. The RESPONSE FIELD it feeds is
14
+ * optional and omitted when empty, like the `files` / `builtinToolCalls` lines
15
+ * it sits beside -- R3 forbids adding a required field to a response type, so
16
+ * the Python port's always-present array is not available here. Callers read
17
+ * `response.citations ?? []`.
18
+ */
19
+ import type { Citation } from '../../types/response';
20
+ /** Every source the answer cited, or `[]`.
21
+ *
22
+ * `api` is the wire surface (`'messages' | 'generate' | 'interactions' |
23
+ * 'responses' | 'completions'`). An unknown surface yields `[]` rather than
24
+ * throwing: a response we cannot read citations from is still a valid answer. */
25
+ export declare function extractCitations(api: string, raw: unknown): Citation[];
@@ -24,6 +24,11 @@ export interface ProviderHttpRequest {
24
24
  /** Override of the default completion path. Used by providers that route
25
25
  * per-API or per-modality. */
26
26
  path?: string;
27
+ /** What the build deliberately left out, and why — a hosted tool this provider
28
+ * refuses to run beside the attached content, for instance. The client emits
29
+ * each as `onWarning`, because dropping a capability the caller asked for and
30
+ * saying nothing is how a missing feature gets mistaken for a working one. */
31
+ notes?: string[];
27
32
  }
28
33
  export interface ProviderAdapter {
29
34
  readonly name: ProviderName;
@@ -20,6 +20,16 @@ export interface CompletionResponse {
20
20
  * search or code-execution run. A durable trail of what was called (the provider
21
21
  * ran them server-side; nothing for the client to execute). Absent when none. */
22
22
  builtinToolCalls?: BuiltinToolCall[];
23
+ /** Sources the answer cited, unified across providers.
24
+ *
25
+ * Distinct from `builtinToolCalls`, which records what the model INVOKED: a turn
26
+ * can run three searches and cite one page, or open a page and cite nothing.
27
+ * Rendering footnotes needs this list, not that one.
28
+ *
29
+ * Absent when the model cited nothing — R3: a response type grows by OPTIONAL
30
+ * fields only, so this cannot be the always-present array the Python port
31
+ * exposes. Read it as `response.citations ?? []`. */
32
+ citations?: Citation[];
23
33
  /** Inline-moderation outcome, when the `moderation` request option was used.
24
34
  * Report-only: present for observability; it never blocks the call. Absent when
25
35
  * moderation was not requested. */
@@ -58,6 +68,19 @@ export interface FileOutput {
58
68
  * Absent for providers that don't need extra context. */
59
69
  ref?: Record<string, unknown>;
60
70
  }
71
+ /** A source the answer cited.
72
+ *
73
+ * Four providers report these four different ways — Anthropic on the text block,
74
+ * Google in `groundingMetadata`, OpenAI as annotations, xAI as bare top-level URLs.
75
+ * The differences stop at the adapter. */
76
+ export interface Citation {
77
+ url: string;
78
+ /** Page title, when the provider supplies one. */
79
+ title?: string;
80
+ /** The passage the source supports. Only Anthropic reports this today; absent
81
+ * elsewhere rather than faked from the answer text. */
82
+ text?: string;
83
+ }
61
84
  /** A hosted builtin tool the model invoked (provider-run), with its inputs/outputs. */
62
85
  export interface BuiltinToolCall {
63
86
  /** Unified tool name: `'web_search'` | `'code_interpreter'` | … (normalized from
@@ -1,7 +1,7 @@
1
1
  /** Universal streaming event types. */
2
2
  import type { ModerationEntry } from '../moderation/types';
3
3
  import type { AssistantPhase } from './messages';
4
- import type { FileOutput, Usage } from './response';
4
+ import type { Citation, FileOutput, Usage } from './response';
5
5
  export type MediaStreamType = 'image' | 'audio' | 'video';
6
6
  export type StreamEvent =
7
7
  /** `itemId` identifies WHICH output item a delta belongs to, when the provider reports
@@ -62,6 +62,19 @@ export type StreamEvent =
62
62
  type: 'file';
63
63
  file: FileOutput;
64
64
  }
65
+ /** The answer cited a source. Emitted as the citation arrives, which is NOT
66
+ * when the search ran: a provider searches early and cites while it writes, so
67
+ * these interleave with `text` deltas. Distinct from `builtin_tool_end`, which
68
+ * reports the search itself.
69
+ *
70
+ * Measured shapes: Anthropic `citations_delta`, OpenAI/xAI Responses
71
+ * `response.output_text.annotation.added`, chat-completions `delta.annotations`,
72
+ * Google's populated `groundingMetadata` chunk. Also collected onto the streamed
73
+ * final response's `citations`, deduped by url. */
74
+ | {
75
+ type: 'citation';
76
+ citation: Citation;
77
+ }
65
78
  /** A hosted (provider-run) builtin tool began executing server-side — e.g. the
66
79
  * model started a web search or code-execution run. `tool` is the unified name
67
80
  * (`'web_search'` | `'code_interpreter'` | …). Informational progress: unlike
@@ -53,6 +53,13 @@ export type Cond = {
53
53
  /** Array at `path` contains `value`. */
54
54
  | {
55
55
  includes: [string, Json];
56
+ }
57
+ /** The request carries a message content part of one of these types
58
+ * (`image` | `audio` | `video` | `document` | `text`). Needed because the
59
+ * parts are nested two levels deep — `messages[].content[].type` — which
60
+ * `includes` cannot reach. */
61
+ | {
62
+ hasPartType: string[];
56
63
  } | {
57
64
  not: Cond;
58
65
  } | {
@@ -93,10 +100,37 @@ export interface BlockRule {
93
100
  /** Named cross-field effects run after the block is written. */
94
101
  effects?: string[];
95
102
  }
103
+ /** A hosted tool this provider refuses to run alongside certain content.
104
+ *
105
+ * Providers reject some combinations outright: Google answers 400 "The mime
106
+ * type: video/mp4 is not supported for code execution" when `code_interpreter`
107
+ * is sent with a PDF or a video. The caller cannot be expected to know that, and
108
+ * the error names a mime type rather than the tool, so it reads as a problem
109
+ * with the attachment.
110
+ *
111
+ * Declared here as data rather than as a provider `if` in the builder: it is one
112
+ * more fact about how this API behaves, and the four other providers stay
113
+ * untouched because their specs simply do not carry the field.
114
+ *
115
+ * A matched constraint DROPS the tool — `hasTool` reports it absent, so the
116
+ * spec's existing `$when` guard omits it with no further edit — and records
117
+ * `why` on the built request so the runtime can say what it did. Dropping
118
+ * quietly would trade a confusing error for a silent loss of a capability the
119
+ * caller asked for, which is worse. */
120
+ export interface ToolConstraint {
121
+ /** Builtin tool type, e.g. `code_interpreter`. */
122
+ tool: string;
123
+ /** When this holds, the tool cannot be sent. */
124
+ conflictsWith: Cond;
125
+ /** Said to the caller, verbatim. */
126
+ why: string;
127
+ }
96
128
  export interface WireSpec {
97
129
  id: string;
98
130
  provider: string;
99
131
  api: string;
132
+ /** Tool/content combinations this provider rejects. See `ToolConstraint`. */
133
+ toolConstraints?: ToolConstraint[];
100
134
  /** Adapter flavor, for specs shared by several providers (openai|xai|openrouter). */
101
135
  flavors?: string[];
102
136
  envelope?: {
@@ -216,6 +250,8 @@ export interface Ctx {
216
250
  };
217
251
  /** Collected multipart fields, when the spec declares a multipart body. */
218
252
  multipart?: MultipartField[];
253
+ /** What the build decided to leave out, and why — surfaced to the caller. */
254
+ notes?: string[];
219
255
  }
220
256
  export interface MultipartField {
221
257
  name: string;
@@ -237,6 +273,10 @@ export declare function evalCond(cond: Cond | undefined, ctx: Ctx, reg: Registry
237
273
  export declare function resolveVariants(spec: WireSpec, model: string, reg: Registry): Set<string>;
238
274
  export interface BuiltRequest {
239
275
  body: Record<string, unknown>;
276
+ /** Anything the spec deliberately left out, and why — e.g. a hosted tool this
277
+ * provider will not run beside the attached content. The runtime turns these
278
+ * into `onWarning`; they are never silent. */
279
+ notes?: string[];
240
280
  headers?: Record<string, string>;
241
281
  path?: string;
242
282
  url?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@combycode/llm-sdk",
3
- "version": "3.0.0",
3
+ "version": "3.2.1",
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",
@@ -34,6 +34,8 @@
34
34
  "prepublishOnly": "bun run build",
35
35
  "test": "bun test",
36
36
  "test:unit": "bun test tests/unit",
37
+ "test:coverage": "bun test tests/unit --coverage",
38
+ "coverage:gate": "bun run scripts/coverage-gate.ts",
37
39
  "test:integration": "bun test tests/integration",
38
40
  "test:helpers": "bun test tests/helpers",
39
41
  "test:live": "bun test tests/live",