@polos/sdk 0.1.1 → 0.1.3

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.cjs CHANGED
@@ -8,10 +8,34 @@ var Fastify = require('fastify');
8
8
  var api = require('@opentelemetry/api');
9
9
  var sdkTraceBase = require('@opentelemetry/sdk-trace-base');
10
10
  var contextAsyncHooks = require('@opentelemetry/context-async-hooks');
11
+ var zod = require('zod');
12
+ var child_process = require('child_process');
13
+ var fs2 = require('fs/promises');
14
+ var path = require('path');
11
15
 
12
16
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
13
17
 
18
+ function _interopNamespace(e) {
19
+ if (e && e.__esModule) return e;
20
+ var n = Object.create(null);
21
+ if (e) {
22
+ Object.keys(e).forEach(function (k) {
23
+ if (k !== 'default') {
24
+ var d = Object.getOwnPropertyDescriptor(e, k);
25
+ Object.defineProperty(n, k, d.get ? d : {
26
+ enumerable: true,
27
+ get: function () { return e[k]; }
28
+ });
29
+ }
30
+ });
31
+ }
32
+ n.default = e;
33
+ return Object.freeze(n);
34
+ }
35
+
14
36
  var Fastify__default = /*#__PURE__*/_interopDefault(Fastify);
37
+ var fs2__namespace = /*#__PURE__*/_interopNamespace(fs2);
38
+ var path__namespace = /*#__PURE__*/_interopNamespace(path);
15
39
 
16
40
  // src/core/registry.ts
