@combycode/llm-sdk 1.2.0 → 1.4.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.
@@ -2524,7 +2524,7 @@ var QueueState = class {
2524
2524
  });
2525
2525
  this.processed++;
2526
2526
  if (response.ok) {
2527
- const body = await parseResponseBody(response, entry.request.responseType ?? "json");
2527
+ const body = entry.request.responseType === "stream" ? response.body : await parseResponseBody(response, entry.request.responseType ?? "json");
2528
2528
  this.semaphore.release();
2529
2529
  entry.resolve({ status: response.status, headers: resHeaders, body });
2530
2530
  return;
@@ -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")) {
@@ -22427,6 +22452,160 @@ async function* wrapParallel(raw, interval, moderate2) {
22427
22452
  }
22428
22453
  }
22429
22454
 
22455
+ // src/llm/files/retrieve.ts
22456
+ var DEFAULT_BASE = {
22457
+ anthropic: "https://api.anthropic.com",
22458
+ openai: "https://api.openai.com",
22459
+ xai: "https://api.x.ai",
22460
+ google: "https://generativelanguage.googleapis.com",
22461
+ openrouter: "https://openrouter.ai/api"
22462
+ };
22463
+ var OCTET_STREAM = "application/octet-stream";
22464
+ function contentRequest(ctx, file) {
22465
+ const base = ctx.baseURL ?? DEFAULT_BASE[ctx.provider];
22466
+ const id = file.id;
22467
+ if (ctx.provider === "anthropic") {
22468
+ return {
22469
+ url: `${base}/v1/files/${id}/content?beta=true`,
22470
+ headers: {
22471
+ "x-api-key": ctx.apiKey,
22472
+ "anthropic-version": "2023-06-01",
22473
+ "anthropic-beta": "files-api-2025-04-14",
22474
+ accept: "application/binary"
22475
+ }
22476
+ };
22477
+ }
22478
+ if (ctx.provider === "openai" || ctx.provider === "xai" || ctx.provider === "openrouter") {
22479
+ const containerId = file.ref?.containerId;
22480
+ const path = containerId ? `/v1/containers/${containerId}/files/${id}/content` : `/v1/files/${id}/content`;
22481
+ return { url: `${base}${path}`, headers: { authorization: `Bearer ${ctx.apiKey}` } };
22482
+ }
22483
+ if (ctx.provider === "google") {
22484
+ return {
22485
+ url: `${base}/v1beta/files/${id}:download?alt=media`,
22486
+ headers: { "x-goog-api-key": ctx.apiKey }
22487
+ };
22488
+ }
22489
+ throw new Error(`retrieveFile: no file-content endpoint for provider "${ctx.provider}"`);
22490
+ }
22491
+ function providerAuth(ctx, url) {
22492
+ const base = ctx.baseURL ?? DEFAULT_BASE[ctx.provider];
22493
+ if (!url.startsWith(base)) return {};
22494
+ if (ctx.provider === "anthropic") {
22495
+ return { "x-api-key": ctx.apiKey, "anthropic-version": "2023-06-01" };
22496
+ }
22497
+ if (ctx.provider === "google") return { "x-goog-api-key": ctx.apiKey };
22498
+ return { authorization: `Bearer ${ctx.apiKey}` };
22499
+ }
22500
+ async function fetchFileResponse(ctx, file, responseType) {
22501
+ if (file.url) {
22502
+ return ctx.fetch({
22503
+ url: file.url,
22504
+ method: "GET",
22505
+ headers: providerAuth(ctx, file.url),
22506
+ body: void 0,
22507
+ provider: ctx.provider,
22508
+ model: "files",
22509
+ responseType
22510
+ });
22511
+ }
22512
+ if (file.id) {
22513
+ const { url, headers } = contentRequest(ctx, file);
22514
+ return ctx.fetch({
22515
+ url,
22516
+ method: "GET",
22517
+ headers,
22518
+ body: void 0,
22519
+ provider: ctx.provider,
22520
+ model: "files",
22521
+ responseType
22522
+ });
22523
+ }
22524
+ throw new Error("retrieveFile: FileOutput has neither `data`, `url`, nor `id`");
22525
+ }
22526
+ function bytesToBlob(bytes, type) {
22527
+ const ab = new ArrayBuffer(bytes.byteLength);
22528
+ new Uint8Array(ab).set(bytes);
22529
+ return new Blob([ab], { type });
22530
+ }
22531
+ function singleChunkStream(bytes) {
22532
+ return new ReadableStream({
22533
+ start(controller) {
22534
+ controller.enqueue(bytes);
22535
+ controller.close();
22536
+ }
22537
+ });
22538
+ }
22539
+ var EXT_MIME = {
22540
+ png: "image/png",
22541
+ jpg: "image/jpeg",
22542
+ jpeg: "image/jpeg",
22543
+ gif: "image/gif",
22544
+ webp: "image/webp",
22545
+ svg: "image/svg+xml",
22546
+ bmp: "image/bmp",
22547
+ csv: "text/csv",
22548
+ tsv: "text/tab-separated-values",
22549
+ txt: "text/plain",
22550
+ json: "application/json",
22551
+ xml: "application/xml",
22552
+ html: "text/html",
22553
+ md: "text/markdown",
22554
+ pdf: "application/pdf",
22555
+ zip: "application/zip",
22556
+ parquet: "application/vnd.apache.parquet",
22557
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
22558
+ };
22559
+ function mimeFromName(name) {
22560
+ const ext = name?.includes(".") ? name.split(".").pop()?.toLowerCase() : void 0;
22561
+ return ext ? EXT_MIME[ext] : void 0;
22562
+ }
22563
+ function header(headers, name) {
22564
+ const lower = name.toLowerCase();
22565
+ for (const k in headers) if (k.toLowerCase() === lower) return headers[k];
22566
+ return void 0;
22567
+ }
22568
+ function filenameFromDisposition(cd) {
22569
+ if (!cd) return void 0;
22570
+ const ext = cd.match(/filename\*=[^']*''([^;]+)/i);
22571
+ if (ext) {
22572
+ try {
22573
+ return decodeURIComponent(ext[1].trim());
22574
+ } catch {
22575
+ }
22576
+ }
22577
+ const plain = cd.match(/filename="?([^";]+)"?/i);
22578
+ return plain ? plain[1].trim() : void 0;
22579
+ }
22580
+ async function retrieveFile(file, ctx) {
22581
+ if (file.data) {
22582
+ const blob2 = bytesToBlob(base64ToBytes(file.data), file.mimeType ?? OCTET_STREAM);
22583
+ return { blob: blob2, name: file.name, mimeType: blob2.type, size: blob2.size };
22584
+ }
22585
+ const res = await fetchFileResponse(ctx, file, "arraybuffer");
22586
+ const headers = res.headers ?? {};
22587
+ const name = filenameFromDisposition(header(headers, "content-disposition")) ?? file.name;
22588
+ const mimeType = header(headers, "content-type")?.split(";")[0].trim() || file.mimeType || mimeFromName(name) || OCTET_STREAM;
22589
+ const blob = new Blob([res.body], { type: mimeType });
22590
+ return { blob, name, mimeType, size: blob.size };
22591
+ }
22592
+ async function streamFile(file, ctx) {
22593
+ if (file.data) {
22594
+ const bytes = base64ToBytes(file.data);
22595
+ return { stream: singleChunkStream(bytes), name: file.name, mimeType: file.mimeType, size: bytes.byteLength };
22596
+ }
22597
+ const res = await fetchFileResponse(ctx, file, "stream");
22598
+ const headers = res.headers ?? {};
22599
+ const len = header(headers, "content-length");
22600
+ const name = filenameFromDisposition(header(headers, "content-disposition")) ?? file.name;
22601
+ return {
22602
+ stream: res.body,
22603
+ name,
22604
+ mimeType: header(headers, "content-type")?.split(";")[0].trim() || file.mimeType || mimeFromName(name),
22605
+ size: len ? Number(len) : void 0
22606
+ };
22607
+ }
22608
+
22430
22609
  // src/util/duration.ts
22431
22610
  var UNIT_MS = {
22432
22611
  s: 1e3,
@@ -22669,6 +22848,28 @@ var LLMClient = class {
22669
22848
  else next.output = result;
22670
22849
  return next;
22671
22850
  }
22851
+ // ─── File retrieval (hosted-tool output files) ─────────────────────────
22852
+ retrieveContext() {
22853
+ return {
22854
+ provider: this.provider,
22855
+ apiKey: this.apiKey,
22856
+ fetch: this.fetchFn,
22857
+ baseURL: this.adapter.baseURL()
22858
+ };
22859
+ }
22860
+ /** Fetch a hosted-tool output file (e.g. a code-execution file from
22861
+ * `response.files`): its bytes as a `Blob` plus `name` / `mimeType` / `size`.
22862
+ * Resolves inline `data`, a `url`, or a provider file `id` — all through this
22863
+ * client's provider + auth + engine. */
22864
+ retrieveFile(file) {
22865
+ return retrieveFile(file, this.retrieveContext());
22866
+ }
22867
+ /** Stream a hosted-tool output file: a `ReadableStream<Uint8Array>` plus
22868
+ * best-effort `name` / `mimeType` / `size` (from the response headers). For large
22869
+ * files piped straight to a file / GridFS / HTTP response without buffering. */
22870
+ streamFile(file) {
22871
+ return streamFile(file, this.retrieveContext());
22872
+ }
22672
22873
  /** Submit a request. Returns the parsed CompletionResponse. */
22673
22874
  async complete(input, options = {}) {
22674
22875
  const rawMessages = normalizeInput(input);
@@ -22880,17 +23081,18 @@ var LLMClient = class {
22880
23081
  let usage = emptyUsage();
22881
23082
  let finishReason = "stop";
22882
23083
  let moderationReport;
23084
+ const files = [];
22883
23085
  const fetchStream = this.fetchStreamFn;
22884
- const adapter = this.adapter;
22885
23086
  const queueName = this.queueName;
22886
23087
  const priority = this.priority;
23088
+ const parseStream = this.adapter.createStreamParser();
22887
23089
  async function* rawEvents() {
22888
23090
  for await (const sseEvent of fetchStream(httpReq, {
22889
23091
  queueName,
22890
23092
  priority,
22891
23093
  ctx
22892
23094
  })) {
22893
- for (const ev of adapter.parseStreamEvent(sseEvent)) yield ev;
23095
+ for (const ev of parseStream(sseEvent)) yield ev;
22894
23096
  }
22895
23097
  }
22896
23098
  const mod = options.moderation;
@@ -22928,6 +23130,9 @@ var LLMClient = class {
22928
23130
  case "done":
22929
23131
  finishReason = event.finishReason;
22930
23132
  break;
23133
+ case "file":
23134
+ files.push(event.file);
23135
+ break;
22931
23136
  case "moderation":
22932
23137
  moderationReport = this.mergeModeration(
22933
23138
  moderationReport,
@@ -22950,6 +23155,7 @@ var LLMClient = class {
22950
23155
  toolCalls: [],
22951
23156
  thinking: thinking || null,
22952
23157
  media: [],
23158
+ ...files.length ? { files } : {},
22953
23159
  ...moderationReport ? { moderation: moderationReport } : {},
22954
23160
  latencyMs: performance.now() - start,
22955
23161
  raw: null
@@ -23210,6 +23416,22 @@ function anthropicRequestTier(t) {
23210
23416
  function anthropicBilledTier(raw) {
23211
23417
  return typeof raw === "string" && raw ? { serviceTier: raw, pricingTier: raw } : {};
23212
23418
  }
23419
+ function filesFromCodeExecBlock(block) {
23420
+ if (block.type !== "bash_code_execution_tool_result" && block.type !== "code_execution_tool_result") {
23421
+ return [];
23422
+ }
23423
+ const result = block.content;
23424
+ if (!result || result.type !== "bash_code_execution_result" && result.type !== "code_execution_result" || !Array.isArray(result.content)) {
23425
+ return [];
23426
+ }
23427
+ const files = [];
23428
+ for (const out of result.content) {
23429
+ if ((out.type === "bash_code_execution_output" || out.type === "code_execution_output") && typeof out.file_id === "string") {
23430
+ files.push({ id: out.file_id, source: "code_execution" });
23431
+ }
23432
+ }
23433
+ return files;
23434
+ }
23213
23435
  var AnthropicAdapter = class {
23214
23436
  name = "anthropic";
23215
23437
  apiKey;
@@ -23390,15 +23612,8 @@ var AnthropicAdapter = class {
23390
23612
  };
23391
23613
  content.push(tc);
23392
23614
  toolCalls.push(tc);
23393
- } else if (block.type === "bash_code_execution_tool_result" || block.type === "code_execution_tool_result") {
23394
- const result = block.content;
23395
- if (result && (result.type === "bash_code_execution_result" || result.type === "code_execution_result") && Array.isArray(result.content)) {
23396
- for (const out of result.content) {
23397
- if ((out.type === "bash_code_execution_output" || out.type === "code_execution_output") && typeof out.file_id === "string") {
23398
- files.push({ id: out.file_id, source: "code_execution" });
23399
- }
23400
- }
23401
- }
23615
+ } else {
23616
+ files.push(...filesFromCodeExecBlock(block));
23402
23617
  }
23403
23618
  }
23404
23619
  const finishReason = extractFinishReason(toolCalls.length > 0, r.stop_reason, {
@@ -23437,6 +23652,8 @@ var AnthropicAdapter = class {
23437
23652
  if (block.type === "tool_use") {
23438
23653
  return [{ type: "tool_call_start", id: block.id, name: block.name }];
23439
23654
  }
23655
+ const files = filesFromCodeExecBlock(block);
23656
+ if (files.length) return files.map((file) => ({ type: "file", file }));
23440
23657
  }
23441
23658
  if (type === "content_block_stop") {
23442
23659
  }
@@ -23460,6 +23677,11 @@ var AnthropicAdapter = class {
23460
23677
  }
23461
23678
  return [];
23462
23679
  }
23680
+ /** Stateless — every event is self-contained (code-execution result blocks
23681
+ * arrive complete in a single content_block_start). */
23682
+ createStreamParser() {
23683
+ return (event) => this.parseStreamEvent(event);
23684
+ }
23463
23685
  parseUsage(u) {
23464
23686
  if (!u) return emptyUsage();
23465
23687
  const inputTokens = u.input_tokens ?? 0;
@@ -23984,6 +24206,18 @@ var GoogleAdapter = class {
23984
24206
  };
23985
24207
  }
23986
24208
  parseStreamEvent(event) {
24209
+ return this.streamEvents(event, { codeExec: false });
24210
+ }
24211
+ /** Stateful — Google splits the code-execution marker (`executableCode` /
24212
+ * `codeExecutionResult`) and the produced file (`inlineData`) across parts and
24213
+ * often across SSE events. The closure remembers "code execution began in this
24214
+ * stream" so a later `inlineData` blob is routed to `files` (a code-exec
24215
+ * artifact) rather than `media` (conversational output). */
24216
+ createStreamParser() {
24217
+ const state = { codeExec: false };
24218
+ return (event) => this.streamEvents(event, state);
24219
+ }
24220
+ streamEvents(event, state) {
23987
24221
  const data = JSON.parse(event.data);
23988
24222
  const candidates = data.candidates ?? [];
23989
24223
  const candidate = candidates[0];
@@ -23997,6 +24231,9 @@ var GoogleAdapter = class {
23997
24231
  const rawContent = candidate.content ?? {};
23998
24232
  const parts = rawContent.parts ?? [];
23999
24233
  const events = [];
24234
+ for (const part of parts) {
24235
+ if (part.executableCode || part.codeExecutionResult) state.codeExec = true;
24236
+ }
24000
24237
  for (const part of parts) {
24001
24238
  if (part.text !== void 0 && !part.thought)
24002
24239
  events.push({ type: "text", text: part.text });
@@ -24004,10 +24241,17 @@ var GoogleAdapter = class {
24004
24241
  if (part.inlineData) {
24005
24242
  const inline = part.inlineData;
24006
24243
  const mime = inline.mimeType;
24007
- const mediaType = mime.startsWith("image/") ? "image" : mime.startsWith("audio/") ? "audio" : "video";
24008
- events.push({ type: "media_start", mediaType, mimeType: mime });
24009
- events.push({ type: "media_chunk", data: inline.data });
24010
- events.push({ type: "media_end" });
24244
+ if (state.codeExec) {
24245
+ events.push({
24246
+ type: "file",
24247
+ file: { data: inline.data, mimeType: mime, source: "code_execution" }
24248
+ });
24249
+ } else {
24250
+ const mediaType = mime.startsWith("image/") ? "image" : mime.startsWith("audio/") ? "audio" : "video";
24251
+ events.push({ type: "media_start", mediaType, mimeType: mime });
24252
+ events.push({ type: "media_chunk", data: inline.data });
24253
+ events.push({ type: "media_end" });
24254
+ }
24011
24255
  }
24012
24256
  if (part.functionCall) {
24013
24257
  const fc = part.functionCall;
@@ -24302,6 +24546,10 @@ var GoogleInteractionsAdapter = class {
24302
24546
  }
24303
24547
  return events;
24304
24548
  }
24549
+ /** Stateless — the Interactions adapter surfaces no code-execution file outputs. */
24550
+ createStreamParser() {
24551
+ return (event) => this.parseStreamEvent(event);
24552
+ }
24305
24553
  parseUsage(u) {
24306
24554
  if (!u) return emptyUsage();
24307
24555
  const input = u.total_input_tokens ?? u.prompt_tokens ?? 0;
@@ -25267,6 +25515,10 @@ var OpenAIAdapter = class {
25267
25515
  }
25268
25516
  return events;
25269
25517
  }
25518
+ /** Stateless — Chat Completions has no hosted code-execution file outputs. */
25519
+ createStreamParser() {
25520
+ return (event) => this.parseStreamEvent(event);
25521
+ }
25270
25522
  parseUsage(u) {
25271
25523
  if (!u) return emptyUsage();
25272
25524
  const input = u.prompt_tokens ?? u.input_tokens ?? 0;
@@ -25782,6 +26034,33 @@ function filenameForMime(mimeType) {
25782
26034
  if (mimeType.startsWith("image/")) return `file.${mimeType.slice("image/".length)}`;
25783
26035
  return "file.bin";
25784
26036
  }
26037
+ function filesFromResponsesOutputItem(item) {
26038
+ const files = [];
26039
+ const type = item.type;
26040
+ if (type === "message") {
26041
+ for (const c of item.content ?? []) {
26042
+ if (c.type !== "output_text") continue;
26043
+ for (const a of c.annotations ?? []) {
26044
+ if (a.type === "container_file_citation" && typeof a.file_id === "string") {
26045
+ files.push({
26046
+ id: a.file_id,
26047
+ ...typeof a.filename === "string" ? { name: a.filename } : {},
26048
+ ...typeof a.container_id === "string" ? { ref: { containerId: a.container_id } } : {},
26049
+ source: "code_execution"
26050
+ });
26051
+ }
26052
+ }
26053
+ }
26054
+ }
26055
+ if (type === "code_interpreter_call") {
26056
+ for (const out of item.outputs ?? []) {
26057
+ if (out.type === "image" && typeof out.url === "string") {
26058
+ files.push({ url: out.url, source: "code_execution" });
26059
+ }
26060
+ }
26061
+ }
26062
+ return files;
26063
+ }
25785
26064
  var OpenAIResponsesAdapter = class {
25786
26065
  name = "openai";
25787
26066
  apiKey;
@@ -25962,6 +26241,7 @@ var OpenAIResponsesAdapter = class {
25962
26241
  let text = "";
25963
26242
  for (const item of output) {
25964
26243
  const type = item.type;
26244
+ files.push(...this.filesFromOutputItem(item));
25965
26245
  if (type === "message") {
25966
26246
  const itemContent = item.content ?? [];
25967
26247
  for (const c of itemContent) {
@@ -25969,22 +26249,6 @@ var OpenAIResponsesAdapter = class {
25969
26249
  const t = c.text;
25970
26250
  text += t;
25971
26251
  content.push({ type: "text", text: t });
25972
- for (const a of c.annotations ?? []) {
25973
- if (a.type === "container_file_citation" && typeof a.file_id === "string") {
25974
- files.push({
25975
- id: a.file_id,
25976
- ...typeof a.filename === "string" ? { name: a.filename } : {},
25977
- source: "code_execution"
25978
- });
25979
- }
25980
- }
25981
- }
25982
- }
25983
- }
25984
- if (type === "code_interpreter_call") {
25985
- for (const out of item.outputs ?? []) {
25986
- if (out.type === "image" && typeof out.url === "string") {
25987
- files.push({ url: out.url, source: "code_execution" });
25988
26252
  }
25989
26253
  }
25990
26254
  }
@@ -26079,6 +26343,7 @@ var OpenAIResponsesAdapter = class {
26079
26343
  }
26080
26344
  if (type === "response.output_item.done") {
26081
26345
  const item = data.item;
26346
+ for (const file of this.filesFromOutputItem(item)) events.push({ type: "file", file });
26082
26347
  if (item?.type === "function_call") {
26083
26348
  events.push({ type: "tool_call_end", id: item.call_id ?? "" });
26084
26349
  }
@@ -26109,6 +26374,17 @@ var OpenAIResponsesAdapter = class {
26109
26374
  }
26110
26375
  return events;
26111
26376
  }
26377
+ /** Stateless — each output item finalizes with all its file annotations in a
26378
+ * single response.output_item.done event. */
26379
+ createStreamParser() {
26380
+ return (event) => this.parseStreamEvent(event);
26381
+ }
26382
+ /** Hosted code-execution output files from one output item. Overridable so
26383
+ * Responses-compatible providers with a different file shape (e.g. xAI, which
26384
+ * embeds files in the code-interpreter `logs` payload) can extend it. */
26385
+ filesFromOutputItem(item) {
26386
+ return filesFromResponsesOutputItem(item);
26387
+ }
26112
26388
  parseUsage(u) {
26113
26389
  if (!u) return emptyUsage();
26114
26390
  const input = u.input_tokens ?? 0;
@@ -26673,6 +26949,29 @@ var XAIMediaAdapter = class {
26673
26949
  };
26674
26950
 
26675
26951
  // src/llm/providers/xai/responses.ts
26952
+ function xaiCodeExecFiles(item) {
26953
+ if (item.type !== "code_interpreter_call") return [];
26954
+ const files = [];
26955
+ for (const out of item.outputs ?? []) {
26956
+ if (out.type !== "logs" || typeof out.logs !== "string") continue;
26957
+ let parsed;
26958
+ try {
26959
+ parsed = JSON.parse(out.logs);
26960
+ } catch {
26961
+ continue;
26962
+ }
26963
+ for (const f of parsed.output_files ?? []) {
26964
+ if (!Array.isArray(f.data)) continue;
26965
+ files.push({
26966
+ data: bytesToBase64(Uint8Array.from(f.data)),
26967
+ ...typeof f.file_name === "string" ? { name: f.file_name } : {},
26968
+ ...typeof f.mime_type === "string" ? { mimeType: f.mime_type } : {},
26969
+ source: "code_execution"
26970
+ });
26971
+ }
26972
+ }
26973
+ return files;
26974
+ }
26676
26975
  var XAIResponsesAdapter = class extends OpenAIResponsesAdapter {
26677
26976
  name = "xai";
26678
26977
  constructor(config) {
@@ -26692,8 +26991,20 @@ var XAIResponsesAdapter = class extends OpenAIResponsesAdapter {
26692
26991
  if (!req.model.includes("multi-agent")) {
26693
26992
  delete body.reasoning;
26694
26993
  }
26994
+ const usesCodeInterpreter = req.tools?.some(
26995
+ (t) => !isFunctionTool(t) && t.type === "code_interpreter"
26996
+ );
26997
+ if (usesCodeInterpreter) {
26998
+ const include = /* @__PURE__ */ new Set([...body.include ?? [], "code_interpreter_call.outputs"]);
26999
+ body.include = [...include];
27000
+ }
26695
27001
  return result;
26696
27002
  }
27003
+ /** xAI embeds code-execution files inline in the `logs` payload — extend the base
27004
+ * extraction (which handles OpenAI-style annotations / image URLs) with the xAI shape. */
27005
+ filesFromOutputItem(item) {
27006
+ return [...super.filesFromOutputItem(item), ...xaiCodeExecFiles(item)];
27007
+ }
26697
27008
  };
26698
27009
 
26699
27010
  // src/util/json-schema.ts
@@ -27830,6 +28141,19 @@ var AgentLoop = class _AgentLoop {
27830
28141
  get model() {
27831
28142
  return this.client.model;
27832
28143
  }
28144
+ // ─── File retrieval (hosted-tool output files) ──────────────────────────
28145
+ /** Fetch a hosted-tool output file from the run's `response.files` — its bytes
28146
+ * as a `Blob` plus `name` / `mimeType` / `size`. Delegates to the underlying
28147
+ * client, so it uses this agent's provider + key (same as `retrieveFile` on a
28148
+ * `complete()` result). */
28149
+ retrieveFile(file) {
28150
+ return this.client.retrieveFile(file);
28151
+ }
28152
+ /** Stream a hosted-tool output file (large files) — a `ReadableStream` plus
28153
+ * best-effort `name` / `mimeType` / `size`. Delegates to the underlying client. */
28154
+ streamFile(file) {
28155
+ return this.client.streamFile(file);
28156
+ }
27833
28157
  get system() {
27834
28158
  return this._system;
27835
28159
  }
@@ -34464,7 +34788,13 @@ async function complete(opts) {
34464
34788
  serviceTier
34465
34789
  });
34466
34790
  }
34467
- const result = { text: res.text, response: res };
34791
+ const result = {
34792
+ text: res.text,
34793
+ response: res,
34794
+ // Bound to this call's client (same provider/model/key/engine).
34795
+ retrieveFile: (file) => llm.retrieveFile(file),
34796
+ streamFile: (file) => llm.streamFile(file)
34797
+ };
34468
34798
  if (opts.structured?.schema) {
34469
34799
  result.parsed = parseStructured(res.text);
34470
34800
  }
@@ -36599,12 +36929,16 @@ var DEFAULT_TAGS = {
36599
36929
  huge: "context:large"
36600
36930
  };
36601
36931
  var CAP_KEYS = {
36602
- search: "webSearch",
36603
36932
  vision: "vision",
36604
36933
  tools: "toolUse",
36605
36934
  audio: "audio",
36606
36935
  structured: "structuredOutput"
36607
36936
  };
36937
+ var BUILTIN_TOOL_KEYS = {
36938
+ search: "web_search",
36939
+ web_search: "web_search",
36940
+ code_interpreter: "code_interpreter"
36941
+ };
36608
36942
  var KNOWN_KEYS = /* @__PURE__ */ new Set([
36609
36943
  "price",
36610
36944
  "context",
@@ -36614,7 +36948,8 @@ var KNOWN_KEYS = /* @__PURE__ */ new Set([
36614
36948
  "status",
36615
36949
  "provider",
36616
36950
  "active",
36617
- ...Object.keys(CAP_KEYS)
36951
+ ...Object.keys(CAP_KEYS),
36952
+ ...Object.keys(BUILTIN_TOOL_KEYS)
36618
36953
  ]);
36619
36954
  function parseNum(v) {
36620
36955
  const m = /^([\d.]+)\s*([kKmM]?)$/.exec(v.trim());
@@ -36683,6 +37018,13 @@ function matches(m, c, th, tier) {
36683
37018
  case "active":
36684
37019
  return isNo(c.value) ? m.active === false : m.active !== false;
36685
37020
  default: {
37021
+ const tool = BUILTIN_TOOL_KEYS[c.key];
37022
+ if (tool) {
37023
+ const inList = m.capabilities.builtinTools?.includes(tool) ?? false;
37024
+ const legacy = tool === "web_search" && !!m.capabilities.webSearch;
37025
+ const has2 = inList || legacy;
37026
+ return isNo(c.value) ? !has2 : has2;
37027
+ }
36686
37028
  const cap = CAP_KEYS[c.key];
36687
37029
  const has = !!m.capabilities[cap];
36688
37030
  return isNo(c.value) ? !has : has;
package/dist/index.d.ts CHANGED
@@ -49,6 +49,7 @@ 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
51
  export type { CompletionResponse, FileOutput, FinishReason, Usage } from './llm/types/response';
52
+ export type { FileStream, RetrievedFile } from './llm/files/retrieve';
52
53
  export type { NormalizedRequest, ReasoningContext, ThinkingConfig, CacheConfig } from './llm/types/request';
53
54
  export type { MediaStreamType, StreamEvent } from './llm/types/stream';
54
55
  export type { ExecuteOptions } from './llm/types/options';