@combycode/llm-sdk 1.4.0 → 1.5.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.
- package/CHANGELOG.md +22 -0
- package/dist/index.browser.js +123 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +123 -4
- package/dist/llm/providers/_shared/builtin-tools.d.ts +5 -0
- package/dist/llm/providers/openrouter/completions.d.ts +7 -0
- package/dist/llm/types/response.d.ts +12 -0
- package/dist/llm/types/stream.d.ts +17 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,28 @@ All notable changes to `@combycode/llm-sdk` are documented here. The format foll
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.5.1] - 2026-07-06
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- **Anthropic file retrieval from the browser** was blocked by CORS. `retrieveFile` /
|
|
13
|
+
`streamFile` now send Anthropic's `anthropic-dangerous-direct-browser-access: true` header
|
|
14
|
+
when running in a browser (matching the completion adapter), so hosted code-execution files
|
|
15
|
+
download directly. No effect on Node/Bun.
|
|
16
|
+
|
|
17
|
+
## [1.5.0] - 2026-07-06
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- **Builtin-tool activity in streams + a durable trail.** Provider-run hosted tools (web search,
|
|
21
|
+
code execution) now surface `{ type: 'builtin_tool_start' }` / `{ type: 'builtin_tool_end' }`
|
|
22
|
+
stream events as they run, and a `response.builtinToolCalls` trail (`BuiltinToolCall[]` —
|
|
23
|
+
`{ tool, id? }` with unified tool names) on both `complete()` and streamed responses (propagated
|
|
24
|
+
through `AgentLoop`). Informational only — unlike `tool_call_*` (a function call the client must
|
|
25
|
+
execute), the provider runs these itself. Normalized across providers (Anthropic `server_tool_use`
|
|
26
|
+
/ `*_tool_result`, OpenAI/xAI `web_search_call` / `code_interpreter_call` output items, Google
|
|
27
|
+
`executableCode` / `codeExecutionResult` parts + `googleSearch` grounding, OpenRouter
|
|
28
|
+
`:online` `url_citation` annotations → `web_search`). Verified live on Anthropic, OpenAI,
|
|
29
|
+
Google, xAI, and OpenRouter. Exports `BuiltinToolCall`.
|
|
30
|
+
|
|
9
31
|
## [1.4.0] - 2026-07-06
|
|
10
32
|
|
|
11
33
|
### Fixed
|
package/dist/index.browser.js
CHANGED
|
@@ -22471,7 +22471,11 @@ function contentRequest(ctx, file) {
|
|
|
22471
22471
|
"x-api-key": ctx.apiKey,
|
|
22472
22472
|
"anthropic-version": "2023-06-01",
|
|
22473
22473
|
"anthropic-beta": "files-api-2025-04-14",
|
|
22474
|
-
accept: "application/binary"
|
|
22474
|
+
accept: "application/binary",
|
|
22475
|
+
// Enable CORS for direct browser requests (same header the completion
|
|
22476
|
+
// adapter sets); harmless on Node/Bun. Without it the browser blocks the
|
|
22477
|
+
// file download by CORS.
|
|
22478
|
+
...isBrowser() ? { "anthropic-dangerous-direct-browser-access": "true" } : {}
|
|
22475
22479
|
}
|
|
22476
22480
|
};
|
|
22477
22481
|
}
|
|
@@ -22492,7 +22496,11 @@ function providerAuth(ctx, url) {
|
|
|
22492
22496
|
const base = ctx.baseURL ?? DEFAULT_BASE[ctx.provider];
|
|
22493
22497
|
if (!url.startsWith(base)) return {};
|
|
22494
22498
|
if (ctx.provider === "anthropic") {
|
|
22495
|
-
return {
|
|
22499
|
+
return {
|
|
22500
|
+
"x-api-key": ctx.apiKey,
|
|
22501
|
+
"anthropic-version": "2023-06-01",
|
|
22502
|
+
...isBrowser() ? { "anthropic-dangerous-direct-browser-access": "true" } : {}
|
|
22503
|
+
};
|
|
22496
22504
|
}
|
|
22497
22505
|
if (ctx.provider === "google") return { "x-goog-api-key": ctx.apiKey };
|
|
22498
22506
|
return { authorization: `Bearer ${ctx.apiKey}` };
|
|
@@ -23082,6 +23090,7 @@ var LLMClient = class {
|
|
|
23082
23090
|
let finishReason = "stop";
|
|
23083
23091
|
let moderationReport;
|
|
23084
23092
|
const files = [];
|
|
23093
|
+
const builtinToolCalls = [];
|
|
23085
23094
|
const fetchStream = this.fetchStreamFn;
|
|
23086
23095
|
const queueName = this.queueName;
|
|
23087
23096
|
const priority = this.priority;
|
|
@@ -23133,6 +23142,9 @@ var LLMClient = class {
|
|
|
23133
23142
|
case "file":
|
|
23134
23143
|
files.push(event.file);
|
|
23135
23144
|
break;
|
|
23145
|
+
case "builtin_tool_start":
|
|
23146
|
+
builtinToolCalls.push({ tool: event.tool, ...event.id ? { id: event.id } : {} });
|
|
23147
|
+
break;
|
|
23136
23148
|
case "moderation":
|
|
23137
23149
|
moderationReport = this.mergeModeration(
|
|
23138
23150
|
moderationReport,
|
|
@@ -23156,6 +23168,7 @@ var LLMClient = class {
|
|
|
23156
23168
|
thinking: thinking || null,
|
|
23157
23169
|
media: [],
|
|
23158
23170
|
...files.length ? { files } : {},
|
|
23171
|
+
...builtinToolCalls.length ? { builtinToolCalls } : {},
|
|
23159
23172
|
...moderationReport ? { moderation: moderationReport } : {},
|
|
23160
23173
|
latencyMs: performance.now() - start,
|
|
23161
23174
|
raw: null
|
|
@@ -23389,6 +23402,24 @@ var AnthropicFileAdapter = class {
|
|
|
23389
23402
|
}
|
|
23390
23403
|
};
|
|
23391
23404
|
|
|
23405
|
+
// src/llm/providers/_shared/builtin-tools.ts
|
|
23406
|
+
var NATIVE_TO_UNIFIED = {
|
|
23407
|
+
// OpenAI / xAI Responses output-item types
|
|
23408
|
+
web_search_call: "web_search",
|
|
23409
|
+
code_interpreter_call: "code_interpreter",
|
|
23410
|
+
// Anthropic server_tool_use names
|
|
23411
|
+
web_search: "web_search",
|
|
23412
|
+
code_execution: "code_interpreter",
|
|
23413
|
+
bash_code_execution: "code_interpreter",
|
|
23414
|
+
// Anthropic *_tool_result block types
|
|
23415
|
+
web_search_tool_result: "web_search",
|
|
23416
|
+
code_execution_tool_result: "code_interpreter",
|
|
23417
|
+
bash_code_execution_tool_result: "code_interpreter"
|
|
23418
|
+
};
|
|
23419
|
+
function unifiedBuiltinTool(native) {
|
|
23420
|
+
return NATIVE_TO_UNIFIED[native] ?? native.replace(/_call$/, "").replace(/_tool_result$/, "");
|
|
23421
|
+
}
|
|
23422
|
+
|
|
23392
23423
|
// src/llm/providers/_shared/constants.ts
|
|
23393
23424
|
var AUDIO_PCM16_SAMPLE_RATE_HZ = 24e3;
|
|
23394
23425
|
var DEFAULT_MAX_TOKENS = 4096;
|
|
@@ -23598,6 +23629,7 @@ var AnthropicAdapter = class {
|
|
|
23598
23629
|
let thinking = null;
|
|
23599
23630
|
const toolCalls = [];
|
|
23600
23631
|
const files = [];
|
|
23632
|
+
const builtinToolCalls = [];
|
|
23601
23633
|
for (const block of contentBlocks) {
|
|
23602
23634
|
if (block.type === "text") {
|
|
23603
23635
|
content.push({ type: "text", text: block.text });
|
|
@@ -23612,6 +23644,11 @@ var AnthropicAdapter = class {
|
|
|
23612
23644
|
};
|
|
23613
23645
|
content.push(tc);
|
|
23614
23646
|
toolCalls.push(tc);
|
|
23647
|
+
} else if (block.type === "server_tool_use") {
|
|
23648
|
+
builtinToolCalls.push({
|
|
23649
|
+
tool: unifiedBuiltinTool(block.name),
|
|
23650
|
+
...typeof block.id === "string" ? { id: block.id } : {}
|
|
23651
|
+
});
|
|
23615
23652
|
} else {
|
|
23616
23653
|
files.push(...filesFromCodeExecBlock(block));
|
|
23617
23654
|
}
|
|
@@ -23629,6 +23666,7 @@ var AnthropicAdapter = class {
|
|
|
23629
23666
|
toolCalls,
|
|
23630
23667
|
media: [],
|
|
23631
23668
|
...files.length ? { files } : {},
|
|
23669
|
+
...builtinToolCalls.length ? { builtinToolCalls } : {},
|
|
23632
23670
|
thinking,
|
|
23633
23671
|
latencyMs,
|
|
23634
23672
|
raw
|
|
@@ -23652,8 +23690,23 @@ var AnthropicAdapter = class {
|
|
|
23652
23690
|
if (block.type === "tool_use") {
|
|
23653
23691
|
return [{ type: "tool_call_start", id: block.id, name: block.name }];
|
|
23654
23692
|
}
|
|
23655
|
-
const
|
|
23656
|
-
|
|
23693
|
+
const events = [];
|
|
23694
|
+
const blockType = block.type;
|
|
23695
|
+
if (blockType === "server_tool_use") {
|
|
23696
|
+
events.push({
|
|
23697
|
+
type: "builtin_tool_start",
|
|
23698
|
+
tool: unifiedBuiltinTool(block.name),
|
|
23699
|
+
...typeof block.id === "string" ? { id: block.id } : {}
|
|
23700
|
+
});
|
|
23701
|
+
} else if (blockType?.endsWith("_tool_result")) {
|
|
23702
|
+
events.push({
|
|
23703
|
+
type: "builtin_tool_end",
|
|
23704
|
+
tool: unifiedBuiltinTool(blockType),
|
|
23705
|
+
...typeof block.tool_use_id === "string" ? { id: block.tool_use_id } : {}
|
|
23706
|
+
});
|
|
23707
|
+
}
|
|
23708
|
+
for (const file of filesFromCodeExecBlock(block)) events.push({ type: "file", file });
|
|
23709
|
+
return events;
|
|
23657
23710
|
}
|
|
23658
23711
|
if (type === "content_block_stop") {
|
|
23659
23712
|
}
|
|
@@ -24189,6 +24242,9 @@ var GoogleAdapter = class {
|
|
|
24189
24242
|
candidate.finishReason,
|
|
24190
24243
|
{ MAX_TOKENS: "length", SAFETY: "content_filter" }
|
|
24191
24244
|
);
|
|
24245
|
+
const builtinToolCalls = [];
|
|
24246
|
+
if (hasCodeExec) builtinToolCalls.push({ tool: "code_interpreter" });
|
|
24247
|
+
if (candidate.groundingMetadata) builtinToolCalls.push({ tool: "web_search" });
|
|
24192
24248
|
return {
|
|
24193
24249
|
id: crypto.randomUUID(),
|
|
24194
24250
|
// Google doesn't return a response ID in generateContent
|
|
@@ -24201,6 +24257,7 @@ var GoogleAdapter = class {
|
|
|
24201
24257
|
thinking,
|
|
24202
24258
|
media,
|
|
24203
24259
|
...files.length ? { files } : {},
|
|
24260
|
+
...builtinToolCalls.length ? { builtinToolCalls } : {},
|
|
24204
24261
|
latencyMs,
|
|
24205
24262
|
raw
|
|
24206
24263
|
};
|
|
@@ -24238,6 +24295,8 @@ var GoogleAdapter = class {
|
|
|
24238
24295
|
if (part.text !== void 0 && !part.thought)
|
|
24239
24296
|
events.push({ type: "text", text: part.text });
|
|
24240
24297
|
if (part.thought && part.text) events.push({ type: "thinking", text: part.text });
|
|
24298
|
+
if (part.executableCode) events.push({ type: "builtin_tool_start", tool: "code_interpreter" });
|
|
24299
|
+
if (part.codeExecutionResult) events.push({ type: "builtin_tool_end", tool: "code_interpreter" });
|
|
24241
24300
|
if (part.inlineData) {
|
|
24242
24301
|
const inline = part.inlineData;
|
|
24243
24302
|
const mime = inline.mimeType;
|
|
@@ -24268,6 +24327,11 @@ var GoogleAdapter = class {
|
|
|
24268
24327
|
events.push({ type: "tool_call_end", id: "" });
|
|
24269
24328
|
}
|
|
24270
24329
|
}
|
|
24330
|
+
if (candidate.groundingMetadata && !state.webSearchEmitted) {
|
|
24331
|
+
state.webSearchEmitted = true;
|
|
24332
|
+
events.push({ type: "builtin_tool_start", tool: "web_search" });
|
|
24333
|
+
events.push({ type: "builtin_tool_end", tool: "web_search" });
|
|
24334
|
+
}
|
|
24271
24335
|
const fr = candidate.finishReason;
|
|
24272
24336
|
if (fr)
|
|
24273
24337
|
events.push({
|
|
@@ -26034,6 +26098,15 @@ function filenameForMime(mimeType) {
|
|
|
26034
26098
|
if (mimeType.startsWith("image/")) return `file.${mimeType.slice("image/".length)}`;
|
|
26035
26099
|
return "file.bin";
|
|
26036
26100
|
}
|
|
26101
|
+
var RESPONSES_BUILTIN_ITEMS = /* @__PURE__ */ new Set(["web_search_call", "code_interpreter_call"]);
|
|
26102
|
+
function builtinCallFromResponsesItem(item) {
|
|
26103
|
+
const type = item.type;
|
|
26104
|
+
if (!RESPONSES_BUILTIN_ITEMS.has(type)) return null;
|
|
26105
|
+
return {
|
|
26106
|
+
tool: unifiedBuiltinTool(type),
|
|
26107
|
+
...typeof item.id === "string" ? { id: item.id } : {}
|
|
26108
|
+
};
|
|
26109
|
+
}
|
|
26037
26110
|
function filesFromResponsesOutputItem(item) {
|
|
26038
26111
|
const files = [];
|
|
26039
26112
|
const type = item.type;
|
|
@@ -26237,11 +26310,14 @@ var OpenAIResponsesAdapter = class {
|
|
|
26237
26310
|
const toolCalls = [];
|
|
26238
26311
|
const media = [];
|
|
26239
26312
|
const files = [];
|
|
26313
|
+
const builtinToolCalls = [];
|
|
26240
26314
|
let thinking = null;
|
|
26241
26315
|
let text = "";
|
|
26242
26316
|
for (const item of output) {
|
|
26243
26317
|
const type = item.type;
|
|
26244
26318
|
files.push(...this.filesFromOutputItem(item));
|
|
26319
|
+
const builtinCall = builtinCallFromResponsesItem(item);
|
|
26320
|
+
if (builtinCall) builtinToolCalls.push(builtinCall);
|
|
26245
26321
|
if (type === "message") {
|
|
26246
26322
|
const itemContent = item.content ?? [];
|
|
26247
26323
|
for (const c of itemContent) {
|
|
@@ -26302,6 +26378,7 @@ var OpenAIResponsesAdapter = class {
|
|
|
26302
26378
|
thinking,
|
|
26303
26379
|
media,
|
|
26304
26380
|
...files.length ? { files } : {},
|
|
26381
|
+
...builtinToolCalls.length ? { builtinToolCalls } : {},
|
|
26305
26382
|
...moderation ? { moderation } : {},
|
|
26306
26383
|
latencyMs,
|
|
26307
26384
|
raw
|
|
@@ -26333,6 +26410,10 @@ var OpenAIResponsesAdapter = class {
|
|
|
26333
26410
|
if (item?.type === "image_generation_call") {
|
|
26334
26411
|
events.push({ type: "media_start", mediaType: "image", mimeType: "image/png" });
|
|
26335
26412
|
}
|
|
26413
|
+
const builtin = builtinCallFromResponsesItem(item ?? {});
|
|
26414
|
+
if (builtin) {
|
|
26415
|
+
events.push({ type: "builtin_tool_start", tool: builtin.tool, ...builtin.id ? { id: builtin.id } : {} });
|
|
26416
|
+
}
|
|
26336
26417
|
}
|
|
26337
26418
|
if (type === "response.image_generation_call.partial_image") {
|
|
26338
26419
|
events.push({
|
|
@@ -26344,6 +26425,10 @@ var OpenAIResponsesAdapter = class {
|
|
|
26344
26425
|
if (type === "response.output_item.done") {
|
|
26345
26426
|
const item = data.item;
|
|
26346
26427
|
for (const file of this.filesFromOutputItem(item)) events.push({ type: "file", file });
|
|
26428
|
+
const builtin = builtinCallFromResponsesItem(item ?? {});
|
|
26429
|
+
if (builtin) {
|
|
26430
|
+
events.push({ type: "builtin_tool_end", tool: builtin.tool, ...builtin.id ? { id: builtin.id } : {} });
|
|
26431
|
+
}
|
|
26347
26432
|
if (item?.type === "function_call") {
|
|
26348
26433
|
events.push({ type: "tool_call_end", id: item.call_id ?? "" });
|
|
26349
26434
|
}
|
|
@@ -26442,6 +26527,9 @@ function filenameFor(mimeType) {
|
|
|
26442
26527
|
}
|
|
26443
26528
|
|
|
26444
26529
|
// src/llm/providers/openrouter/completions.ts
|
|
26530
|
+
function hasUrlCitation(annotations) {
|
|
26531
|
+
return Array.isArray(annotations) && annotations.some((a) => a?.type === "url_citation");
|
|
26532
|
+
}
|
|
26445
26533
|
var OpenRouterAdapter = class extends OpenAIAdapter {
|
|
26446
26534
|
name = "openrouter";
|
|
26447
26535
|
constructor(config) {
|
|
@@ -26470,6 +26558,35 @@ var OpenRouterAdapter = class extends OpenAIAdapter {
|
|
|
26470
26558
|
}
|
|
26471
26559
|
return result;
|
|
26472
26560
|
}
|
|
26561
|
+
parseResponse(raw, latencyMs) {
|
|
26562
|
+
const result = super.parseResponse(raw, latencyMs);
|
|
26563
|
+
const choices = raw.choices;
|
|
26564
|
+
const annotations = choices?.[0]?.message?.annotations;
|
|
26565
|
+
if (hasUrlCitation(annotations)) {
|
|
26566
|
+
result.builtinToolCalls = [...result.builtinToolCalls ?? [], { tool: "web_search" }];
|
|
26567
|
+
}
|
|
26568
|
+
return result;
|
|
26569
|
+
}
|
|
26570
|
+
/** Stateful — emit a single `web_search` builtin-tool pair the first time
|
|
26571
|
+
* `url_citation` annotations appear in the stream (the `:online` search signal). */
|
|
26572
|
+
createStreamParser() {
|
|
26573
|
+
let webSearchEmitted = false;
|
|
26574
|
+
return (event) => {
|
|
26575
|
+
const events = this.parseStreamEvent(event);
|
|
26576
|
+
if (!webSearchEmitted) {
|
|
26577
|
+
const choice = JSON.parse(event.data).choices?.[0];
|
|
26578
|
+
const annotations = choice?.delta?.annotations ?? choice?.message?.annotations;
|
|
26579
|
+
if (hasUrlCitation(annotations)) {
|
|
26580
|
+
webSearchEmitted = true;
|
|
26581
|
+
events.push(
|
|
26582
|
+
{ type: "builtin_tool_start", tool: "web_search" },
|
|
26583
|
+
{ type: "builtin_tool_end", tool: "web_search" }
|
|
26584
|
+
);
|
|
26585
|
+
}
|
|
26586
|
+
}
|
|
26587
|
+
return events;
|
|
26588
|
+
};
|
|
26589
|
+
}
|
|
26473
26590
|
};
|
|
26474
26591
|
|
|
26475
26592
|
// src/llm/providers/openrouter/responses.ts
|
|
@@ -28338,6 +28455,7 @@ var AgentLoop = class _AgentLoop {
|
|
|
28338
28455
|
// Propagate hosted-tool file outputs + inline-moderation result from the final
|
|
28339
28456
|
// LLM response (e.g. code-execution files produced during the run).
|
|
28340
28457
|
...lastResponse?.files ? { files: lastResponse.files } : {},
|
|
28458
|
+
...lastResponse?.builtinToolCalls ? { builtinToolCalls: lastResponse.builtinToolCalls } : {},
|
|
28341
28459
|
...lastResponse?.moderation ? { moderation: lastResponse.moderation } : {},
|
|
28342
28460
|
latencyMs: performance.now() - startPerf,
|
|
28343
28461
|
raw: lastResponse?.raw ?? null
|
|
@@ -28534,6 +28652,7 @@ var AgentLoop = class _AgentLoop {
|
|
|
28534
28652
|
thinking: lastResponse?.thinking ?? null,
|
|
28535
28653
|
media: [],
|
|
28536
28654
|
...lastResponse?.files ? { files: lastResponse.files } : {},
|
|
28655
|
+
...lastResponse?.builtinToolCalls ? { builtinToolCalls: lastResponse.builtinToolCalls } : {},
|
|
28537
28656
|
...lastResponse?.moderation ? { moderation: lastResponse.moderation } : {},
|
|
28538
28657
|
latencyMs: performance.now() - startPerf,
|
|
28539
28658
|
raw: null
|
package/dist/index.d.ts
CHANGED
|
@@ -48,7 +48,7 @@ export type { Role, MessageOrigin, ContentPart, TextPart, ImagePart, DocumentPar
|
|
|
48
48
|
export { isFunctionTool, isBuiltinTool } from './llm/types/tools';
|
|
49
49
|
export type { FunctionTool, BuiltinTool, McpToolParams, Tool, ToolChoice, JsonSchema } from './llm/types/tools';
|
|
50
50
|
export { emptyUsage } from './llm/types/response';
|
|
51
|
-
export type { CompletionResponse, FileOutput, FinishReason, Usage } from './llm/types/response';
|
|
51
|
+
export type { BuiltinToolCall, CompletionResponse, FileOutput, FinishReason, Usage, } from './llm/types/response';
|
|
52
52
|
export type { FileStream, RetrievedFile } from './llm/files/retrieve';
|
|
53
53
|
export type { NormalizedRequest, ReasoningContext, ThinkingConfig, CacheConfig } from './llm/types/request';
|
|
54
54
|
export type { MediaStreamType, StreamEvent } from './llm/types/stream';
|
package/dist/index.js
CHANGED
|
@@ -22398,7 +22398,11 @@ function contentRequest(ctx, file) {
|
|
|
22398
22398
|
"x-api-key": ctx.apiKey,
|
|
22399
22399
|
"anthropic-version": "2023-06-01",
|
|
22400
22400
|
"anthropic-beta": "files-api-2025-04-14",
|
|
22401
|
-
accept: "application/binary"
|
|
22401
|
+
accept: "application/binary",
|
|
22402
|
+
// Enable CORS for direct browser requests (same header the completion
|
|
22403
|
+
// adapter sets); harmless on Node/Bun. Without it the browser blocks the
|
|
22404
|
+
// file download by CORS.
|
|
22405
|
+
...isBrowser() ? { "anthropic-dangerous-direct-browser-access": "true" } : {}
|
|
22402
22406
|
}
|
|
22403
22407
|
};
|
|
22404
22408
|
}
|
|
@@ -22419,7 +22423,11 @@ function providerAuth(ctx, url) {
|
|
|
22419
22423
|
const base = ctx.baseURL ?? DEFAULT_BASE[ctx.provider];
|
|
22420
22424
|
if (!url.startsWith(base)) return {};
|
|
22421
22425
|
if (ctx.provider === "anthropic") {
|
|
22422
|
-
return {
|
|
22426
|
+
return {
|
|
22427
|
+
"x-api-key": ctx.apiKey,
|
|
22428
|
+
"anthropic-version": "2023-06-01",
|
|
22429
|
+
...isBrowser() ? { "anthropic-dangerous-direct-browser-access": "true" } : {}
|
|
22430
|
+
};
|
|
22423
22431
|
}
|
|
22424
22432
|
if (ctx.provider === "google") return { "x-goog-api-key": ctx.apiKey };
|
|
22425
22433
|
return { authorization: `Bearer ${ctx.apiKey}` };
|
|
@@ -23009,6 +23017,7 @@ var LLMClient = class {
|
|
|
23009
23017
|
let finishReason = "stop";
|
|
23010
23018
|
let moderationReport;
|
|
23011
23019
|
const files = [];
|
|
23020
|
+
const builtinToolCalls = [];
|
|
23012
23021
|
const fetchStream = this.fetchStreamFn;
|
|
23013
23022
|
const queueName = this.queueName;
|
|
23014
23023
|
const priority = this.priority;
|
|
@@ -23060,6 +23069,9 @@ var LLMClient = class {
|
|
|
23060
23069
|
case "file":
|
|
23061
23070
|
files.push(event.file);
|
|
23062
23071
|
break;
|
|
23072
|
+
case "builtin_tool_start":
|
|
23073
|
+
builtinToolCalls.push({ tool: event.tool, ...event.id ? { id: event.id } : {} });
|
|
23074
|
+
break;
|
|
23063
23075
|
case "moderation":
|
|
23064
23076
|
moderationReport = this.mergeModeration(
|
|
23065
23077
|
moderationReport,
|
|
@@ -23083,6 +23095,7 @@ var LLMClient = class {
|
|
|
23083
23095
|
thinking: thinking || null,
|
|
23084
23096
|
media: [],
|
|
23085
23097
|
...files.length ? { files } : {},
|
|
23098
|
+
...builtinToolCalls.length ? { builtinToolCalls } : {},
|
|
23086
23099
|
...moderationReport ? { moderation: moderationReport } : {},
|
|
23087
23100
|
latencyMs: performance.now() - start,
|
|
23088
23101
|
raw: null
|
|
@@ -23316,6 +23329,24 @@ var AnthropicFileAdapter = class {
|
|
|
23316
23329
|
}
|
|
23317
23330
|
};
|
|
23318
23331
|
|
|
23332
|
+
// src/llm/providers/_shared/builtin-tools.ts
|
|
23333
|
+
var NATIVE_TO_UNIFIED = {
|
|
23334
|
+
// OpenAI / xAI Responses output-item types
|
|
23335
|
+
web_search_call: "web_search",
|
|
23336
|
+
code_interpreter_call: "code_interpreter",
|
|
23337
|
+
// Anthropic server_tool_use names
|
|
23338
|
+
web_search: "web_search",
|
|
23339
|
+
code_execution: "code_interpreter",
|
|
23340
|
+
bash_code_execution: "code_interpreter",
|
|
23341
|
+
// Anthropic *_tool_result block types
|
|
23342
|
+
web_search_tool_result: "web_search",
|
|
23343
|
+
code_execution_tool_result: "code_interpreter",
|
|
23344
|
+
bash_code_execution_tool_result: "code_interpreter"
|
|
23345
|
+
};
|
|
23346
|
+
function unifiedBuiltinTool(native) {
|
|
23347
|
+
return NATIVE_TO_UNIFIED[native] ?? native.replace(/_call$/, "").replace(/_tool_result$/, "");
|
|
23348
|
+
}
|
|
23349
|
+
|
|
23319
23350
|
// src/llm/providers/_shared/constants.ts
|
|
23320
23351
|
var AUDIO_PCM16_SAMPLE_RATE_HZ = 24e3;
|
|
23321
23352
|
var DEFAULT_MAX_TOKENS = 4096;
|
|
@@ -23525,6 +23556,7 @@ var AnthropicAdapter = class {
|
|
|
23525
23556
|
let thinking = null;
|
|
23526
23557
|
const toolCalls = [];
|
|
23527
23558
|
const files = [];
|
|
23559
|
+
const builtinToolCalls = [];
|
|
23528
23560
|
for (const block of contentBlocks) {
|
|
23529
23561
|
if (block.type === "text") {
|
|
23530
23562
|
content.push({ type: "text", text: block.text });
|
|
@@ -23539,6 +23571,11 @@ var AnthropicAdapter = class {
|
|
|
23539
23571
|
};
|
|
23540
23572
|
content.push(tc);
|
|
23541
23573
|
toolCalls.push(tc);
|
|
23574
|
+
} else if (block.type === "server_tool_use") {
|
|
23575
|
+
builtinToolCalls.push({
|
|
23576
|
+
tool: unifiedBuiltinTool(block.name),
|
|
23577
|
+
...typeof block.id === "string" ? { id: block.id } : {}
|
|
23578
|
+
});
|
|
23542
23579
|
} else {
|
|
23543
23580
|
files.push(...filesFromCodeExecBlock(block));
|
|
23544
23581
|
}
|
|
@@ -23556,6 +23593,7 @@ var AnthropicAdapter = class {
|
|
|
23556
23593
|
toolCalls,
|
|
23557
23594
|
media: [],
|
|
23558
23595
|
...files.length ? { files } : {},
|
|
23596
|
+
...builtinToolCalls.length ? { builtinToolCalls } : {},
|
|
23559
23597
|
thinking,
|
|
23560
23598
|
latencyMs,
|
|
23561
23599
|
raw
|
|
@@ -23579,8 +23617,23 @@ var AnthropicAdapter = class {
|
|
|
23579
23617
|
if (block.type === "tool_use") {
|
|
23580
23618
|
return [{ type: "tool_call_start", id: block.id, name: block.name }];
|
|
23581
23619
|
}
|
|
23582
|
-
const
|
|
23583
|
-
|
|
23620
|
+
const events = [];
|
|
23621
|
+
const blockType = block.type;
|
|
23622
|
+
if (blockType === "server_tool_use") {
|
|
23623
|
+
events.push({
|
|
23624
|
+
type: "builtin_tool_start",
|
|
23625
|
+
tool: unifiedBuiltinTool(block.name),
|
|
23626
|
+
...typeof block.id === "string" ? { id: block.id } : {}
|
|
23627
|
+
});
|
|
23628
|
+
} else if (blockType?.endsWith("_tool_result")) {
|
|
23629
|
+
events.push({
|
|
23630
|
+
type: "builtin_tool_end",
|
|
23631
|
+
tool: unifiedBuiltinTool(blockType),
|
|
23632
|
+
...typeof block.tool_use_id === "string" ? { id: block.tool_use_id } : {}
|
|
23633
|
+
});
|
|
23634
|
+
}
|
|
23635
|
+
for (const file of filesFromCodeExecBlock(block)) events.push({ type: "file", file });
|
|
23636
|
+
return events;
|
|
23584
23637
|
}
|
|
23585
23638
|
if (type === "content_block_stop") {
|
|
23586
23639
|
}
|
|
@@ -24116,6 +24169,9 @@ var GoogleAdapter = class {
|
|
|
24116
24169
|
candidate.finishReason,
|
|
24117
24170
|
{ MAX_TOKENS: "length", SAFETY: "content_filter" }
|
|
24118
24171
|
);
|
|
24172
|
+
const builtinToolCalls = [];
|
|
24173
|
+
if (hasCodeExec) builtinToolCalls.push({ tool: "code_interpreter" });
|
|
24174
|
+
if (candidate.groundingMetadata) builtinToolCalls.push({ tool: "web_search" });
|
|
24119
24175
|
return {
|
|
24120
24176
|
id: crypto.randomUUID(),
|
|
24121
24177
|
// Google doesn't return a response ID in generateContent
|
|
@@ -24128,6 +24184,7 @@ var GoogleAdapter = class {
|
|
|
24128
24184
|
thinking,
|
|
24129
24185
|
media,
|
|
24130
24186
|
...files.length ? { files } : {},
|
|
24187
|
+
...builtinToolCalls.length ? { builtinToolCalls } : {},
|
|
24131
24188
|
latencyMs,
|
|
24132
24189
|
raw
|
|
24133
24190
|
};
|
|
@@ -24165,6 +24222,8 @@ var GoogleAdapter = class {
|
|
|
24165
24222
|
if (part.text !== void 0 && !part.thought)
|
|
24166
24223
|
events.push({ type: "text", text: part.text });
|
|
24167
24224
|
if (part.thought && part.text) events.push({ type: "thinking", text: part.text });
|
|
24225
|
+
if (part.executableCode) events.push({ type: "builtin_tool_start", tool: "code_interpreter" });
|
|
24226
|
+
if (part.codeExecutionResult) events.push({ type: "builtin_tool_end", tool: "code_interpreter" });
|
|
24168
24227
|
if (part.inlineData) {
|
|
24169
24228
|
const inline = part.inlineData;
|
|
24170
24229
|
const mime = inline.mimeType;
|
|
@@ -24195,6 +24254,11 @@ var GoogleAdapter = class {
|
|
|
24195
24254
|
events.push({ type: "tool_call_end", id: "" });
|
|
24196
24255
|
}
|
|
24197
24256
|
}
|
|
24257
|
+
if (candidate.groundingMetadata && !state.webSearchEmitted) {
|
|
24258
|
+
state.webSearchEmitted = true;
|
|
24259
|
+
events.push({ type: "builtin_tool_start", tool: "web_search" });
|
|
24260
|
+
events.push({ type: "builtin_tool_end", tool: "web_search" });
|
|
24261
|
+
}
|
|
24198
24262
|
const fr = candidate.finishReason;
|
|
24199
24263
|
if (fr)
|
|
24200
24264
|
events.push({
|
|
@@ -25961,6 +26025,15 @@ function filenameForMime(mimeType) {
|
|
|
25961
26025
|
if (mimeType.startsWith("image/")) return `file.${mimeType.slice("image/".length)}`;
|
|
25962
26026
|
return "file.bin";
|
|
25963
26027
|
}
|
|
26028
|
+
var RESPONSES_BUILTIN_ITEMS = /* @__PURE__ */ new Set(["web_search_call", "code_interpreter_call"]);
|
|
26029
|
+
function builtinCallFromResponsesItem(item) {
|
|
26030
|
+
const type = item.type;
|
|
26031
|
+
if (!RESPONSES_BUILTIN_ITEMS.has(type)) return null;
|
|
26032
|
+
return {
|
|
26033
|
+
tool: unifiedBuiltinTool(type),
|
|
26034
|
+
...typeof item.id === "string" ? { id: item.id } : {}
|
|
26035
|
+
};
|
|
26036
|
+
}
|
|
25964
26037
|
function filesFromResponsesOutputItem(item) {
|
|
25965
26038
|
const files = [];
|
|
25966
26039
|
const type = item.type;
|
|
@@ -26164,11 +26237,14 @@ var OpenAIResponsesAdapter = class {
|
|
|
26164
26237
|
const toolCalls = [];
|
|
26165
26238
|
const media = [];
|
|
26166
26239
|
const files = [];
|
|
26240
|
+
const builtinToolCalls = [];
|
|
26167
26241
|
let thinking = null;
|
|
26168
26242
|
let text = "";
|
|
26169
26243
|
for (const item of output) {
|
|
26170
26244
|
const type = item.type;
|
|
26171
26245
|
files.push(...this.filesFromOutputItem(item));
|
|
26246
|
+
const builtinCall = builtinCallFromResponsesItem(item);
|
|
26247
|
+
if (builtinCall) builtinToolCalls.push(builtinCall);
|
|
26172
26248
|
if (type === "message") {
|
|
26173
26249
|
const itemContent = item.content ?? [];
|
|
26174
26250
|
for (const c of itemContent) {
|
|
@@ -26229,6 +26305,7 @@ var OpenAIResponsesAdapter = class {
|
|
|
26229
26305
|
thinking,
|
|
26230
26306
|
media,
|
|
26231
26307
|
...files.length ? { files } : {},
|
|
26308
|
+
...builtinToolCalls.length ? { builtinToolCalls } : {},
|
|
26232
26309
|
...moderation ? { moderation } : {},
|
|
26233
26310
|
latencyMs,
|
|
26234
26311
|
raw
|
|
@@ -26260,6 +26337,10 @@ var OpenAIResponsesAdapter = class {
|
|
|
26260
26337
|
if (item?.type === "image_generation_call") {
|
|
26261
26338
|
events.push({ type: "media_start", mediaType: "image", mimeType: "image/png" });
|
|
26262
26339
|
}
|
|
26340
|
+
const builtin = builtinCallFromResponsesItem(item ?? {});
|
|
26341
|
+
if (builtin) {
|
|
26342
|
+
events.push({ type: "builtin_tool_start", tool: builtin.tool, ...builtin.id ? { id: builtin.id } : {} });
|
|
26343
|
+
}
|
|
26263
26344
|
}
|
|
26264
26345
|
if (type === "response.image_generation_call.partial_image") {
|
|
26265
26346
|
events.push({
|
|
@@ -26271,6 +26352,10 @@ var OpenAIResponsesAdapter = class {
|
|
|
26271
26352
|
if (type === "response.output_item.done") {
|
|
26272
26353
|
const item = data.item;
|
|
26273
26354
|
for (const file of this.filesFromOutputItem(item)) events.push({ type: "file", file });
|
|
26355
|
+
const builtin = builtinCallFromResponsesItem(item ?? {});
|
|
26356
|
+
if (builtin) {
|
|
26357
|
+
events.push({ type: "builtin_tool_end", tool: builtin.tool, ...builtin.id ? { id: builtin.id } : {} });
|
|
26358
|
+
}
|
|
26274
26359
|
if (item?.type === "function_call") {
|
|
26275
26360
|
events.push({ type: "tool_call_end", id: item.call_id ?? "" });
|
|
26276
26361
|
}
|
|
@@ -26369,6 +26454,9 @@ function filenameFor(mimeType) {
|
|
|
26369
26454
|
}
|
|
26370
26455
|
|
|
26371
26456
|
// src/llm/providers/openrouter/completions.ts
|
|
26457
|
+
function hasUrlCitation(annotations) {
|
|
26458
|
+
return Array.isArray(annotations) && annotations.some((a) => a?.type === "url_citation");
|
|
26459
|
+
}
|
|
26372
26460
|
var OpenRouterAdapter = class extends OpenAIAdapter {
|
|
26373
26461
|
name = "openrouter";
|
|
26374
26462
|
constructor(config) {
|
|
@@ -26397,6 +26485,35 @@ var OpenRouterAdapter = class extends OpenAIAdapter {
|
|
|
26397
26485
|
}
|
|
26398
26486
|
return result;
|
|
26399
26487
|
}
|
|
26488
|
+
parseResponse(raw, latencyMs) {
|
|
26489
|
+
const result = super.parseResponse(raw, latencyMs);
|
|
26490
|
+
const choices = raw.choices;
|
|
26491
|
+
const annotations = choices?.[0]?.message?.annotations;
|
|
26492
|
+
if (hasUrlCitation(annotations)) {
|
|
26493
|
+
result.builtinToolCalls = [...result.builtinToolCalls ?? [], { tool: "web_search" }];
|
|
26494
|
+
}
|
|
26495
|
+
return result;
|
|
26496
|
+
}
|
|
26497
|
+
/** Stateful — emit a single `web_search` builtin-tool pair the first time
|
|
26498
|
+
* `url_citation` annotations appear in the stream (the `:online` search signal). */
|
|
26499
|
+
createStreamParser() {
|
|
26500
|
+
let webSearchEmitted = false;
|
|
26501
|
+
return (event) => {
|
|
26502
|
+
const events = this.parseStreamEvent(event);
|
|
26503
|
+
if (!webSearchEmitted) {
|
|
26504
|
+
const choice = JSON.parse(event.data).choices?.[0];
|
|
26505
|
+
const annotations = choice?.delta?.annotations ?? choice?.message?.annotations;
|
|
26506
|
+
if (hasUrlCitation(annotations)) {
|
|
26507
|
+
webSearchEmitted = true;
|
|
26508
|
+
events.push(
|
|
26509
|
+
{ type: "builtin_tool_start", tool: "web_search" },
|
|
26510
|
+
{ type: "builtin_tool_end", tool: "web_search" }
|
|
26511
|
+
);
|
|
26512
|
+
}
|
|
26513
|
+
}
|
|
26514
|
+
return events;
|
|
26515
|
+
};
|
|
26516
|
+
}
|
|
26400
26517
|
};
|
|
26401
26518
|
|
|
26402
26519
|
// src/llm/providers/openrouter/responses.ts
|
|
@@ -28265,6 +28382,7 @@ var AgentLoop = class _AgentLoop {
|
|
|
28265
28382
|
// Propagate hosted-tool file outputs + inline-moderation result from the final
|
|
28266
28383
|
// LLM response (e.g. code-execution files produced during the run).
|
|
28267
28384
|
...lastResponse?.files ? { files: lastResponse.files } : {},
|
|
28385
|
+
...lastResponse?.builtinToolCalls ? { builtinToolCalls: lastResponse.builtinToolCalls } : {},
|
|
28268
28386
|
...lastResponse?.moderation ? { moderation: lastResponse.moderation } : {},
|
|
28269
28387
|
latencyMs: performance.now() - startPerf,
|
|
28270
28388
|
raw: lastResponse?.raw ?? null
|
|
@@ -28461,6 +28579,7 @@ var AgentLoop = class _AgentLoop {
|
|
|
28461
28579
|
thinking: lastResponse?.thinking ?? null,
|
|
28462
28580
|
media: [],
|
|
28463
28581
|
...lastResponse?.files ? { files: lastResponse.files } : {},
|
|
28582
|
+
...lastResponse?.builtinToolCalls ? { builtinToolCalls: lastResponse.builtinToolCalls } : {},
|
|
28464
28583
|
...lastResponse?.moderation ? { moderation: lastResponse.moderation } : {},
|
|
28465
28584
|
latencyMs: performance.now() - startPerf,
|
|
28466
28585
|
raw: null
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Normalize a provider-native builtin-tool marker (output-item type, server-tool
|
|
2
|
+
* name, or result-block type) to the unified tool name used across the SDK
|
|
3
|
+
* (`web_search` | `code_interpreter` | …). Unknown values fall back to a stripped
|
|
4
|
+
* form so a new provider tool still yields a sensible name. */
|
|
5
|
+
export declare function unifiedBuiltinTool(native: string): string;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/** OpenRouter provider adapter — OpenAI-compatible with extensions. */
|
|
2
|
+
import type { SSEEvent } from '../../../network/types';
|
|
2
3
|
import type { ProviderAdapter, ProviderHttpRequest } from '../../types/provider';
|
|
3
4
|
import type { NormalizedRequest } from '../../types/request';
|
|
5
|
+
import type { CompletionResponse } from '../../types/response';
|
|
6
|
+
import type { StreamEvent } from '../../types/stream';
|
|
4
7
|
import { OpenAIAdapter } from '../openai/completions';
|
|
5
8
|
export interface OpenRouterAdapterConfig {
|
|
6
9
|
apiKey: string;
|
|
@@ -12,4 +15,8 @@ export declare class OpenRouterAdapter extends OpenAIAdapter {
|
|
|
12
15
|
baseURL(): string;
|
|
13
16
|
completionPath(): string;
|
|
14
17
|
buildRequest(req: NormalizedRequest): ProviderHttpRequest;
|
|
18
|
+
parseResponse(raw: unknown, latencyMs: number): CompletionResponse;
|
|
19
|
+
/** Stateful — emit a single `web_search` builtin-tool pair the first time
|
|
20
|
+
* `url_citation` annotations appear in the stream (the `:online` search signal). */
|
|
21
|
+
createStreamParser(): (event: SSEEvent) => StreamEvent[];
|
|
15
22
|
}
|
|
@@ -16,6 +16,10 @@ export interface CompletionResponse {
|
|
|
16
16
|
* etc. Unified across providers. When only `id` is set, fetch the bytes via the
|
|
17
17
|
* provider's files API; some providers return `data` inline. Absent when none. */
|
|
18
18
|
files?: FileOutput[];
|
|
19
|
+
/** Hosted (provider-run) builtin tools the model invoked this turn — e.g. a web
|
|
20
|
+
* search or code-execution run. A durable trail of what was called (the provider
|
|
21
|
+
* ran them server-side; nothing for the client to execute). Absent when none. */
|
|
22
|
+
builtinToolCalls?: BuiltinToolCall[];
|
|
19
23
|
/** Inline-moderation outcome, when the `moderation` request option was used.
|
|
20
24
|
* Report-only: present for observability; it never blocks the call. Absent when
|
|
21
25
|
* moderation was not requested. */
|
|
@@ -44,6 +48,14 @@ export interface FileOutput {
|
|
|
44
48
|
* Absent for providers that don't need extra context. */
|
|
45
49
|
ref?: Record<string, unknown>;
|
|
46
50
|
}
|
|
51
|
+
/** A hosted builtin tool the model invoked (provider-run). */
|
|
52
|
+
export interface BuiltinToolCall {
|
|
53
|
+
/** Unified tool name: `'web_search'` | `'code_interpreter'` | … (normalized from
|
|
54
|
+
* each provider's native name — e.g. Anthropic `code_execution`). */
|
|
55
|
+
tool: string;
|
|
56
|
+
/** Provider call id, when available (correlates the stream start/end events). */
|
|
57
|
+
id?: string;
|
|
58
|
+
}
|
|
47
59
|
export type FinishReason = 'stop' | 'tool_use' | 'length' | 'content_filter' | 'error';
|
|
48
60
|
export interface Usage {
|
|
49
61
|
inputTokens: number;
|
|
@@ -49,6 +49,23 @@ export type StreamEvent = {
|
|
|
49
49
|
type: 'file';
|
|
50
50
|
file: FileOutput;
|
|
51
51
|
}
|
|
52
|
+
/** A hosted (provider-run) builtin tool began executing server-side — e.g. the
|
|
53
|
+
* model started a web search or code-execution run. `tool` is the unified name
|
|
54
|
+
* (`'web_search'` | `'code_interpreter'` | …). Informational progress: unlike
|
|
55
|
+
* `tool_call_*` (a function call the CLIENT must run), the provider runs these
|
|
56
|
+
* itself, so there is nothing to execute or return. Also collected onto the
|
|
57
|
+
* streamed final response's `builtinToolCalls`. */
|
|
58
|
+
| {
|
|
59
|
+
type: 'builtin_tool_start';
|
|
60
|
+
tool: string;
|
|
61
|
+
id?: string;
|
|
62
|
+
}
|
|
63
|
+
/** A hosted builtin tool finished executing server-side. */
|
|
64
|
+
| {
|
|
65
|
+
type: 'builtin_tool_end';
|
|
66
|
+
tool: string;
|
|
67
|
+
id?: string;
|
|
68
|
+
}
|
|
52
69
|
/** A moderation result for the input or output. `source` distinguishes a
|
|
53
70
|
* provider-native result from a client-emulated one. Emitted by the moderation
|
|
54
71
|
* option (report-only). */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@combycode/llm-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.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",
|