@combycode/llm-sdk 1.1.0 → 1.2.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 CHANGED
@@ -6,6 +6,35 @@ All notable changes to `@combycode/llm-sdk` are documented here. The format foll
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.2.0] - 2026-07-05
10
+
11
+ ### Fixed
12
+ - Anthropic hosted code-execution **file outputs** now surface on `response.files` (verified
13
+ live). Three fixes, found by real-key testing: (1) the producer parsed the outdated
14
+ `code_execution_tool_result` shape, but the current `code_execution_20260521` tool emits
15
+ `bash_code_execution_tool_result` → `bash_code_execution_output.file_id` (now both are
16
+ handled); (2) code execution needs the beta endpoint — requests using `code_interpreter` now
17
+ hit `/v1/messages?beta=true`, without which no file outputs are returned; (3) `AgentLoop`'s
18
+ final response dropped `files` (and `moderation`) — both are now propagated from the final
19
+ LLM response in `complete()` and `stream()`.
20
+
21
+ ### Added
22
+ - `AgentLoopConfig.toolInputGuardrails` (`ToolInputGuardrail[]`) — per-tool-call input
23
+ guardrails that validate a call's arguments before the permission/approval check. A trip
24
+ denies just that call (error result to the model) without halting the run or invoking the
25
+ HITL approver; a pass runs the normal permission/approval/execution path.
26
+ - `AgentTool.customDataExtractor` — optional hook to derive out-of-band metadata from a
27
+ successful tool result, attached to that call's `ToolCallReport.customData`. The model never
28
+ sees it (for your own telemetry/routing/audit); a throwing extractor is swallowed.
29
+ - Code-execution **file outputs** now surface on `response.files` across **all** providers
30
+ (completes the channel shipped in 1.1, which had only the Anthropic producer):
31
+ - OpenAI Responses: code-interpreter image outputs (by URL) and downloadable container
32
+ files (`container_file_citation` → file id + name).
33
+ - Google: hosted code-execution `inlineData` artifacts (base64), routed to `files`
34
+ instead of `media` when the turn used code execution.
35
+ - xAI: inherited from the OpenAI Responses adapter.
36
+ - `FileOutput` gains a `url` field (for providers that return a fetchable URL).
37
+
9
38
  ## [1.1.0] - 2026-06-30
10
39
 
11
40
  ### Added
@@ -53,5 +82,6 @@ First public release.
53
82
  - Service tiers end to end (request → bill → cost).
54
83
  - Cross-environment: runs on Node, Bun, and the browser. ESM, zero runtime deps.
55
84
 
85
+ [1.2.0]: https://github.com/combycode/llm-sdk-ts/releases/tag/v1.2.0
56
86
  [1.1.0]: https://github.com/combycode/llm-sdk-ts/releases/tag/v1.1.0
57
87
  [1.0.0]: https://github.com/combycode/llm-sdk-ts/releases/tag/v1.0.0
@@ -46,6 +46,31 @@ export interface Guardrail {
46
46
  /** Return a decision; throw only for unexpected infrastructure errors. */
47
47
  check(ctx: GuardrailCheckContext): Promise<GuardrailDecision>;
48
48
  }
