@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.
package/dist/index.js CHANGED
@@ -2451,7 +2451,7 @@ var QueueState = class {
2451
2451
  });
2452
2452
  this.processed++;
2453
2453
  if (response.ok) {
2454
- const body = await parseResponseBody(response, entry.request.responseType ?? "json");
2454
+ const body = entry.request.responseType === "stream" ? response.body : await parseResponseBody(response, entry.request.responseType ?? "json");
2455
2455
  this.semaphore.release();
2456
2456
  entry.resolve({ status: response.status, headers: resHeaders, body });
2457
2457
  return;
@@ -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")) {
@@ -22354,6 +22379,160 @@ async function* wrapParallel(raw, interval, moderate2) {
22354
22379
  }
22355
22380
  }
22356
22381
 
22382
+ // src/llm/files/retrieve.ts
22383
+ var DEFAULT_BASE = {
22384
+ anthropic: "https://api.anthropic.com",
22385
+ openai: "https://api.openai.com",
22386
+ xai: "https://api.x.ai",
22387
+ google: "https://generativelanguage.googleapis.com",
22388
+ openrouter: "https://openrouter.ai/api"
22389
+ };
22390
+ var OCTET_STREAM = "application/octet-stream";
22391
+ function contentRequest(ctx, file) {
22392
+ const base = ctx.baseURL ?? DEFAULT_BASE[ctx.provider];
22393
+ const id = file.id;
22394
+ if (ctx.provider === "anthropic") {
22395
+ return {
22396
+ url: `${base}/v1/files/${id}/content?beta=true`,
22397
+ headers: {
22398
+ "x-api-key": ctx.apiKey,
22399
+ "anthropic-version": "2023-06-01",
22400
+ "anthropic-beta": "files-api-2025-04-14",
22401
+ accept: "application/binary"
22402
+ }
22403
+ };
22404
+ }
22405
+ if (ctx.provider === "openai" || ctx.provider === "xai" || ctx.provider === "openrouter") {
22406
+ const containerId = file.ref?.containerId;
22407
+ const path = containerId ? `/v1/containers/${containerId}/files/${id}/content` : `/v1/files/${id}/content`;
22408
+ return { url: `${base}${path}`, headers: { authorization: `Bearer ${ctx.apiKey}` } };
22409
+ }
22410
+ if (ctx.provider === "google") {
22411
+ return {
22412
+ url: `${base}/v1beta/files/${id}:download?alt=media`,
22413
+ headers: { "x-goog-api-key": ctx.apiKey }
22414
+ };
22415
+ }
22416
+ throw new Error(`retrieveFile: no file-content endpoint for provider "${ctx.provider}"`);
22417
+ }
22418
+ function providerAuth(ctx, url) {
22419
+ const base = ctx.baseURL ?? DEFAULT_BASE[ctx.provider];
22420
+ if (!url.startsWith(base)) return {};
22421
+ if (ctx.provider === "anthropic") {
22422
+ return { "x-api-key": ctx.apiKey, "anthropic-version": "2023-06-01" };
22423
+ }
22424
+ if (ctx.provider === "google") return { "x-goog-api-key": ctx.apiKey };
22425
+ return { authorization: `Bearer ${ctx.apiKey}` };
22426
+ }
22427
+ async function fetchFileResponse(ctx, file, responseType) {
22428
+ if (file.url) {
22429
+ return ctx.fetch({
22430
+ url: file.url,
22431
+ method: "GET",
22432
+ headers: providerAuth(ctx, file.url),
22433
+ body: void 0,
22434
+ provider: ctx.provider,
22435
+ model: "files",
22436
+ responseType
22437
+ });
22438
+ }
22439
+ if (file.id) {
22440
+ const { url, headers } = contentRequest(ctx, file);
22441
+ return ctx.fetch({
22442
+ url,
22443
+ method: "GET",
22444
+ headers,
22445
+ body: void 0,
22446
+ provider: ctx.provider,
22447
+ model: "files",
22448
+ responseType
22449
+ });
22450
+ }
22451
+ throw new Error("retrieveFile: FileOutput has neither `data`, `url`, nor `id`");
22452
+ }
22453
+ function bytesToBlob(bytes, type) {
22454
+ const ab = new ArrayBuffer(bytes.byteLength);
22455
+ new Uint8Array(ab).set(bytes);
22456
+ return new Blob([ab], { type });
22457
+ }
22458
+ function singleChunkStream(bytes) {
22459
+ return new ReadableStream({
22460
+ start(controller) {
22461
+ controller.enqueue(bytes);
22462
+ controller.close();
22463
+ }
22464
+ });
22465
+ }
22466
+ var EXT_MIME = {
22467
+ png: "image/png",
22468
+ jpg: "image/jpeg",
22469
+ jpeg: "image/jpeg",
22470
+ gif: "image/gif",
22471
+ webp: "image/webp",
22472
+ svg: "image/svg+xml",
22473
+ bmp: "image/bmp",
22474
+ csv: "text/csv",
22475
+ tsv: "text/tab-separated-values",
22476
+ txt: "text/plain",
22477
+ json: "application/json",
22478
+ xml: "application/xml",
22479
+ html: "text/html",
22480
+ md: "text/markdown",
22481
+ pdf: "application/pdf",
22482
+ zip: "application/zip",
22483
+ parquet: "application/vnd.apache.parquet",
22484
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
22485
+ };
22486
+ function mimeFromName(name) {
22487
+ const ext = name?.includes(".") ? name.split(".").pop()?.toLowerCase() : void 0;
22488
+ return ext ? EXT_MIME[ext] : void 0;
22489
+ }
22490
+ function header(headers, name) {
22491
+ const lower = name.toLowerCase();
22492
+ for (const k in headers) if (k.toLowerCase() === lower) return headers[k];
22493
+ return void 0;
22494
+ }
22495
+ function filenameFromDisposition(cd) {
22496
+ if (!cd) return void 0;
22497
+ const ext = cd.match(/filename\*=[^']*''([^;]+)/i);
22498
+ if (ext) {
22499
+ try {
22500
+ return decodeURIComponent(ext[1].trim());
22501
+ } catch {
22502
+ }
22503
+ }
22504
+ const plain = cd.match(/filename="?([^";]+)"?/i);
22505
+ return plain ? plain[1].trim() : void 0;
22506
+ }
22507
+ async function retrieveFile(file, ctx) {
22508
+ if (file.data) {
22509
+ const blob2 = bytesToBlob(base64ToBytes(file.data), file.mimeType ?? OCTET_STREAM);
22510
+ return { blob: blob2, name: file.name, mimeType: blob2.type, size: blob2.size };
22511
+ }
22512
+ const res = await fetchFileResponse(ctx, file, "arraybuffer");
22513
+ const headers = res.headers ?? {};
22514
+ const name = filenameFromDisposition(header(headers, "content-disposition")) ?? file.name;
22515
+ const mimeType = header(headers, "content-type")?.split(";")[0].trim() || file.mimeType || mimeFromName(name) || OCTET_STREAM;
22516
+ const blob = new Blob([res.body], { type: mimeType });
22517
+ return { blob, name, mimeType, size: blob.size };
22518
+ }
22519
+ async function streamFile(file, ctx) {
22520
+ if (file.data) {
22521
+ const bytes = base64ToBytes(file.data);
22522
+ return { stream: singleChunkStream(bytes), name: file.name, mimeType: file.mimeType, size: bytes.byteLength };
22523
+ }
22524
+ const res = await fetchFileResponse(ctx, file, "stream");
22525
+ const headers = res.headers ?? {};
22526
+ const len = header(headers, "content-length");
22527
+ const name = filenameFromDisposition(header(headers, "content-disposition")) ?? file.name;
22528
+ return {
22529
+ stream: res.body,
22530
+ name,
22531
+ mimeType: header(headers, "content-type")?.split(";")[0].trim() || file.mimeType || mimeFromName(name),
22532
+ size: len ? Number(len) : void 0
22533
+ };
22534
+ }
22535
+
22357
22536
  // src/util/duration.ts
22358
22537
  var UNIT_MS = {
22359
22538
  s: 1e3,
@@ -22596,6 +22775,28 @@ var LLMClient = class {
22596
22775
  else next.output = result;
22597
22776
  return next;
22598
22777
  }
22778
+ // ─── File retrieval (hosted-tool output files) ─────────────────────────
22779
+ retrieveContext() {
22780
+ return {
22781
+ provider: this.provider,
22782
+ apiKey: this.apiKey,
22783
+ fetch: this.fetchFn,
22784
+ baseURL: this.adapter.baseURL()
22785
+ };
22786
+ }
22787
+ /** Fetch a hosted-tool output file (e.g. a code-execution file from
22788
+ * `response.files`): its bytes as a `Blob` plus `name` / `mimeType` / `size`.
22789
+ * Resolves inline `data`, a `url`, or a provider file `id` — all through this
22790
+ * client's provider + auth + engine. */
22791
+ retrieveFile(file) {
22792
+ return retrieveFile(file, this.retrieveContext());
22793
+ }
22794
+ /** Stream a hosted-tool output file: a `ReadableStream<Uint8Array>` plus
22795
+ * best-effort `name` / `mimeType` / `size` (from the response headers). For large
22796
+ * files piped straight to a file / GridFS / HTTP response without buffering. */
22797
+ streamFile(file) {
22798
+ return streamFile(file, this.retrieveContext());
22799
+ }
22599
22800
  /** Submit a request. Returns the parsed CompletionResponse. */
22600
22801
  async complete(input, options = {}) {
22601
22802
  const rawMessages = normalizeInput(input);
@@ -22807,17 +23008,18 @@ var LLMClient = class {
22807
23008
  let usage = emptyUsage();
22808
23009
  let finishReason = "stop";
22809
23010
  let moderationReport;
23011
+ const files = [];
22810
23012
  const fetchStream = this.fetchStreamFn;
22811
- const adapter = this.adapter;
22812
23013
  const queueName = this.queueName;
22813
23014
  const priority = this.priority;
23015
+ const parseStream = this.adapter.createStreamParser();
22814
23016
  async function* rawEvents() {
22815
23017
  for await (const sseEvent of fetchStream(httpReq, {
22816
23018
  queueName,
22817
23019
  priority,
22818
23020
  ctx
22819
23021
  })) {
22820
- for (const ev of adapter.parseStreamEvent(sseEvent)) yield ev;
23022
+ for (const ev of parseStream(sseEvent)) yield ev;
22821
23023
  }
22822
23024
  }
22823
23025
  const mod = options.moderation;
@@ -22855,6 +23057,9 @@ var LLMClient = class {
22855
23057
  case "done":
22856
23058
  finishReason = event.finishReason;
22857
23059
  break;
23060
+ case "file":
23061
+ files.push(event.file);
23062
+ break;
22858
23063
  case "moderation":
22859
23064
  moderationReport = this.mergeModeration(
22860
23065
  moderationReport,
@@ -22877,6 +23082,7 @@ var LLMClient = class {
22877
23082
  toolCalls: [],
22878
23083
  thinking: thinking || null,
22879
23084
  media: [],
23085
+ ...files.length ? { files } : {},
22880
23086
  ...moderationReport ? { moderation: moderationReport } : {},
22881
23087
  latencyMs: performance.now() - start,
22882
23088
  raw: null
@@ -23137,6 +23343,22 @@ function anthropicRequestTier(t) {
23137
23343
  function anthropicBilledTier(raw) {
23138
23344
  return typeof raw === "string" && raw ? { serviceTier: raw, pricingTier: raw } : {};
23139
23345
  }
23346
+ function filesFromCodeExecBlock(block) {
23347
+ if (block.type !== "bash_code_execution_tool_result" && block.type !== "code_execution_tool_result") {
23348
+ return [];
23349
+ }
23350
+ const result = block.content;
23351
+ if (!result || result.type !== "bash_code_execution_result" && result.type !== "code_execution_result" || !Array.isArray(result.content)) {
23352
+ return [];
23353
+ }
23354
+ const files = [];
23355
+ for (const out of result.content) {
23356
+ if ((out.type === "bash_code_execution_output" || out.type === "code_execution_output") && typeof out.file_id === "string") {
23357
+ files.push({ id: out.file_id, source: "code_execution" });
23358
+ }
23359
+ }
23360
+ return files;
23361
+ }
23140
23362
  var AnthropicAdapter = class {
23141
23363
  name = "anthropic";
23142
23364
  apiKey;
@@ -23317,15 +23539,8 @@ var AnthropicAdapter = class {
23317
23539
  };
23318
23540
  content.push(tc);
23319
23541
  toolCalls.push(tc);
23320
- } else if (block.type === "bash_code_execution_tool_result" || block.type === "code_execution_tool_result") {
23321
- const result = block.content;
23322
- if (result && (result.type === "bash_code_execution_result" || result.type === "code_execution_result") && Array.isArray(result.content)) {
23323
- for (const out of result.content) {
23324
- if ((out.type === "bash_code_execution_output" || out.type === "code_execution_output") && typeof out.file_id === "string") {
23325
- files.push({ id: out.file_id, source: "code_execution" });
23326
- }
23327
- }
23328
- }
23542
+ } else {
23543
+ files.push(...filesFromCodeExecBlock(block));
23329
23544
  }
23330
23545
  }
23331
23546
  const finishReason = extractFinishReason(toolCalls.length > 0, r.stop_reason, {
@@ -23364,6 +23579,8 @@ var AnthropicAdapter = class {
23364
23579
  if (block.type === "tool_use") {
23365
23580
  return [{ type: "tool_call_start", id: block.id, name: block.name }];
23366
23581
  }
23582
+ const files = filesFromCodeExecBlock(block);
23583
+ if (files.length) return files.map((file) => ({ type: "file", file }));
23367
23584
  }
23368
23585
  if (type === "content_block_stop") {
23369
23586
  }
@@ -23387,6 +23604,11 @@ var AnthropicAdapter = class {
23387
23604
  }
23388
23605
  return [];
23389
23606
  }
23607
+ /** Stateless — every event is self-contained (code-execution result blocks
23608
+ * arrive complete in a single content_block_start). */
23609
+ createStreamParser() {
23610
+ return (event) => this.parseStreamEvent(event);
23611
+ }
23390
23612
  parseUsage(u) {
23391
23613
  if (!u) return emptyUsage();
23392
23614
  const inputTokens = u.input_tokens ?? 0;
@@ -23911,6 +24133,18 @@ var GoogleAdapter = class {
23911
24133
  };
23912
24134
  }
23913
24135
  parseStreamEvent(event) {
24136
+ return this.streamEvents(event, { codeExec: false });
24137
+ }
24138
+ /** Stateful — Google splits the code-execution marker (`executableCode` /
24139
+ * `codeExecutionResult`) and the produced file (`inlineData`) across parts and
24140
+ * often across SSE events. The closure remembers "code execution began in this
24141
+ * stream" so a later `inlineData` blob is routed to `files` (a code-exec
24142
+ * artifact) rather than `media` (conversational output). */
24143
+ createStreamParser() {
24144
+ const state = { codeExec: false };
24145
+ return (event) => this.streamEvents(event, state);
24146
+ }
24147
+ streamEvents(event, state) {
23914
24148
  const data = JSON.parse(event.data);
23915
24149
  const candidates = data.candidates ?? [];
23916
24150
  const candidate = candidates[0];
@@ -23924,6 +24158,9 @@ var GoogleAdapter = class {
23924
24158
  const rawContent = candidate.content ?? {};
23925
24159
  const parts = rawContent.parts ?? [];
23926
24160
  const events = [];
24161
+ for (const part of parts) {
24162
+ if (part.executableCode || part.codeExecutionResult) state.codeExec = true;
24163
+ }
23927
24164
  for (const part of parts) {
23928
24165
  if (part.text !== void 0 && !part.thought)
23929
24166
  events.push({ type: "text", text: part.text });
@@ -23931,10 +24168,17 @@ var GoogleAdapter = class {
23931
24168
  if (part.inlineData) {
23932
24169
  const inline = part.inlineData;
23933
24170
  const mime = inline.mimeType;
23934
- const mediaType = mime.startsWith("image/") ? "image" : mime.startsWith("audio/") ? "audio" : "video";
23935
- events.push({ type: "media_start", mediaType, mimeType: mime });
23936
- events.push({ type: "media_chunk", data: inline.data });
23937
- events.push({ type: "media_end" });
24171
+ if (state.codeExec) {
24172
+ events.push({
24173
+ type: "file",
24174
+ file: { data: inline.data, mimeType: mime, source: "code_execution" }
24175
+ });
24176
+ } else {
24177
+ const mediaType = mime.startsWith("image/") ? "image" : mime.startsWith("audio/") ? "audio" : "video";
24178
+ events.push({ type: "media_start", mediaType, mimeType: mime });
24179
+ events.push({ type: "media_chunk", data: inline.data });
24180
+ events.push({ type: "media_end" });
24181
+ }
23938
24182
  }
23939
24183
  if (part.functionCall) {
23940
24184
  const fc = part.functionCall;
@@ -24229,6 +24473,10 @@ var GoogleInteractionsAdapter = class {
24229
24473
  }
24230
24474
  return events;
24231
24475
  }
24476
+ /** Stateless — the Interactions adapter surfaces no code-execution file outputs. */
24477
+ createStreamParser() {
24478
+ return (event) => this.parseStreamEvent(event);
24479
+ }
24232
24480
  parseUsage(u) {
24233
24481
  if (!u) return emptyUsage();
24234
24482
  const input = u.total_input_tokens ?? u.prompt_tokens ?? 0;
@@ -25194,6 +25442,10 @@ var OpenAIAdapter = class {
25194
25442
  }
25195
25443
  return events;
25196
25444
  }
25445
+ /** Stateless — Chat Completions has no hosted code-execution file outputs. */
25446
+ createStreamParser() {
25447
+ return (event) => this.parseStreamEvent(event);
25448
+ }
25197
25449
  parseUsage(u) {
25198
25450
  if (!u) return emptyUsage();
25199
25451
  const input = u.prompt_tokens ?? u.input_tokens ?? 0;
@@ -25709,6 +25961,33 @@ function filenameForMime(mimeType) {
25709
25961
  if (mimeType.startsWith("image/")) return `file.${mimeType.slice("image/".length)}`;
25710
25962
  return "file.bin";
25711
25963
  }
25964
+ function filesFromResponsesOutputItem(item) {
25965
+ const files = [];
25966
+ const type = item.type;
25967
+ if (type === "message") {
25968
+ for (const c of item.content ?? []) {
25969
+ if (c.type !== "output_text") continue;
25970
+ for (const a of c.annotations ?? []) {
25971
+ if (a.type === "container_file_citation" && typeof a.file_id === "string") {
25972
+ files.push({
25973
+ id: a.file_id,
25974
+ ...typeof a.filename === "string" ? { name: a.filename } : {},
25975
+ ...typeof a.container_id === "string" ? { ref: { containerId: a.container_id } } : {},
25976
+ source: "code_execution"
25977
+ });
25978
+ }
25979
+ }
25980
+ }
25981
+ }
25982
+ if (type === "code_interpreter_call") {
25983
+ for (const out of item.outputs ?? []) {
25984
+ if (out.type === "image" && typeof out.url === "string") {
25985
+ files.push({ url: out.url, source: "code_execution" });
25986
+ }
25987
+ }
25988
+ }
25989
+ return files;
25990
+ }
25712
25991
  var OpenAIResponsesAdapter = class {
25713
25992
  name = "openai";
25714
25993
  apiKey;
@@ -25889,6 +26168,7 @@ var OpenAIResponsesAdapter = class {
25889
26168
  let text = "";
25890
26169
  for (const item of output) {
25891
26170
  const type = item.type;
26171
+ files.push(...this.filesFromOutputItem(item));
25892
26172
  if (type === "message") {
25893
26173
  const itemContent = item.content ?? [];
25894
26174
  for (const c of itemContent) {
@@ -25896,22 +26176,6 @@ var OpenAIResponsesAdapter = class {
25896
26176
  const t = c.text;
25897
26177
  text += t;
25898
26178
  content.push({ type: "text", text: t });
25899
- for (const a of c.annotations ?? []) {
25900
- if (a.type === "container_file_citation" && typeof a.file_id === "string") {
25901
- files.push({
25902
- id: a.file_id,
25903
- ...typeof a.filename === "string" ? { name: a.filename } : {},
25904
- source: "code_execution"
25905
- });
25906
- }
25907
- }
25908
- }
25909
- }
25910
- }
25911
- if (type === "code_interpreter_call") {
25912
- for (const out of item.outputs ?? []) {
25913
- if (out.type === "image" && typeof out.url === "string") {
25914
- files.push({ url: out.url, source: "code_execution" });
25915
26179
  }
25916
26180
  }
25917
26181
  }
@@ -26006,6 +26270,7 @@ var OpenAIResponsesAdapter = class {
26006
26270
  }
26007
26271
  if (type === "response.output_item.done") {
26008
26272
  const item = data.item;
26273
+ for (const file of this.filesFromOutputItem(item)) events.push({ type: "file", file });
26009
26274
  if (item?.type === "function_call") {
26010
26275
  events.push({ type: "tool_call_end", id: item.call_id ?? "" });
26011
26276
  }
@@ -26036,6 +26301,17 @@ var OpenAIResponsesAdapter = class {
26036
26301
  }
26037
26302
  return events;
26038
26303
  }
26304
+ /** Stateless — each output item finalizes with all its file annotations in a
26305
+ * single response.output_item.done event. */
26306
+ createStreamParser() {
26307
+ return (event) => this.parseStreamEvent(event);
26308
+ }
26309
+ /** Hosted code-execution output files from one output item. Overridable so
26310
+ * Responses-compatible providers with a different file shape (e.g. xAI, which
26311
+ * embeds files in the code-interpreter `logs` payload) can extend it. */
26312
+ filesFromOutputItem(item) {
26313
+ return filesFromResponsesOutputItem(item);
26314
+ }
26039
26315
  parseUsage(u) {
26040
26316
  if (!u) return emptyUsage();
26041
26317
  const input = u.input_tokens ?? 0;
@@ -26600,6 +26876,29 @@ var XAIMediaAdapter = class {
26600
26876
  };
26601
26877
 
26602
26878
  // src/llm/providers/xai/responses.ts
26879
+ function xaiCodeExecFiles(item) {
26880
+ if (item.type !== "code_interpreter_call") return [];
26881
+ const files = [];
26882
+ for (const out of item.outputs ?? []) {
26883
+ if (out.type !== "logs" || typeof out.logs !== "string") continue;
26884
+ let parsed;
26885
+ try {
26886
+ parsed = JSON.parse(out.logs);
26887
+ } catch {
26888
+ continue;
26889
+ }
26890
+ for (const f of parsed.output_files ?? []) {
26891
+ if (!Array.isArray(f.data)) continue;
26892
+ files.push({
26893
+ data: bytesToBase64(Uint8Array.from(f.data)),
26894
+ ...typeof f.file_name === "string" ? { name: f.file_name } : {},
26895
+ ...typeof f.mime_type === "string" ? { mimeType: f.mime_type } : {},
26896
+ source: "code_execution"
26897
+ });
26898
+ }
26899
+ }
26900
+ return files;
26901
+ }
26603
26902
  var XAIResponsesAdapter = class extends OpenAIResponsesAdapter {
26604
26903
  name = "xai";
26605
26904
  constructor(config) {
@@ -26619,8 +26918,20 @@ var XAIResponsesAdapter = class extends OpenAIResponsesAdapter {
26619
26918
  if (!req.model.includes("multi-agent")) {
26620
26919
  delete body.reasoning;
26621
26920
  }
26921
+ const usesCodeInterpreter = req.tools?.some(
26922
+ (t) => !isFunctionTool(t) && t.type === "code_interpreter"
26923
+ );
26924
+ if (usesCodeInterpreter) {
26925
+ const include = /* @__PURE__ */ new Set([...body.include ?? [], "code_interpreter_call.outputs"]);
26926
+ body.include = [...include];
26927
+ }
26622
26928
  return result;
26623
26929
  }
26930
+ /** xAI embeds code-execution files inline in the `logs` payload — extend the base
26931
+ * extraction (which handles OpenAI-style annotations / image URLs) with the xAI shape. */
26932
+ filesFromOutputItem(item) {
26933
+ return [...super.filesFromOutputItem(item), ...xaiCodeExecFiles(item)];
26934
+ }
26624
26935
  };
26625
26936
 
26626
26937
  // src/util/json-schema.ts
@@ -27757,6 +28068,19 @@ var AgentLoop = class _AgentLoop {
27757
28068
  get model() {
27758
28069
  return this.client.model;
27759
28070
  }
28071
+ // ─── File retrieval (hosted-tool output files) ──────────────────────────
28072
+ /** Fetch a hosted-tool output file from the run's `response.files` — its bytes
28073
+ * as a `Blob` plus `name` / `mimeType` / `size`. Delegates to the underlying
28074
+ * client, so it uses this agent's provider + key (same as `retrieveFile` on a
28075
+ * `complete()` result). */
28076
+ retrieveFile(file) {
28077
+ return this.client.retrieveFile(file);
28078
+ }
28079
+ /** Stream a hosted-tool output file (large files) — a `ReadableStream` plus
28080
+ * best-effort `name` / `mimeType` / `size`. Delegates to the underlying client. */
28081
+ streamFile(file) {
28082
+ return this.client.streamFile(file);
28083
+ }
27760
28084
  get system() {
27761
28085
  return this._system;
27762
28086
  }
@@ -34391,7 +34715,13 @@ async function complete(opts) {
34391
34715
  serviceTier
34392
34716
  });
34393
34717
  }
34394
- const result = { text: res.text, response: res };
34718
+ const result = {
34719
+ text: res.text,
34720
+ response: res,
34721
+ // Bound to this call's client (same provider/model/key/engine).
34722
+ retrieveFile: (file) => llm.retrieveFile(file),
34723
+ streamFile: (file) => llm.streamFile(file)
34724
+ };
34395
34725
  if (opts.structured?.schema) {
34396
34726
  result.parsed = parseStructured(res.text);
34397
34727
  }
@@ -36526,12 +36856,16 @@ var DEFAULT_TAGS = {
36526
36856
  huge: "context:large"
36527
36857
  };
36528
36858
  var CAP_KEYS = {
36529
- search: "webSearch",
36530
36859
  vision: "vision",
36531
36860
  tools: "toolUse",
36532
36861
  audio: "audio",
36533
36862
  structured: "structuredOutput"
36534
36863
  };
36864
+ var BUILTIN_TOOL_KEYS = {
36865
+ search: "web_search",
36866
+ web_search: "web_search",
36867
+ code_interpreter: "code_interpreter"
36868
+ };
36535
36869
  var KNOWN_KEYS = /* @__PURE__ */ new Set([
36536
36870
  "price",
36537
36871
  "context",
@@ -36541,7 +36875,8 @@ var KNOWN_KEYS = /* @__PURE__ */ new Set([
36541
36875
  "status",
36542
36876
  "provider",
36543
36877
  "active",
36544
- ...Object.keys(CAP_KEYS)
36878
+ ...Object.keys(CAP_KEYS),
36879
+ ...Object.keys(BUILTIN_TOOL_KEYS)
36545
36880
  ]);
36546
36881
  function parseNum(v) {
36547
36882
  const m = /^([\d.]+)\s*([kKmM]?)$/.exec(v.trim());
@@ -36610,6 +36945,13 @@ function matches(m, c, th, tier) {
36610
36945
  case "active":
36611
36946
  return isNo(c.value) ? m.active === false : m.active !== false;
36612
36947
  default: {
36948
+ const tool = BUILTIN_TOOL_KEYS[c.key];
36949
+ if (tool) {
36950
+ const inList = m.capabilities.builtinTools?.includes(tool) ?? false;
36951
+ const legacy = tool === "web_search" && !!m.capabilities.webSearch;
36952
+ const has2 = inList || legacy;
36953
+ return isNo(c.value) ? !has2 : has2;
36954
+ }
36613
36955
  const cap = CAP_KEYS[c.key];
36614
36956
  const has = !!m.capabilities[cap];
36615
36957
  return isNo(c.value) ? !has : has;
@@ -18,10 +18,11 @@
18
18
  * Hooks emitted: onClientCreate (in ctor), onMessageResolve, onBeforeSubmit,
19
19
  * onCompletion, onClientDestroy. */
20
20
  import type { HookBus } from '../bus/hook-bus';
21
+ import type { FileStream, RetrievedFile } from './files/retrieve';
21
22
  import type { ContentPart, Message } from './types/messages';
22
23
  import type { ExecuteOptions } from './types/options';
23
24
  import type { ApiType, ProviderName } from './types/provider';
24
- import type { CompletionResponse } from './types/response';
25
+ import type { CompletionResponse, FileOutput } from './types/response';
25
26
  import type { StreamEvent } from './types/stream';
26
27
  import type { LLMClientConfig } from './client-config';
27
28
  export declare class LLMClient {
@@ -67,6 +68,16 @@ export declare class LLMClient {
67
68
  private moderationConfig;
68
69
  /** Fold a moderation stream event into the accumulating report. */
69
70
  private mergeModeration;
71
+ private retrieveContext;
72
+ /** Fetch a hosted-tool output file (e.g. a code-execution file from
73
+ * `response.files`): its bytes as a `Blob` plus `name` / `mimeType` / `size`.
74
+ * Resolves inline `data`, a `url`, or a provider file `id` — all through this
75
+ * client's provider + auth + engine. */
76
+ retrieveFile(file: FileOutput): Promise<RetrievedFile>;
77
+ /** Stream a hosted-tool output file: a `ReadableStream<Uint8Array>` plus
78
+ * best-effort `name` / `mimeType` / `size` (from the response headers). For large
79
+ * files piped straight to a file / GridFS / HTTP response without buffering. */
80
+ streamFile(file: FileOutput): Promise<FileStream>;
70
81
  /** Submit a request. Returns the parsed CompletionResponse. */
71
82
  complete(input: string | ContentPart[] | Message[], options?: ExecuteOptions): Promise<CompletionResponse>;
72
83
  /** Run `complete` with a JSON Schema enforced via `structured`. Strips any