17
41
  var WorkflowNotFoundError = class extends Error {
@@ -32,9 +56,6 @@ function createWorkflowRegistry() {
32
56
  const workflows = /* @__PURE__ */ new Map();
33
57
  return {
34
58
  register(workflow) {
35
- if (workflows.has(workflow.id)) {
36
- throw new DuplicateWorkflowError(workflow.id);
37
- }
38
59
  workflows.set(workflow.id, workflow);
39
60
  },
40
61
  get(workflowId) {
@@ -212,6 +233,12 @@ var OrchestratorClient = class {
212
233
  this.timeout = config.timeout ?? 3e4;
213
234
  this.maxRetries = config.maxRetries ?? 3;
214
235
  }
236
+ /**
237
+ * Get the API URL.
238
+ */
239
+ getApiUrl() {
240
+ return this.apiUrl;
241
+ }
215
242
  /**
216
243
  * Get the project ID.
217
244
  */
@@ -235,8 +262,8 @@ var OrchestratorClient = class {
235
262
  /**
236
263
  * Make an HTTP request with retry logic.
237
264
  */
238
- async request(method, path, options) {
239
- const url = `${this.apiUrl}${path}`;
265
+ async request(method, path3, options) {
266
+ const url = `${this.apiUrl}${path3}`;
240
267
  const retries = options?.retries ?? this.maxRetries;
241
268
  let lastError;
242
269
  for (let attempt = 0; attempt <= retries; attempt++) {
@@ -288,7 +315,7 @@ var OrchestratorClient = class {
288
315
  }
289
316
  if (attempt < retries) {
290
317
  const delay = Math.min(1e3 * Math.pow(2, attempt), 16e3);
291
- await new Promise((resolve) => setTimeout(resolve, delay));
318
+ await new Promise((resolve8) => setTimeout(resolve8, delay));
292
319
  }
293
320
  }
294
321
  }
@@ -727,7 +754,7 @@ var OrchestratorClient = class {
727
754
  if (execution.status === "completed" || execution.status === "failed" || execution.status === "cancelled") {
728
755
  return execution;
729
756
  }
730
- await new Promise((resolve) => setTimeout(resolve, pollInterval));
757
+ await new Promise((resolve8) => setTimeout(resolve8, pollInterval));
731
758
  }
732
759
  throw new Error(`Execution ${executionId} timed out after ${String(timeout)}ms`);
733
760
  }
@@ -1193,6 +1220,46 @@ function isToolWorkflow(workflow) {
1193
1220
  }
1194
1221
  function defineTool(config, handler) {
1195
1222
  const toolParameters = config.inputSchema ? zodToJsonSchema.zodToJsonSchema(config.inputSchema, { target: "openApi3" }) : { type: "object", properties: {} };
1223
+ const effectiveHandler = config.approval === "always" ? async (ctx, input) => {
1224
+ const approvalId = await ctx.step.uuid("_approval_id");
1225
+ const response = await ctx.step.suspend(
1226
+ `approve_${config.id}_${approvalId}`,
1227
+ {
1228
+ data: {
1229
+ _form: {
1230
+ title: `Approve tool: ${config.id}`,
1231
+ description: `The agent wants to use the "${config.id}" tool.`,
1232
+ fields: [
1233
+ {
1234
+ key: "approved",
1235
+ type: "boolean",
1236
+ label: "Approve this tool call?",
1237
+ required: true,
1238
+ default: false
1239
+ },
1240
+ {
1241
+ key: "feedback",
1242
+ type: "textarea",
1243
+ label: "Feedback for the agent (optional)",
1244
+ description: "If rejecting, tell the agent what to do instead.",
1245
+ required: false
1246
+ }
1247
+ ],
1248
+ context: { tool: config.id, input }
1249
+ },
1250
+ _source: "tool_approval",
1251
+ _tool: config.id
1252
+ }
1253
+ }
1254
+ );
1255
+ if (response.data?.approved !== true) {
1256
+ const feedback = response.data?.feedback;
1257
+ throw new Error(
1258
+ `Tool "${config.id}" was rejected by the user.${feedback ? ` Feedback: ${feedback}` : ""}`
1259
+ );
1260
+ }
1261
+ return handler(ctx, input);
1262
+ } : handler;
1196
1263
  const workflow = defineWorkflow(
1197
1264
  {
1198
1265
  id: config.id,
@@ -1205,7 +1272,8 @@ function defineTool(config, handler) {
1205
1272
  onStart: config.onStart,
1206
1273
  onEnd: config.onEnd
1207
1274
  },
1208
- handler
1275
+ effectiveHandler,
1276
+ config.autoRegister === void 0 ? void 0 : { autoRegister: config.autoRegister }
1209
1277
  );
1210
1278
  const toolWorkflow = Object.assign(workflow, {
1211
1279
  toolDescription: config.description,
@@ -1249,7 +1317,7 @@ function calculateDelay(attempt, options) {
1249
1317
  return Math.round(delay);
1250
1318
  }
1251
1319
  function sleep(ms) {
1252
- return new Promise((resolve) => setTimeout(resolve, ms));
1320
+ return new Promise((resolve8) => setTimeout(resolve8, ms));
1253
1321
  }
1254
1322
  async function retry(fn, options = {}) {
1255
1323
  const opts = { ...DEFAULT_OPTIONS, ...options };
@@ -1438,7 +1506,7 @@ function createStepHelper(options) {
1438
1506
  if (options2.days) totalSeconds += options2.days * 86400;
1439
1507
  if (options2.weeks) totalSeconds += options2.weeks * 604800;
1440
1508
  const totalMs = totalSeconds * 1e3;
1441
- await new Promise((resolve) => setTimeout(resolve, totalMs));
1509
+ await new Promise((resolve8) => setTimeout(resolve8, totalMs));
1442
1510
  store.set(key, { wait_until: new Date(Date.now() + totalMs).toISOString() });
1443
1511
  },
1444
1512
  async waitUntil(key, date) {
@@ -1446,7 +1514,7 @@ function createStepHelper(options) {
1446
1514
  if (cached) return;
1447
1515
  const now = Date.now();
1448
1516
  const waitMs = Math.max(0, date.getTime() - now);
1449
- await new Promise((resolve) => setTimeout(resolve, waitMs));
1517
+ await new Promise((resolve8) => setTimeout(resolve8, waitMs));
1450
1518
  store.set(key, { wait_until: date.toISOString() });
1451
1519
  },
1452
1520
  // eslint-disable-next-line @typescript-eslint/require-await -- stub implementation
@@ -1739,11 +1807,18 @@ function convertMiddlewareToolCallToPython(tc) {
1739
1807
  function convertVercelUsageToPython(usage) {
1740
1808
  const input = usage.inputTokens ?? 0;
1741
1809
  const output = usage.outputTokens ?? 0;
1742
- return {
1810
+ const result = {
1743
1811
  input_tokens: input,
1744
1812
  output_tokens: output,
1745
1813
  total_tokens: usage.totalTokens ?? input + output
1746
1814
  };
1815
+ if (usage.inputTokenDetails?.cacheReadTokens != null) {
1816
+ result.cache_read_input_tokens = usage.inputTokenDetails.cacheReadTokens;
1817
+ }
1818
+ if (usage.inputTokenDetails?.cacheWriteTokens != null) {
1819
+ result.cache_creation_input_tokens = usage.inputTokenDetails.cacheWriteTokens;
1820
+ }
1821
+ return result;
1747
1822
  }
1748
1823
  function convertFinishReason(reason) {
1749
1824
  if (!reason) return null;
@@ -1758,6 +1833,41 @@ function convertFinishReason(reason) {
1758
1833
  }
1759
1834
 
1760
1835
  // src/llm/llm.ts
1836
+ var ANTHROPIC_CACHE_BREAKPOINT = {
1837
+ anthropic: { cacheControl: { type: "ephemeral" } }
1838
+ };
1839
+ function isAnthropicModel(model) {
1840
+ return getModelProvider(model).startsWith("anthropic");
1841
+ }
1842
+ function applyAnthropicCacheControl(args, model) {
1843
+ if (!isAnthropicModel(model)) return;
1844
+ if (typeof args["system"] === "string") {
1845
+ args["system"] = {
1846
+ role: "system",
1847
+ content: args["system"],
1848
+ providerOptions: ANTHROPIC_CACHE_BREAKPOINT
1849
+ };
1850
+ }
1851
+ const tools = args["tools"];
1852
+ if (tools) {
1853
+ const toolNames = Object.keys(tools);
1854
+ if (toolNames.length > 0) {
1855
+ const lastToolName = toolNames[toolNames.length - 1];
1856
+ tools[lastToolName] = {
1857
+ ...tools[lastToolName],
1858
+ providerOptions: ANTHROPIC_CACHE_BREAKPOINT
1859
+ };
1860
+ }
1861
+ }
1862
+ const messages = args["messages"];
1863
+ if (messages && messages.length > 0) {
1864
+ const lastMsg = messages[messages.length - 1];
1865
+ messages[messages.length - 1] = {
1866
+ ...lastMsg,
1867
+ providerOptions: ANTHROPIC_CACHE_BREAKPOINT
1868
+ };
1869
+ }
1870
+ }
1761
1871
  function buildGenerateArgs(model, messages, options) {
1762
1872
  const args = { model, messages };
1763
1873
  const tools = convertToolsToVercel(options.tools);
@@ -1769,6 +1879,7 @@ function buildGenerateArgs(model, messages, options) {
1769
1879
  if (options.outputSchema) {
1770
1880
  args["experimental_output"] = ai.Output.object({ schema: options.outputSchema });
1771
1881
  }
1882
+ applyAnthropicCacheControl(args, model);
1772
1883
  return args;
1773
1884
  }
1774
1885
  var LLM = class {
@@ -2514,11 +2625,12 @@ async function agentStreamFunction(ctx, payload, agentDef) {
2514
2625
  let finalInputTokens = 0;
2515
2626
  let finalOutputTokens = 0;
2516
2627
  let finalTotalTokens = 0;
2628
+ let finalCacheReadInputTokens = 0;
2629
+ let finalCacheCreationInputTokens = 0;
2517
2630
  let lastLlmResultContent = null;
2518
2631
  const allToolResults = [];
2519
2632
  const steps = [];
2520
2633
  let endSteps = false;
2521
- let toolResults;
2522
2634
  let checkedStructuredOutput = false;
2523
2635
  let cachedHistoryMessages = null;
2524
2636
  if (agentDef.conversationHistory > 0 && conversationId) {
@@ -2559,7 +2671,7 @@ async function agentStreamFunction(ctx, payload, agentDef) {
2559
2671
  break;
2560
2672
  }
2561
2673
  }
2562
- const safetyMaxSteps = hasMaxStepsCondition ? null : parseInt(process.env["POLOS_AGENT_MAX_STEPS"] ?? "10", 10);
2674
+ const safetyMaxSteps = hasMaxStepsCondition ? null : parseInt(process.env["POLOS_AGENT_MAX_STEPS"] ?? "20", 10);
2563
2675
  const execCtxForPublish = getExecutionContext();
2564
2676
  const publishEvent = async (topic, eventData) => {
2565
2677
  if (execCtxForPublish?.orchestratorClient) {
@@ -2608,7 +2720,6 @@ async function agentStreamFunction(ctx, payload, agentDef) {
2608
2720
  agent_step: agentStep,
2609
2721
  guardrails,
2610
2722
  guardrail_max_retries: guardrailMaxRetries,
2611
- tool_results: toolResults,
2612
2723
  outputSchema: outputSchemaForLlm
2613
2724
  });
2614
2725
  if (guardrails.length > 0 && streaming && llmResult.content) {
@@ -2637,17 +2748,21 @@ async function agentStreamFunction(ctx, payload, agentDef) {
2637
2748
  temperature: agentConfig.temperature,
2638
2749
  maxTokens: agentConfig.maxOutputTokens,
2639
2750
  agent_step: agentStep,
2640
- tool_results: toolResults,
2641
2751
  outputSchema: outputSchemaForLlm
2642
2752
  },
2643
2753
  publishEvent
2644
2754
  );
2645
2755
  }
2646
- toolResults = void 0;
2647
2756
  if (llmResult.usage) {
2648
2757
  finalInputTokens += llmResult.usage.input_tokens;
2649
2758
  finalOutputTokens += llmResult.usage.output_tokens;
2650
2759
  finalTotalTokens += llmResult.usage.total_tokens;
2760
+ if (llmResult.usage.cache_read_input_tokens) {
2761
+ finalCacheReadInputTokens += llmResult.usage.cache_read_input_tokens;
2762
+ }
2763
+ if (llmResult.usage.cache_creation_input_tokens) {
2764
+ finalCacheCreationInputTokens += llmResult.usage.cache_creation_input_tokens;
2765
+ }
2651
2766
  }
2652
2767
  lastLlmResultContent = llmResult.content;
2653
2768
  const toolCalls = llmResult.tool_calls ?? [];
@@ -2656,6 +2771,7 @@ async function agentStreamFunction(ctx, payload, agentDef) {
2656
2771
  `LLM failed to generate output: agent_id=${agentDef.id}, agent_step=${String(agentStep)}`
2657
2772
  );
2658
2773
  }
2774
+ conversationMessages.push(...llmResult.raw_output);
2659
2775
  const batchWorkflows = [];
2660
2776
  const toolCallList = [];
2661
2777
  let toolResultsRecordedList = [];
@@ -2716,7 +2832,7 @@ async function agentStreamFunction(ctx, payload, agentDef) {
2716
2832
  const toolSpec = batchWorkflows[i];
2717
2833
  const toolCallInfo = toolCallList[i];
2718
2834
  const toolName = toolCallInfo.tool_name;
2719
- const toolResult = batchToolResult;
2835
+ const toolResult = batchToolResult.success ? batchToolResult.result : `Error: ${batchToolResult.error ?? "unknown error"}`;
2720
2836
  const toolCallId = toolCallInfo.tool_call_id;
2721
2837
  const toolCallCallId = toolCallInfo.tool_call_call_id;
2722
2838
  if (agentDef.agentHooks.onToolEnd.length > 0) {
@@ -2747,7 +2863,8 @@ async function agentStreamFunction(ctx, payload, agentDef) {
2747
2863
  });
2748
2864
  }
2749
2865
  allToolResults.push(...toolResultsRecordedList);
2750
- toolResults = currentIterationToolResults;
2866
+ const toolResultMessages = convertToolResultsToMessages(currentIterationToolResults);
2867
+ conversationMessages.push(...toolResultMessages);
2751
2868
  }
2752
2869
  steps.push({
2753
2870
  step: agentStep,
@@ -2755,7 +2872,7 @@ async function agentStreamFunction(ctx, payload, agentDef) {
2755
2872
  tool_calls: toolCalls,
2756
2873
  tool_results: toolResultsRecordedList,
2757
2874
  usage: llmResult.usage,
2758
- raw_output: llmResult.raw_output
2875
+ raw_output: conversationMessages
2759
2876
  });
2760
2877
  if (agentDef.agentHooks.onAgentStepEnd.length > 0) {
2761
2878
  const hookResult = await executeHookChain(agentDef.agentHooks.onAgentStepEnd, {
@@ -2772,7 +2889,7 @@ async function agentStreamFunction(ctx, payload, agentDef) {
2772
2889
  throw new Error(hookResult.error ?? "on_agent_step_end hook failed");
2773
2890
  }
2774
2891
  }
2775
- if (toolResults === void 0 || toolResults.length === 0) {
2892
+ if (currentIterationToolResults.length === 0) {
2776
2893
  endSteps = true;
2777
2894
  }
2778
2895
  if (stopConditions.length > 0 && !endSteps) {
@@ -2805,7 +2922,6 @@ async function agentStreamFunction(ctx, payload, agentDef) {
2805
2922
  checkedStructuredOutput = true;
2806
2923
  if (!parseResult.success) {
2807
2924
  endSteps = false;
2808
- conversationMessages = llmResult.raw_output;
2809
2925
  const schemaJson = JSON.stringify(agentDef.outputSchema, null, 2);
2810
2926
  const fixPrompt = `The previous response was not valid JSON matching the required schema. Please reformat your response to be valid JSON that strictly conforms to this schema:
2811
2927
 
@@ -2818,9 +2934,6 @@ Please provide ONLY valid JSON that matches the schema, with no additional text
2818
2934
  }
2819
2935
  }
2820
2936
  if (!endSteps) {
2821
- if (!checkedStructuredOutput) {
2822
- conversationMessages = llmResult.raw_output;
2823
- }
2824
2937
  agentStep++;
2825
2938
  }
2826
2939
  }
@@ -2873,7 +2986,11 @@ Please provide ONLY valid JSON that matches the schema, with no additional text
2873
2986
  usage: {
2874
2987
  input_tokens: finalInputTokens,
2875
2988
  output_tokens: finalOutputTokens,
2876
- total_tokens: finalTotalTokens
2989
+ total_tokens: finalTotalTokens,
2990
+ ...finalCacheReadInputTokens > 0 && { cache_read_input_tokens: finalCacheReadInputTokens },
2991
+ ...finalCacheCreationInputTokens > 0 && {
2992
+ cache_creation_input_tokens: finalCacheCreationInputTokens
2993
+ }
2877
2994
  }
2878
2995
  };
2879
2996
  }
@@ -3034,7 +3151,7 @@ function defineAgent(config) {
3034
3151
  maxOutputTokens: config.maxOutputTokens
3035
3152
  },
3036
3153
  input,
3037
- streaming: streamingFlag ?? true,
3154
+ streaming: streamingFlag ?? config.streamToWorkflow ?? false,
3038
3155
  conversation_id: conversationIdValue
3039
3156
  };
3040
3157
  return agentStreamFunction(ctx, streamPayload, {
@@ -4091,16 +4208,27 @@ function createOrchestratorStepHelper(orchestratorClient, cachedSteps, execCtx,
4091
4208
  if (items.length === 0) return [];
4092
4209
  const existing = checkExistingStep(key);
4093
4210
  if (existing) {
4094
- const raw = handleExistingStep(existing);
4211
+ let raw;
4212
+ try {
4213
+ raw = handleExistingStep(existing);
4214
+ } catch {
4215
+ raw = existing.outputs;
4216
+ }
4095
4217
  if (Array.isArray(raw)) {
4096
4218
  return raw.map((item) => {
4097
- if (item && typeof item === "object" && "result" in item) {
4098
- return item["result"];
4219
+ if (item && typeof item === "object" && "success" in item) {
4220
+ const rec = item;
4221
+ return {
4222
+ workflowId: typeof rec["workflow_id"] === "string" ? rec["workflow_id"] : "",
4223
+ success: rec["success"] === true,
4224
+ result: rec["result"] ?? null,
4225
+ error: typeof rec["error"] === "string" ? rec["error"] : null
4226
+ };
4099
4227
  }
4100
- return item;
4228
+ return { workflowId: "", success: true, result: item, error: null };
4101
4229
  });
4102
4230
  }
4103
- return raw;
4231
+ return raw.map((r) => ({ workflowId: "", success: true, result: r, error: null }));
4104
4232
  }
4105
4233
  const workflows = items.map((item) => {
4106
4234
  const workflowId = typeof item.workflow === "string" ? item.workflow : item.workflow.id;
@@ -4163,7 +4291,7 @@ function createOrchestratorStepHelper(orchestratorClient, cachedSteps, execCtx,
4163
4291
  });
4164
4292
  const waitThreshold = Number(process.env["POLOS_WAIT_THRESHOLD_SECONDS"] ?? "10");
4165
4293
  if (totalSeconds <= waitThreshold) {
4166
- await new Promise((resolve) => setTimeout(resolve, totalSeconds * 1e3));
4294
+ await new Promise((resolve8) => setTimeout(resolve8, totalSeconds * 1e3));
4167
4295
  await saveStepOutput(key, { wait_until: waitUntil.toISOString() });
4168
4296
  return;
4169
4297
  }
@@ -4199,7 +4327,7 @@ function createOrchestratorStepHelper(orchestratorClient, cachedSteps, execCtx,
4199
4327
  });
4200
4328
  const waitThreshold = Number(process.env["POLOS_WAIT_THRESHOLD_SECONDS"] ?? "10");
4201
4329
  if (waitSeconds <= waitThreshold) {
4202
- await new Promise((resolve) => setTimeout(resolve, waitSeconds * 1e3));
4330
+ await new Promise((resolve8) => setTimeout(resolve8, waitSeconds * 1e3));
4203
4331
  await saveStepOutput(key, { wait_until: date.toISOString() });
4204
4332
  return;
4205
4333
  }
@@ -4273,12 +4401,14 @@ function createOrchestratorStepHelper(orchestratorClient, cachedSteps, execCtx,
4273
4401
  return handleExistingStep(existing);
4274
4402
  }
4275
4403
  const topic = `workflow/${execCtx.rootWorkflowId}/${execCtx.rootExecutionId}`;
4404
+ const approvalUrl = `${orchestratorClient.getApiUrl()}/approve/${execCtx.rootExecutionId}/${key}`;
4405
+ const eventData = options?.data != null && typeof options.data === "object" && !Array.isArray(options.data) ? { ...options.data, _approval_url: approvalUrl } : { _original: options?.data, _approval_url: approvalUrl };
4276
4406
  await orchestratorClient.publishEvent({
4277
4407
  topic,
4278
4408
  events: [
4279
4409
  {
4280
4410
  eventType: `suspend_${key}`,
4281
- data: options?.data
4411
+ data: eventData
4282
4412
  }
4283
4413
  ],
4284
4414
  executionId: execCtx.executionId,
@@ -4941,10 +5071,10 @@ var Worker = class {
4941
5071
  process.on("SIGINT", signalHandler);
4942
5072
  process.on("SIGTERM", signalHandler);
4943
5073
  this.signalHandler = signalHandler;
4944
- await new Promise((resolve) => {
5074
+ await new Promise((resolve8) => {
4945
5075
  const checkState = () => {
4946
5076
  if (this.state === "stopping" || this.state === "stopped") {
4947
- resolve();
5077
+ resolve8();
4948
5078
  } else {
4949
5079
  setTimeout(checkState, 100);
4950
5080
  }
@@ -4983,7 +5113,7 @@ var Worker = class {
4983
5113
  const waitTimeout = 3e4;
4984
5114
  const waitStart = Date.now();
4985
5115
  while (this.activeExecutions.size > 0 && Date.now() - waitStart < waitTimeout) {
4986
- await new Promise((resolve) => setTimeout(resolve, 100));
5116
+ await new Promise((resolve8) => setTimeout(resolve8, 100));
4987
5117
  }
4988
5118
  if (this.activeExecutions.size > 0) {
4989
5119
  logger6.warn(`${String(this.activeExecutions.size)} executions did not complete in time`);
@@ -5539,8 +5669,1009 @@ var GuardrailResult2 = {
5539
5669
  error
5540
5670
  })
5541
5671
  };
5672
+ var askUserInputSchema = zod.z.object({
5673
+ question: zod.z.string().describe("The question to ask the user"),
5674
+ title: zod.z.string().optional().describe("Short title for the question (shown as heading)"),
5675
+ fields: zod.z.array(
5676
+ zod.z.object({
5677
+ key: zod.z.string().describe("Unique key for this field"),
5678
+ type: zod.z.enum(["text", "textarea", "number", "boolean", "select"]).describe("Field type"),
5679
+ label: zod.z.string().describe("Label shown to user"),
5680
+ description: zod.z.string().optional().describe("Help text for the field"),
5681
+ required: zod.z.boolean().optional().describe("Whether this field is required"),
5682
+ options: zod.z.array(
5683
+ zod.z.object({
5684
+ label: zod.z.string(),
5685
+ value: zod.z.string()
5686
+ })
5687
+ ).optional().describe("Options for select fields")
5688
+ })
5689
+ ).optional().describe(
5690
+ "Structured form fields for the response. If omitted, shows a single text response field."
5691
+ )
5692
+ });
5693
+ function createAskUserTool() {
5694
+ return defineTool(
5695
+ {
5696
+ id: "ask_user",
5697
+ description: "Ask the user a question and wait for their response. Use this when you need clarification, a decision, or any input from the user. You can define structured fields (text, select, boolean, etc.) for specific response formats, or omit fields for a free-text response.",
5698
+ inputSchema: askUserInputSchema
5699
+ },
5700
+ async (ctx, input) => {
5701
+ const fields = input.fields ?? [
5702
+ {
5703
+ key: "response",
5704
+ type: "textarea",
5705
+ label: input.question,
5706
+ required: true
5707
+ }
5708
+ ];
5709
+ const askId = await ctx.step.uuid("_ask_user_id");
5710
+ const response = await ctx.step.suspend(`ask_user_${askId}`, {
5711
+ data: {
5712
+ _form: {
5713
+ title: input.title ?? "Agent Question",
5714
+ description: input.question,
5715
+ fields
5716
+ },
5717
+ _source: "ask_user",
5718
+ _tool: "ask_user"
5719
+ }
5720
+ });
5721
+ return response?.["data"] ?? {};
5722
+ }
5723
+ );
5724
+ }
5725
+ var webSearchInputSchema = zod.z.object({
5726
+ query: zod.z.string().describe("The search query"),
5727
+ maxResults: zod.z.number().optional().describe("Maximum number of results to return"),
5728
+ topic: zod.z.enum(["general", "news"]).optional().describe("Topic filter: general web search or news")
5729
+ });
5730
+ function createTavilySearchFn(config) {
5731
+ return async (query, options) => {
5732
+ const apiKey = config.apiKey ?? process.env["TAVILY_API_KEY"];
5733
+ if (!apiKey) {
5734
+ throw new Error(
5735
+ "Tavily API key is required. Provide it via the apiKey option or set the TAVILY_API_KEY environment variable."
5736
+ );
5737
+ }
5738
+ const baseUrl = (config.baseUrl ?? "https://api.tavily.com").replace(/\/+$/, "");
5739
+ const body = {
5740
+ query,
5741
+ max_results: options.maxResults,
5742
+ search_depth: config.searchDepth ?? "basic",
5743
+ include_answer: config.includeAnswer ?? true,
5744
+ include_raw_content: config.includeRawContent ?? false,
5745
+ topic: options.topic
5746
+ };
5747
+ const response = await fetch(`${baseUrl}/search`, {
5748
+ method: "POST",
5749
+ headers: {
5750
+ "Content-Type": "application/json",
5751
+ Authorization: `Bearer ${apiKey}`
5752
+ },
5753
+ body: JSON.stringify(body)
5754
+ });
5755
+ if (!response.ok) {
5756
+ let errorMessage;
5757
+ try {
5758
+ const errorBody = await response.json();
5759
+ errorMessage = typeof errorBody["detail"] === "string" ? errorBody["detail"] : JSON.stringify(errorBody);
5760
+ } catch {
5761
+ errorMessage = await response.text();
5762
+ }
5763
+ throw new Error(`Tavily API error (${String(response.status)}): ${errorMessage}`);
5764
+ }
5765
+ const data = await response.json();
5766
+ return {
5767
+ query: data.query,
5768
+ answer: data.answer,
5769
+ results: data.results.map((r) => ({
5770
+ title: r.title,
5771
+ url: r.url,
5772
+ content: r.content,
5773
+ score: r.score,
5774
+ publishedDate: r.published_date
5775
+ }))
5776
+ };
5777
+ };
5778
+ }
5779
+ function createWebSearchTool(config) {
5780
+ const toolId = config?.toolId ?? "web_search";
5781
+ const defaultMaxResults = config?.maxResults ?? 5;
5782
+ const defaultTopic = config?.topic ?? "general";
5783
+ const searchFn = config?.search ?? createTavilySearchFn(config ?? {});
5784
+ return defineTool(
5785
+ {
5786
+ id: toolId,
5787
+ description: "Search the web for current information. Returns a list of relevant results with titles, URLs, and content snippets.",
5788
+ inputSchema: webSearchInputSchema,
5789
+ approval: config?.approval
5790
+ },
5791
+ async (ctx, input) => {
5792
+ const options = {
5793
+ maxResults: input.maxResults ?? defaultMaxResults,
5794
+ topic: input.topic ?? defaultTopic
5795
+ };
5796
+ const result = await ctx.step.run("web_search", () => searchFn(input.query, options), {
5797
+ input: { query: input.query, options }
5798
+ });
5799
+ return result;
5800
+ }
5801
+ );
5802
+ }
5803
+
5804
+ // src/execution/output.ts
5805
+ var DEFAULT_MAX_CHARS = 1e5;
5806
+ var HEAD_RATIO = 0.2;
5807
+ function truncateOutput(output, maxChars) {
5808
+ const max = maxChars ?? DEFAULT_MAX_CHARS;
5809
+ if (output.length <= max) {
5810
+ return { text: output, truncated: false };
5811
+ }
5812
+ const headSize = Math.floor(max * HEAD_RATIO);
5813
+ const tailSize = max - headSize;
5814
+ const omitted = output.length - headSize - tailSize;
5815
+ const head = output.slice(0, headSize);
5816
+ const tail = output.slice(-tailSize);
5817
+ const text = `${head}
5818
+
5819
+ --- truncated ${String(omitted)} characters ---
5820
+
5821
+ ${tail}`;
5822
+ return { text, truncated: true };
5823
+ }
5824
+ function isBinary(buffer) {
5825
+ const checkLength = Math.min(buffer.length, 8192);
5826
+ for (let i = 0; i < checkLength; i++) {
5827
+ if (buffer[i] === 0) {
5828
+ return true;
5829
+ }
5830
+ }
5831
+ return false;
5832
+ }
5833
+ function parseGrepOutput(output) {
5834
+ if (!output.trim()) {
5835
+ return [];
5836
+ }
5837
+ const matches = [];
5838
+ const lines = output.split("\n");
5839
+ for (const line of lines) {
5840
+ if (!line) continue;
5841
+ const match = /^(.+?):(\d+):(.*)$/.exec(line);
5842
+ if (match?.[1] && match[2] && match[3] !== void 0) {
5843
+ matches.push({ path: match[1], line: parseInt(match[2], 10), text: match[3] });
5844
+ }
5845
+ }
5846
+ return matches;
5847
+ }
5848
+ function stripAnsi(text) {
5849
+ return text.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "");
5850
+ }
5851
+
5852
+ // src/execution/docker.ts
5853
+ var DEFAULT_CONTAINER_WORKDIR = "/workspace";
5854
+ var DEFAULT_TIMEOUT_SECONDS = 300;
5855
+ var DEFAULT_MAX_OUTPUT_CHARS = 1e5;
5856
+ function spawnCommand(command, args, options) {
5857
+ return new Promise((resolve8, reject) => {
5858
+ let settled = false;
5859
+ const settle = (fn) => {
5860
+ if (!settled) {
5861
+ settled = true;
5862
+ fn();
5863
+ }
5864
+ };
5865
+ const proc = child_process.spawn(command, args, { stdio: ["pipe", "pipe", "pipe"] });
5866
+ let stdout = "";
5867
+ let stderr = "";
5868
+ let killed = false;
5869
+ proc.stdout.on("data", (data) => {
5870
+ stdout += data.toString();
5871
+ });
5872
+ proc.stderr.on("data", (data) => {
5873
+ stderr += data.toString();
5874
+ });
5875
+ proc.stdin.on("error", () => {
5876
+ });
5877
+ proc.stdout.on("error", () => {
5878
+ });
5879
+ proc.stderr.on("error", () => {
5880
+ });
5881
+ const timeoutMs = (options?.timeout ?? DEFAULT_TIMEOUT_SECONDS) * 1e3;
5882
+ const timer = setTimeout(() => {
5883
+ killed = true;
5884
+ proc.kill("SIGKILL");
5885
+ }, timeoutMs);
5886
+ proc.on("close", (code) => {
5887
+ clearTimeout(timer);
5888
+ settle(() => {
5889
+ if (killed) {
5890
+ resolve8({
5891
+ exitCode: 137,
5892
+ stdout,
5893
+ stderr: stderr + "\n[Process killed: timeout exceeded]"
5894
+ });
5895
+ } else {
5896
+ resolve8({ exitCode: code ?? 1, stdout, stderr });
5897
+ }
5898
+ });
5899
+ });
5900
+ proc.on("error", (err) => {
5901
+ clearTimeout(timer);
5902
+ settle(() => {
5903
+ reject(err);
5904
+ });
5905
+ });
5906
+ if (options?.stdin) {
5907
+ proc.stdin.write(options.stdin, () => {
5908
+ proc.stdin.end();
5909
+ });
5910
+ } else {
5911
+ proc.stdin.end();
5912
+ }
5913
+ });
5914
+ }
5915
+ var DockerEnvironment = class {
5916
+ type = "docker";
5917
+ containerId = null;
5918
+ containerName;
5919
+ config;
5920
+ containerWorkdir;
5921
+ maxOutputChars;
5922
+ constructor(config, maxOutputChars) {
5923
+ this.config = config;
5924
+ this.containerWorkdir = config.containerWorkdir ?? DEFAULT_CONTAINER_WORKDIR;
5925
+ this.containerName = `polos-sandbox-${crypto.randomUUID().slice(0, 8)}`;
5926
+ this.maxOutputChars = maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
5927
+ }
5928
+ async initialize() {
5929
+ const args = [
5930
+ "run",
5931
+ "-d",
5932
+ "--name",
5933
+ this.containerName,
5934
+ "-v",
5935
+ `${this.config.workspaceDir}:${this.containerWorkdir}:rw`,
5936
+ "-w",
5937
+ this.containerWorkdir
5938
+ ];
5939
+ if (this.config.memory) {
5940
+ args.push("--memory", this.config.memory);
5941
+ }
5942
+ if (this.config.cpus) {
5943
+ args.push("--cpus", this.config.cpus);
5944
+ }
5945
+ args.push("--network", this.config.network ?? "none");
5946
+ if (this.config.env) {
5947
+ for (const [key, value] of Object.entries(this.config.env)) {
5948
+ args.push("-e", `${key}=${value}`);
5949
+ }
5950
+ }
5951
+ args.push(this.config.image, "sleep", "infinity");
5952
+ const result = await spawnCommand("docker", args, { timeout: 60 });
5953
+ if (result.exitCode !== 0) {
5954
+ throw new Error(`Failed to create Docker container: ${result.stderr.trim()}`);
5955
+ }
5956
+ this.containerId = result.stdout.trim().slice(0, 12);
5957
+ if (this.config.setupCommand) {
5958
+ const setupResult = await this.exec(this.config.setupCommand);
5959
+ if (setupResult.exitCode !== 0) {
5960
+ throw new Error(
5961
+ `Setup command failed (exit ${String(setupResult.exitCode)}): ${setupResult.stderr.trim()}`
5962
+ );
5963
+ }
5964
+ }
5965
+ }
5966
+ async exec(command, opts) {
5967
+ this.assertInitialized();
5968
+ const args = opts?.stdin ? ["exec", "-i"] : ["exec"];
5969
+ const cwd = opts?.cwd ?? this.containerWorkdir;
5970
+ args.push("-w", cwd);
5971
+ if (opts?.env) {
5972
+ for (const [key, value] of Object.entries(opts.env)) {
5973
+ args.push("-e", `${key}=${value}`);
5974
+ }
5975
+ }
5976
+ args.push(this.containerName, "sh", "-c", command);
5977
+ const timeout = opts?.timeout ?? DEFAULT_TIMEOUT_SECONDS;
5978
+ const start = Date.now();
5979
+ const result = await spawnCommand("docker", args, {
5980
+ timeout,
5981
+ stdin: opts?.stdin
5982
+ });
5983
+ const durationMs = Date.now() - start;
5984
+ const { text: stdout, truncated: stdoutTruncated } = truncateOutput(
5985
+ stripAnsi(result.stdout),
5986
+ this.maxOutputChars
5987
+ );
5988
+ const { text: stderr } = truncateOutput(stripAnsi(result.stderr), this.maxOutputChars);
5989
+ return {
5990
+ exitCode: result.exitCode,
5991
+ stdout,
5992
+ stderr,
5993
+ durationMs,
5994
+ truncated: stdoutTruncated
5995
+ };
5996
+ }
5997
+ async readFile(filePath) {
5998
+ const hostPath = this.toHostPath(filePath);
5999
+ const buffer = await fs2__namespace.readFile(hostPath);
6000
+ if (isBinary(buffer)) {
6001
+ throw new Error(`Cannot read binary file: ${filePath}`);
6002
+ }
6003
+ return buffer.toString("utf-8");
6004
+ }
6005
+ async writeFile(filePath, content) {
6006
+ const hostPath = this.toHostPath(filePath);
6007
+ await fs2__namespace.mkdir(path__namespace.dirname(hostPath), { recursive: true });
6008
+ await fs2__namespace.writeFile(hostPath, content, "utf-8");
6009
+ }
6010
+ async fileExists(filePath) {
6011
+ const hostPath = this.toHostPath(filePath);
6012
+ try {
6013
+ await fs2__namespace.access(hostPath);
6014
+ return true;
6015
+ } catch {
6016
+ return false;
6017
+ }
6018
+ }
6019
+ async glob(pattern, opts) {
6020
+ const cwd = opts?.cwd ?? this.containerWorkdir;
6021
+ let command = `find ${cwd} -type f -name '${pattern}'`;
6022
+ if (opts?.ignore) {
6023
+ for (const ignore of opts.ignore) {
6024
+ command += ` ! -path '${ignore}'`;
6025
+ }
6026
+ }
6027
+ command += " 2>/dev/null | sort | head -1000";
6028
+ const result = await this.exec(command);
6029
+ if (!result.stdout.trim()) return [];
6030
+ return result.stdout.trim().split("\n").filter(Boolean);
6031
+ }
6032
+ async grep(pattern, opts) {
6033
+ const cwd = opts?.cwd ?? this.containerWorkdir;
6034
+ const maxResults = opts?.maxResults ?? 100;
6035
+ let command = "grep -rn";
6036
+ if (opts?.contextLines !== void 0) {
6037
+ command += ` -C ${String(opts.contextLines)}`;
6038
+ }
6039
+ if (opts?.include) {
6040
+ for (const inc of opts.include) {
6041
+ command += ` --include='${inc}'`;
6042
+ }
6043
+ }
6044
+ const escapedPattern = pattern.replace(/'/g, "'\\''");
6045
+ command += ` -- '${escapedPattern}' ${cwd}`;
6046
+ command += ` 2>/dev/null | head -${String(maxResults)}`;
6047
+ const result = await this.exec(command);
6048
+ return parseGrepOutput(result.stdout);
6049
+ }
6050
+ async destroy() {
6051
+ if (!this.containerId) return;
6052
+ try {
6053
+ await spawnCommand("docker", ["rm", "-f", this.containerName], { timeout: 30 });
6054
+ } finally {
6055
+ this.containerId = null;
6056
+ }
6057
+ }
6058
+ getCwd() {
6059
+ return this.containerWorkdir;
6060
+ }
6061
+ getInfo() {
6062
+ return {
6063
+ type: "docker",
6064
+ cwd: this.containerWorkdir,
6065
+ sandboxId: this.containerId ?? void 0
6066
+ };
6067
+ }
6068
+ /**
6069
+ * Translate a container path to the corresponding host filesystem path.
6070
+ * Validates the path stays within the workspace to prevent traversal.
6071
+ */
6072
+ toHostPath(containerPath) {
6073
+ const resolved = path__namespace.posix.resolve(this.containerWorkdir, containerPath);
6074
+ if (!resolved.startsWith(this.containerWorkdir)) {
6075
+ throw new Error(`Path traversal detected: "${containerPath}" resolves outside workspace`);
6076
+ }
6077
+ const relative2 = path__namespace.posix.relative(this.containerWorkdir, resolved);
6078
+ return path__namespace.join(this.config.workspaceDir, relative2);
6079
+ }
6080
+ /**
6081
+ * Translate a host filesystem path to the corresponding container path.
6082
+ */
6083
+ toContainerPath(hostPath) {
6084
+ const resolved = path__namespace.resolve(hostPath);
6085
+ if (!resolved.startsWith(this.config.workspaceDir)) {
6086
+ throw new Error(
6087
+ `Path outside workspace: "${hostPath}" is not within "${this.config.workspaceDir}"`
6088
+ );
6089
+ }
6090
+ const relative2 = path__namespace.relative(this.config.workspaceDir, resolved);
6091
+ return path__namespace.posix.join(this.containerWorkdir, relative2);
6092
+ }
6093
+ assertInitialized() {
6094
+ if (!this.containerId) {
6095
+ throw new Error("Docker environment not initialized. Call initialize() first.");
6096
+ }
6097
+ }
6098
+ };
6099
+ var DEFAULT_TIMEOUT_SECONDS2 = 300;
6100
+ var DEFAULT_MAX_OUTPUT_CHARS2 = 1e5;
6101
+ function spawnLocal(command, options) {
6102
+ return new Promise((resolve8, reject) => {
6103
+ let settled = false;
6104
+ const settle = (fn) => {
6105
+ if (!settled) {
6106
+ settled = true;
6107
+ fn();
6108
+ }
6109
+ };
6110
+ const proc = child_process.spawn("sh", ["-c", command], {
6111
+ cwd: options.cwd,
6112
+ env: options.env ? { ...process.env, ...options.env } : void 0,
6113
+ stdio: ["pipe", "pipe", "pipe"]
6114
+ });
6115
+ let stdout = "";
6116
+ let stderr = "";
6117
+ let killed = false;
6118
+ proc.stdout.on("data", (data) => {
6119
+ stdout += data.toString();
6120
+ });
6121
+ proc.stderr.on("data", (data) => {
6122
+ stderr += data.toString();
6123
+ });
6124
+ proc.stdin.on("error", () => {
6125
+ });
6126
+ proc.stdout.on("error", () => {
6127
+ });
6128
+ proc.stderr.on("error", () => {
6129
+ });
6130
+ const timeoutMs = (options.timeout ?? DEFAULT_TIMEOUT_SECONDS2) * 1e3;
6131
+ const timer = setTimeout(() => {
6132
+ killed = true;
6133
+ proc.kill("SIGKILL");
6134
+ }, timeoutMs);
6135
+ proc.on("close", (code) => {
6136
+ clearTimeout(timer);
6137
+ settle(() => {
6138
+ if (killed) {
6139
+ resolve8({
6140
+ exitCode: 137,
6141
+ stdout,
6142
+ stderr: stderr + "\n[Process killed: timeout exceeded]"
6143
+ });
6144
+ } else {
6145
+ resolve8({ exitCode: code ?? 1, stdout, stderr });
6146
+ }
6147
+ });
6148
+ });
6149
+ proc.on("error", (err) => {
6150
+ clearTimeout(timer);
6151
+ settle(() => {
6152
+ reject(err);
6153
+ });
6154
+ });
6155
+ if (options.stdin) {
6156
+ proc.stdin.write(options.stdin, () => {
6157
+ proc.stdin.end();
6158
+ });
6159
+ } else {
6160
+ proc.stdin.end();
6161
+ }
6162
+ });
6163
+ }
6164
+ var LocalEnvironment = class {
6165
+ type = "local";
6166
+ config;
6167
+ cwd;
6168
+ maxOutputChars;
6169
+ constructor(config, maxOutputChars) {
6170
+ this.config = config ?? {};
6171
+ this.cwd = path__namespace.resolve(config?.cwd ?? process.cwd());
6172
+ this.maxOutputChars = maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS2;
6173
+ }
6174
+ async initialize() {
6175
+ try {
6176
+ const stat2 = await fs2__namespace.stat(this.cwd);
6177
+ if (!stat2.isDirectory()) {
6178
+ throw new Error(`Working directory is not a directory: ${this.cwd}`);
6179
+ }
6180
+ } catch (err) {
6181
+ if (err.code === "ENOENT") {
6182
+ throw new Error(`Working directory does not exist: ${this.cwd}`);
6183
+ }
6184
+ throw err;
6185
+ }
6186
+ }
6187
+ async exec(command, opts) {
6188
+ const cwd = opts?.cwd ? this.resolvePath(opts.cwd) : this.cwd;
6189
+ const timeout = opts?.timeout ?? DEFAULT_TIMEOUT_SECONDS2;
6190
+ const start = Date.now();
6191
+ const result = await spawnLocal(command, {
6192
+ cwd,
6193
+ env: opts?.env,
6194
+ timeout,
6195
+ stdin: opts?.stdin
6196
+ });
6197
+ const durationMs = Date.now() - start;
6198
+ const { text: stdout, truncated: stdoutTruncated } = truncateOutput(
6199
+ stripAnsi(result.stdout),
6200
+ this.maxOutputChars
6201
+ );
6202
+ const { text: stderr } = truncateOutput(stripAnsi(result.stderr), this.maxOutputChars);
6203
+ return {
6204
+ exitCode: result.exitCode,
6205
+ stdout,
6206
+ stderr,
6207
+ durationMs,
6208
+ truncated: stdoutTruncated
6209
+ };
6210
+ }
6211
+ async readFile(filePath) {
6212
+ const resolved = this.resolvePath(filePath);
6213
+ await this.assertNotSymlink(resolved);
6214
+ const buffer = await fs2__namespace.readFile(resolved);
6215
+ if (isBinary(buffer)) {
6216
+ throw new Error(`Cannot read binary file: ${filePath}`);
6217
+ }
6218
+ return buffer.toString("utf-8");
6219
+ }
6220
+ async writeFile(filePath, content) {
6221
+ const resolved = this.resolvePath(filePath);
6222
+ this.assertPathSafe(resolved);
6223
+ const parentDir = path__namespace.dirname(resolved);
6224
+ await fs2__namespace.mkdir(parentDir, { recursive: true });
6225
+ await fs2__namespace.writeFile(resolved, content, "utf-8");
6226
+ }
6227
+ async fileExists(filePath) {
6228
+ const resolved = this.resolvePath(filePath);
6229
+ try {
6230
+ await fs2__namespace.access(resolved);
6231
+ return true;
6232
+ } catch {
6233
+ return false;
6234
+ }
6235
+ }
6236
+ async glob(pattern, opts) {
6237
+ const cwd = opts?.cwd ? this.resolvePath(opts.cwd) : this.cwd;
6238
+ let command = `find ${cwd} -type f -name '${pattern}'`;
6239
+ if (opts?.ignore) {
6240
+ for (const ignore of opts.ignore) {
6241
+ command += ` ! -path '${ignore}'`;
6242
+ }
6243
+ }
6244
+ command += " 2>/dev/null | sort | head -1000";
6245
+ const result = await this.exec(command);
6246
+ if (!result.stdout.trim()) return [];
6247
+ return result.stdout.trim().split("\n").filter(Boolean);
6248
+ }
6249
+ async grep(pattern, opts) {
6250
+ const cwd = opts?.cwd ? this.resolvePath(opts.cwd) : this.cwd;
6251
+ const maxResults = opts?.maxResults ?? 100;
6252
+ let command = "grep -rn";
6253
+ if (opts?.contextLines !== void 0) {
6254
+ command += ` -C ${String(opts.contextLines)}`;
6255
+ }
6256
+ if (opts?.include) {
6257
+ for (const inc of opts.include) {
6258
+ command += ` --include='${inc}'`;
6259
+ }
6260
+ }
6261
+ const escapedPattern = pattern.replace(/'/g, "'\\''");
6262
+ command += ` -- '${escapedPattern}' ${cwd}`;
6263
+ command += ` 2>/dev/null | head -${String(maxResults)}`;
6264
+ const result = await this.exec(command);
6265
+ return parseGrepOutput(result.stdout);
6266
+ }
6267
+ async destroy() {
6268
+ }
6269
+ getCwd() {
6270
+ return this.cwd;
6271
+ }
6272
+ getInfo() {
6273
+ return {
6274
+ type: "local",
6275
+ cwd: this.cwd
6276
+ };
6277
+ }
6278
+ /**
6279
+ * Resolve a path relative to the working directory.
6280
+ */
6281
+ resolvePath(p) {
6282
+ return path__namespace.resolve(this.cwd, p);
6283
+ }
6284
+ /**
6285
+ * Assert that a resolved path stays within the path restriction.
6286
+ * No-op when path restriction is not configured.
6287
+ */
6288
+ assertPathSafe(resolvedPath) {
6289
+ if (!this.config.pathRestriction) return;
6290
+ const restriction = path__namespace.resolve(this.config.pathRestriction);
6291
+ if (resolvedPath !== restriction && !resolvedPath.startsWith(restriction + "/")) {
6292
+ throw new Error(`Path traversal detected: "${resolvedPath}" is outside of "${restriction}"`);
6293
+ }
6294
+ }
6295
+ /**
6296
+ * Assert that a path is not a symbolic link.
6297
+ * Only enforced when path restriction is configured.
6298
+ */
6299
+ async assertNotSymlink(resolvedPath) {
6300
+ if (!this.config.pathRestriction) return;
6301
+ try {
6302
+ const stat2 = await fs2__namespace.lstat(resolvedPath);
6303
+ if (stat2.isSymbolicLink()) {
6304
+ throw new Error(
6305
+ `Symbolic link detected: "${resolvedPath}". Symlinks are blocked when pathRestriction is set.`
6306
+ );
6307
+ }
6308
+ } catch (err) {
6309
+ if (err.code === "ENOENT") return;
6310
+ throw err;
6311
+ }
6312
+ }
6313
+ };
6314
+ function matchGlob(text, pattern) {
6315
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
6316
+ const regexStr = `^${escaped.replace(/\*/g, ".*")}$`;
6317
+ return new RegExp(regexStr).test(text);
6318
+ }
6319
+ function evaluateAllowlist(command, patterns) {
6320
+ const trimmed = command.trim();
6321
+ return patterns.some((pattern) => matchGlob(trimmed, pattern));
6322
+ }
6323
+ function isWithinRestriction(resolvedPath, restriction) {
6324
+ const base = path.resolve(restriction);
6325
+ return resolvedPath === base || resolvedPath.startsWith(base + "/");
6326
+ }
6327
+ function assertSafePath(filePath, restriction) {
6328
+ const base = path.resolve(restriction);
6329
+ const resolved = path.resolve(base, filePath);
6330
+ if (!isWithinRestriction(resolved, base)) {
6331
+ throw new Error(`Path traversal detected: "${filePath}" resolves outside of "${restriction}"`);
6332
+ }
6333
+ }
6334
+
6335
+ // src/execution/tools/exec.ts
6336
+ async function requestApproval(ctx, command, env) {
6337
+ const envInfo = env.getInfo();
6338
+ const approvalId = await ctx.step.uuid("_approval_id");
6339
+ const response = await ctx.step.suspend(
6340
+ `approve_exec_${approvalId}`,
6341
+ {
6342
+ data: {
6343
+ _form: {
6344
+ title: "Approve command execution",
6345
+ description: `The agent wants to run a shell command in the ${envInfo.type} environment.`,
6346
+ fields: [
6347
+ {
6348
+ key: "approved",
6349
+ type: "boolean",
6350
+ label: "Approve this command?",
6351
+ required: true,
6352
+ default: false
6353
+ },
6354
+ {
6355
+ key: "allow_always",
6356
+ type: "boolean",
6357
+ label: "Always allow this command in the future?",
6358
+ required: false,
6359
+ default: false
6360
+ },
6361
+ {
6362
+ key: "feedback",
6363
+ type: "textarea",
6364
+ label: "Feedback for the agent (optional)",
6365
+ description: "If rejecting, tell the agent what to do instead.",
6366
+ required: false
6367
+ }
6368
+ ],
6369
+ context: {
6370
+ command,
6371
+ cwd: env.getCwd(),
6372
+ environment: envInfo.type
6373
+ }
6374
+ },
6375
+ _source: "exec_security",
6376
+ _tool: "exec"
6377
+ }
6378
+ }
6379
+ );
6380
+ const feedback = response.data?.feedback;
6381
+ return {
6382
+ approved: response.data?.approved === true,
6383
+ ...feedback ? { feedback } : {}
6384
+ };
6385
+ }
6386
+ function rejectedResult(command, feedback) {
6387
+ let stderr = `Command rejected by user: ${command}`;
6388
+ if (feedback) {
6389
+ stderr += `
6390
+ User feedback: ${feedback}`;
6391
+ }
6392
+ return {
6393
+ exitCode: -1,
6394
+ stdout: "",
6395
+ stderr,
6396
+ durationMs: 0,
6397
+ truncated: false
6398
+ };
6399
+ }
6400
+ function createExecTool(getEnv, config) {
6401
+ return defineTool(
6402
+ {
6403
+ id: "exec",
6404
+ description: "Execute a shell command in the sandbox environment. Returns stdout, stderr, and exit code. Use this for running builds, tests, installing packages, or any shell operation.",
6405
+ inputSchema: zod.z.object({
6406
+ command: zod.z.string().describe("The shell command to execute"),
6407
+ cwd: zod.z.string().optional().describe("Working directory for the command"),
6408
+ env: zod.z.record(zod.z.string()).optional().describe("Environment variables to set"),
6409
+ timeout: zod.z.number().optional().describe("Timeout in seconds (default: 300)")
6410
+ })
6411
+ },
6412
+ async (ctx, input) => {
6413
+ const env = await getEnv();
6414
+ if (config?.security === "approval-always") {
6415
+ const result = await requestApproval(ctx, input.command, env);
6416
+ if (!result.approved) return rejectedResult(input.command, result.feedback);
6417
+ } else if (config?.security === "allowlist") {
6418
+ if (!evaluateAllowlist(input.command, config.allowlist ?? [])) {
6419
+ const result = await requestApproval(ctx, input.command, env);
6420
+ if (!result.approved) return rejectedResult(input.command, result.feedback);
6421
+ }
6422
+ }
6423
+ return env.exec(input.command, {
6424
+ cwd: input.cwd,
6425
+ env: input.env,
6426
+ timeout: input.timeout ?? config?.timeout
6427
+ });
6428
+ }
6429
+ );
6430
+ }
6431
+ function isPathAllowed(resolvedPath, restriction) {
6432
+ return isWithinRestriction(resolvedPath, path.resolve(restriction));
6433
+ }
6434
+ async function requirePathApproval(ctx, toolName, targetPath, restriction) {
6435
+ const approvalId = await ctx.step.uuid("_approval_id");
6436
+ const response = await ctx.step.suspend(
6437
+ `approve_${toolName}_${approvalId}`,
6438
+ {
6439
+ data: {
6440
+ _form: {
6441
+ title: `${toolName}: access outside workspace`,
6442
+ description: `The agent wants to ${toolName} a path outside the workspace.`,
6443
+ fields: [
6444
+ {
6445
+ key: "approved",
6446
+ type: "boolean",
6447
+ label: "Allow this operation?",
6448
+ required: true,
6449
+ default: false
6450
+ },
6451
+ {
6452
+ key: "feedback",
6453
+ type: "textarea",
6454
+ label: "Feedback for the agent (optional)",
6455
+ description: "If rejecting, tell the agent what to do instead.",
6456
+ required: false
6457
+ }
6458
+ ],
6459
+ context: {
6460
+ tool: toolName,
6461
+ path: targetPath,
6462
+ restriction
6463
+ }
6464
+ },
6465
+ _source: "path_approval",
6466
+ _tool: toolName
6467
+ }
6468
+ }
6469
+ );
6470
+ if (response.data?.approved !== true) {
6471
+ const feedback = response.data?.feedback;
6472
+ throw new Error(
6473
+ `Access to "${targetPath}" was rejected by the user.${feedback ? ` Feedback: ${feedback}` : ""}`
6474
+ );
6475
+ }
6476
+ }
6477
+
6478
+ // src/execution/tools/read.ts
6479
+ function createReadTool(getEnv, pathConfig) {
6480
+ return defineTool(
6481
+ {
6482
+ id: "read",
6483
+ description: "Read the contents of a file. Returns the file content as text. Optionally specify offset (line number to start from, 0-based) and limit (number of lines).",
6484
+ inputSchema: zod.z.object({
6485
+ path: zod.z.string().describe("Path to the file to read"),
6486
+ offset: zod.z.number().optional().describe("Line offset to start reading from (0-based)"),
6487
+ limit: zod.z.number().optional().describe("Maximum number of lines to return")
6488
+ })
6489
+ },
6490
+ async (ctx, input) => {
6491
+ const env = await getEnv();
6492
+ if (pathConfig?.pathRestriction) {
6493
+ const resolved = path.resolve(env.getCwd(), input.path);
6494
+ if (!isPathAllowed(resolved, pathConfig.pathRestriction)) {
6495
+ await requirePathApproval(ctx, "read", resolved, pathConfig.pathRestriction);
6496
+ }
6497
+ }
6498
+ let content = await env.readFile(input.path);
6499
+ if (input.offset !== void 0 || input.limit !== void 0) {
6500
+ const lines = content.split("\n");
6501
+ const start = input.offset ?? 0;
6502
+ const end = input.limit !== void 0 ? start + input.limit : lines.length;
6503
+ content = lines.slice(start, end).join("\n");
6504
+ }
6505
+ return { content, path: input.path };
6506
+ }
6507
+ );
6508
+ }
6509
+ function createWriteTool(getEnv, approval) {
6510
+ return defineTool(
6511
+ {
6512
+ id: "write",
6513
+ description: "Write content to a file. Creates the file if it does not exist, or overwrites it if it does. Parent directories are created automatically.",
6514
+ inputSchema: zod.z.object({
6515
+ path: zod.z.string().describe("Path to the file to write"),
6516
+ content: zod.z.string().describe("Content to write to the file")
6517
+ }),
6518
+ approval
6519
+ },
6520
+ async (_ctx, input) => {
6521
+ const env = await getEnv();
6522
+ await env.writeFile(input.path, input.content);
6523
+ return { success: true, path: input.path };
6524
+ }
6525
+ );
6526
+ }
6527
+ function createEditTool(getEnv, approval) {
6528
+ return defineTool(
6529
+ {
6530
+ id: "edit",
6531
+ description: "Edit a file by replacing an exact string match. The old_text must match exactly (including whitespace and indentation). Use this for precise code modifications.",
6532
+ inputSchema: zod.z.object({
6533
+ path: zod.z.string().describe("Path to the file to edit"),
6534
+ old_text: zod.z.string().describe("Exact text to find and replace"),
6535
+ new_text: zod.z.string().describe("Text to replace the old_text with")
6536
+ }),
6537
+ approval
6538
+ },
6539
+ async (_ctx, input) => {
6540
+ const env = await getEnv();
6541
+ const content = await env.readFile(input.path);
6542
+ if (!content.includes(input.old_text)) {
6543
+ throw new Error(
6544
+ `old_text not found in ${input.path}. Make sure the text matches exactly, including whitespace and indentation.`
6545
+ );
6546
+ }
6547
+ const newContent = content.replace(input.old_text, input.new_text);
6548
+ await env.writeFile(input.path, newContent);
6549
+ return { success: true, path: input.path };
6550
+ }
6551
+ );
6552
+ }
6553
+ function createGlobTool(getEnv, pathConfig) {
6554
+ return defineTool(
6555
+ {
6556
+ id: "glob",
6557
+ description: "Find files matching a glob pattern. Returns a list of file paths. Use this to discover files in the project structure.",
6558
+ inputSchema: zod.z.object({
6559
+ pattern: zod.z.string().describe('Glob pattern to match (e.g., "*.ts", "src/**/*.js")'),
6560
+ cwd: zod.z.string().optional().describe("Directory to search in"),
6561
+ ignore: zod.z.array(zod.z.string()).optional().describe("Patterns to exclude from results")
6562
+ })
6563
+ },
6564
+ async (ctx, input) => {
6565
+ const env = await getEnv();
6566
+ if (pathConfig?.pathRestriction && input.cwd) {
6567
+ const resolved = path.resolve(env.getCwd(), input.cwd);
6568
+ if (!isPathAllowed(resolved, pathConfig.pathRestriction)) {
6569
+ await requirePathApproval(ctx, "glob", resolved, pathConfig.pathRestriction);
6570
+ }
6571
+ }
6572
+ const files = await env.glob(input.pattern, {
6573
+ cwd: input.cwd,
6574
+ ignore: input.ignore
6575
+ });
6576
+ return { files };
6577
+ }
6578
+ );
6579
+ }
6580
+ function createGrepTool(getEnv, pathConfig) {
6581
+ return defineTool(
6582
+ {
6583
+ id: "grep",
6584
+ description: "Search file contents for a pattern using grep. Returns matching lines with file paths and line numbers. Use this to find code patterns, references, or specific text.",
6585
+ inputSchema: zod.z.object({
6586
+ pattern: zod.z.string().describe("Search pattern (regex supported)"),
6587
+ cwd: zod.z.string().optional().describe("Directory to search in"),
6588
+ include: zod.z.array(zod.z.string()).optional().describe('File patterns to include (e.g., ["*.ts", "*.js"])'),
6589
+ maxResults: zod.z.number().optional().describe("Maximum number of matches to return (default: 100)"),
6590
+ contextLines: zod.z.number().optional().describe("Number of context lines around each match")
6591
+ })
6592
+ },
6593
+ async (ctx, input) => {
6594
+ const env = await getEnv();
6595
+ if (pathConfig?.pathRestriction && input.cwd) {
6596
+ const resolved = path.resolve(env.getCwd(), input.cwd);
6597
+ if (!isPathAllowed(resolved, pathConfig.pathRestriction)) {
6598
+ await requirePathApproval(ctx, "grep", resolved, pathConfig.pathRestriction);
6599
+ }
6600
+ }
6601
+ const matches = await env.grep(input.pattern, {
6602
+ cwd: input.cwd,
6603
+ include: input.include,
6604
+ maxResults: input.maxResults,
6605
+ contextLines: input.contextLines
6606
+ });
6607
+ return { matches };
6608
+ }
6609
+ );
6610
+ }
6611
+
6612
+ // src/execution/sandbox-tools.ts
6613
+ function createEnvironment(config) {
6614
+ const envType = config?.env ?? "docker";
6615
+ switch (envType) {
6616
+ case "docker": {
6617
+ const dockerConfig = config?.docker ?? {
6618
+ image: "node:20-slim",
6619
+ workspaceDir: process.cwd()
6620
+ };
6621
+ return new DockerEnvironment(dockerConfig, config?.exec?.maxOutputChars);
6622
+ }
6623
+ case "e2b":
6624
+ throw new Error("E2B environment is not yet implemented.");
6625
+ case "local":
6626
+ return new LocalEnvironment(config?.local, config?.exec?.maxOutputChars);
6627
+ default:
6628
+ throw new Error(`Unknown environment type: ${String(envType)}`);
6629
+ }
6630
+ }
6631
+ function sandboxTools(config) {
6632
+ let env = null;
6633
+ let envPromise = null;
6634
+ async function getEnv() {
6635
+ if (env) return env;
6636
+ if (envPromise) return envPromise;
6637
+ envPromise = (async () => {
6638
+ const created = createEnvironment(config);
6639
+ await created.initialize();
6640
+ env = created;
6641
+ return env;
6642
+ })();
6643
+ return envPromise;
6644
+ }
6645
+ const envType = config?.env ?? "docker";
6646
+ if (envType === "e2b") {
6647
+ throw new Error("E2B environment is not yet implemented.");
6648
+ }
6649
+ const effectiveExecConfig = envType === "local" && !config?.exec?.security ? { ...config?.exec, security: "approval-always" } : config?.exec;
6650
+ const fileApproval = config?.fileApproval ?? (envType === "local" ? "always" : void 0);
6651
+ const pathConfig = config?.local?.pathRestriction ? { pathRestriction: config.local.pathRestriction } : void 0;
6652
+ const include = new Set(
6653
+ config?.tools ?? ["exec", "read", "write", "edit", "glob", "grep"]
6654
+ );
6655
+ const tools = [];
6656
+ if (include.has("exec")) tools.push(createExecTool(getEnv, effectiveExecConfig));
6657
+ if (include.has("read")) tools.push(createReadTool(getEnv, pathConfig));
6658
+ if (include.has("write")) tools.push(createWriteTool(getEnv, fileApproval));
6659
+ if (include.has("edit")) tools.push(createEditTool(getEnv, fileApproval));
6660
+ if (include.has("glob")) tools.push(createGlobTool(getEnv, pathConfig));
6661
+ if (include.has("grep")) tools.push(createGrepTool(getEnv, pathConfig));
6662
+ const result = tools;
6663
+ result.cleanup = async () => {
6664
+ if (env) {
6665
+ await env.destroy();
6666
+ env = null;
6667
+ envPromise = null;
6668
+ }
6669
+ };
6670
+ return result;
6671
+ }
5542
6672
 
5543
6673
  exports.AgentRunConfig = AgentRunConfig;
6674
+ exports.DockerEnvironment = DockerEnvironment;
5544
6675
  exports.DuplicateWorkflowError = DuplicateWorkflowError;
5545
6676
  exports.ExecutionHandle = ExecutionHandle;
5546
6677
  exports.GuardrailError = GuardrailError;
@@ -5562,6 +6693,7 @@ exports.Worker = Worker;
5562
6693
  exports.WorkerServer = WorkerServer;
5563
6694
  exports.WorkflowNotFoundError = WorkflowNotFoundError;
5564
6695
  exports.agentStreamFunction = agentStreamFunction;
6696
+ exports.assertSafePath = assertSafePath;
5565
6697
  exports.batchAgentInvoke = batchAgentInvoke;
5566
6698
  exports.batchInvoke = batchInvoke;
5567
6699
  exports.composeGuardrails = composeGuardrails;
@@ -5574,15 +6706,24 @@ exports.convertToolResultsToMessages = convertToolResultsToMessages;
5574
6706
  exports.convertToolsToVercel = convertToolsToVercel;
5575
6707
  exports.convertVercelToolCallToPython = convertVercelToolCallToPython;
5576
6708
  exports.convertVercelUsageToPython = convertVercelUsageToPython;
6709
+ exports.createAskUserTool = createAskUserTool;
6710
+ exports.createEditTool = createEditTool;
6711
+ exports.createExecTool = createExecTool;
6712
+ exports.createGlobTool = createGlobTool;
6713
+ exports.createGrepTool = createGrepTool;
5577
6714
  exports.createLogger = createLogger;
6715
+ exports.createReadTool = createReadTool;
5578
6716
  exports.createStepHelper = createStepHelper;
5579
6717
  exports.createStepStore = createStepStore;
6718
+ exports.createWebSearchTool = createWebSearchTool;
5580
6719
  exports.createWorkflowRegistry = createWorkflowRegistry;
6720
+ exports.createWriteTool = createWriteTool;
5581
6721
  exports.defineAgent = defineAgent;
5582
6722
  exports.defineGuardrail = defineGuardrail;
5583
6723
  exports.defineHook = defineHook;
5584
6724
  exports.defineTool = defineTool;
5585
6725
  exports.defineWorkflow = defineWorkflow;
6726
+ exports.evaluateAllowlist = evaluateAllowlist;
5586
6727
  exports.executeGuardrailChain = executeGuardrailChain;
5587
6728
  exports.executeGuardrailsOrThrow = executeGuardrailsOrThrow;
5588
6729
  exports.executeHookChain = executeHookChain;
@@ -5597,6 +6738,7 @@ exports.hasText = hasText;
5597
6738
  exports.initializeOtel = initializeOtel;
5598
6739
  exports.initializeState = initializeState;
5599
6740
  exports.isAgentWorkflow = isAgentWorkflow;
6741
+ exports.isBinary = isBinary;
5600
6742
  exports.isGuardrail = isGuardrail;
5601
6743
  exports.isHook = isHook;
5602
6744
  exports.isOtelAvailable = isOtelAvailable;
@@ -5610,10 +6752,14 @@ exports.normalizeGuardrail = normalizeGuardrail;
5610
6752
  exports.normalizeGuardrails = normalizeGuardrails;
5611
6753
  exports.normalizeHook = normalizeHook;
5612
6754
  exports.normalizeHooks = normalizeHooks;
6755
+ exports.parseGrepOutput = parseGrepOutput;
5613
6756
  exports.retry = retry;
6757
+ exports.sandboxTools = sandboxTools;
5614
6758
  exports.serializeFinalState = serializeFinalState;
5615
6759
  exports.sleep = sleep;
5616
6760
  exports.stopCondition = stopCondition;
6761
+ exports.stripAnsi = stripAnsi;
6762
+ exports.truncateOutput = truncateOutput;
5617
6763
  exports.validateState = validateState;
5618
6764
  //# sourceMappingURL=index.cjs.map
5619
6765
  //# sourceMappingURL=index.cjs.map