49
+ /** Context for a tool-input guardrail: the specific tool call about to run. */
50
+ export interface ToolInputGuardrailContext {
51
+ toolName: string;
52
+ arguments: Record<string, unknown>;
53
+ callId: string;
54
+ step: number;
55
+ trace: TraceContext;
56
+ }
57
+ /** A tool-input guardrail decision. Unlike message-level guardrails it does NOT
58
+ * halt the run — a trip denies just this one tool call. */
59
+ export type ToolInputGuardrailDecision = {
60
+ pass: true;
61
+ } | {
62
+ pass: false;
63
+ reason: string;
64
+ };
65
+ /** Validates a tool call's arguments BEFORE it executes (and before any HITL
66
+ * approval interruption). On a trip the call is denied — the model receives the
67
+ * denial reason as an error tool result; the run continues and the approver is
68
+ * never consulted. Arguments are immutable once the model emits them, so a single
69
+ * pre-execution check is sufficient. */
70
+ export interface ToolInputGuardrail {
71
+ name: string;
72
+ check(ctx: ToolInputGuardrailContext): Promise<ToolInputGuardrailDecision> | ToolInputGuardrailDecision;
73
+ }
49
74
  export interface GuardrailTriggeredContext {
50
75
  runId: string;
51
76
  agentId: string;
@@ -5,7 +5,7 @@ import type { CacheConfig, ThinkingConfig } from '../llm/types/request';
5
5
  import type { ConversationHistory } from './history';
6
6
  import type { HistorySnapshot } from './history-types';
7
7
  import type { AgentTool } from './types';
8
- import type { Guardrail } from './guardrail-types';
8
+ import type { Guardrail, ToolInputGuardrail } from './guardrail-types';
9
9
  import type { PermissionPolicy } from '../plugins/permissions/policy';
10
10
  import type { ApprovalRequest, ApprovalDecision } from './approval-types';
11
11
  import type { Persistence } from '../plugins/persistence/types';
@@ -48,6 +48,10 @@ export interface AgentLoopConfig {
48
48
  * output guardrails run after each step's response is produced.
49
49
  * A tripwire decision halts the run with finishReason 'guardrail'. */
50
50
  guardrails?: Guardrail[];
51
+ /** Per-tool-call input guardrails. Each runs against a tool call's arguments
52
+ * BEFORE the permission/approval check and execution. A trip denies just that
53
+ * call (error result to the model) without halting the run or invoking `approve`. */
54
+ toolInputGuardrails?: ToolInputGuardrail[];
51
55
  /** Permission policy wired into the tool-execution path.
52
56
  * Called after lookup, before execution.
53
57
  * 'allow' -> proceed; 'deny' -> tool is blocked (error result to model);
@@ -39,6 +39,7 @@ export declare class AgentLoop {
39
39
  private _toolTimeout;
40
40
  private _maxSteps;
41
41
  private _guardrails;
42
+ private _toolInputGuardrails;
42
43
  private _policy;
43
44
  private _approve;
44
45
  private _checkpoint;
@@ -39,6 +39,11 @@ export interface AgentTool {
39
39
  definition: Tool;
40
40
  /** Execute the tool. Return string or structured content. */
41
41
  execute: (args: Record<string, unknown>, context: ToolExecutionContext) => Promise<string | ContentPart[]>;
42
+ /** Optional: derive out-of-band metadata from a successful tool result, attached
43
+ * to this call's `ToolCallReport.customData`. The MODEL NEVER SEES IT — it is for
44
+ * your own telemetry/routing/audit. Runs after `execute`; may be async. Errors are
45
+ * swallowed (opt-in convenience must never break the tool result). */
46
+ customDataExtractor?: (result: string | ContentPart[], args: Record<string, unknown>, context: Omit<ToolExecutionContext, 'signal'>) => unknown | Promise<unknown>;
42
47
  }
43
48
  export interface ToolExecutionContext {
44
49
  step: number;
@@ -65,6 +70,9 @@ export interface ToolCallReport {
65
70
  value: number | string | boolean;
66
71
  type: string;
67
72
  }>;
73
+ /** Out-of-band metadata from the tool's `customDataExtractor`, if any. Never sent
74
+ * to the model; present only when an extractor returned a value. */
75
+ customData?: unknown;
68
76
  }
69
77
  export interface StepReport {
70
78
  index: number;
@@ -23305,7 +23305,10 @@ var AnthropicAdapter = class {
23305
23305
  });
23306
23306
  const headers = {};
23307
23307
  if (hasFileRef) headers["anthropic-beta"] = "files-api-2025-04-14";
23308
- return { body, headers };
23308
+ const usesCodeExec = req.tools?.some(
23309
+ (t) => !isFunctionTool(t) && t.type === "code_interpreter"
23310
+ );
23311
+ return { body, headers, ...usesCodeExec ? { path: "/v1/messages?beta=true" } : {} };
23309
23312
  }
