@combycode/llm-sdk 1.4.0 → 1.5.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.
- package/CHANGELOG.md +14 -0
- package/dist/index.browser.js +113 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +113 -2
- 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,20 @@ All notable changes to `@combycode/llm-sdk` are documented here. The format foll
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.5.0] - 2026-07-06
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **Builtin-tool activity in streams + a durable trail.** Provider-run hosted tools (web search,
|
|
13
|
+
code execution) now surface `{ type: 'builtin_tool_start' }` / `{ type: 'builtin_tool_end' }`
|
|
14
|
+
stream events as they run, and a `response.builtinToolCalls` trail (`BuiltinToolCall[]` —
|
|
15
|
+
`{ tool, id? }` with unified tool names) on both `complete()` and streamed responses (propagated
|
|
16
|
+
through `AgentLoop`). Informational only — unlike `tool_call_*` (a function call the client must
|
|
17
|
+
execute), the provider runs these itself. Normalized across providers (Anthropic `server_tool_use`
|
|
18
|
+
/ `*_tool_result`, OpenAI/xAI `web_search_call` / `code_interpreter_call` output items, Google
|
|
19
|
+
`executableCode` / `codeExecutionResult` parts + `googleSearch` grounding, OpenRouter
|
|
20
|
+
`:online` `url_citation` annotations → `web_search`). Verified live on Anthropic, OpenAI,
|
|
21
|
+
Google, xAI, and OpenRouter. Exports `BuiltinToolCall`.
|
|
22
|
+
|
|
9
23
|
## [1.4.0] - 2026-07-06
|
|
10
24
|
|
|
11
25
|
### Fixed
|
package/dist/index.browser.js
CHANGED
|
@@ -23082,6 +23082,7 @@ var LLMClient = class {
|
|
|
23082
23082
|
let finishReason = "stop";
|
|
23083
23083
|
let moderationReport;
|
|
23084
23084
|
const files = [];
|
|
23085
|
+
const builtinToolCalls = [];
|
|
23085
23086
|
const fetchStream = this.fetchStreamFn;
|
|
23086
23087
|
const queueName = this.queueName;
|
|
23087
23088
|
const priority = this.priority;
|
|
@@ -23133,6 +23134,9 @@ var LLMClient = class {
|
|
|
23133
23134
|
case "file":
|
|
23134
23135
|
files.push(event.file);
|
|
23135
23136
|
break;
|
|
23137
|
+
case "builtin_tool_start":
|
|
23138
|
+
builtinToolCalls.push({ tool: event.tool, ...event.id ? { id: event.id } : {} });
|
|
23139
|
+
break;
|
|
23136
23140
|
case "moderation":
|
|
23137
23141
|
moderationReport = this.mergeModeration(
|
|
23138
23142
|
moderationReport,
|
|
@@ -23156,6 +23160,7 @@ var LLMClient = class {
|
|
|
23156
23160
|
thinking: thinking || null,
|
|
23157
23161
|
media: [],
|
|
23158
23162
|
...files.length ? { files } : {},
|
|
23163
|
+
...builtinToolCalls.length ? { builtinToolCalls } : {},
|
|
23159
23164
|
...moderationReport ? { moderation: moderationReport } : {},
|
|
23160
23165
|
latencyMs: performance.now() - start,
|
|
23161
23166
|
raw: null
|
|
@@ -23389,6 +23394,24 @@ var AnthropicFileAdapter = class {
|
|
|
23389
23394
|
}
|
|
23390
23395
|
};
|
|
23391
23396
|
|
|
23397
|
+
// src/llm/providers/_shared/builtin-tools.ts
|
|
23398
|
+
var NATIVE_TO_UNIFIED = {
|
|
23399
|
+
// OpenAI / xAI Responses output-item types
|
|
23400
|
+
web_search_call: "web_search",
|
|
23401
|
+
code_interpreter_call: "code_interpreter",
|
|
23402
|
+
// Anthropic server_tool_use names
|
|
23403
|
+
web_search: "web_search",
|
|
23404
|
+
code_execution: "code_interpreter",
|
|
23405
|
+
bash_code_execution: "code_interpreter",
|
|
23406
|
+
// Anthropic *_tool_result block types
|
|
23407
|
+
web_search_tool_result: "web_search",
|
|
23408
|
+
code_execution_tool_result: "code_interpreter",
|
|
23409
|
+
bash_code_execution_tool_result: "code_interpreter"
|
|
23410
|
+
};
|
|
23411
|
+
function unifiedBuiltinTool(native) {
|
|
23412
|
+
return NATIVE_TO_UNIFIED[native] ?? native.replace(/_call$/, "").replace(/_tool_result$/, "");
|
|
23413
|
+
}
|
|
23414
|
+
|
|
23392
23415
|
// src/llm/providers/_shared/constants.ts
|
|
23393
23416
|
var AUDIO_PCM16_SAMPLE_RATE_HZ = 24e3;
|
|
23394
23417
|
var DEFAULT_MAX_TOKENS = 4096;
|
|
@@ -23598,6 +23621,7 @@ var AnthropicAdapter = class {
|
|
|
23598
23621
|
let thinking = null;
|
|
23599
23622
|
const toolCalls = [];
|
|
23600
23623
|
const files = [];
|
|
23624
|
+
const builtinToolCalls = [];
|
|
23601
23625
|
for (const block of contentBlocks) {
|
|
23602
23626
|
if (block.type === "text") {
|
|
23603
23627
|
content.push({ type: "text", text: block.text });
|
|
@@ -23612,6 +23636,11 @@ var AnthropicAdapter = class {
|
|
|
23612
23636
|
};
|
|
23613
23637
|
content.push(tc);
|
|
23614
23638
|
toolCalls.push(tc);
|
|
23639
|
+
} else if (block.type === "server_tool_use") {
|
|
23640
|
+
builtinToolCalls.push({
|
|
23641
|
+
tool: unifiedBuiltinTool(block.name),
|
|
23642
|
+
...typeof block.id === "string" ? { id: block.id } : {}
|
|
23643
|
+
});
|
|
23615
23644
|
} else {
|
|
23616
23645
|
files.push(...filesFromCodeExecBlock(block));
|
|
23617
23646
|
}
|
|
@@ -23629,6 +23658,7 @@ var AnthropicAdapter = class {
|
|
|
23629
23658
|
toolCalls,
|
|
23630
23659
|
media: [],
|
|
23631
23660
|
...files.length ? { files } : {},
|
|
23661
|
+
...builtinToolCalls.length ? { builtinToolCalls } : {},
|
|
23632
23662
|
thinking,
|
|
23633
23663
|
latencyMs,
|
|
23634
23664
|
raw
|
|
@@ -23652,8 +23682,23 @@ var AnthropicAdapter = class {
|
|
|
23652
23682
|
if (block.type === "tool_use") {
|
|
23653
23683
|
return [{ type: "tool_call_start", id: block.id, name: block.name }];
|
|
23654
23684
|
}
|
|
23655
|
-
const
|
|
23656
|
-
|
|
23685
|
+
const events = [];
|
|
23686
|
+
const blockType = block.type;
|
|
23687
|
+
if (blockType === "server_tool_use") {
|
|
23688
|
+
events.push({
|
|
23689
|
+
type: "builtin_tool_start",
|
|
23690
|
+
tool: unifiedBuiltinTool(block.name),
|
|
23691
|
+
...typeof block.id === "string" ? { id: block.id } : {}
|
|
23692
|
+
});
|
|
23693
|
+
} else if (blockType?.endsWith("_tool_result")) {
|
|
23694
|
+
events.push({
|
|
23695
|
+
type: "builtin_tool_end",
|
|
23696
|
+
tool: unifiedBuiltinTool(blockType),
|
|
23697
|
+
...typeof block.tool_use_id === "string" ? { id: block.tool_use_id } : {}
|
|
23698
|
+
});
|
|
23699
|
+
}
|
|
23700
|
+
for (const file of filesFromCodeExecBlock(block)) events.push({ type: "file", file });
|
|
23701
|
+
return events;
|
|
23657
23702
|
}
|
|
23658
23703
|
if (type === "content_block_stop") {
|
|
23659
23704
|
}
|
|
@@ -24189,6 +24234,9 @@ var GoogleAdapter = class {
|
|
|
24189
24234
|
candidate.finishReason,
|
|
24190
24235
|
{ MAX_TOKENS: "length", SAFETY: "content_filter" }
|
|
24191
24236
|
);
|
|
24237
|
+
const builtinToolCalls = [];
|
|
24238
|
+
if (hasCodeExec) builtinToolCalls.push({ tool: "code_interpreter" });
|
|
24239
|
+
if (candidate.groundingMetadata) builtinToolCalls.push({ tool: "web_search" });
|
|
24192
24240
|
return {
|
|
24193
24241
|
id: crypto.randomUUID(),
|
|
24194
24242
|
// Google doesn't return a response ID in generateContent
|
|
@@ -24201,6 +24249,7 @@ var GoogleAdapter = class {
|
|
|
24201
24249
|
thinking,
|
|
24202
24250
|
media,
|
|
24203
24251
|
...files.length ? { files } : {},
|
|
24252
|
+
...builtinToolCalls.length ? { builtinToolCalls } : {},
|
|
24204
24253
|
latencyMs,
|
|
24205
24254
|
raw
|
|
24206
24255
|
};
|
|
@@ -24238,6 +24287,8 @@ var GoogleAdapter = class {
|
|
|
24238
24287
|
if (part.text !== void 0 && !part.thought)
|
|
24239
24288
|
events.push({ type: "text", text: part.text });
|
|
24240
24289
|
if (part.thought && part.text) events.push({ type: "thinking", text: part.text });
|
|
24290
|
+
if (part.executableCode) events.push({ type: "builtin_tool_start", tool: "code_interpreter" });
|
|
24291
|
+
if (part.codeExecutionResult) events.push({ type: "builtin_tool_end", tool: "code_interpreter" });
|
|
24241
24292
|
if (part.inlineData) {
|
|
24242
24293
|
const inline = part.inlineData;
|
|
24243
24294
|
const mime = inline.mimeType;
|
|
@@ -24268,6 +24319,11 @@ var GoogleAdapter = class {
|
|
|
24268
24319
|
events.push({ type: "tool_call_end", id: "" });
|
|
24269
24320
|
}
|
|
24270
24321
|
}
|
|
24322
|
+
if (candidate.groundingMetadata && !state.webSearchEmitted) {
|
|
24323
|
+
state.webSearchEmitted = true;
|
|
24324
|
+
events.push({ type: "builtin_tool_start", tool: "web_search" });
|
|
24325
|
+
events.push({ type: "builtin_tool_end", tool: "web_search" });
|
|
24326
|
+
}
|
|
24271
24327
|
const fr = candidate.finishReason;
|
|
24272
24328
|
if (fr)
|
|
24273
24329
|
events.push({
|
|
@@ -26034,6 +26090,15 @@ function filenameForMime(mimeType) {
|
|
|
26034
26090
|
if (mimeType.startsWith("image/")) return `file.${mimeType.slice("image/".length)}`;
|
|
26035
26091
|
return "file.bin";
|
|
26036
26092
|
}
|
|
26093
|
+
var RESPONSES_BUILTIN_ITEMS = /* @__PURE__ */ new Set(["web_search_call", "code_interpreter_call"]);
|
|
26094
|
+
function builtinCallFromResponsesItem(item) {
|
|
26095
|
+
const type = item.type;
|
|
26096
|
+
if (!RESPONSES_BUILTIN_ITEMS.has(type)) return null;
|
|
26097
|
+
return {
|
|
26098
|
+
tool: unifiedBuiltinTool(type),
|
|
26099
|
+
...typeof item.id === "string" ? { id: item.id } : {}
|
|
26100
|
+
};
|
|
26101
|
+
}
|
|
26037
26102
|
function filesFromResponsesOutputItem(item) {
|
|
26038
26103
|
const files = [];
|
|
26039
26104
|
const type = item.type;
|
|
@@ -26237,11 +26302,14 @@ var OpenAIResponsesAdapter = class {
|
|
|
26237
26302
|
const toolCalls = [];
|
|
26238
26303
|
const media = [];
|
|
26239
26304
|
const files = [];
|
|
26305
|
+
const builtinToolCalls = [];
|
|
26240
26306
|
let thinking = null;
|
|
26241
26307
|
let text = "";
|
|
26242
26308
|
for (const item of output) {
|
|
26243
26309
|
const type = item.type;
|
|
26244
26310
|
files.push(...this.filesFromOutputItem(item));
|
|
26311
|
+
const builtinCall = builtinCallFromResponsesItem(item);
|
|
26312
|
+
if (builtinCall) builtinToolCalls.push(builtinCall);
|
|
26245
26313
|
if (type === "message") {
|
|
26246
26314
|
const itemContent = item.content ?? [];
|
|
26247
26315
|
for (const c of itemContent) {
|
|
@@ -26302,6 +26370,7 @@ var OpenAIResponsesAdapter = class {
|
|
|
26302
26370
|
thinking,
|
|
26303
26371
|
media,
|
|
26304
26372
|
...files.length ? { files } : {},
|
|
26373
|
+
...builtinToolCalls.length ? { builtinToolCalls } : {},
|
|
26305
26374
|
...moderation ? { moderation } : {},
|
|
26306
26375
|
latencyMs,
|
|
26307
26376
|
raw
|
|
@@ -26333,6 +26402,10 @@ var OpenAIResponsesAdapter = class {
|
|
|
26333
26402
|
if (item?.type === "image_generation_call") {
|
|
26334
26403
|
events.push({ type: "media_start", mediaType: "image", mimeType: "image/png" });
|
|
26335
26404
|
}
|
|
26405
|
+
const builtin = builtinCallFromResponsesItem(item ?? {});
|
|
26406
|
+
if (builtin) {
|
|
26407
|
+
events.push({ type: "builtin_tool_start", tool: builtin.tool, ...builtin.id ? { id: builtin.id } : {} });
|
|
26408
|
+
}
|
|
26336
26409
|
}
|
|
26337
26410
|
if (type === "response.image_generation_call.partial_image") {
|
|
26338
26411
|
events.push({
|
|
@@ -26344,6 +26417,10 @@ var OpenAIResponsesAdapter = class {
|
|
|
26344
26417
|
if (type === "response.output_item.done") {
|
|
26345
26418
|
const item = data.item;
|
|
26346
26419
|
for (const file of this.filesFromOutputItem(item)) events.push({ type: "file", file });
|
|
26420
|
+
const builtin = builtinCallFromResponsesItem(item ?? {});
|
|
26421
|
+
if (builtin) {
|
|
26422
|
+
events.push({ type: "builtin_tool_end", tool: builtin.tool, ...builtin.id ? { id: builtin.id } : {} });
|
|
26423
|
+
}
|
|
26347
26424
|
if (item?.type === "function_call") {
|
|
26348
26425
|
events.push({ type: "tool_call_end", id: item.call_id ?? "" });
|
|
26349
26426
|
}
|
|
@@ -26442,6 +26519,9 @@ function filenameFor(mimeType) {
|
|
|
26442
26519
|
}
|
|
26443
26520
|
|
|
26444
26521
|
// src/llm/providers/openrouter/completions.ts
|
|
26522
|
+
function hasUrlCitation(annotations) {
|
|
26523
|
+
return Array.isArray(annotations) && annotations.some((a) => a?.type === "url_citation");
|
|
26524
|
+
}
|
|
26445
26525
|
var OpenRouterAdapter = class extends OpenAIAdapter {
|
|
26446
26526
|
name = "openrouter";
|
|
26447
26527
|
constructor(config) {
|
|
@@ -26470,6 +26550,35 @@ var OpenRouterAdapter = class extends OpenAIAdapter {
|
|
|
26470
26550
|
}
|
|
26471
26551
|
return result;
|
|
26472
26552
|
}
|
|
26553
|
+
parseResponse(raw, latencyMs) {
|
|
26554
|
+
const result = super.parseResponse(raw, latencyMs);
|
|
26555
|
+
const choices = raw.choices;
|
|
26556
|
+
const annotations = choices?.[0]?.message?.annotations;
|
|
26557
|
+
if (hasUrlCitation(annotations)) {
|
|
26558
|
+
result.builtinToolCalls = [...result.builtinToolCalls ?? [], { tool: "web_search" }];
|
|
26559
|
+
}
|
|
26560
|
+
return result;
|
|
26561
|
+
}
|
|
26562
|
+
/** Stateful — emit a single `web_search` builtin-tool pair the first time
|
|
26563
|
+
* `url_citation` annotations appear in the stream (the `:online` search signal). */
|
|
26564
|
+
createStreamParser() {
|
|
26565
|
+
let webSearchEmitted = false;
|
|
26566
|
+
return (event) => {
|
|
26567
|
+
const events = this.parseStreamEvent(event);
|
|
26568
|
+
if (!webSearchEmitted) {
|
|
26569
|
+
const choice = JSON.parse(event.data).choices?.[0];
|
|
26570
|
+
const annotations = choice?.delta?.annotations ?? choice?.message?.annotations;
|
|
26571
|
+
if (hasUrlCitation(annotations)) {
|
|
26572
|
+
webSearchEmitted = true;
|
|
26573
|
+
events.push(
|
|
26574
|
+
{ type: "builtin_tool_start", tool: "web_search" },
|
|
26575
|
+
{ type: "builtin_tool_end", tool: "web_search" }
|
|
26576
|
+
);
|
|
26577
|
+
}
|
|
26578
|
+
}
|
|
26579
|
+
return events;
|
|
26580
|
+
};
|
|
26581
|
+
}
|
|
26473
26582
|
};
|
|
26474
26583
|
|
|
26475
26584
|
// src/llm/providers/openrouter/responses.ts
|
|
@@ -28338,6 +28447,7 @@ var AgentLoop = class _AgentLoop {
|
|
|
28338
28447
|
// Propagate hosted-tool file outputs + inline-moderation result from the final
|
|
28339
28448
|
// LLM response (e.g. code-execution files produced during the run).
|
|
28340
28449
|
...lastResponse?.files ? { files: lastResponse.files } : {},
|
|
28450
|
+
...lastResponse?.builtinToolCalls ? { builtinToolCalls: lastResponse.builtinToolCalls } : {},
|
|
28341
28451
|
...lastResponse?.moderation ? { moderation: lastResponse.moderation } : {},
|
|
28342
28452
|
latencyMs: performance.now() - startPerf,
|
|
28343
28453
|
raw: lastResponse?.raw ?? null
|
|
@@ -28534,6 +28644,7 @@ var AgentLoop = class _AgentLoop {
|
|
|
28534
28644
|
thinking: lastResponse?.thinking ?? null,
|
|
28535
28645
|
media: [],
|
|
28536
28646
|
...lastResponse?.files ? { files: lastResponse.files } : {},
|
|
28647
|
+
...lastResponse?.builtinToolCalls ? { builtinToolCalls: lastResponse.builtinToolCalls } : {},
|
|
28537
28648
|
...lastResponse?.moderation ? { moderation: lastResponse.moderation } : {},
|
|
28538
28649
|
latencyMs: performance.now() - startPerf,
|
|
28539
28650
|
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
|
@@ -23009,6 +23009,7 @@ var LLMClient = class {
|
|
|
23009
23009
|
let finishReason = "stop";
|
|
23010
23010
|
let moderationReport;
|
|
23011
23011
|
const files = [];
|
|
23012
|
+
const builtinToolCalls = [];
|
|
23012
23013
|
const fetchStream = this.fetchStreamFn;
|
|
23013
23014
|
const queueName = this.queueName;
|
|
23014
23015
|
const priority = this.priority;
|
|
@@ -23060,6 +23061,9 @@ var LLMClient = class {
|
|
|
23060
23061
|
case "file":
|
|
23061
23062
|
files.push(event.file);
|
|
23062
23063
|
break;
|
|
23064
|
+
case "builtin_tool_start":
|
|
23065
|
+
builtinToolCalls.push({ tool: event.tool, ...event.id ? { id: event.id } : {} });
|
|
23066
|
+
break;
|
|
23063
23067
|
case "moderation":
|
|
23064
23068
|
moderationReport = this.mergeModeration(
|
|
23065
23069
|
moderationReport,
|
|
@@ -23083,6 +23087,7 @@ var LLMClient = class {
|
|
|
23083
23087
|
thinking: thinking || null,
|
|
23084
23088
|
media: [],
|
|
23085
23089
|
...files.length ? { files } : {},
|
|
23090
|
+
...builtinToolCalls.length ? { builtinToolCalls } : {},
|
|
23086
23091
|
...moderationReport ? { moderation: moderationReport } : {},
|
|
23087
23092
|
latencyMs: performance.now() - start,
|
|
23088
23093
|
raw: null
|
|
@@ -23316,6 +23321,24 @@ var AnthropicFileAdapter = class {
|
|
|
23316
23321
|
}
|
|
23317
23322
|
};
|
|
23318
23323
|
|
|
23324
|
+
// src/llm/providers/_shared/builtin-tools.ts
|
|
23325
|
+
var NATIVE_TO_UNIFIED = {
|
|
23326
|
+
// OpenAI / xAI Responses output-item types
|
|
23327
|
+
web_search_call: "web_search",
|
|
23328
|
+
code_interpreter_call: "code_interpreter",
|
|
23329
|
+
// Anthropic server_tool_use names
|
|
23330
|
+
web_search: "web_search",
|
|
23331
|
+
code_execution: "code_interpreter",
|
|
23332
|
+
bash_code_execution: "code_interpreter",
|
|
23333
|
+
// Anthropic *_tool_result block types
|
|
23334
|
+
web_search_tool_result: "web_search",
|
|
23335
|
+
code_execution_tool_result: "code_interpreter",
|
|
23336
|
+
bash_code_execution_tool_result: "code_interpreter"
|
|
23337
|
+
};
|
|
23338
|
+
function unifiedBuiltinTool(native) {
|
|
23339
|
+
return NATIVE_TO_UNIFIED[native] ?? native.replace(/_call$/, "").replace(/_tool_result$/, "");
|
|
23340
|
+
}
|
|
23341
|
+
|
|
23319
23342
|
// src/llm/providers/_shared/constants.ts
|
|
23320
23343
|
var AUDIO_PCM16_SAMPLE_RATE_HZ = 24e3;
|
|
23321
23344
|
var DEFAULT_MAX_TOKENS = 4096;
|
|
@@ -23525,6 +23548,7 @@ var AnthropicAdapter = class {
|
|
|
23525
23548
|
let thinking = null;
|
|
23526
23549
|
const toolCalls = [];
|
|
23527
23550
|
const files = [];
|
|
23551
|
+
const builtinToolCalls = [];
|
|
23528
23552
|
for (const block of contentBlocks) {
|
|
23529
23553
|
if (block.type === "text") {
|
|
23530
23554
|
content.push({ type: "text", text: block.text });
|
|
@@ -23539,6 +23563,11 @@ var AnthropicAdapter = class {
|
|
|
23539
23563
|
};
|
|
23540
23564
|
content.push(tc);
|
|
23541
23565
|
toolCalls.push(tc);
|
|
23566
|
+
} else if (block.type === "server_tool_use") {
|
|
23567
|
+
builtinToolCalls.push({
|
|
23568
|
+
tool: unifiedBuiltinTool(block.name),
|
|
23569
|
+
...typeof block.id === "string" ? { id: block.id } : {}
|
|
23570
|
+
});
|
|
23542
23571
|
} else {
|
|
23543
23572
|
files.push(...filesFromCodeExecBlock(block));
|
|
23544
23573
|
}
|
|
@@ -23556,6 +23585,7 @@ var AnthropicAdapter = class {
|
|
|
23556
23585
|
toolCalls,
|
|
23557
23586
|
media: [],
|
|
23558
23587
|
...files.length ? { files } : {},
|
|
23588
|
+
...builtinToolCalls.length ? { builtinToolCalls } : {},
|
|
23559
23589
|
thinking,
|
|
23560
23590
|
latencyMs,
|
|
23561
23591
|
raw
|
|
@@ -23579,8 +23609,23 @@ var AnthropicAdapter = class {
|
|
|
23579
23609
|
if (block.type === "tool_use") {
|
|
23580
23610
|
return [{ type: "tool_call_start", id: block.id, name: block.name }];
|
|
23581
23611
|
}
|
|
23582
|
-
const
|
|
23583
|
-
|
|
23612
|
+
const events = [];
|
|
23613
|
+
const blockType = block.type;
|
|
23614
|
+
if (blockType === "server_tool_use") {
|
|
23615
|
+
events.push({
|
|
23616
|
+
type: "builtin_tool_start",
|
|
23617
|
+
tool: unifiedBuiltinTool(block.name),
|
|
23618
|
+
...typeof block.id === "string" ? { id: block.id } : {}
|
|
23619
|
+
});
|
|
23620
|
+
} else if (blockType?.endsWith("_tool_result")) {
|
|
23621
|
+
events.push({
|
|
23622
|
+
type: "builtin_tool_end",
|
|
23623
|
+
tool: unifiedBuiltinTool(blockType),
|
|
23624
|
+
...typeof block.tool_use_id === "string" ? { id: block.tool_use_id } : {}
|
|
23625
|
+
});
|
|
23626
|
+
}
|
|
23627
|
+
for (const file of filesFromCodeExecBlock(block)) events.push({ type: "file", file });
|
|
23628
|
+
return events;
|
|
23584
23629
|
}
|
|
23585
23630
|
if (type === "content_block_stop") {
|
|
23586
23631
|
}
|
|
@@ -24116,6 +24161,9 @@ var GoogleAdapter = class {
|
|
|
24116
24161
|
candidate.finishReason,
|
|
24117
24162
|
{ MAX_TOKENS: "length", SAFETY: "content_filter" }
|
|
24118
24163
|
);
|
|
24164
|
+
const builtinToolCalls = [];
|
|
24165
|
+
if (hasCodeExec) builtinToolCalls.push({ tool: "code_interpreter" });
|
|
24166
|
+
if (candidate.groundingMetadata) builtinToolCalls.push({ tool: "web_search" });
|
|
24119
24167
|
return {
|
|
24120
24168
|
id: crypto.randomUUID(),
|
|
24121
24169
|
// Google doesn't return a response ID in generateContent
|
|
@@ -24128,6 +24176,7 @@ var GoogleAdapter = class {
|
|
|
24128
24176
|
thinking,
|
|
24129
24177
|
media,
|
|
24130
24178
|
...files.length ? { files } : {},
|
|
24179
|
+
...builtinToolCalls.length ? { builtinToolCalls } : {},
|
|
24131
24180
|
latencyMs,
|
|
24132
24181
|
raw
|
|
24133
24182
|
};
|
|
@@ -24165,6 +24214,8 @@ var GoogleAdapter = class {
|
|
|
24165
24214
|
if (part.text !== void 0 && !part.thought)
|
|
24166
24215
|
events.push({ type: "text", text: part.text });
|
|
24167
24216
|
if (part.thought && part.text) events.push({ type: "thinking", text: part.text });
|
|
24217
|
+
if (part.executableCode) events.push({ type: "builtin_tool_start", tool: "code_interpreter" });
|
|
24218
|
+
if (part.codeExecutionResult) events.push({ type: "builtin_tool_end", tool: "code_interpreter" });
|
|
24168
24219
|
if (part.inlineData) {
|
|
24169
24220
|
const inline = part.inlineData;
|
|
24170
24221
|
const mime = inline.mimeType;
|
|
@@ -24195,6 +24246,11 @@ var GoogleAdapter = class {
|
|
|
24195
24246
|
events.push({ type: "tool_call_end", id: "" });
|
|
24196
24247
|
}
|
|
24197
24248
|
}
|
|
24249
|
+
if (candidate.groundingMetadata && !state.webSearchEmitted) {
|
|
24250
|
+
state.webSearchEmitted = true;
|
|
24251
|
+
events.push({ type: "builtin_tool_start", tool: "web_search" });
|
|
24252
|
+
events.push({ type: "builtin_tool_end", tool: "web_search" });
|
|
24253
|
+
}
|
|
24198
24254
|
const fr = candidate.finishReason;
|
|
24199
24255
|
if (fr)
|
|
24200
24256
|
events.push({
|
|
@@ -25961,6 +26017,15 @@ function filenameForMime(mimeType) {
|
|
|
25961
26017
|
if (mimeType.startsWith("image/")) return `file.${mimeType.slice("image/".length)}`;
|
|
25962
26018
|
return "file.bin";
|
|
25963
26019
|
}
|
|
26020
|
+
var RESPONSES_BUILTIN_ITEMS = /* @__PURE__ */ new Set(["web_search_call", "code_interpreter_call"]);
|
|
26021
|
+
function builtinCallFromResponsesItem(item) {
|
|
26022
|
+
const type = item.type;
|
|
26023
|
+
if (!RESPONSES_BUILTIN_ITEMS.has(type)) return null;
|
|
26024
|
+
return {
|
|
26025
|
+
tool: unifiedBuiltinTool(type),
|
|
26026
|
+
...typeof item.id === "string" ? { id: item.id } : {}
|
|
26027
|
+
};
|
|
26028
|
+
}
|
|
25964
26029
|
function filesFromResponsesOutputItem(item) {
|
|
25965
26030
|
const files = [];
|
|
25966
26031
|
const type = item.type;
|
|
@@ -26164,11 +26229,14 @@ var OpenAIResponsesAdapter = class {
|
|
|
26164
26229
|
const toolCalls = [];
|
|
26165
26230
|
const media = [];
|
|
26166
26231
|
const files = [];
|
|
26232
|
+
const builtinToolCalls = [];
|
|
26167
26233
|
let thinking = null;
|
|
26168
26234
|
let text = "";
|
|
26169
26235
|
for (const item of output) {
|
|
26170
26236
|
const type = item.type;
|
|
26171
26237
|
files.push(...this.filesFromOutputItem(item));
|
|
26238
|
+
const builtinCall = builtinCallFromResponsesItem(item);
|
|
26239
|
+
if (builtinCall) builtinToolCalls.push(builtinCall);
|
|
26172
26240
|
if (type === "message") {
|
|
26173
26241
|
const itemContent = item.content ?? [];
|
|
26174
26242
|
for (const c of itemContent) {
|
|
@@ -26229,6 +26297,7 @@ var OpenAIResponsesAdapter = class {
|
|
|
26229
26297
|
thinking,
|
|
26230
26298
|
media,
|
|
26231
26299
|
...files.length ? { files } : {},
|
|
26300
|
+
...builtinToolCalls.length ? { builtinToolCalls } : {},
|
|
26232
26301
|
...moderation ? { moderation } : {},
|
|
26233
26302
|
latencyMs,
|
|
26234
26303
|
raw
|
|
@@ -26260,6 +26329,10 @@ var OpenAIResponsesAdapter = class {
|
|
|
26260
26329
|
if (item?.type === "image_generation_call") {
|
|
26261
26330
|
events.push({ type: "media_start", mediaType: "image", mimeType: "image/png" });
|
|
26262
26331
|
}
|
|
26332
|
+
const builtin = builtinCallFromResponsesItem(item ?? {});
|
|
26333
|
+
if (builtin) {
|
|
26334
|
+
events.push({ type: "builtin_tool_start", tool: builtin.tool, ...builtin.id ? { id: builtin.id } : {} });
|
|
26335
|
+
}
|
|
26263
26336
|
}
|
|
26264
26337
|
if (type === "response.image_generation_call.partial_image") {
|
|
26265
26338
|
events.push({
|
|
@@ -26271,6 +26344,10 @@ var OpenAIResponsesAdapter = class {
|
|
|
26271
26344
|
if (type === "response.output_item.done") {
|
|
26272
26345
|
const item = data.item;
|
|
26273
26346
|
for (const file of this.filesFromOutputItem(item)) events.push({ type: "file", file });
|
|
26347
|
+
const builtin = builtinCallFromResponsesItem(item ?? {});
|
|
26348
|
+
if (builtin) {
|
|
26349
|
+
events.push({ type: "builtin_tool_end", tool: builtin.tool, ...builtin.id ? { id: builtin.id } : {} });
|
|
26350
|
+
}
|
|
26274
26351
|
if (item?.type === "function_call") {
|
|
26275
26352
|
events.push({ type: "tool_call_end", id: item.call_id ?? "" });
|
|
26276
26353
|
}
|
|
@@ -26369,6 +26446,9 @@ function filenameFor(mimeType) {
|
|
|
26369
26446
|
}
|
|
26370
26447
|
|
|
26371
26448
|
// src/llm/providers/openrouter/completions.ts
|
|
26449
|
+
function hasUrlCitation(annotations) {
|
|
26450
|
+
return Array.isArray(annotations) && annotations.some((a) => a?.type === "url_citation");
|
|
26451
|
+
}
|
|
26372
26452
|
var OpenRouterAdapter = class extends OpenAIAdapter {
|
|
26373
26453
|
name = "openrouter";
|
|
26374
26454
|
constructor(config) {
|
|
@@ -26397,6 +26477,35 @@ var OpenRouterAdapter = class extends OpenAIAdapter {
|
|
|
26397
26477
|
}
|
|
26398
26478
|
return result;
|
|
26399
26479
|
}
|
|
26480
|
+
parseResponse(raw, latencyMs) {
|
|
26481
|
+
const result = super.parseResponse(raw, latencyMs);
|
|
26482
|
+
const choices = raw.choices;
|
|
26483
|
+
const annotations = choices?.[0]?.message?.annotations;
|
|
26484
|
+
if (hasUrlCitation(annotations)) {
|
|
26485
|
+
result.builtinToolCalls = [...result.builtinToolCalls ?? [], { tool: "web_search" }];
|
|
26486
|
+
}
|
|
26487
|
+
return result;
|
|
26488
|
+
}
|
|
26489
|
+
/** Stateful — emit a single `web_search` builtin-tool pair the first time
|
|
26490
|
+
* `url_citation` annotations appear in the stream (the `:online` search signal). */
|
|
26491
|
+
createStreamParser() {
|
|
26492
|
+
let webSearchEmitted = false;
|
|
26493
|
+
return (event) => {
|
|
26494
|
+
const events = this.parseStreamEvent(event);
|
|
26495
|
+
if (!webSearchEmitted) {
|
|
26496
|
+
const choice = JSON.parse(event.data).choices?.[0];
|
|
26497
|
+
const annotations = choice?.delta?.annotations ?? choice?.message?.annotations;
|
|
26498
|
+
if (hasUrlCitation(annotations)) {
|
|
26499
|
+
webSearchEmitted = true;
|
|
26500
|
+
events.push(
|
|
26501
|
+
{ type: "builtin_tool_start", tool: "web_search" },
|
|
26502
|
+
{ type: "builtin_tool_end", tool: "web_search" }
|
|
26503
|
+
);
|
|
26504
|
+
}
|
|
26505
|
+
}
|
|
26506
|
+
return events;
|
|
26507
|
+
};
|
|
26508
|
+
}
|
|
26400
26509
|
};
|
|
26401
26510
|
|
|
26402
26511
|
// src/llm/providers/openrouter/responses.ts
|
|
@@ -28265,6 +28374,7 @@ var AgentLoop = class _AgentLoop {
|
|
|
28265
28374
|
// Propagate hosted-tool file outputs + inline-moderation result from the final
|
|
28266
28375
|
// LLM response (e.g. code-execution files produced during the run).
|
|
28267
28376
|
...lastResponse?.files ? { files: lastResponse.files } : {},
|
|
28377
|
+
...lastResponse?.builtinToolCalls ? { builtinToolCalls: lastResponse.builtinToolCalls } : {},
|
|
28268
28378
|
...lastResponse?.moderation ? { moderation: lastResponse.moderation } : {},
|
|
28269
28379
|
latencyMs: performance.now() - startPerf,
|
|
28270
28380
|
raw: lastResponse?.raw ?? null
|
|
@@ -28461,6 +28571,7 @@ var AgentLoop = class _AgentLoop {
|
|
|
28461
28571
|
thinking: lastResponse?.thinking ?? null,
|
|
28462
28572
|
media: [],
|
|
28463
28573
|
...lastResponse?.files ? { files: lastResponse.files } : {},
|
|
28574
|
+
...lastResponse?.builtinToolCalls ? { builtinToolCalls: lastResponse.builtinToolCalls } : {},
|
|
28464
28575
|
...lastResponse?.moderation ? { moderation: lastResponse.moderation } : {},
|
|
28465
28576
|
latencyMs: performance.now() - startPerf,
|
|
28466
28577
|
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.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",
|