@combycode/llm-sdk 1.3.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 +35 -0
- package/dist/index.browser.js +196 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.js +196 -7
- package/dist/llm/providers/_shared/builtin-tools.d.ts +5 -0
- package/dist/llm/providers/builtin-tools.d.ts +15 -0
- package/dist/llm/providers/openai/responses.d.ts +5 -1
- package/dist/llm/providers/openrouter/completions.d.ts +7 -0
- package/dist/llm/providers/xai/responses.d.ts +4 -0
- package/dist/llm/types/response.d.ts +12 -0
- package/dist/llm/types/stream.d.ts +17 -0
- package/dist/plugins/model-catalog/catalog.d.ts +5 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,41 @@ 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
|
+
|
|
23
|
+
## [1.4.0] - 2026-07-06
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
- **xAI hosted code-execution files** now surface on `response.files` (parity with the other
|
|
27
|
+
providers). xAI returns code-interpreter output files inline inside the `code_interpreter_call`
|
|
28
|
+
`logs` JSON (`output_files:[{file_name, mime_type, data:[…bytes]}]`) and only when the request
|
|
29
|
+
asks for them — so `XAIResponsesAdapter` now sends `include:['code_interpreter_call.outputs']`
|
|
30
|
+
when `code_interpreter` is used and extracts the inline bytes into `FileOutput`. Verified live on
|
|
31
|
+
`complete()` and `stream()`. `OpenAIResponsesAdapter.filesFromOutputItem` is now a `protected`
|
|
32
|
+
overridable method so Responses-compatible providers can extend file extraction.
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
- **Adapter-sourced builtin-tool capabilities in the catalog.** Every tool-capable (chat-family)
|
|
36
|
+
model now carries `capabilities.builtinTools` — `['web_search', 'code_interpreter']` for
|
|
37
|
+
anthropic/openai/google/xai, `['web_search']` for openrouter (it doesn't route hosted code
|
|
38
|
+
execution) — injected at catalog load from a single provider map (`PROVIDER_BUILTIN_TOOLS`) that
|
|
39
|
+
mirrors what the adapters actually support. New `catalog.builtinToolsFor()` /
|
|
40
|
+
`supportsBuiltinTool()` accessors and `select('web_search')` / `select('code_interpreter')`
|
|
41
|
+
queries (`search` stays an alias for `web_search`). Non-tool models (embeddings/tts/image) get
|
|
42
|
+
none. A reliable per-model source can refine this later.
|
|
43
|
+
|
|
9
44
|
## [1.3.0] - 2026-07-06
|
|
10
45
|
|
|
11
46
|
### Added
|
package/dist/index.browser.js
CHANGED
|
@@ -22105,6 +22105,15 @@ var catalog_default5 = {
|
|
|
22105
22105
|
}
|
|
22106
22106
|
};
|
|
22107
22107
|
|
|
22108
|
+
// src/llm/providers/builtin-tools.ts
|
|
22109
|
+
var PROVIDER_BUILTIN_TOOLS = {
|
|
22110
|
+
anthropic: ["web_search", "code_interpreter"],
|
|
22111
|
+
openai: ["web_search", "code_interpreter"],
|
|
22112
|
+
google: ["web_search", "code_interpreter"],
|
|
22113
|
+
xai: ["web_search", "code_interpreter"],
|
|
22114
|
+
openrouter: ["web_search"]
|
|
22115
|
+
};
|
|
22116
|
+
|
|
22108
22117
|
// src/plugins/model-catalog/catalog.ts
|
|
22109
22118
|
var PROVIDER_DEFAULT_CATALOGS = [
|
|
22110
22119
|
catalog_default,
|
|
@@ -22142,6 +22151,13 @@ var DEFAULT_REASONING = {
|
|
|
22142
22151
|
encryptedContent: false,
|
|
22143
22152
|
summaryAvailable: false
|
|
22144
22153
|
};
|
|
22154
|
+
function withBuiltinTools(provider, caps) {
|
|
22155
|
+
if (caps.builtinTools !== void 0) return caps;
|
|
22156
|
+
if (!caps.toolUse) return caps;
|
|
22157
|
+
const tools = PROVIDER_BUILTIN_TOOLS[provider];
|
|
22158
|
+
if (!tools || tools.length === 0) return caps;
|
|
22159
|
+
return { ...caps, builtinTools: [...tools] };
|
|
22160
|
+
}
|
|
22145
22161
|
var ModelCatalog = class {
|
|
22146
22162
|
models = /* @__PURE__ */ new Map();
|
|
22147
22163
|
/** `provider/alias` → `provider/canonical-slug`. Lets get()/resolveModelId
|
|
@@ -22160,7 +22176,7 @@ var ModelCatalog = class {
|
|
|
22160
22176
|
supportedApis: info.supportedApis ?? [info.preferredApi ?? "completions"],
|
|
22161
22177
|
contextWindow: info.contextWindow,
|
|
22162
22178
|
maxOutput: info.maxOutput,
|
|
22163
|
-
capabilities: { ...DEFAULT_CAPABILITIES, ...info.capabilities },
|
|
22179
|
+
capabilities: withBuiltinTools(provider, { ...DEFAULT_CAPABILITIES, ...info.capabilities }),
|
|
22164
22180
|
reasoning: { ...DEFAULT_REASONING, ...info.reasoning },
|
|
22165
22181
|
mediaOnly: info.mediaOnly,
|
|
22166
22182
|
tokenizer: info.tokenizer,
|
|
@@ -22214,6 +22230,15 @@ var ModelCatalog = class {
|
|
|
22214
22230
|
supportsTools(provider, model) {
|
|
22215
22231
|
return this.get(provider, model)?.capabilities.toolUse ?? false;
|
|
22216
22232
|
}
|
|
22233
|
+
/** Hosted server-side builtin tools this model supports (e.g. `['web_search',
|
|
22234
|
+
* 'code_interpreter']`). Empty when unknown or the model isn't tool-capable. */
|
|
22235
|
+
builtinToolsFor(provider, model) {
|
|
22236
|
+
return [...this.get(provider, model)?.capabilities.builtinTools ?? []];
|
|
22237
|
+
}
|
|
22238
|
+
/** Whether this model supports a specific hosted builtin tool. */
|
|
22239
|
+
supportsBuiltinTool(provider, model, tool) {
|
|
22240
|
+
return this.get(provider, model)?.capabilities.builtinTools?.includes(tool) ?? false;
|
|
22241
|
+
}
|
|
22217
22242
|
supportsPreviousResponseId(provider, model) {
|
|
22218
22243
|
const info = this.get(provider, model);
|
|
22219
22244
|
if (info && !info.supportedApis.some((a) => a === "responses" || a === "interactions")) {
|
|
@@ -23057,6 +23082,7 @@ var LLMClient = class {
|
|
|
23057
23082
|
let finishReason = "stop";
|
|
23058
23083
|
let moderationReport;
|
|
23059
23084
|
const files = [];
|
|
23085
|
+
const builtinToolCalls = [];
|
|
23060
23086
|
const fetchStream = this.fetchStreamFn;
|
|
23061
23087
|
const queueName = this.queueName;
|
|
23062
23088
|
const priority = this.priority;
|
|
@@ -23108,6 +23134,9 @@ var LLMClient = class {
|
|
|
23108
23134
|
case "file":
|
|
23109
23135
|
files.push(event.file);
|
|
23110
23136
|
break;
|
|
23137
|
+
case "builtin_tool_start":
|
|
23138
|
+
builtinToolCalls.push({ tool: event.tool, ...event.id ? { id: event.id } : {} });
|
|
23139
|
+
break;
|
|
23111
23140
|
case "moderation":
|
|
23112
23141
|
moderationReport = this.mergeModeration(
|
|
23113
23142
|
moderationReport,
|
|
@@ -23131,6 +23160,7 @@ var LLMClient = class {
|
|
|
23131
23160
|
thinking: thinking || null,
|
|
23132
23161
|
media: [],
|
|
23133
23162
|
...files.length ? { files } : {},
|
|
23163
|
+
...builtinToolCalls.length ? { builtinToolCalls } : {},
|
|
23134
23164
|
...moderationReport ? { moderation: moderationReport } : {},
|
|
23135
23165
|
latencyMs: performance.now() - start,
|
|
23136
23166
|
raw: null
|
|
@@ -23364,6 +23394,24 @@ var AnthropicFileAdapter = class {
|
|
|
23364
23394
|
}
|
|
23365
23395
|
};
|
|
23366
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
|
+
|
|
23367
23415
|
// src/llm/providers/_shared/constants.ts
|
|
23368
23416
|
var AUDIO_PCM16_SAMPLE_RATE_HZ = 24e3;
|
|
23369
23417
|
var DEFAULT_MAX_TOKENS = 4096;
|
|
@@ -23573,6 +23621,7 @@ var AnthropicAdapter = class {
|
|
|
23573
23621
|
let thinking = null;
|
|
23574
23622
|
const toolCalls = [];
|
|
23575
23623
|
const files = [];
|
|
23624
|
+
const builtinToolCalls = [];
|
|
23576
23625
|
for (const block of contentBlocks) {
|
|
23577
23626
|
if (block.type === "text") {
|
|
23578
23627
|
content.push({ type: "text", text: block.text });
|
|
@@ -23587,6 +23636,11 @@ var AnthropicAdapter = class {
|
|
|
23587
23636
|
};
|
|
23588
23637
|
content.push(tc);
|
|
23589
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
|
+
});
|
|
23590
23644
|
} else {
|
|
23591
23645
|
files.push(...filesFromCodeExecBlock(block));
|
|
23592
23646
|
}
|
|
@@ -23604,6 +23658,7 @@ var AnthropicAdapter = class {
|
|
|
23604
23658
|
toolCalls,
|
|
23605
23659
|
media: [],
|
|
23606
23660
|
...files.length ? { files } : {},
|
|
23661
|
+
...builtinToolCalls.length ? { builtinToolCalls } : {},
|
|
23607
23662
|
thinking,
|
|
23608
23663
|
latencyMs,
|
|
23609
23664
|
raw
|
|
@@ -23627,8 +23682,23 @@ var AnthropicAdapter = class {
|
|
|
23627
23682
|
if (block.type === "tool_use") {
|
|
23628
23683
|
return [{ type: "tool_call_start", id: block.id, name: block.name }];
|
|
23629
23684
|
}
|
|
23630
|
-
const
|
|
23631
|
-
|
|
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;
|
|
23632
23702
|
}
|
|
23633
23703
|
if (type === "content_block_stop") {
|
|
23634
23704
|
}
|
|
@@ -24164,6 +24234,9 @@ var GoogleAdapter = class {
|
|
|
24164
24234
|
candidate.finishReason,
|
|
24165
24235
|
{ MAX_TOKENS: "length", SAFETY: "content_filter" }
|
|
24166
24236
|
);
|
|
24237
|
+
const builtinToolCalls = [];
|
|
24238
|
+
if (hasCodeExec) builtinToolCalls.push({ tool: "code_interpreter" });
|
|
24239
|
+
if (candidate.groundingMetadata) builtinToolCalls.push({ tool: "web_search" });
|
|
24167
24240
|
return {
|
|
24168
24241
|
id: crypto.randomUUID(),
|
|
24169
24242
|
// Google doesn't return a response ID in generateContent
|
|
@@ -24176,6 +24249,7 @@ var GoogleAdapter = class {
|
|
|
24176
24249
|
thinking,
|
|
24177
24250
|
media,
|
|
24178
24251
|
...files.length ? { files } : {},
|
|
24252
|
+
...builtinToolCalls.length ? { builtinToolCalls } : {},
|
|
24179
24253
|
latencyMs,
|
|
24180
24254
|
raw
|
|
24181
24255
|
};
|
|
@@ -24213,6 +24287,8 @@ var GoogleAdapter = class {
|
|
|
24213
24287
|
if (part.text !== void 0 && !part.thought)
|
|
24214
24288
|
events.push({ type: "text", text: part.text });
|
|
24215
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" });
|
|
24216
24292
|
if (part.inlineData) {
|
|
24217
24293
|
const inline = part.inlineData;
|
|
24218
24294
|
const mime = inline.mimeType;
|
|
@@ -24243,6 +24319,11 @@ var GoogleAdapter = class {
|
|
|
24243
24319
|
events.push({ type: "tool_call_end", id: "" });
|
|
24244
24320
|
}
|
|
24245
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
|
+
}
|
|
24246
24327
|
const fr = candidate.finishReason;
|
|
24247
24328
|
if (fr)
|
|
24248
24329
|
events.push({
|
|
@@ -26009,6 +26090,15 @@ function filenameForMime(mimeType) {
|
|
|
26009
26090
|
if (mimeType.startsWith("image/")) return `file.${mimeType.slice("image/".length)}`;
|
|
26010
26091
|
return "file.bin";
|
|
26011
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
|
+
}
|
|
26012
26102
|
function filesFromResponsesOutputItem(item) {
|
|
26013
26103
|
const files = [];
|
|
26014
26104
|
const type = item.type;
|
|
@@ -26212,11 +26302,14 @@ var OpenAIResponsesAdapter = class {
|
|
|
26212
26302
|
const toolCalls = [];
|
|
26213
26303
|
const media = [];
|
|
26214
26304
|
const files = [];
|
|
26305
|
+
const builtinToolCalls = [];
|
|
26215
26306
|
let thinking = null;
|
|
26216
26307
|
let text = "";
|
|
26217
26308
|
for (const item of output) {
|
|
26218
26309
|
const type = item.type;
|
|
26219
|
-
files.push(...
|
|
26310
|
+
files.push(...this.filesFromOutputItem(item));
|
|
26311
|
+
const builtinCall = builtinCallFromResponsesItem(item);
|
|
26312
|
+
if (builtinCall) builtinToolCalls.push(builtinCall);
|
|
26220
26313
|
if (type === "message") {
|
|
26221
26314
|
const itemContent = item.content ?? [];
|
|
26222
26315
|
for (const c of itemContent) {
|
|
@@ -26277,6 +26370,7 @@ var OpenAIResponsesAdapter = class {
|
|
|
26277
26370
|
thinking,
|
|
26278
26371
|
media,
|
|
26279
26372
|
...files.length ? { files } : {},
|
|
26373
|
+
...builtinToolCalls.length ? { builtinToolCalls } : {},
|
|
26280
26374
|
...moderation ? { moderation } : {},
|
|
26281
26375
|
latencyMs,
|
|
26282
26376
|
raw
|
|
@@ -26308,6 +26402,10 @@ var OpenAIResponsesAdapter = class {
|
|
|
26308
26402
|
if (item?.type === "image_generation_call") {
|
|
26309
26403
|
events.push({ type: "media_start", mediaType: "image", mimeType: "image/png" });
|
|
26310
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
|
+
}
|
|
26311
26409
|
}
|
|
26312
26410
|
if (type === "response.image_generation_call.partial_image") {
|
|
26313
26411
|
events.push({
|
|
@@ -26318,7 +26416,11 @@ var OpenAIResponsesAdapter = class {
|
|
|
26318
26416
|
}
|
|
26319
26417
|
if (type === "response.output_item.done") {
|
|
26320
26418
|
const item = data.item;
|
|
26321
|
-
for (const file of
|
|
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
|
+
}
|
|
26322
26424
|
if (item?.type === "function_call") {
|
|
26323
26425
|
events.push({ type: "tool_call_end", id: item.call_id ?? "" });
|
|
26324
26426
|
}
|
|
@@ -26354,6 +26456,12 @@ var OpenAIResponsesAdapter = class {
|
|
|
26354
26456
|
createStreamParser() {
|
|
26355
26457
|
return (event) => this.parseStreamEvent(event);
|
|
26356
26458
|
}
|
|
26459
|
+
/** Hosted code-execution output files from one output item. Overridable so
|
|
26460
|
+
* Responses-compatible providers with a different file shape (e.g. xAI, which
|
|
26461
|
+
* embeds files in the code-interpreter `logs` payload) can extend it. */
|
|
26462
|
+
filesFromOutputItem(item) {
|
|
26463
|
+
return filesFromResponsesOutputItem(item);
|
|
26464
|
+
}
|
|
26357
26465
|
parseUsage(u) {
|
|
26358
26466
|
if (!u) return emptyUsage();
|
|
26359
26467
|
const input = u.input_tokens ?? 0;
|
|
@@ -26411,6 +26519,9 @@ function filenameFor(mimeType) {
|
|
|
26411
26519
|
}
|
|
26412
26520
|
|
|
26413
26521
|
// src/llm/providers/openrouter/completions.ts
|
|
26522
|
+
function hasUrlCitation(annotations) {
|
|
26523
|
+
return Array.isArray(annotations) && annotations.some((a) => a?.type === "url_citation");
|
|
26524
|
+
}
|
|
26414
26525
|
var OpenRouterAdapter = class extends OpenAIAdapter {
|
|
26415
26526
|
name = "openrouter";
|
|
26416
26527
|
constructor(config) {
|
|
@@ -26439,6 +26550,35 @@ var OpenRouterAdapter = class extends OpenAIAdapter {
|
|
|
26439
26550
|
}
|
|
26440
26551
|
return result;
|
|
26441
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
|
+
}
|
|
26442
26582
|
};
|
|
26443
26583
|
|
|
26444
26584
|
// src/llm/providers/openrouter/responses.ts
|
|
@@ -26918,6 +27058,29 @@ var XAIMediaAdapter = class {
|
|
|
26918
27058
|
};
|
|
26919
27059
|
|
|
26920
27060
|
// src/llm/providers/xai/responses.ts
|
|
27061
|
+
function xaiCodeExecFiles(item) {
|
|
27062
|
+
if (item.type !== "code_interpreter_call") return [];
|
|
27063
|
+
const files = [];
|
|
27064
|
+
for (const out of item.outputs ?? []) {
|
|
27065
|
+
if (out.type !== "logs" || typeof out.logs !== "string") continue;
|
|
27066
|
+
let parsed;
|
|
27067
|
+
try {
|
|
27068
|
+
parsed = JSON.parse(out.logs);
|
|
27069
|
+
} catch {
|
|
27070
|
+
continue;
|
|
27071
|
+
}
|
|
27072
|
+
for (const f of parsed.output_files ?? []) {
|
|
27073
|
+
if (!Array.isArray(f.data)) continue;
|
|
27074
|
+
files.push({
|
|
27075
|
+
data: bytesToBase64(Uint8Array.from(f.data)),
|
|
27076
|
+
...typeof f.file_name === "string" ? { name: f.file_name } : {},
|
|
27077
|
+
...typeof f.mime_type === "string" ? { mimeType: f.mime_type } : {},
|
|
27078
|
+
source: "code_execution"
|
|
27079
|
+
});
|
|
27080
|
+
}
|
|
27081
|
+
}
|
|
27082
|
+
return files;
|
|
27083
|
+
}
|
|
26921
27084
|
var XAIResponsesAdapter = class extends OpenAIResponsesAdapter {
|
|
26922
27085
|
name = "xai";
|
|
26923
27086
|
constructor(config) {
|
|
@@ -26937,8 +27100,20 @@ var XAIResponsesAdapter = class extends OpenAIResponsesAdapter {
|
|
|
26937
27100
|
if (!req.model.includes("multi-agent")) {
|
|
26938
27101
|
delete body.reasoning;
|
|
26939
27102
|
}
|
|
27103
|
+
const usesCodeInterpreter = req.tools?.some(
|
|
27104
|
+
(t) => !isFunctionTool(t) && t.type === "code_interpreter"
|
|
27105
|
+
);
|
|
27106
|
+
if (usesCodeInterpreter) {
|
|
27107
|
+
const include = /* @__PURE__ */ new Set([...body.include ?? [], "code_interpreter_call.outputs"]);
|
|
27108
|
+
body.include = [...include];
|
|
27109
|
+
}
|
|
26940
27110
|
return result;
|
|
26941
27111
|
}
|
|
27112
|
+
/** xAI embeds code-execution files inline in the `logs` payload — extend the base
|
|
27113
|
+
* extraction (which handles OpenAI-style annotations / image URLs) with the xAI shape. */
|
|
27114
|
+
filesFromOutputItem(item) {
|
|
27115
|
+
return [...super.filesFromOutputItem(item), ...xaiCodeExecFiles(item)];
|
|
27116
|
+
}
|
|
26942
27117
|
};
|
|
26943
27118
|
|
|
26944
27119
|
// src/util/json-schema.ts
|
|
@@ -28272,6 +28447,7 @@ var AgentLoop = class _AgentLoop {
|
|
|
28272
28447
|
// Propagate hosted-tool file outputs + inline-moderation result from the final
|
|
28273
28448
|
// LLM response (e.g. code-execution files produced during the run).
|
|
28274
28449
|
...lastResponse?.files ? { files: lastResponse.files } : {},
|
|
28450
|
+
...lastResponse?.builtinToolCalls ? { builtinToolCalls: lastResponse.builtinToolCalls } : {},
|
|
28275
28451
|
...lastResponse?.moderation ? { moderation: lastResponse.moderation } : {},
|
|
28276
28452
|
latencyMs: performance.now() - startPerf,
|
|
28277
28453
|
raw: lastResponse?.raw ?? null
|
|
@@ -28468,6 +28644,7 @@ var AgentLoop = class _AgentLoop {
|
|
|
28468
28644
|
thinking: lastResponse?.thinking ?? null,
|
|
28469
28645
|
media: [],
|
|
28470
28646
|
...lastResponse?.files ? { files: lastResponse.files } : {},
|
|
28647
|
+
...lastResponse?.builtinToolCalls ? { builtinToolCalls: lastResponse.builtinToolCalls } : {},
|
|
28471
28648
|
...lastResponse?.moderation ? { moderation: lastResponse.moderation } : {},
|
|
28472
28649
|
latencyMs: performance.now() - startPerf,
|
|
28473
28650
|
raw: null
|
|
@@ -36863,12 +37040,16 @@ var DEFAULT_TAGS = {
|
|
|
36863
37040
|
huge: "context:large"
|
|
36864
37041
|
};
|
|
36865
37042
|
var CAP_KEYS = {
|
|
36866
|
-
search: "webSearch",
|
|
36867
37043
|
vision: "vision",
|
|
36868
37044
|
tools: "toolUse",
|
|
36869
37045
|
audio: "audio",
|
|
36870
37046
|
structured: "structuredOutput"
|
|
36871
37047
|
};
|
|
37048
|
+
var BUILTIN_TOOL_KEYS = {
|
|
37049
|
+
search: "web_search",
|
|
37050
|
+
web_search: "web_search",
|
|
37051
|
+
code_interpreter: "code_interpreter"
|
|
37052
|
+
};
|
|
36872
37053
|
var KNOWN_KEYS = /* @__PURE__ */ new Set([
|
|
36873
37054
|
"price",
|
|
36874
37055
|
"context",
|
|
@@ -36878,7 +37059,8 @@ var KNOWN_KEYS = /* @__PURE__ */ new Set([
|
|
|
36878
37059
|
"status",
|
|
36879
37060
|
"provider",
|
|
36880
37061
|
"active",
|
|
36881
|
-
...Object.keys(CAP_KEYS)
|
|
37062
|
+
...Object.keys(CAP_KEYS),
|
|
37063
|
+
...Object.keys(BUILTIN_TOOL_KEYS)
|
|
36882
37064
|
]);
|
|
36883
37065
|
function parseNum(v) {
|
|
36884
37066
|
const m = /^([\d.]+)\s*([kKmM]?)$/.exec(v.trim());
|
|
@@ -36947,6 +37129,13 @@ function matches(m, c, th, tier) {
|
|
|
36947
37129
|
case "active":
|
|
36948
37130
|
return isNo(c.value) ? m.active === false : m.active !== false;
|
|
36949
37131
|
default: {
|
|
37132
|
+
const tool = BUILTIN_TOOL_KEYS[c.key];
|
|
37133
|
+
if (tool) {
|
|
37134
|
+
const inList = m.capabilities.builtinTools?.includes(tool) ?? false;
|
|
37135
|
+
const legacy = tool === "web_search" && !!m.capabilities.webSearch;
|
|
37136
|
+
const has2 = inList || legacy;
|
|
37137
|
+
return isNo(c.value) ? !has2 : has2;
|
|
37138
|
+
}
|
|
36950
37139
|
const cap = CAP_KEYS[c.key];
|
|
36951
37140
|
const has = !!m.capabilities[cap];
|
|
36952
37141
|
return isNo(c.value) ? !has : has;
|
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
|
@@ -22032,6 +22032,15 @@ var catalog_default5 = {
|
|
|
22032
22032
|
}
|
|
22033
22033
|
};
|
|
22034
22034
|
|
|
22035
|
+
// src/llm/providers/builtin-tools.ts
|
|
22036
|
+
var PROVIDER_BUILTIN_TOOLS = {
|
|
22037
|
+
anthropic: ["web_search", "code_interpreter"],
|
|
22038
|
+
openai: ["web_search", "code_interpreter"],
|
|
22039
|
+
google: ["web_search", "code_interpreter"],
|
|
22040
|
+
xai: ["web_search", "code_interpreter"],
|
|
22041
|
+
openrouter: ["web_search"]
|
|
22042
|
+
};
|
|
22043
|
+
|
|
22035
22044
|
// src/plugins/model-catalog/catalog.ts
|
|
22036
22045
|
var PROVIDER_DEFAULT_CATALOGS = [
|
|
22037
22046
|
catalog_default,
|
|
@@ -22069,6 +22078,13 @@ var DEFAULT_REASONING = {
|
|
|
22069
22078
|
encryptedContent: false,
|
|
22070
22079
|
summaryAvailable: false
|
|
22071
22080
|
};
|
|
22081
|
+
function withBuiltinTools(provider, caps) {
|
|
22082
|
+
if (caps.builtinTools !== void 0) return caps;
|
|
22083
|
+
if (!caps.toolUse) return caps;
|
|
22084
|
+
const tools = PROVIDER_BUILTIN_TOOLS[provider];
|
|
22085
|
+
if (!tools || tools.length === 0) return caps;
|
|
22086
|
+
return { ...caps, builtinTools: [...tools] };
|
|
22087
|
+
}
|
|
22072
22088
|
var ModelCatalog = class {
|
|
22073
22089
|
models = /* @__PURE__ */ new Map();
|
|
22074
22090
|
/** `provider/alias` → `provider/canonical-slug`. Lets get()/resolveModelId
|
|
@@ -22087,7 +22103,7 @@ var ModelCatalog = class {
|
|
|
22087
22103
|
supportedApis: info.supportedApis ?? [info.preferredApi ?? "completions"],
|
|
22088
22104
|
contextWindow: info.contextWindow,
|
|
22089
22105
|
maxOutput: info.maxOutput,
|
|
22090
|
-
capabilities: { ...DEFAULT_CAPABILITIES, ...info.capabilities },
|
|
22106
|
+
capabilities: withBuiltinTools(provider, { ...DEFAULT_CAPABILITIES, ...info.capabilities }),
|
|
22091
22107
|
reasoning: { ...DEFAULT_REASONING, ...info.reasoning },
|
|
22092
22108
|
mediaOnly: info.mediaOnly,
|
|
22093
22109
|
tokenizer: info.tokenizer,
|
|
@@ -22141,6 +22157,15 @@ var ModelCatalog = class {
|
|
|
22141
22157
|
supportsTools(provider, model) {
|
|
22142
22158
|
return this.get(provider, model)?.capabilities.toolUse ?? false;
|
|
22143
22159
|
}
|
|
22160
|
+
/** Hosted server-side builtin tools this model supports (e.g. `['web_search',
|
|
22161
|
+
* 'code_interpreter']`). Empty when unknown or the model isn't tool-capable. */
|
|
22162
|
+
builtinToolsFor(provider, model) {
|
|
22163
|
+
return [...this.get(provider, model)?.capabilities.builtinTools ?? []];
|
|
22164
|
+
}
|
|
22165
|
+
/** Whether this model supports a specific hosted builtin tool. */
|
|
22166
|
+
supportsBuiltinTool(provider, model, tool) {
|
|
22167
|
+
return this.get(provider, model)?.capabilities.builtinTools?.includes(tool) ?? false;
|
|
22168
|
+
}
|
|
22144
22169
|
supportsPreviousResponseId(provider, model) {
|
|
22145
22170
|
const info = this.get(provider, model);
|
|
22146
22171
|
if (info && !info.supportedApis.some((a) => a === "responses" || a === "interactions")) {
|
|
@@ -22984,6 +23009,7 @@ var LLMClient = class {
|
|
|
22984
23009
|
let finishReason = "stop";
|
|
22985
23010
|
let moderationReport;
|
|
22986
23011
|
const files = [];
|
|
23012
|
+
const builtinToolCalls = [];
|
|
22987
23013
|
const fetchStream = this.fetchStreamFn;
|
|
22988
23014
|
const queueName = this.queueName;
|
|
22989
23015
|
const priority = this.priority;
|
|
@@ -23035,6 +23061,9 @@ var LLMClient = class {
|
|
|
23035
23061
|
case "file":
|
|
23036
23062
|
files.push(event.file);
|
|
23037
23063
|
break;
|
|
23064
|
+
case "builtin_tool_start":
|
|
23065
|
+
builtinToolCalls.push({ tool: event.tool, ...event.id ? { id: event.id } : {} });
|
|
23066
|
+
break;
|
|
23038
23067
|
case "moderation":
|
|
23039
23068
|
moderationReport = this.mergeModeration(
|
|
23040
23069
|
moderationReport,
|
|
@@ -23058,6 +23087,7 @@ var LLMClient = class {
|
|
|
23058
23087
|
thinking: thinking || null,
|
|
23059
23088
|
media: [],
|
|
23060
23089
|
...files.length ? { files } : {},
|
|
23090
|
+
...builtinToolCalls.length ? { builtinToolCalls } : {},
|
|
23061
23091
|
...moderationReport ? { moderation: moderationReport } : {},
|
|
23062
23092
|
latencyMs: performance.now() - start,
|
|
23063
23093
|
raw: null
|
|
@@ -23291,6 +23321,24 @@ var AnthropicFileAdapter = class {
|
|
|
23291
23321
|
}
|
|
23292
23322
|
};
|
|
23293
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
|
+
|
|
23294
23342
|
// src/llm/providers/_shared/constants.ts
|
|
23295
23343
|
var AUDIO_PCM16_SAMPLE_RATE_HZ = 24e3;
|
|
23296
23344
|
var DEFAULT_MAX_TOKENS = 4096;
|
|
@@ -23500,6 +23548,7 @@ var AnthropicAdapter = class {
|
|
|
23500
23548
|
let thinking = null;
|
|
23501
23549
|
const toolCalls = [];
|
|
23502
23550
|
const files = [];
|
|
23551
|
+
const builtinToolCalls = [];
|
|
23503
23552
|
for (const block of contentBlocks) {
|
|
23504
23553
|
if (block.type === "text") {
|
|
23505
23554
|
content.push({ type: "text", text: block.text });
|
|
@@ -23514,6 +23563,11 @@ var AnthropicAdapter = class {
|
|
|
23514
23563
|
};
|
|
23515
23564
|
content.push(tc);
|
|
23516
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
|
+
});
|
|
23517
23571
|
} else {
|
|
23518
23572
|
files.push(...filesFromCodeExecBlock(block));
|
|
23519
23573
|
}
|
|
@@ -23531,6 +23585,7 @@ var AnthropicAdapter = class {
|
|
|
23531
23585
|
toolCalls,
|
|
23532
23586
|
media: [],
|
|
23533
23587
|
...files.length ? { files } : {},
|
|
23588
|
+
...builtinToolCalls.length ? { builtinToolCalls } : {},
|
|
23534
23589
|
thinking,
|
|
23535
23590
|
latencyMs,
|
|
23536
23591
|
raw
|
|
@@ -23554,8 +23609,23 @@ var AnthropicAdapter = class {
|
|
|
23554
23609
|
if (block.type === "tool_use") {
|
|
23555
23610
|
return [{ type: "tool_call_start", id: block.id, name: block.name }];
|
|
23556
23611
|
}
|
|
23557
|
-
const
|
|
23558
|
-
|
|
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;
|
|
23559
23629
|
}
|
|
23560
23630
|
if (type === "content_block_stop") {
|
|
23561
23631
|
}
|
|
@@ -24091,6 +24161,9 @@ var GoogleAdapter = class {
|
|
|
24091
24161
|
candidate.finishReason,
|
|
24092
24162
|
{ MAX_TOKENS: "length", SAFETY: "content_filter" }
|
|
24093
24163
|
);
|
|
24164
|
+
const builtinToolCalls = [];
|
|
24165
|
+
if (hasCodeExec) builtinToolCalls.push({ tool: "code_interpreter" });
|
|
24166
|
+
if (candidate.groundingMetadata) builtinToolCalls.push({ tool: "web_search" });
|
|
24094
24167
|
return {
|
|
24095
24168
|
id: crypto.randomUUID(),
|
|
24096
24169
|
// Google doesn't return a response ID in generateContent
|
|
@@ -24103,6 +24176,7 @@ var GoogleAdapter = class {
|
|
|
24103
24176
|
thinking,
|
|
24104
24177
|
media,
|
|
24105
24178
|
...files.length ? { files } : {},
|
|
24179
|
+
...builtinToolCalls.length ? { builtinToolCalls } : {},
|
|
24106
24180
|
latencyMs,
|
|
24107
24181
|
raw
|
|
24108
24182
|
};
|
|
@@ -24140,6 +24214,8 @@ var GoogleAdapter = class {
|
|
|
24140
24214
|
if (part.text !== void 0 && !part.thought)
|
|
24141
24215
|
events.push({ type: "text", text: part.text });
|
|
24142
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" });
|
|
24143
24219
|
if (part.inlineData) {
|
|
24144
24220
|
const inline = part.inlineData;
|
|
24145
24221
|
const mime = inline.mimeType;
|
|
@@ -24170,6 +24246,11 @@ var GoogleAdapter = class {
|
|
|
24170
24246
|
events.push({ type: "tool_call_end", id: "" });
|
|
24171
24247
|
}
|
|
24172
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
|
+
}
|
|
24173
24254
|
const fr = candidate.finishReason;
|
|
24174
24255
|
if (fr)
|
|
24175
24256
|
events.push({
|
|
@@ -25936,6 +26017,15 @@ function filenameForMime(mimeType) {
|
|
|
25936
26017
|
if (mimeType.startsWith("image/")) return `file.${mimeType.slice("image/".length)}`;
|
|
25937
26018
|
return "file.bin";
|
|
25938
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
|
+
}
|
|
25939
26029
|
function filesFromResponsesOutputItem(item) {
|
|
25940
26030
|
const files = [];
|
|
25941
26031
|
const type = item.type;
|
|
@@ -26139,11 +26229,14 @@ var OpenAIResponsesAdapter = class {
|
|
|
26139
26229
|
const toolCalls = [];
|
|
26140
26230
|
const media = [];
|
|
26141
26231
|
const files = [];
|
|
26232
|
+
const builtinToolCalls = [];
|
|
26142
26233
|
let thinking = null;
|
|
26143
26234
|
let text = "";
|
|
26144
26235
|
for (const item of output) {
|
|
26145
26236
|
const type = item.type;
|
|
26146
|
-
files.push(...
|
|
26237
|
+
files.push(...this.filesFromOutputItem(item));
|
|
26238
|
+
const builtinCall = builtinCallFromResponsesItem(item);
|
|
26239
|
+
if (builtinCall) builtinToolCalls.push(builtinCall);
|
|
26147
26240
|
if (type === "message") {
|
|
26148
26241
|
const itemContent = item.content ?? [];
|
|
26149
26242
|
for (const c of itemContent) {
|
|
@@ -26204,6 +26297,7 @@ var OpenAIResponsesAdapter = class {
|
|
|
26204
26297
|
thinking,
|
|
26205
26298
|
media,
|
|
26206
26299
|
...files.length ? { files } : {},
|
|
26300
|
+
...builtinToolCalls.length ? { builtinToolCalls } : {},
|
|
26207
26301
|
...moderation ? { moderation } : {},
|
|
26208
26302
|
latencyMs,
|
|
26209
26303
|
raw
|
|
@@ -26235,6 +26329,10 @@ var OpenAIResponsesAdapter = class {
|
|
|
26235
26329
|
if (item?.type === "image_generation_call") {
|
|
26236
26330
|
events.push({ type: "media_start", mediaType: "image", mimeType: "image/png" });
|
|
26237
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
|
+
}
|
|
26238
26336
|
}
|
|
26239
26337
|
if (type === "response.image_generation_call.partial_image") {
|
|
26240
26338
|
events.push({
|
|
@@ -26245,7 +26343,11 @@ var OpenAIResponsesAdapter = class {
|
|
|
26245
26343
|
}
|
|
26246
26344
|
if (type === "response.output_item.done") {
|
|
26247
26345
|
const item = data.item;
|
|
26248
|
-
for (const file of
|
|
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
|
+
}
|
|
26249
26351
|
if (item?.type === "function_call") {
|
|
26250
26352
|
events.push({ type: "tool_call_end", id: item.call_id ?? "" });
|
|
26251
26353
|
}
|
|
@@ -26281,6 +26383,12 @@ var OpenAIResponsesAdapter = class {
|
|
|
26281
26383
|
createStreamParser() {
|
|
26282
26384
|
return (event) => this.parseStreamEvent(event);
|
|
26283
26385
|
}
|
|
26386
|
+
/** Hosted code-execution output files from one output item. Overridable so
|
|
26387
|
+
* Responses-compatible providers with a different file shape (e.g. xAI, which
|
|
26388
|
+
* embeds files in the code-interpreter `logs` payload) can extend it. */
|
|
26389
|
+
filesFromOutputItem(item) {
|
|
26390
|
+
return filesFromResponsesOutputItem(item);
|
|
26391
|
+
}
|
|
26284
26392
|
parseUsage(u) {
|
|
26285
26393
|
if (!u) return emptyUsage();
|
|
26286
26394
|
const input = u.input_tokens ?? 0;
|
|
@@ -26338,6 +26446,9 @@ function filenameFor(mimeType) {
|
|
|
26338
26446
|
}
|
|
26339
26447
|
|
|
26340
26448
|
// src/llm/providers/openrouter/completions.ts
|
|
26449
|
+
function hasUrlCitation(annotations) {
|
|
26450
|
+
return Array.isArray(annotations) && annotations.some((a) => a?.type === "url_citation");
|
|
26451
|
+
}
|
|
26341
26452
|
var OpenRouterAdapter = class extends OpenAIAdapter {
|
|
26342
26453
|
name = "openrouter";
|
|
26343
26454
|
constructor(config) {
|
|
@@ -26366,6 +26477,35 @@ var OpenRouterAdapter = class extends OpenAIAdapter {
|
|
|
26366
26477
|
}
|
|
26367
26478
|
return result;
|
|
26368
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
|
+
}
|
|
26369
26509
|
};
|
|
26370
26510
|
|
|
26371
26511
|
// src/llm/providers/openrouter/responses.ts
|
|
@@ -26845,6 +26985,29 @@ var XAIMediaAdapter = class {
|
|
|
26845
26985
|
};
|
|
26846
26986
|
|
|
26847
26987
|
// src/llm/providers/xai/responses.ts
|
|
26988
|
+
function xaiCodeExecFiles(item) {
|
|
26989
|
+
if (item.type !== "code_interpreter_call") return [];
|
|
26990
|
+
const files = [];
|
|
26991
|
+
for (const out of item.outputs ?? []) {
|
|
26992
|
+
if (out.type !== "logs" || typeof out.logs !== "string") continue;
|
|
26993
|
+
let parsed;
|
|
26994
|
+
try {
|
|
26995
|
+
parsed = JSON.parse(out.logs);
|
|
26996
|
+
} catch {
|
|
26997
|
+
continue;
|
|
26998
|
+
}
|
|
26999
|
+
for (const f of parsed.output_files ?? []) {
|
|
27000
|
+
if (!Array.isArray(f.data)) continue;
|
|
27001
|
+
files.push({
|
|
27002
|
+
data: bytesToBase64(Uint8Array.from(f.data)),
|
|
27003
|
+
...typeof f.file_name === "string" ? { name: f.file_name } : {},
|
|
27004
|
+
...typeof f.mime_type === "string" ? { mimeType: f.mime_type } : {},
|
|
27005
|
+
source: "code_execution"
|
|
27006
|
+
});
|
|
27007
|
+
}
|
|
27008
|
+
}
|
|
27009
|
+
return files;
|
|
27010
|
+
}
|
|
26848
27011
|
var XAIResponsesAdapter = class extends OpenAIResponsesAdapter {
|
|
26849
27012
|
name = "xai";
|
|
26850
27013
|
constructor(config) {
|
|
@@ -26864,8 +27027,20 @@ var XAIResponsesAdapter = class extends OpenAIResponsesAdapter {
|
|
|
26864
27027
|
if (!req.model.includes("multi-agent")) {
|
|
26865
27028
|
delete body.reasoning;
|
|
26866
27029
|
}
|
|
27030
|
+
const usesCodeInterpreter = req.tools?.some(
|
|
27031
|
+
(t) => !isFunctionTool(t) && t.type === "code_interpreter"
|
|
27032
|
+
);
|
|
27033
|
+
if (usesCodeInterpreter) {
|
|
27034
|
+
const include = /* @__PURE__ */ new Set([...body.include ?? [], "code_interpreter_call.outputs"]);
|
|
27035
|
+
body.include = [...include];
|
|
27036
|
+
}
|
|
26867
27037
|
return result;
|
|
26868
27038
|
}
|
|
27039
|
+
/** xAI embeds code-execution files inline in the `logs` payload — extend the base
|
|
27040
|
+
* extraction (which handles OpenAI-style annotations / image URLs) with the xAI shape. */
|
|
27041
|
+
filesFromOutputItem(item) {
|
|
27042
|
+
return [...super.filesFromOutputItem(item), ...xaiCodeExecFiles(item)];
|
|
27043
|
+
}
|
|
26869
27044
|
};
|
|
26870
27045
|
|
|
26871
27046
|
// src/util/json-schema.ts
|
|
@@ -28199,6 +28374,7 @@ var AgentLoop = class _AgentLoop {
|
|
|
28199
28374
|
// Propagate hosted-tool file outputs + inline-moderation result from the final
|
|
28200
28375
|
// LLM response (e.g. code-execution files produced during the run).
|
|
28201
28376
|
...lastResponse?.files ? { files: lastResponse.files } : {},
|
|
28377
|
+
...lastResponse?.builtinToolCalls ? { builtinToolCalls: lastResponse.builtinToolCalls } : {},
|
|
28202
28378
|
...lastResponse?.moderation ? { moderation: lastResponse.moderation } : {},
|
|
28203
28379
|
latencyMs: performance.now() - startPerf,
|
|
28204
28380
|
raw: lastResponse?.raw ?? null
|
|
@@ -28395,6 +28571,7 @@ var AgentLoop = class _AgentLoop {
|
|
|
28395
28571
|
thinking: lastResponse?.thinking ?? null,
|
|
28396
28572
|
media: [],
|
|
28397
28573
|
...lastResponse?.files ? { files: lastResponse.files } : {},
|
|
28574
|
+
...lastResponse?.builtinToolCalls ? { builtinToolCalls: lastResponse.builtinToolCalls } : {},
|
|
28398
28575
|
...lastResponse?.moderation ? { moderation: lastResponse.moderation } : {},
|
|
28399
28576
|
latencyMs: performance.now() - startPerf,
|
|
28400
28577
|
raw: null
|
|
@@ -36790,12 +36967,16 @@ var DEFAULT_TAGS = {
|
|
|
36790
36967
|
huge: "context:large"
|
|
36791
36968
|
};
|
|
36792
36969
|
var CAP_KEYS = {
|
|
36793
|
-
search: "webSearch",
|
|
36794
36970
|
vision: "vision",
|
|
36795
36971
|
tools: "toolUse",
|
|
36796
36972
|
audio: "audio",
|
|
36797
36973
|
structured: "structuredOutput"
|
|
36798
36974
|
};
|
|
36975
|
+
var BUILTIN_TOOL_KEYS = {
|
|
36976
|
+
search: "web_search",
|
|
36977
|
+
web_search: "web_search",
|
|
36978
|
+
code_interpreter: "code_interpreter"
|
|
36979
|
+
};
|
|
36799
36980
|
var KNOWN_KEYS = /* @__PURE__ */ new Set([
|
|
36800
36981
|
"price",
|
|
36801
36982
|
"context",
|
|
@@ -36805,7 +36986,8 @@ var KNOWN_KEYS = /* @__PURE__ */ new Set([
|
|
|
36805
36986
|
"status",
|
|
36806
36987
|
"provider",
|
|
36807
36988
|
"active",
|
|
36808
|
-
...Object.keys(CAP_KEYS)
|
|
36989
|
+
...Object.keys(CAP_KEYS),
|
|
36990
|
+
...Object.keys(BUILTIN_TOOL_KEYS)
|
|
36809
36991
|
]);
|
|
36810
36992
|
function parseNum(v) {
|
|
36811
36993
|
const m = /^([\d.]+)\s*([kKmM]?)$/.exec(v.trim());
|
|
@@ -36874,6 +37056,13 @@ function matches(m, c, th, tier) {
|
|
|
36874
37056
|
case "active":
|
|
36875
37057
|
return isNo(c.value) ? m.active === false : m.active !== false;
|
|
36876
37058
|
default: {
|
|
37059
|
+
const tool = BUILTIN_TOOL_KEYS[c.key];
|
|
37060
|
+
if (tool) {
|
|
37061
|
+
const inList = m.capabilities.builtinTools?.includes(tool) ?? false;
|
|
37062
|
+
const legacy = tool === "web_search" && !!m.capabilities.webSearch;
|
|
37063
|
+
const has2 = inList || legacy;
|
|
37064
|
+
return isNo(c.value) ? !has2 : has2;
|
|
37065
|
+
}
|
|
36877
37066
|
const cap = CAP_KEYS[c.key];
|
|
36878
37067
|
const has = !!m.capabilities[cap];
|
|
36879
37068
|
return isNo(c.value) ? !has : has;
|
|
@@ -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;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Hosted server-side tools each provider's chat models support.
|
|
2
|
+
*
|
|
3
|
+
* This is ADAPTER-SOURCED: it reflects what the provider adapters actually map
|
|
4
|
+
* the unified `{ type: 'web_search' | 'code_interpreter' }` builtins onto (each
|
|
5
|
+
* to the provider's native shape), verified by the live tools-parity test. It is
|
|
6
|
+
* applied PROVIDER-LEVEL to tool-capable (chat-family) models at catalog load —
|
|
7
|
+
* a deliberate first pass; a reliable per-model source can refine it later.
|
|
8
|
+
*
|
|
9
|
+
* Coverage (verified live 2026-07):
|
|
10
|
+
* - web_search → anthropic, openai, google, xai, openrouter
|
|
11
|
+
* - code_interpreter → anthropic, openai, google, xai (NOT openrouter: it
|
|
12
|
+
* proxies function tools + its own plugins, but does not route hosted code
|
|
13
|
+
* execution — confirmed against the API). */
|
|
14
|
+
import type { ProviderName } from '../types/provider';
|
|
15
|
+
export declare const PROVIDER_BUILTIN_TOOLS: Record<ProviderName, readonly string[]>;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import type { SSEEvent } from '../../../network/types';
|
|
6
6
|
import type { ProviderAdapter, ProviderHttpRequest } from '../../types/provider';
|
|
7
7
|
import type { NormalizedRequest } from '../../types/request';
|
|
8
|
-
import { type CompletionResponse, type Usage } from '../../types/response';
|
|
8
|
+
import { type CompletionResponse, type FileOutput, type Usage } from '../../types/response';
|
|
9
9
|
import type { StreamEvent } from '../../types/stream';
|
|
10
10
|
export interface OpenAIResponsesAdapterConfig {
|
|
11
11
|
apiKey: string;
|
|
@@ -28,5 +28,9 @@ export declare class OpenAIResponsesAdapter implements ProviderAdapter {
|
|
|
28
28
|
/** Stateless — each output item finalizes with all its file annotations in a
|
|
29
29
|
* single response.output_item.done event. */
|
|
30
30
|
createStreamParser(): (event: SSEEvent) => StreamEvent[];
|
|
31
|
+
/** Hosted code-execution output files from one output item. Overridable so
|
|
32
|
+
* Responses-compatible providers with a different file shape (e.g. xAI, which
|
|
33
|
+
* embeds files in the code-interpreter `logs` payload) can extend it. */
|
|
34
|
+
protected filesFromOutputItem(item: Record<string, unknown>): FileOutput[];
|
|
31
35
|
protected parseUsage(u: Record<string, unknown> | undefined): Usage;
|
|
32
36
|
}
|
|
@@ -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
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import type { ProviderAdapter, ProviderHttpRequest } from '../../types/provider';
|
|
9
9
|
import type { NormalizedRequest } from '../../types/request';
|
|
10
|
+
import type { FileOutput } from '../../types/response';
|
|
10
11
|
import { OpenAIResponsesAdapter } from '../openai/responses';
|
|
11
12
|
export interface XAIResponsesAdapterConfig {
|
|
12
13
|
apiKey: string;
|
|
@@ -17,4 +18,7 @@ export declare class XAIResponsesAdapter extends OpenAIResponsesAdapter {
|
|
|
17
18
|
constructor(config: XAIResponsesAdapterConfig);
|
|
18
19
|
baseURL(): string;
|
|
19
20
|
buildRequest(req: NormalizedRequest): ProviderHttpRequest;
|
|
21
|
+
/** xAI embeds code-execution files inline in the `logs` payload — extend the base
|
|
22
|
+
* extraction (which handles OpenAI-style annotations / image URLs) with the xAI shape. */
|
|
23
|
+
protected filesFromOutputItem(item: Record<string, unknown>): FileOutput[];
|
|
20
24
|
}
|
|
@@ -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). */
|
|
@@ -140,6 +140,11 @@ export declare class ModelCatalog {
|
|
|
140
140
|
getPreferredApi(provider: string, model: string): ApiType | null;
|
|
141
141
|
supportsApi(provider: string, model: string, api: ApiType): boolean;
|
|
142
142
|
supportsTools(provider: string, model: string): boolean;
|
|
143
|
+
/** Hosted server-side builtin tools this model supports (e.g. `['web_search',
|
|
144
|
+
* 'code_interpreter']`). Empty when unknown or the model isn't tool-capable. */
|
|
145
|
+
builtinToolsFor(provider: string, model: string): string[];
|
|
146
|
+
/** Whether this model supports a specific hosted builtin tool. */
|
|
147
|
+
supportsBuiltinTool(provider: string, model: string, tool: string): boolean;
|
|
143
148
|
supportsPreviousResponseId(provider: string, model: string): boolean;
|
|
144
149
|
/** Server-state retention as a duration string ("30d", "72h"), or null if unsupported. */
|
|
145
150
|
getStateRetention(provider: string, model: string): string | null;
|
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",
|