23310
23313
  enableStreaming(providerReq, _req) {
23311
23314
  providerReq.body.stream = true;
@@ -23387,11 +23390,11 @@ var AnthropicAdapter = class {
23387
23390
  };
23388
23391
  content.push(tc);
23389
23392
  toolCalls.push(tc);
23390
- } else if (block.type === "code_execution_tool_result") {
23393
+ } else if (block.type === "bash_code_execution_tool_result" || block.type === "code_execution_tool_result") {
23391
23394
  const result = block.content;
23392
- if (result?.type === "code_execution_result" && Array.isArray(result.content)) {
23395
+ if (result && (result.type === "bash_code_execution_result" || result.type === "code_execution_result") && Array.isArray(result.content)) {
23393
23396
  for (const out of result.content) {
23394
- if (out.type === "code_execution_output" && typeof out.file_id === "string") {
23397
+ if ((out.type === "bash_code_execution_output" || out.type === "code_execution_output") && typeof out.file_id === "string") {
23395
23398
  files.push({ id: out.file_id, source: "code_execution" });
23396
23399
  }
23397
23400
  }
@@ -23899,7 +23902,9 @@ var GoogleAdapter = class {
23899
23902
  const content = [];
23900
23903
  const toolCalls = [];
23901
23904
  const media = [];
23905
+ const files = [];
23902
23906
  let thinking = null;
23907
+ const hasCodeExec = parts.some((p) => p.executableCode || p.codeExecutionResult);
23903
23908
  for (const part of parts) {
23904
23909
  if (part.text !== void 0 && !part.thought) {
23905
23910
  content.push({ type: "text", text: part.text });
@@ -23910,7 +23915,9 @@ var GoogleAdapter = class {
23910
23915
  if (part.inlineData) {
23911
23916
  const inline = part.inlineData;
23912
23917
  const mime = inline.mimeType;
23913
- if (mime.startsWith("image/")) {
23918
+ if (hasCodeExec) {
23919
+ files.push({ data: inline.data, mimeType: mime, source: "code_execution" });
23920
+ } else if (mime.startsWith("image/")) {
23914
23921
  const p = {
23915
23922
  type: "image_output",
23916
23923
  mediaId: "",
@@ -23971,6 +23978,7 @@ var GoogleAdapter = class {
23971
23978
  toolCalls,
23972
23979
  thinking,
23973
23980
  media,
23981
+ ...files.length ? { files } : {},
23974
23982
  latencyMs,
23975
23983
  raw
23976
23984
  };
@@ -25949,6 +25957,7 @@ var OpenAIResponsesAdapter = class {
25949
25957
  const content = [];
25950
25958
  const toolCalls = [];
25951
25959
  const media = [];
25960
+ const files = [];
25952
25961
  let thinking = null;
25953
25962
  let text = "";
25954
25963
  for (const item of output) {
@@ -25960,6 +25969,22 @@ var OpenAIResponsesAdapter = class {
25960
25969
  const t = c.text;
25961
25970
  text += t;
25962
25971
  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" });
25963
25988
  }
25964
25989
  }
25965
25990
  }
@@ -26012,6 +26037,7 @@ var OpenAIResponsesAdapter = class {
26012
26037
  toolCalls,
26013
26038
  thinking,
26014
26039
  media,
26040
+ ...files.length ? { files } : {},
26015
26041
  ...moderation ? { moderation } : {},
26016
26042
  latencyMs,
26017
26043
  raw
@@ -27738,6 +27764,7 @@ var AgentLoop = class _AgentLoop {
27738
27764
  _toolTimeout;
27739
27765
  _maxSteps;
27740
27766
  _guardrails;
27767
+ _toolInputGuardrails;
27741
27768
  _policy;
27742
27769
  _approve;
27743
27770
  _checkpoint;
@@ -27765,6 +27792,7 @@ var AgentLoop = class _AgentLoop {
27765
27792
  this._toolTimeout = config.toolTimeout ?? DEFAULT_TOOL_TIMEOUT_MS;
27766
27793
  this._maxSteps = config.maxSteps !== void 0 && config.maxSteps > 0 ? config.maxSteps : DEFAULT_MAX_STEPS;
27767
27794
  this._guardrails = config.guardrails ?? [];
27795
+ this._toolInputGuardrails = config.toolInputGuardrails ?? [];
27768
27796
  this._policy = config.policy ?? null;
27769
27797
  this._approve = config.approve ?? null;
27770
27798
  this._checkpoint = config.checkpoint ?? null;
@@ -27983,6 +28011,10 @@ var AgentLoop = class _AgentLoop {
27983
28011
  toolCalls: lastResponse?.toolCalls ?? [],
27984
28012
  thinking: lastResponse?.thinking ?? null,
27985
28013
  media: lastResponse?.media ?? [],
28014
+ // Propagate hosted-tool file outputs + inline-moderation result from the final
28015
+ // LLM response (e.g. code-execution files produced during the run).
28016
+ ...lastResponse?.files ? { files: lastResponse.files } : {},
28017
+ ...lastResponse?.moderation ? { moderation: lastResponse.moderation } : {},
27986
28018
  latencyMs: performance.now() - startPerf,
27987
28019
  raw: lastResponse?.raw ?? null
27988
28020
  };
@@ -28177,6 +28209,8 @@ var AgentLoop = class _AgentLoop {
28177
28209
  toolCalls: lastResponse?.toolCalls ?? [],
28178
28210
  thinking: lastResponse?.thinking ?? null,
28179
28211
  media: [],
28212
+ ...lastResponse?.files ? { files: lastResponse.files } : {},
28213
+ ...lastResponse?.moderation ? { moderation: lastResponse.moderation } : {},
28180
28214
  latencyMs: performance.now() - startPerf,
28181
28215
  raw: null
28182
28216
  };
@@ -28263,6 +28297,18 @@ var AgentLoop = class _AgentLoop {
28263
28297
  runTrace
28264
28298
  );
28265
28299
  if (!lookup.found) return lookup.errorResult;
28300
+ for (const g of this._toolInputGuardrails) {
28301
+ const decision = await g.check({
28302
+ toolName: tc.name,
28303
+ arguments: tc.arguments,
28304
+ callId: tc.id,
28305
+ step,
28306
+ trace: { sessionId: runTrace.sessionId, requestId: runTrace.requestId, callId: tc.id }
28307
+ });
28308
+ if (!decision.pass) {
28309
+ return this.buildDeniedResult(tc, decision.reason, reports);
28310
+ }
28311
+ }
28266
28312
  if (this._policy !== null) {
28267
28313
  const target = {
28268
28314
  kind: "tool",
@@ -28279,7 +28325,7 @@ var AgentLoop = class _AgentLoop {
28279
28325
  try {
28280
28326
  const baseCtx = { step, callId: tc.id, metrics, trace: { sessionId: runTrace.sessionId, requestId: runTrace.requestId, callId: tc.id } };
28281
28327
  const result = await executeWithTimeout(lookup.tool, tc, baseCtx, this._toolTimeout);
28282
- return await this.buildSuccessResult(tc, result, runId, step, metrics, reports, toolStart, runTrace);
28328
+ return await this.buildSuccessResult(tc, result, runId, step, metrics, reports, toolStart, runTrace, lookup.tool, baseCtx);
28283
28329
  } catch (e) {
28284
28330
  return handleToolError(e, tc, this.hooks, runId, this.id, step, metrics, reports, toolStart, runTrace);
28285
28331
  }
@@ -28370,7 +28416,7 @@ var AgentLoop = class _AgentLoop {
28370
28416
  try {
28371
28417
  const baseCtx = { step, callId: tc.id, metrics, trace: { sessionId: runTrace.sessionId, requestId: runTrace.requestId, callId: tc.id } };
28372
28418
  const result = await executeWithTimeout(tool, tc, baseCtx, this._toolTimeout);
28373
- return await this.buildSuccessResult(tc, result, runId, step, metrics, reports, toolStart, runTrace);
28419
+ return await this.buildSuccessResult(tc, result, runId, step, metrics, reports, toolStart, runTrace, tool, baseCtx);
28374
28420
  } catch (e) {
28375
28421
  return handleToolError(e, tc, this.hooks, runId, this.id, step, metrics, reports, toolStart, runTrace);
28376
28422
  }
@@ -28382,9 +28428,16 @@ var AgentLoop = class _AgentLoop {
28382
28428
  return this.buildDeniedResult(tc, denyMsg, reports);
28383
28429
  }
28384
28430
  /** Emit onToolCallComplete, push report, and return success content part. */
28385
- async buildSuccessResult(tc, result, runId, step, metrics, reports, toolStart, runTrace) {
28431
+ async buildSuccessResult(tc, result, runId, step, metrics, reports, toolStart, runTrace, tool, context) {
28386
28432
  const latencyMs = performance.now() - toolStart;
28387
28433
  const resultStr = typeof result === "string" ? result : JSON.stringify(result);
28434
+ let customData;
28435
+ if (tool?.customDataExtractor && context) {
28436
+ try {
28437
+ customData = await tool.customDataExtractor(result, tc.arguments, context);
28438
+ } catch {
28439
+ }
28440
+ }
28388
28441
  await this.hooks.emit("onToolCallComplete", {
28389
28442
  runId,
28390
28443
  agentId: this.id,
@@ -28406,7 +28459,8 @@ var AgentLoop = class _AgentLoop {
28406
28459
  latencyMs,
28407
28460
  skipped: false,
28408
28461
  error: null,
28409
- metrics: Object.fromEntries(metrics)
28462
+ metrics: Object.fromEntries(metrics),
28463
+ ...customData !== void 0 ? { customData } : {}
28410
28464
  });
28411
28465
  return { type: "tool_result", id: tc.id, content: resultStr };
28412
28466
  }
package/dist/index.d.ts CHANGED
@@ -98,7 +98,7 @@ export type { ConversationHistoryConfig, HistoryEntry, HistorySnapshot } from '.
98
98
  export { AgentLoop } from './agent/loop';
99
99
  export type { AgentLoopConfig } from './agent/loop-config';
100
100
  export type { AgentLoopSnapshot, AgentRunReport, AgentStreamEvent, AgentTool, ContentClass, LearnInput, StepReport, TokenCountContext, TokenCounter, ToolCallReport, ToolExecutionContext } from './agent/types';
101
- export type { Guardrail, GuardrailDecision, GuardrailPass, GuardrailTrip, GuardrailCheckContext, InputGuardrailContext, OutputGuardrailContext, GuardrailTriggeredContext } from './agent/guardrail-types';
101
+ export type { Guardrail, GuardrailDecision, GuardrailPass, GuardrailTrip, GuardrailCheckContext, InputGuardrailContext, OutputGuardrailContext, GuardrailTriggeredContext, ToolInputGuardrail, ToolInputGuardrailContext, ToolInputGuardrailDecision } from './agent/guardrail-types';
102
102
  export { BearerKeyAuth } from './server/auth';
103
103
  export type { AuthPlugin, AuthVerifyResult } from './server/auth';
104
104
  export { dispatch } from './server/dispatch';
package/dist/index.js CHANGED
@@ -23232,7 +23232,10 @@ var AnthropicAdapter = class {
23232
23232
  });
23233
23233
  const headers = {};
23234
23234
  if (hasFileRef) headers["anthropic-beta"] = "files-api-2025-04-14";
23235
- return { body, headers };
23235
+ const usesCodeExec = req.tools?.some(
23236
+ (t) => !isFunctionTool(t) && t.type === "code_interpreter"
23237
+ );
23238
+ return { body, headers, ...usesCodeExec ? { path: "/v1/messages?beta=true" } : {} };
23236
23239
  }
23237
23240
  enableStreaming(providerReq, _req) {
23238
23241
  providerReq.body.stream = true;
@@ -23314,11 +23317,11 @@ var AnthropicAdapter = class {
23314
23317
  };
23315
23318
  content.push(tc);
23316
23319
  toolCalls.push(tc);
23317
- } else if (block.type === "code_execution_tool_result") {
23320
+ } else if (block.type === "bash_code_execution_tool_result" || block.type === "code_execution_tool_result") {
23318
23321
  const result = block.content;
23319
- if (result?.type === "code_execution_result" && Array.isArray(result.content)) {
23322
+ if (result && (result.type === "bash_code_execution_result" || result.type === "code_execution_result") && Array.isArray(result.content)) {
23320
23323
  for (const out of result.content) {
23321
- if (out.type === "code_execution_output" && typeof out.file_id === "string") {
23324
+ if ((out.type === "bash_code_execution_output" || out.type === "code_execution_output") && typeof out.file_id === "string") {
23322
23325
  files.push({ id: out.file_id, source: "code_execution" });
23323
23326
  }
23324
23327
  }
@@ -23826,7 +23829,9 @@ var GoogleAdapter = class {
23826
23829
  const content = [];
23827
23830
  const toolCalls = [];
23828
23831
  const media = [];
23832
+ const files = [];
23829
23833
  let thinking = null;
23834
+ const hasCodeExec = parts.some((p) => p.executableCode || p.codeExecutionResult);
23830
23835
  for (const part of parts) {
23831
23836
  if (part.text !== void 0 && !part.thought) {
23832
23837
  content.push({ type: "text", text: part.text });
@@ -23837,7 +23842,9 @@ var GoogleAdapter = class {
23837
23842
  if (part.inlineData) {
23838
23843
  const inline = part.inlineData;
23839
23844
  const mime = inline.mimeType;
23840
- if (mime.startsWith("image/")) {
23845
+ if (hasCodeExec) {
23846
+ files.push({ data: inline.data, mimeType: mime, source: "code_execution" });
23847
+ } else if (mime.startsWith("image/")) {
23841
23848
  const p = {
23842
23849
  type: "image_output",
23843
23850
  mediaId: "",
@@ -23898,6 +23905,7 @@ var GoogleAdapter = class {
23898
23905
  toolCalls,
23899
23906
  thinking,
23900
23907
  media,
23908
+ ...files.length ? { files } : {},
23901
23909
  latencyMs,
23902
23910
  raw
23903
23911
  };
@@ -25876,6 +25884,7 @@ var OpenAIResponsesAdapter = class {
25876
25884
  const content = [];
25877
25885
  const toolCalls = [];
25878
25886
  const media = [];
25887
+ const files = [];
25879
25888
  let thinking = null;
25880
25889
  let text = "";
25881
25890
  for (const item of output) {
@@ -25887,6 +25896,22 @@ var OpenAIResponsesAdapter = class {
25887
25896
  const t = c.text;
25888
25897
  text += t;
25889
25898
  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" });
25890
25915
  }
25891
25916
  }
25892
25917
  }
@@ -25939,6 +25964,7 @@ var OpenAIResponsesAdapter = class {
25939
25964
  toolCalls,
25940
25965
  thinking,
25941
25966
  media,
25967
+ ...files.length ? { files } : {},
25942
25968
  ...moderation ? { moderation } : {},
25943
25969
  latencyMs,
25944
25970
  raw
@@ -27665,6 +27691,7 @@ var AgentLoop = class _AgentLoop {
27665
27691
  _toolTimeout;
27666
27692
  _maxSteps;
27667
27693
  _guardrails;
27694
+ _toolInputGuardrails;
27668
27695
  _policy;
27669
27696
  _approve;
27670
27697
  _checkpoint;
@@ -27692,6 +27719,7 @@ var AgentLoop = class _AgentLoop {
27692
27719
  this._toolTimeout = config.toolTimeout ?? DEFAULT_TOOL_TIMEOUT_MS;
27693
27720
  this._maxSteps = config.maxSteps !== void 0 && config.maxSteps > 0 ? config.maxSteps : DEFAULT_MAX_STEPS;
27694
27721
  this._guardrails = config.guardrails ?? [];
27722
+ this._toolInputGuardrails = config.toolInputGuardrails ?? [];
27695
27723
  this._policy = config.policy ?? null;
27696
27724
  this._approve = config.approve ?? null;
27697
27725
  this._checkpoint = config.checkpoint ?? null;
@@ -27910,6 +27938,10 @@ var AgentLoop = class _AgentLoop {
27910
27938
  toolCalls: lastResponse?.toolCalls ?? [],
27911
27939
  thinking: lastResponse?.thinking ?? null,
27912
27940
  media: lastResponse?.media ?? [],
27941
+ // Propagate hosted-tool file outputs + inline-moderation result from the final
27942
+ // LLM response (e.g. code-execution files produced during the run).
27943
+ ...lastResponse?.files ? { files: lastResponse.files } : {},
27944
+ ...lastResponse?.moderation ? { moderation: lastResponse.moderation } : {},
27913
27945
  latencyMs: performance.now() - startPerf,
27914
27946
  raw: lastResponse?.raw ?? null
27915
27947
  };
@@ -28104,6 +28136,8 @@ var AgentLoop = class _AgentLoop {
28104
28136
  toolCalls: lastResponse?.toolCalls ?? [],
28105
28137
  thinking: lastResponse?.thinking ?? null,
28106
28138
  media: [],
28139
+ ...lastResponse?.files ? { files: lastResponse.files } : {},
28140
+ ...lastResponse?.moderation ? { moderation: lastResponse.moderation } : {},
28107
28141
  latencyMs: performance.now() - startPerf,
28108
28142
  raw: null
28109
28143
  };
@@ -28190,6 +28224,18 @@ var AgentLoop = class _AgentLoop {
28190
28224
  runTrace
28191
28225
  );
28192
28226
  if (!lookup.found) return lookup.errorResult;
28227
+ for (const g of this._toolInputGuardrails) {
28228
+ const decision = await g.check({
28229
+ toolName: tc.name,
28230
+ arguments: tc.arguments,
28231
+ callId: tc.id,
28232
+ step,
28233
+ trace: { sessionId: runTrace.sessionId, requestId: runTrace.requestId, callId: tc.id }
28234
+ });
28235
+ if (!decision.pass) {
28236
+ return this.buildDeniedResult(tc, decision.reason, reports);
28237
+ }
28238
+ }
28193
28239
  if (this._policy !== null) {
28194
28240
  const target = {
28195
28241
  kind: "tool",
@@ -28206,7 +28252,7 @@ var AgentLoop = class _AgentLoop {
28206
28252
  try {
28207
28253
  const baseCtx = { step, callId: tc.id, metrics, trace: { sessionId: runTrace.sessionId, requestId: runTrace.requestId, callId: tc.id } };
28208
28254
  const result = await executeWithTimeout(lookup.tool, tc, baseCtx, this._toolTimeout);
28209
- return await this.buildSuccessResult(tc, result, runId, step, metrics, reports, toolStart, runTrace);
28255
+ return await this.buildSuccessResult(tc, result, runId, step, metrics, reports, toolStart, runTrace, lookup.tool, baseCtx);
28210
28256
  } catch (e) {
28211
28257
  return handleToolError(e, tc, this.hooks, runId, this.id, step, metrics, reports, toolStart, runTrace);
28212
28258
  }
@@ -28297,7 +28343,7 @@ var AgentLoop = class _AgentLoop {
28297
28343
  try {
28298
28344
  const baseCtx = { step, callId: tc.id, metrics, trace: { sessionId: runTrace.sessionId, requestId: runTrace.requestId, callId: tc.id } };
28299
28345
  const result = await executeWithTimeout(tool, tc, baseCtx, this._toolTimeout);
28300
- return await this.buildSuccessResult(tc, result, runId, step, metrics, reports, toolStart, runTrace);
28346
+ return await this.buildSuccessResult(tc, result, runId, step, metrics, reports, toolStart, runTrace, tool, baseCtx);
28301
28347
  } catch (e) {
28302
28348
  return handleToolError(e, tc, this.hooks, runId, this.id, step, metrics, reports, toolStart, runTrace);
28303
28349
  }
@@ -28309,9 +28355,16 @@ var AgentLoop = class _AgentLoop {
28309
28355
  return this.buildDeniedResult(tc, denyMsg, reports);
28310
28356
  }
28311
28357
  /** Emit onToolCallComplete, push report, and return success content part. */
28312
- async buildSuccessResult(tc, result, runId, step, metrics, reports, toolStart, runTrace) {
28358
+ async buildSuccessResult(tc, result, runId, step, metrics, reports, toolStart, runTrace, tool, context) {
28313
28359
  const latencyMs = performance.now() - toolStart;
28314
28360
  const resultStr = typeof result === "string" ? result : JSON.stringify(result);
28361
+ let customData;
28362
+ if (tool?.customDataExtractor && context) {
28363
+ try {
28364
+ customData = await tool.customDataExtractor(result, tc.arguments, context);
28365
+ } catch {
28366
+ }
28367
+ }
28315
28368
  await this.hooks.emit("onToolCallComplete", {
28316
28369
  runId,
28317
28370
  agentId: this.id,
@@ -28333,7 +28386,8 @@ var AgentLoop = class _AgentLoop {
28333
28386
  latencyMs,
28334
28387
  skipped: false,
28335
28388
  error: null,
28336
- metrics: Object.fromEntries(metrics)
28389
+ metrics: Object.fromEntries(metrics),
28390
+ ...customData !== void 0 ? { customData } : {}
28337
28391
  });
28338
28392
  return { type: "tool_result", id: tc.id, content: resultStr };
28339
28393
  }
@@ -34,6 +34,9 @@ export interface FileOutput {
34
34
  mimeType?: string;
35
35
  /** Inline bytes (base64) when the provider returns the file inline; else absent. */
36
36
  data?: string;
37
+ /** URL to fetch the file when the provider returns one (e.g. OpenAI code-interpreter
38
+ * image outputs); else absent. */
39
+ url?: string;
37
40
  /** What produced it (e.g. 'code_execution'). */
38
41
  source?: string;
39
42
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@combycode/llm-sdk",
3
- "version": "1.1.0",
3
+ "version": "1.2.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",