@deepagents/context 0.38.1 → 1.0.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.
Files changed (122) hide show
  1. package/README.md +44 -11
  2. package/dist/browser.js +171 -15
  3. package/dist/browser.js.map +3 -3
  4. package/dist/index.d.ts +9 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +2584 -1361
  7. package/dist/index.js.map +4 -4
  8. package/dist/lib/agent.d.ts +6 -0
  9. package/dist/lib/agent.d.ts.map +1 -1
  10. package/dist/lib/chain-summary.d.ts +18 -0
  11. package/dist/lib/chain-summary.d.ts.map +1 -0
  12. package/dist/lib/engine.d.ts +25 -6
  13. package/dist/lib/engine.d.ts.map +1 -1
  14. package/dist/lib/fragments/message/user.d.ts +22 -2
  15. package/dist/lib/fragments/message/user.d.ts.map +1 -1
  16. package/dist/lib/fragments/reminders/combinators.d.ts +23 -0
  17. package/dist/lib/fragments/reminders/combinators.d.ts.map +1 -1
  18. package/dist/lib/fragments/reminders/message-predicates.d.ts +10 -0
  19. package/dist/lib/fragments/reminders/message-predicates.d.ts.map +1 -0
  20. package/dist/lib/fragments/reminders/temporal/elapsed.d.ts +3 -0
  21. package/dist/lib/fragments/reminders/temporal/elapsed.d.ts.map +1 -0
  22. package/dist/lib/fragments/reminders/temporal/index.d.ts +1 -0
  23. package/dist/lib/fragments/reminders/temporal/index.d.ts.map +1 -1
  24. package/dist/lib/fragments/reminders/tool-predicates.d.ts +17 -0
  25. package/dist/lib/fragments/reminders/tool-predicates.d.ts.map +1 -0
  26. package/dist/lib/fragments/reminders/usage-predicates.d.ts +3 -0
  27. package/dist/lib/fragments/reminders/usage-predicates.d.ts.map +1 -0
  28. package/dist/lib/fragments.d.ts +8 -3
  29. package/dist/lib/fragments.d.ts.map +1 -1
  30. package/dist/lib/resolvers/async-resolver.d.ts +8 -0
  31. package/dist/lib/resolvers/async-resolver.d.ts.map +1 -0
  32. package/dist/lib/resolvers/function-resolver.d.ts +8 -0
  33. package/dist/lib/resolvers/function-resolver.d.ts.map +1 -0
  34. package/dist/lib/resolvers/generator-resolver.d.ts +13 -0
  35. package/dist/lib/resolvers/generator-resolver.d.ts.map +1 -0
  36. package/dist/lib/resolvers/index.d.ts +10 -0
  37. package/dist/lib/resolvers/index.d.ts.map +1 -0
  38. package/dist/lib/resolvers/iterable-resolver.d.ts +12 -0
  39. package/dist/lib/resolvers/iterable-resolver.d.ts.map +1 -0
  40. package/dist/lib/resolvers/loader-resolver.d.ts +13 -0
  41. package/dist/lib/resolvers/loader-resolver.d.ts.map +1 -0
  42. package/dist/lib/resolvers/promise-resolver.d.ts +7 -0
  43. package/dist/lib/resolvers/promise-resolver.d.ts.map +1 -0
  44. package/dist/lib/resolvers/types.d.ts +45 -0
  45. package/dist/lib/resolvers/types.d.ts.map +1 -0
  46. package/dist/lib/sandbox/ast-utils.d.ts +5 -2
  47. package/dist/lib/sandbox/ast-utils.d.ts.map +1 -1
  48. package/dist/lib/sandbox/bash-tool.d.ts.map +1 -1
  49. package/dist/lib/sandbox/container-tool.d.ts +32 -8
  50. package/dist/lib/sandbox/container-tool.d.ts.map +1 -1
  51. package/dist/lib/sandbox/docker-sandbox-errors.d.ts +58 -0
  52. package/dist/lib/sandbox/docker-sandbox-errors.d.ts.map +1 -0
  53. package/dist/lib/sandbox/docker-sandbox.d.ts +69 -357
  54. package/dist/lib/sandbox/docker-sandbox.d.ts.map +1 -1
  55. package/dist/lib/sandbox/index.d.ts +2 -7
  56. package/dist/lib/sandbox/index.d.ts.map +1 -1
  57. package/dist/lib/sandbox/installers/github-release.d.ts +46 -0
  58. package/dist/lib/sandbox/installers/github-release.d.ts.map +1 -0
  59. package/dist/lib/sandbox/installers/index.d.ts +7 -0
  60. package/dist/lib/sandbox/installers/index.d.ts.map +1 -0
  61. package/dist/lib/sandbox/installers/installer.d.ts +44 -0
  62. package/dist/lib/sandbox/installers/installer.d.ts.map +1 -0
  63. package/dist/lib/sandbox/installers/npm.d.ts +32 -0
  64. package/dist/lib/sandbox/installers/npm.d.ts.map +1 -0
  65. package/dist/lib/sandbox/installers/package-manager.d.ts +19 -0
  66. package/dist/lib/sandbox/installers/package-manager.d.ts.map +1 -0
  67. package/dist/lib/sandbox/installers/pip.d.ts +37 -0
  68. package/dist/lib/sandbox/installers/pip.d.ts.map +1 -0
  69. package/dist/lib/sandbox/installers/url-binary.d.ts +41 -0
  70. package/dist/lib/sandbox/installers/url-binary.d.ts.map +1 -0
  71. package/dist/lib/sandbox/subcommand.d.ts +11 -11
  72. package/dist/lib/sandbox/subcommand.d.ts.map +1 -1
  73. package/dist/lib/sandbox/upload-skills.d.ts +6 -0
  74. package/dist/lib/sandbox/upload-skills.d.ts.map +1 -1
  75. package/dist/lib/sandbox/virtual-sandbox.d.ts +10 -0
  76. package/dist/lib/sandbox/virtual-sandbox.d.ts.map +1 -0
  77. package/dist/lib/save/reminder-target-handler.d.ts +36 -0
  78. package/dist/lib/save/reminder-target-handler.d.ts.map +1 -0
  79. package/dist/lib/save/save-pipeline.d.ts +28 -0
  80. package/dist/lib/save/save-pipeline.d.ts.map +1 -0
  81. package/dist/lib/save/tool-output-target-handler.d.ts +7 -0
  82. package/dist/lib/save/tool-output-target-handler.d.ts.map +1 -0
  83. package/dist/lib/save/user-target-handler.d.ts +7 -0
  84. package/dist/lib/save/user-target-handler.d.ts.map +1 -0
  85. package/dist/lib/stream/change-source.d.ts +11 -0
  86. package/dist/lib/stream/change-source.d.ts.map +1 -0
  87. package/dist/lib/stream/ddl.stream.postgres-notify.d.ts +3 -0
  88. package/dist/lib/stream/ddl.stream.postgres-notify.d.ts.map +1 -0
  89. package/dist/lib/stream/ddl.stream.postgres.d.ts +2 -0
  90. package/dist/lib/stream/ddl.stream.postgres.d.ts.map +1 -0
  91. package/dist/lib/stream/polling-change-source.d.ts +27 -0
  92. package/dist/lib/stream/polling-change-source.d.ts.map +1 -0
  93. package/dist/lib/stream/polling-policy.d.ts +0 -1
  94. package/dist/lib/stream/polling-policy.d.ts.map +1 -1
  95. package/dist/lib/stream/postgres-notify-change-source.d.ts +15 -0
  96. package/dist/lib/stream/postgres-notify-change-source.d.ts.map +1 -0
  97. package/dist/lib/stream/postgres.stream-store.d.ts +29 -0
  98. package/dist/lib/stream/postgres.stream-store.d.ts.map +1 -0
  99. package/dist/lib/stream/stream-manager.d.ts +8 -27
  100. package/dist/lib/stream/stream-manager.d.ts.map +1 -1
  101. package/dist/lib/ui-message-guards.d.ts +10 -0
  102. package/dist/lib/ui-message-guards.d.ts.map +1 -0
  103. package/package.json +8 -13
  104. package/dist/lib/sandbox/extension.d.ts +0 -39
  105. package/dist/lib/sandbox/extension.d.ts.map +0 -1
  106. package/dist/lib/sandbox/openapi/demo.d.ts +0 -2
  107. package/dist/lib/sandbox/openapi/demo.d.ts.map +0 -1
  108. package/dist/lib/sandbox/openapi/extension.d.ts +0 -19
  109. package/dist/lib/sandbox/openapi/extension.d.ts.map +0 -1
  110. package/dist/lib/sandbox/openapi/format.d.ts +0 -11
  111. package/dist/lib/sandbox/openapi/format.d.ts.map +0 -1
  112. package/dist/lib/sandbox/openapi/schema.d.ts +0 -9
  113. package/dist/lib/sandbox/openapi/schema.d.ts.map +0 -1
  114. package/dist/lib/sandbox/openapi/skill/index.d.ts +0 -6
  115. package/dist/lib/sandbox/openapi/skill/index.d.ts.map +0 -1
  116. package/dist/lib/sandbox/openapi/skill/index.js +0 -14
  117. package/dist/lib/sandbox/openapi/skill/index.js.map +0 -7
  118. package/dist/lib/sandbox/openapi/skill/openapi-cli/SKILL.md +0 -109
  119. package/dist/lib/sandbox/openapi/validate.d.ts +0 -7
  120. package/dist/lib/sandbox/openapi/validate.d.ts.map +0 -1
  121. package/dist/lib/sandbox/routing-sandbox.d.ts +0 -28
  122. package/dist/lib/sandbox/routing-sandbox.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -163,7 +163,12 @@ function isFragment(data) {
163
163
  return typeof data === "object" && data !== null && "name" in data && ("data" in data || "codec" in data) && typeof data.name === "string";
164
164
  }
165
165
  function isFragmentObject(data) {
166
- return typeof data === "object" && data !== null && !Array.isArray(data) && !isFragment(data);
166
+ if (typeof data !== "object" || data === null) return false;
167
+ if (Array.isArray(data)) return false;
168
+ if (data instanceof Promise) return false;
169
+ if (Symbol.asyncIterator in data || Symbol.iterator in data) return false;
170
+ if (isFragment(data)) return false;
171
+ return true;
167
172
  }
168
173
  function isMessageFragment(fragment2) {
169
174
  return fragment2.type === "message";
@@ -236,7 +241,10 @@ function assistantText(content, options) {
236
241
  }
237
242
 
238
243
  // packages/context/src/lib/fragments/message/user.ts
239
- import { generateId as generateId2 } from "ai";
244
+ import {
245
+ generateId as generateId2,
246
+ isStaticToolUIPart
247
+ } from "ai";
240
248
 
241
249
  // packages/context/src/lib/renderers/abstract.renderer.ts
242
250
  import pluralize from "pluralize";
@@ -1130,7 +1138,56 @@ function isConditionalReminder(fragment2) {
1130
1138
  var SYSTEM_REMINDER_OPEN_TAG = "<system-reminder>";
1131
1139
  var SYSTEM_REMINDER_CLOSE_TAG = "</system-reminder>";
1132
1140
  function getReminderRanges(metadata) {
1133
- return metadata?.reminders ?? [];
1141
+ return getReminderMetadataRecords(metadata).map((record) => ({
1142
+ partIndex: record.partIndex,
1143
+ start: record.start,
1144
+ end: record.end
1145
+ }));
1146
+ }
1147
+ function getReminderMetadataRecords(metadata) {
1148
+ const reminders = metadata?.reminders;
1149
+ if (!Array.isArray(reminders)) return [];
1150
+ return reminders.filter(
1151
+ (item) => isRecord(item) && typeof item.partIndex === "number" && typeof item.start === "number" && typeof item.end === "number"
1152
+ );
1153
+ }
1154
+ function reminderTargetOf(record) {
1155
+ return record.target === "tool-output" ? "tool-output" : "user";
1156
+ }
1157
+ function normalizeReminderTarget(target) {
1158
+ if (target === void 0 || target === "user") return "user";
1159
+ if (target === "tool-output") return "tool-output";
1160
+ throw new Error(`Unsupported reminder target: ${String(target)}`);
1161
+ }
1162
+ function isConditionalReminderOptions(options) {
1163
+ return options !== void 0 && "when" in options;
1164
+ }
1165
+ function isPromiseLike(value) {
1166
+ return (typeof value === "object" || typeof value === "function") && value !== null && "then" in value && typeof value.then === "function";
1167
+ }
1168
+ function normalizeImmediateReminderText(textOrFragment) {
1169
+ if (isFragment(textOrFragment)) {
1170
+ return new XmlRenderer().render([textOrFragment]);
1171
+ }
1172
+ if (typeof textOrFragment === "string") {
1173
+ return textOrFragment;
1174
+ }
1175
+ return (ctx) => {
1176
+ const resolved = textOrFragment(ctx);
1177
+ if (isPromiseLike(resolved)) {
1178
+ throw new Error("Async reminder text requires a when predicate");
1179
+ }
1180
+ return resolved;
1181
+ };
1182
+ }
1183
+ function normalizeConditionalReminderText(textOrFragment) {
1184
+ return isFragment(textOrFragment) ? new XmlRenderer().render([textOrFragment]) : textOrFragment;
1185
+ }
1186
+ function isOutputAvailableToolPart(part) {
1187
+ return isStaticToolUIPart(part) && part.state === "output-available";
1188
+ }
1189
+ function isToolOutputReminderEnvelope(value) {
1190
+ return isRecord(value) && typeof value.systemReminder === "string";
1134
1191
  }
1135
1192
  function stripTextByRanges(text, ranges) {
1136
1193
  if (ranges.length === 0) {
@@ -1159,17 +1216,44 @@ function stripTextByRanges(text, ranges) {
1159
1216
  return output.trimEnd();
1160
1217
  }
1161
1218
  function stripReminders(message2) {
1162
- const reminderRanges = getReminderRanges(
1219
+ const reminderRecords = getReminderMetadataRecords(
1163
1220
  isRecord(message2.metadata) ? message2.metadata : void 0
1164
1221
  );
1165
1222
  const rangesByPartIndex = /* @__PURE__ */ new Map();
1166
- for (const range of reminderRanges) {
1223
+ const toolRemindersByPartIndex = /* @__PURE__ */ new Map();
1224
+ for (const range of reminderRecords) {
1225
+ if (reminderTargetOf(range) === "tool-output") {
1226
+ const records = toolRemindersByPartIndex.get(range.partIndex) ?? [];
1227
+ records.push(range);
1228
+ toolRemindersByPartIndex.set(range.partIndex, records);
1229
+ continue;
1230
+ }
1167
1231
  const partRanges = rangesByPartIndex.get(range.partIndex) ?? [];
1168
1232
  partRanges.push({ start: range.start, end: range.end });
1169
1233
  rangesByPartIndex.set(range.partIndex, partRanges);
1170
1234
  }
1171
1235
  const strippedParts = message2.parts.flatMap((part, partIndex) => {
1172
1236
  const clonedPart = { ...part };
1237
+ const toolReminderRecords = toolRemindersByPartIndex.get(partIndex);
1238
+ if (toolReminderRecords !== void 0 && isOutputAvailableToolPart(clonedPart)) {
1239
+ if (typeof clonedPart.output === "string") {
1240
+ return [
1241
+ {
1242
+ ...clonedPart,
1243
+ output: stripTextByRanges(
1244
+ clonedPart.output,
1245
+ toolReminderRecords.map((record) => ({
1246
+ start: record.start,
1247
+ end: record.end
1248
+ }))
1249
+ )
1250
+ }
1251
+ ];
1252
+ }
1253
+ if (isToolOutputReminderEnvelope(clonedPart.output)) {
1254
+ return [{ ...clonedPart, output: clonedPart.output.result }];
1255
+ }
1256
+ }
1173
1257
  const ranges = rangesByPartIndex.get(partIndex);
1174
1258
  if (clonedPart.type !== "text" || ranges === void 0) {
1175
1259
  return [clonedPart];
@@ -1239,6 +1323,7 @@ function applyInlineReminder(message2, value) {
1239
1323
  return {
1240
1324
  id: generateId2(),
1241
1325
  text: value,
1326
+ target: "user",
1242
1327
  partIndex,
1243
1328
  start,
1244
1329
  end: start + reminderText.length,
@@ -1252,6 +1337,7 @@ function applyPartReminder(message2, value) {
1252
1337
  return {
1253
1338
  id: generateId2(),
1254
1339
  text: value,
1340
+ target: "user",
1255
1341
  partIndex,
1256
1342
  start: 0,
1257
1343
  end: value.length,
@@ -1306,6 +1392,65 @@ function applyReminderToMessage(message2, item, ctx) {
1306
1392
  }
1307
1393
  return item.asPart ? applyPartReminder(message2, resolved.text) : applyInlineReminder(message2, resolved.text);
1308
1394
  }
1395
+ function findSingleOutputAvailableToolPart(message2) {
1396
+ let match = null;
1397
+ for (let partIndex = 0; partIndex < message2.parts.length; partIndex++) {
1398
+ const part = message2.parts[partIndex];
1399
+ if (!isOutputAvailableToolPart(part)) continue;
1400
+ if (match) return null;
1401
+ match = { partIndex, part };
1402
+ }
1403
+ return match;
1404
+ }
1405
+ function applyToolOutputRemindersToMessage(message2, reminders) {
1406
+ if (reminders.length === 0) return [];
1407
+ const target = findSingleOutputAvailableToolPart(message2);
1408
+ if (!target) return [];
1409
+ for (const reminder2 of reminders) {
1410
+ if (reminder2.metadata) {
1411
+ mergeMessageMetadata(message2, reminder2.metadata);
1412
+ }
1413
+ }
1414
+ const added = [];
1415
+ if (typeof target.part.output === "string") {
1416
+ let output = target.part.output;
1417
+ for (const reminder2 of reminders) {
1418
+ const reminderText = formatTaggedReminder(reminder2.text);
1419
+ const start = output.length;
1420
+ output = `${output}${reminderText}`;
1421
+ added.push({
1422
+ id: generateId2(),
1423
+ text: reminder2.text,
1424
+ target: "tool-output",
1425
+ partIndex: target.partIndex,
1426
+ start,
1427
+ end: start + reminderText.length,
1428
+ mode: "tool-output"
1429
+ });
1430
+ }
1431
+ message2.parts[target.partIndex] = {
1432
+ ...target.part,
1433
+ output
1434
+ };
1435
+ return added;
1436
+ }
1437
+ message2.parts[target.partIndex] = {
1438
+ ...target.part,
1439
+ output: {
1440
+ result: target.part.output,
1441
+ systemReminder: reminders.map((reminder2) => reminder2.text).join("\n")
1442
+ }
1443
+ };
1444
+ return reminders.map((reminder2) => ({
1445
+ id: generateId2(),
1446
+ text: reminder2.text,
1447
+ target: "tool-output",
1448
+ partIndex: target.partIndex,
1449
+ start: 0,
1450
+ end: 0,
1451
+ mode: "tool-output"
1452
+ }));
1453
+ }
1309
1454
  function mergeReminderMetadata(message2, addedReminders) {
1310
1455
  if (addedReminders.length === 0) return;
1311
1456
  const metadata = isRecord(message2.metadata) ? { ...message2.metadata } : {};
@@ -1314,28 +1459,37 @@ function mergeReminderMetadata(message2, addedReminders) {
1314
1459
  message2.metadata = metadata;
1315
1460
  }
1316
1461
  function reminder(textOrFragment, options) {
1317
- const fromFragment2 = isFragment(textOrFragment);
1318
- const text = fromFragment2 ? new XmlRenderer().render([textOrFragment]) : textOrFragment;
1319
- if (typeof text === "string") {
1320
- assertReminderText(text);
1321
- }
1322
- const asPart = options?.asPart ?? false;
1323
- if (options && "when" in options && options.when) {
1462
+ const target = normalizeReminderTarget(options?.target);
1463
+ const asPart = target === "user" ? options?.asPart ?? false : false;
1464
+ if (isConditionalReminderOptions(options)) {
1465
+ const text2 = normalizeConditionalReminderText(textOrFragment);
1466
+ if (typeof text2 === "string") {
1467
+ assertReminderText(text2);
1468
+ }
1324
1469
  return {
1325
1470
  name: "reminder",
1326
1471
  data: null,
1327
1472
  metadata: {
1328
1473
  reminder: {
1329
- text,
1474
+ text: text2,
1330
1475
  when: options.when,
1331
- asPart
1476
+ asPart,
1477
+ target
1332
1478
  }
1333
1479
  }
1334
1480
  };
1335
1481
  }
1482
+ if (target !== "user") {
1483
+ throw new Error('Reminder target "tool-output" requires a when predicate');
1484
+ }
1485
+ const text = normalizeImmediateReminderText(textOrFragment);
1486
+ if (typeof text === "string") {
1487
+ assertReminderText(text);
1488
+ }
1336
1489
  return {
1337
1490
  text,
1338
- asPart
1491
+ asPart,
1492
+ target
1339
1493
  };
1340
1494
  }
1341
1495
  function user(content, ...reminders) {
@@ -1402,27 +1556,27 @@ import {
1402
1556
  } from "ai";
1403
1557
  import chalk from "chalk";
1404
1558
  function createRepairToolCall(model, abortSignal) {
1405
- return async ({ toolCall, tools, inputSchema, error }) => {
1559
+ return async ({ toolCall: toolCall2, tools, inputSchema, error }) => {
1406
1560
  if (NoSuchToolError.isInstance(error)) {
1407
1561
  return null;
1408
1562
  }
1409
1563
  console.log(
1410
- `Debug: ${chalk.yellow("RepairingToolCall")}: ${toolCall.toolName}`
1564
+ `Debug: ${chalk.yellow("RepairingToolCall")}: ${toolCall2.toolName}`
1411
1565
  );
1412
- const tool3 = tools[toolCall.toolName];
1566
+ const tool3 = tools[toolCall2.toolName];
1413
1567
  const { output } = await generateText({
1414
1568
  abortSignal,
1415
1569
  model,
1416
1570
  output: Output.object({ schema: tool3.inputSchema }),
1417
1571
  prompt: [
1418
- `The model tried to call the tool "${toolCall.toolName}" with the following inputs:`,
1419
- JSON.stringify(toolCall.input),
1572
+ `The model tried to call the tool "${toolCall2.toolName}" with the following inputs:`,
1573
+ JSON.stringify(toolCall2.input),
1420
1574
  `The tool accepts the following schema:`,
1421
- JSON.stringify(inputSchema(toolCall)),
1575
+ JSON.stringify(inputSchema(toolCall2)),
1422
1576
  "Please fix the inputs."
1423
1577
  ].join("\n")
1424
1578
  });
1425
- return { ...toolCall, input: JSON.stringify(output) };
1579
+ return { ...toolCall2, input: JSON.stringify(output) };
1426
1580
  };
1427
1581
  }
1428
1582
 
@@ -1450,7 +1604,8 @@ var Agent = class _Agent {
1450
1604
  throw new Error(`Agent ${this.#options.name} is missing a model.`);
1451
1605
  }
1452
1606
  const { messages, systemPrompt } = await this.#options.context.resolve({
1453
- renderer: new XmlRenderer()
1607
+ renderer: new XmlRenderer(),
1608
+ sandbox: this.#options.sandbox
1454
1609
  });
1455
1610
  return generateText2({
1456
1611
  abortSignal: config?.abortSignal,
@@ -1470,10 +1625,10 @@ var Agent = class _Agent {
1470
1625
  ),
1471
1626
  toolChoice: this.#options.toolChoice,
1472
1627
  onStepFinish: (step) => {
1473
- const toolCall = step.toolCalls.at(-1);
1474
- if (toolCall) {
1628
+ const toolCall2 = step.toolCalls.at(-1);
1629
+ if (toolCall2) {
1475
1630
  console.log(
1476
- `Debug: ${chalk2.yellow("ToolCalled")}: ${toolCall.toolName}(${JSON.stringify(toolCall.input)})`
1631
+ `Debug: ${chalk2.yellow("ToolCalled")}: ${toolCall2.toolName}(${JSON.stringify(toolCall2.input)})`
1477
1632
  );
1478
1633
  }
1479
1634
  }
@@ -1522,7 +1677,8 @@ var Agent = class _Agent {
1522
1677
  throw new Error(`Agent ${this.#options.name} is missing a model.`);
1523
1678
  }
1524
1679
  const { messages, systemPrompt } = await context.resolve({
1525
- renderer: new XmlRenderer()
1680
+ renderer: new XmlRenderer(),
1681
+ sandbox: this.#options.sandbox
1526
1682
  });
1527
1683
  const runId = generateId3();
1528
1684
  return streamText({
@@ -1544,10 +1700,10 @@ var Agent = class _Agent {
1544
1700
  experimental_context: contextVariables,
1545
1701
  toolChoice: this.#options.toolChoice,
1546
1702
  onStepFinish: (step) => {
1547
- const toolCall = step.toolCalls.at(-1);
1548
- if (toolCall) {
1703
+ const toolCall2 = step.toolCalls.at(-1);
1704
+ if (toolCall2) {
1549
1705
  console.log(
1550
- `Debug: (${runId}) ${chalk2.bold.yellow("ToolCalled")}: ${toolCall.toolName}(${JSON.stringify(toolCall.input)})`
1706
+ `Debug: (${runId}) ${chalk2.bold.yellow("ToolCalled")}: ${toolCall2.toolName}(${JSON.stringify(toolCall2.input)})`
1551
1707
  );
1552
1708
  }
1553
1709
  }
@@ -1797,7 +1953,8 @@ function structuredOutput(options) {
1797
1953
  throw new Error(`structuredOutput is missing a model.`);
1798
1954
  }
1799
1955
  const { messages, systemPrompt } = await options.context.resolve({
1800
- renderer: new XmlRenderer()
1956
+ renderer: new XmlRenderer(),
1957
+ sandbox: options.sandbox
1801
1958
  });
1802
1959
  const result = await generateText2({
1803
1960
  abortSignal: config?.abortSignal,
@@ -1827,7 +1984,8 @@ function structuredOutput(options) {
1827
1984
  throw new Error(`structuredOutput is missing a model.`);
1828
1985
  }
1829
1986
  const { messages, systemPrompt } = await options.context.resolve({
1830
- renderer: new XmlRenderer()
1987
+ renderer: new XmlRenderer(),
1988
+ sandbox: options.sandbox
1831
1989
  });
1832
1990
  return streamText({
1833
1991
  abortSignal: config?.abortSignal,
@@ -1886,7 +2044,74 @@ import {
1886
2044
  generateId as generateId4,
1887
2045
  validateUIMessages
1888
2046
  } from "ai";
1889
- import { mergeWith } from "lodash-es";
2047
+
2048
+ // packages/context/src/lib/ui-message-guards.ts
2049
+ function isRecord2(value) {
2050
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2051
+ }
2052
+ function isUIMessage(value) {
2053
+ return isRecord2(value) && typeof value.id === "string" && (value.role === "system" || value.role === "user" || value.role === "assistant") && Array.isArray(value.parts);
2054
+ }
2055
+ function isUserUIMessage(value) {
2056
+ return isUIMessage(value) && value.role === "user";
2057
+ }
2058
+ function requireUIMessage(value, source) {
2059
+ if (isUIMessage(value)) {
2060
+ return value;
2061
+ }
2062
+ throw new Error(`${source} is not a UIMessage`);
2063
+ }
2064
+ function requireUserUIMessage(value, source) {
2065
+ if (isUserUIMessage(value)) {
2066
+ return value;
2067
+ }
2068
+ throw new Error(`${source} is not a user UIMessage`);
2069
+ }
2070
+
2071
+ // packages/context/src/lib/chain-summary.ts
2072
+ var ChainSummaryBuilder = class {
2073
+ #turn = 0;
2074
+ #messageCount = 0;
2075
+ #lastMessageAt;
2076
+ #lastMessage;
2077
+ #lastAssistantMessage;
2078
+ #lastAssistantMessages = [];
2079
+ ingestStored(msg) {
2080
+ this.#messageCount++;
2081
+ if (msg.name === "assistant") {
2082
+ const message2 = requireUIMessage(
2083
+ msg.data,
2084
+ `Stored assistant message "${msg.id}"`
2085
+ );
2086
+ this.#lastAssistantMessage = message2;
2087
+ this.#lastAssistantMessages.push(message2);
2088
+ return;
2089
+ }
2090
+ if (msg.name !== "user") {
2091
+ return;
2092
+ }
2093
+ this.#turn++;
2094
+ this.#lastMessageAt = msg.createdAt;
2095
+ this.#lastMessage = requireUIMessage(
2096
+ msg.data,
2097
+ `Stored user message "${msg.id}"`
2098
+ );
2099
+ }
2100
+ ingestPending(fragment2) {
2101
+ this.#messageCount++;
2102
+ if (fragment2.name === "user") this.#turn++;
2103
+ }
2104
+ build() {
2105
+ return {
2106
+ turn: this.#turn,
2107
+ messageCount: this.#messageCount,
2108
+ lastMessageAt: this.#lastMessageAt,
2109
+ lastMessage: this.#lastMessage,
2110
+ lastAssistantMessage: this.#lastAssistantMessage,
2111
+ lastAssistantMessages: this.#lastAssistantMessages
2112
+ };
2113
+ }
2114
+ };
1890
2115
 
1891
2116
  // packages/context/src/lib/estimate.ts
1892
2117
  import { encode } from "gpt-tokenizer";
@@ -2048,6 +2273,490 @@ async function estimate(modelId, renderer, ...fragments) {
2048
2273
  };
2049
2274
  }
2050
2275
 
2276
+ // packages/context/src/lib/resolvers/async-resolver.ts
2277
+ var AsyncResolver = class {
2278
+ name = "AsyncResolver";
2279
+ requiresSandbox = true;
2280
+ canResolve(value) {
2281
+ return typeof value === "function" && value.constructor.name === "AsyncFunction";
2282
+ }
2283
+ resolve(value, ctx) {
2284
+ return value(ctx);
2285
+ }
2286
+ };
2287
+
2288
+ // packages/context/src/lib/resolvers/function-resolver.ts
2289
+ var FunctionResolver = class {
2290
+ name = "FunctionResolver";
2291
+ requiresSandbox = true;
2292
+ canResolve(value) {
2293
+ return typeof value === "function";
2294
+ }
2295
+ async resolve(value, ctx) {
2296
+ const loader = value;
2297
+ return loader(ctx);
2298
+ }
2299
+ };
2300
+
2301
+ // packages/context/src/lib/resolvers/generator-resolver.ts
2302
+ var DEFAULT_MAX_ITEMS = 1e4;
2303
+ var GENERATOR_CTOR_NAMES = /* @__PURE__ */ new Set([
2304
+ "GeneratorFunction",
2305
+ "AsyncGeneratorFunction"
2306
+ ]);
2307
+ var GeneratorResolver = class {
2308
+ name = "GeneratorResolver";
2309
+ requiresSandbox = true;
2310
+ #maxItems;
2311
+ constructor(options = {}) {
2312
+ this.#maxItems = options.maxItems ?? DEFAULT_MAX_ITEMS;
2313
+ }
2314
+ canResolve(value) {
2315
+ return typeof value === "function" && GENERATOR_CTOR_NAMES.has(value.constructor.name);
2316
+ }
2317
+ async resolve(value, ctx) {
2318
+ const iterable = value(ctx);
2319
+ const collected = [];
2320
+ if (isAsyncIterable(iterable)) {
2321
+ for await (const chunk of iterable) {
2322
+ pushLimited(collected, chunk, this.#maxItems, this.name);
2323
+ }
2324
+ } else {
2325
+ for (const chunk of iterable) {
2326
+ pushLimited(collected, chunk, this.#maxItems, this.name);
2327
+ }
2328
+ }
2329
+ return collected;
2330
+ }
2331
+ };
2332
+ function isAsyncIterable(value) {
2333
+ return typeof value === "object" && value !== null && Symbol.asyncIterator in value;
2334
+ }
2335
+ function pushLimited(collected, chunk, maxItems, resolverName) {
2336
+ if (collected.length >= maxItems) {
2337
+ throw new Error(
2338
+ `${resolverName}: generator yielded more than ${maxItems} items`
2339
+ );
2340
+ }
2341
+ collected.push(chunk);
2342
+ }
2343
+
2344
+ // packages/context/src/lib/resolvers/iterable-resolver.ts
2345
+ var DEFAULT_MAX_ITEMS2 = 1e4;
2346
+ function hasIterableProtocol(value) {
2347
+ return Symbol.asyncIterator in value || Symbol.iterator in value;
2348
+ }
2349
+ function isBoxedPrimitive(value) {
2350
+ return value instanceof String || value instanceof Number || value instanceof Boolean;
2351
+ }
2352
+ var IterableResolver = class {
2353
+ name = "IterableResolver";
2354
+ #maxItems;
2355
+ constructor(options = {}) {
2356
+ this.#maxItems = options.maxItems ?? DEFAULT_MAX_ITEMS2;
2357
+ }
2358
+ canResolve(value) {
2359
+ if (value === null || value === void 0) return false;
2360
+ if (typeof value !== "object") return false;
2361
+ if (Array.isArray(value)) return false;
2362
+ if (value instanceof Promise) return false;
2363
+ if (isBoxedPrimitive(value)) return false;
2364
+ if (isFragment(value)) return false;
2365
+ return hasIterableProtocol(value);
2366
+ }
2367
+ async resolve(value, _ctx) {
2368
+ const iterable = value;
2369
+ const collected = [];
2370
+ if (isAsyncIterable2(iterable)) {
2371
+ for await (const chunk of iterable) {
2372
+ pushLimited2(collected, chunk, this.#maxItems, this.name);
2373
+ }
2374
+ } else {
2375
+ for (const chunk of iterable) {
2376
+ pushLimited2(collected, chunk, this.#maxItems, this.name);
2377
+ }
2378
+ }
2379
+ return collected;
2380
+ }
2381
+ };
2382
+ function isAsyncIterable2(value) {
2383
+ return typeof value === "object" && value !== null && Symbol.asyncIterator in value;
2384
+ }
2385
+ function pushLimited2(collected, chunk, maxItems, resolverName) {
2386
+ if (collected.length >= maxItems) {
2387
+ throw new Error(
2388
+ `${resolverName}: iterable yielded more than ${maxItems} items`
2389
+ );
2390
+ }
2391
+ collected.push(chunk);
2392
+ }
2393
+
2394
+ // packages/context/src/lib/resolvers/promise-resolver.ts
2395
+ var PromiseResolver = class {
2396
+ name = "PromiseResolver";
2397
+ canResolve(value) {
2398
+ return value instanceof Promise;
2399
+ }
2400
+ resolve(value, _ctx) {
2401
+ return value;
2402
+ }
2403
+ };
2404
+
2405
+ // packages/context/src/lib/resolvers/loader-resolver.ts
2406
+ var FragmentLoaderResolver = class {
2407
+ #resolvers;
2408
+ #maxDepth;
2409
+ constructor(resolvers, options = {}) {
2410
+ this.#resolvers = resolvers;
2411
+ this.#maxDepth = options.maxDepth ?? 10;
2412
+ }
2413
+ async resolve(fragments, ctx) {
2414
+ await Promise.all(
2415
+ fragments.map((f) => this.#walkFragment(f, ctx, 0, /* @__PURE__ */ new Set(), f.name))
2416
+ );
2417
+ }
2418
+ async #walkFragment(fragment2, ctx, depth, ancestors, path5) {
2419
+ if (!("data" in fragment2)) return;
2420
+ fragment2.data = await this.#walkData(
2421
+ fragment2.data,
2422
+ ctx,
2423
+ depth,
2424
+ ancestors,
2425
+ path5
2426
+ );
2427
+ }
2428
+ async #walkData(value, ctx, depth, ancestors, path5) {
2429
+ if (ctx.signal?.aborted) {
2430
+ throw ctx.signal.reason ?? new Error("Resolver aborted");
2431
+ }
2432
+ if (depth > this.#maxDepth) {
2433
+ throw new Error(
2434
+ `Resolver recursion exceeded maxDepth=${this.#maxDepth} at fragment '${path5}'`
2435
+ );
2436
+ }
2437
+ const handler = this.#resolvers.find((r) => r.canResolve(value));
2438
+ if (handler) {
2439
+ if (handler.requiresSandbox && !ctx.sandbox) {
2440
+ throw new Error(
2441
+ `Fragment '${path5}' is dispatched to ${handler.name}, which requires a sandbox; none was provided`
2442
+ );
2443
+ }
2444
+ let resolved;
2445
+ try {
2446
+ resolved = await this.#raceWithSignal(
2447
+ handler.resolve(value, ctx),
2448
+ ctx.signal
2449
+ );
2450
+ } catch (cause) {
2451
+ throw new Error(
2452
+ `Async fragment '${path5}' failed in ${handler.name}: ${cause instanceof Error ? cause.message : String(cause)}`,
2453
+ { cause }
2454
+ );
2455
+ }
2456
+ return this.#walkData(resolved, ctx, depth + 1, ancestors, path5);
2457
+ }
2458
+ if (value === null || value === void 0) {
2459
+ return value;
2460
+ }
2461
+ if (isFragment(value)) {
2462
+ if (ancestors.has(value)) return void 0;
2463
+ const childAncestors = new Set(ancestors);
2464
+ childAncestors.add(value);
2465
+ const childPath = `${path5}.${value.name}`;
2466
+ await this.#walkFragment(
2467
+ value,
2468
+ ctx,
2469
+ depth + 1,
2470
+ childAncestors,
2471
+ childPath
2472
+ );
2473
+ return value;
2474
+ }
2475
+ if (Array.isArray(value)) {
2476
+ if (ancestors.has(value)) return void 0;
2477
+ const childAncestors = new Set(ancestors);
2478
+ childAncestors.add(value);
2479
+ return Promise.all(
2480
+ value.map(
2481
+ (item) => this.#walkData(item, ctx, depth, childAncestors, path5)
2482
+ )
2483
+ );
2484
+ }
2485
+ if (isFragmentObject(value)) {
2486
+ if (ancestors.has(value)) return void 0;
2487
+ const childAncestors = new Set(ancestors);
2488
+ childAncestors.add(value);
2489
+ const out = {};
2490
+ const entries = Object.entries(value);
2491
+ const resolvedValues = await Promise.all(
2492
+ entries.map(
2493
+ ([, v]) => this.#walkData(v, ctx, depth, childAncestors, path5)
2494
+ )
2495
+ );
2496
+ for (let i = 0; i < entries.length; i += 1) {
2497
+ const [k] = entries[i];
2498
+ const v = resolvedValues[i];
2499
+ if (v !== void 0) out[k] = v;
2500
+ }
2501
+ return out;
2502
+ }
2503
+ return value;
2504
+ }
2505
+ #raceWithSignal(promise, signal) {
2506
+ if (!signal) return promise;
2507
+ if (signal.aborted) {
2508
+ return Promise.reject(signal.reason ?? new Error("Resolver aborted"));
2509
+ }
2510
+ return new Promise((resolve4, reject) => {
2511
+ const onAbort = () => reject(signal.reason ?? new Error("Resolver aborted"));
2512
+ signal.addEventListener("abort", onAbort, { once: true });
2513
+ promise.then(
2514
+ (value) => {
2515
+ signal.removeEventListener("abort", onAbort);
2516
+ resolve4(value);
2517
+ },
2518
+ (err) => {
2519
+ signal.removeEventListener("abort", onAbort);
2520
+ reject(err);
2521
+ }
2522
+ );
2523
+ });
2524
+ }
2525
+ };
2526
+
2527
+ // packages/context/src/lib/resolvers/index.ts
2528
+ function defaultResolvers() {
2529
+ return [
2530
+ new AsyncResolver(),
2531
+ new GeneratorResolver(),
2532
+ new FunctionResolver(),
2533
+ new PromiseResolver(),
2534
+ new IterableResolver()
2535
+ ];
2536
+ }
2537
+
2538
+ // packages/context/src/lib/save/save-pipeline.ts
2539
+ var SavePipeline = class {
2540
+ #engine;
2541
+ #pending;
2542
+ #fragments;
2543
+ #shouldBranch = true;
2544
+ constructor(engine, pending, fragments) {
2545
+ this.#engine = engine;
2546
+ this.#pending = pending;
2547
+ this.#fragments = fragments;
2548
+ }
2549
+ async applyUpdateBranching(shouldBranch) {
2550
+ this.#shouldBranch = shouldBranch;
2551
+ if (!shouldBranch) return this;
2552
+ for (const fragment2 of this.#pending) {
2553
+ if (!fragment2.id) continue;
2554
+ const existing = await this.#engine.store.getMessage(fragment2.id);
2555
+ if (existing && existing.parentId) {
2556
+ await this.#engine.rewindForUpdate(existing.parentId);
2557
+ fragment2.id = crypto.randomUUID();
2558
+ return this;
2559
+ }
2560
+ }
2561
+ return this;
2562
+ }
2563
+ async evaluateReminders(handlers) {
2564
+ const conditional = this.#fragments.filter(isConditionalReminder);
2565
+ if (conditional.length === 0) return this;
2566
+ const configsByTarget = /* @__PURE__ */ new Map();
2567
+ for (const fragment2 of conditional) {
2568
+ const config = fragment2.metadata.reminder;
2569
+ const target = config.target;
2570
+ const list = configsByTarget.get(target) ?? [];
2571
+ list.push(config);
2572
+ configsByTarget.set(target, list);
2573
+ }
2574
+ const chain = await this.#engine.getChainSummary();
2575
+ const base = this.#engine.buildBaseWhenCtx(chain);
2576
+ const sharedUserMessage = this.#encodePendingUserMessage();
2577
+ for (const handler of handlers) {
2578
+ const configs = configsByTarget.get(handler.target);
2579
+ if (!configs || configs.length === 0) continue;
2580
+ const prepared = handler.prepare({
2581
+ pending: this.#pending,
2582
+ base,
2583
+ chain,
2584
+ sharedUserMessage
2585
+ });
2586
+ if (!prepared) continue;
2587
+ const whenResults = await Promise.all(
2588
+ configs.map((config) => config.when(prepared.whenCtx))
2589
+ );
2590
+ const fired = configs.filter((_, i) => whenResults[i]);
2591
+ if (fired.length === 0) continue;
2592
+ const resolvedOrNull = await Promise.all(
2593
+ fired.map((config) => resolveReminderAsync(config, prepared.whenCtx))
2594
+ );
2595
+ const matched = [];
2596
+ for (let i = 0; i < resolvedOrNull.length; i++) {
2597
+ const resolution = resolvedOrNull[i];
2598
+ if (resolution)
2599
+ matched.push({ config: fired[i], resolved: resolution });
2600
+ }
2601
+ if (matched.length === 0) continue;
2602
+ handler.apply({
2603
+ pending: this.#pending,
2604
+ carrier: prepared.carrier,
2605
+ fired: matched.map((m) => m.config),
2606
+ resolved: matched.map((m) => m.resolved)
2607
+ });
2608
+ }
2609
+ return this;
2610
+ }
2611
+ #encodePendingUserMessage() {
2612
+ const fragmentIndex = this.#pending.findLastIndex(
2613
+ (fragment3) => fragment3.name === "user"
2614
+ );
2615
+ if (fragmentIndex < 0) return void 0;
2616
+ const fragment2 = this.#pending[fragmentIndex];
2617
+ if (!fragment2.codec) return void 0;
2618
+ return requireUserUIMessage(
2619
+ fragment2.codec.encode(),
2620
+ `Pending user fragment "${fragment2.name}"`
2621
+ );
2622
+ }
2623
+ async persist() {
2624
+ let parentId = this.#engine.getActiveBranch().headMessageId;
2625
+ const now = Date.now();
2626
+ for (const fragment2 of this.#pending) {
2627
+ if (!fragment2.codec) {
2628
+ throw new Error(`Fragment "${fragment2.name}" is missing codec.`);
2629
+ }
2630
+ const msgId = fragment2.id ?? crypto.randomUUID();
2631
+ let msgParentId = parentId;
2632
+ if (!this.#shouldBranch && msgId === parentId) {
2633
+ const existing = await this.#engine.store.getMessage(msgId);
2634
+ if (existing) msgParentId = existing.parentId;
2635
+ }
2636
+ const messageData = {
2637
+ id: msgId,
2638
+ chatId: this.#engine.chatId,
2639
+ parentId: msgParentId,
2640
+ name: fragment2.name,
2641
+ type: fragment2.type,
2642
+ data: fragment2.codec.encode(),
2643
+ createdAt: now
2644
+ };
2645
+ await this.#engine.store.addMessage(messageData);
2646
+ parentId = messageData.id;
2647
+ }
2648
+ if (parentId === null) {
2649
+ throw new Error(
2650
+ "Pipeline persisted no messages but pending was not empty"
2651
+ );
2652
+ }
2653
+ await this.#engine.commitHead(parentId);
2654
+ return { headMessageId: parentId };
2655
+ }
2656
+ };
2657
+
2658
+ // packages/context/src/lib/save/tool-output-target-handler.ts
2659
+ var ToolOutputTargetHandler = class {
2660
+ target = "tool-output";
2661
+ prepare({
2662
+ pending,
2663
+ base,
2664
+ chain,
2665
+ sharedUserMessage
2666
+ }) {
2667
+ const fragmentIndex = pending.findLastIndex(
2668
+ (fragment3) => fragment3.name === "assistant"
2669
+ );
2670
+ if (fragmentIndex < 0) return null;
2671
+ const fragment2 = pending[fragmentIndex];
2672
+ if (!fragment2.codec) return null;
2673
+ const currentMessage = sharedUserMessage ?? chain.lastMessage;
2674
+ if (!currentMessage) return null;
2675
+ const message2 = requireUIMessage(
2676
+ fragment2.codec.encode(),
2677
+ `Pending assistant fragment "${fragment2.name}"`
2678
+ );
2679
+ if (!findSingleOutputAvailableToolPart(message2)) return null;
2680
+ return {
2681
+ whenCtx: {
2682
+ ...base,
2683
+ content: extractPlainText(currentMessage),
2684
+ currentMessage,
2685
+ lastAssistantMessage: message2,
2686
+ lastAssistantMessages: [
2687
+ ...chain.lastAssistantMessages ?? [],
2688
+ message2
2689
+ ]
2690
+ },
2691
+ carrier: { message: message2, fragmentIndex }
2692
+ };
2693
+ }
2694
+ apply({ pending, carrier, resolved }) {
2695
+ const reminders = resolved.map((resolution) => ({
2696
+ text: resolution.text,
2697
+ metadata: resolution.metadata
2698
+ }));
2699
+ const metadata = applyToolOutputRemindersToMessage(
2700
+ carrier.message,
2701
+ reminders
2702
+ );
2703
+ mergeReminderMetadata(carrier.message, metadata);
2704
+ const originalId = pending[carrier.fragmentIndex].id;
2705
+ const message2 = originalId ? { ...carrier.message, id: originalId } : carrier.message;
2706
+ const updated = assistant(message2);
2707
+ if (originalId) updated.id = originalId;
2708
+ pending[carrier.fragmentIndex] = updated;
2709
+ }
2710
+ };
2711
+
2712
+ // packages/context/src/lib/save/user-target-handler.ts
2713
+ var UserTargetHandler = class {
2714
+ target = "user";
2715
+ prepare({
2716
+ pending,
2717
+ base,
2718
+ chain
2719
+ }) {
2720
+ const fragmentIndex = pending.findLastIndex(
2721
+ (fragment3) => fragment3.name === "user"
2722
+ );
2723
+ if (fragmentIndex < 0) return null;
2724
+ const fragment2 = pending[fragmentIndex];
2725
+ if (!fragment2.codec) return null;
2726
+ const message2 = requireUserUIMessage(
2727
+ fragment2.codec.encode(),
2728
+ `Pending user fragment "${fragment2.name}"`
2729
+ );
2730
+ return {
2731
+ whenCtx: {
2732
+ ...base,
2733
+ content: extractPlainText(message2),
2734
+ currentMessage: message2,
2735
+ lastAssistantMessage: chain.lastAssistantMessage,
2736
+ lastAssistantMessages: chain.lastAssistantMessages
2737
+ },
2738
+ carrier: { message: message2, fragmentIndex },
2739
+ sharedUserMessage: message2
2740
+ };
2741
+ }
2742
+ apply({ pending, carrier, fired, resolved }) {
2743
+ const reminders = resolved.map((resolution, i) => ({
2744
+ text: resolution.text,
2745
+ asPart: fired[i].asPart,
2746
+ target: "user",
2747
+ metadata: resolution.metadata
2748
+ }));
2749
+ const originalId = pending[carrier.fragmentIndex].id;
2750
+ const message2 = requireUserUIMessage(
2751
+ originalId ? { ...carrier.message, id: originalId } : carrier.message,
2752
+ "Pending user reminder carrier"
2753
+ );
2754
+ const recreated = user(message2, ...reminders);
2755
+ if (originalId) recreated.id = originalId;
2756
+ pending[carrier.fragmentIndex] = recreated;
2757
+ }
2758
+ };
2759
+
2051
2760
  // packages/context/src/lib/store/sqlite.store.ts
2052
2761
  import { DatabaseSync } from "node:sqlite";
2053
2762
 
@@ -2593,6 +3302,55 @@ function estimateMessageContent(data) {
2593
3302
  function isLanguageModelUsage(value) {
2594
3303
  return typeof value === "object" && value !== null && "totalTokens" in value;
2595
3304
  }
3305
+ function addUsageValue(current, next) {
3306
+ if (current === void 0 && next === void 0) {
3307
+ return void 0;
3308
+ }
3309
+ return (current ?? 0) + (next ?? 0);
3310
+ }
3311
+ function mergeLanguageModelUsage(current, next) {
3312
+ return {
3313
+ inputTokens: addUsageValue(current?.inputTokens, next.inputTokens),
3314
+ inputTokenDetails: {
3315
+ noCacheTokens: addUsageValue(
3316
+ current?.inputTokenDetails?.noCacheTokens,
3317
+ next.inputTokenDetails?.noCacheTokens
3318
+ ),
3319
+ cacheReadTokens: addUsageValue(
3320
+ current?.inputTokenDetails?.cacheReadTokens,
3321
+ next.inputTokenDetails?.cacheReadTokens
3322
+ ),
3323
+ cacheWriteTokens: addUsageValue(
3324
+ current?.inputTokenDetails?.cacheWriteTokens,
3325
+ next.inputTokenDetails?.cacheWriteTokens
3326
+ )
3327
+ },
3328
+ outputTokens: addUsageValue(current?.outputTokens, next.outputTokens),
3329
+ outputTokenDetails: {
3330
+ textTokens: addUsageValue(
3331
+ current?.outputTokenDetails?.textTokens,
3332
+ next.outputTokenDetails?.textTokens
3333
+ ),
3334
+ reasoningTokens: addUsageValue(
3335
+ current?.outputTokenDetails?.reasoningTokens,
3336
+ next.outputTokenDetails?.reasoningTokens
3337
+ )
3338
+ },
3339
+ totalTokens: addUsageValue(current?.totalTokens, next.totalTokens),
3340
+ reasoningTokens: addUsageValue(
3341
+ current?.reasoningTokens,
3342
+ next.reasoningTokens
3343
+ ),
3344
+ cachedInputTokens: addUsageValue(
3345
+ current?.cachedInputTokens,
3346
+ next.cachedInputTokens
3347
+ ),
3348
+ raw: next.raw ?? current?.raw
3349
+ };
3350
+ }
3351
+ function isSkillPathMapping(value) {
3352
+ return typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.name === "string" && typeof value.description === "string" && typeof value.host === "string" && typeof value.sandbox === "string";
3353
+ }
2596
3354
  function isEmptyAssistantPlaceholder(message2) {
2597
3355
  return message2.role === "assistant" && message2.parts.length === 0;
2598
3356
  }
@@ -2610,6 +3368,7 @@ var ContextEngine = class _ContextEngine {
2610
3368
  #initialized = false;
2611
3369
  /** Initial metadata to merge on first initialization */
2612
3370
  #initialMetadata;
3371
+ #loaderResolver;
2613
3372
  get #activeBranch() {
2614
3373
  if (!this.#branch) {
2615
3374
  throw new Error(
@@ -2633,6 +3392,9 @@ var ContextEngine = class _ContextEngine {
2633
3392
  this.#userId = options.userId;
2634
3393
  this.#branchName = "main";
2635
3394
  this.#initialMetadata = options.metadata;
3395
+ this.#loaderResolver = new FragmentLoaderResolver(
3396
+ options.resolvers ?? defaultResolvers()
3397
+ );
2636
3398
  }
2637
3399
  /**
2638
3400
  * Initialize the chat and branch if they don't exist.
@@ -2739,39 +3501,16 @@ var ContextEngine = class _ContextEngine {
2739
3501
  */
2740
3502
  async #getChainContext() {
2741
3503
  await this.#ensureInitialized();
2742
- let turn = 0;
2743
- let messageCount = 0;
2744
- let lastMessageAt;
2745
- let lastMessage;
2746
- let lastAssistantMessage;
3504
+ const builder = new ChainSummaryBuilder();
2747
3505
  if (this.#branch?.headMessageId) {
2748
3506
  const chain = await this.#store.getMessageChain(
2749
3507
  this.#branch.headMessageId
2750
3508
  );
2751
- for (const msg of chain) {
2752
- messageCount++;
2753
- if (msg.name === "assistant") {
2754
- lastAssistantMessage = msg.data;
2755
- }
2756
- if (msg.name !== "user") {
2757
- continue;
2758
- }
2759
- turn++;
2760
- lastMessageAt = msg.createdAt;
2761
- lastMessage = msg.data;
2762
- }
3509
+ for (const msg of chain) builder.ingestStored(msg);
2763
3510
  }
2764
- for (const fragment2 of this.#pendingMessages) {
2765
- messageCount++;
2766
- if (fragment2.name === "user") turn++;
2767
- }
2768
- return {
2769
- turn,
2770
- messageCount,
2771
- lastMessageAt,
2772
- lastMessage,
2773
- lastAssistantMessage
2774
- };
3511
+ for (const fragment2 of this.#pendingMessages)
3512
+ builder.ingestPending(fragment2);
3513
+ return builder.build();
2775
3514
  }
2776
3515
  async getTurnCount() {
2777
3516
  const { turn } = await this.#getChainContext();
@@ -2784,7 +3523,9 @@ var ContextEngine = class _ContextEngine {
2784
3523
  this.#branch.headMessageId
2785
3524
  );
2786
3525
  for (const msg of chain) {
2787
- if (msg.name === "user") return msg.data;
3526
+ if (msg.name === "user") {
3527
+ return requireUIMessage(msg.data, `Stored user message "${msg.id}"`);
3528
+ }
2788
3529
  }
2789
3530
  }
2790
3531
  for (const fragment2 of this.#pendingMessages) {
@@ -2792,7 +3533,10 @@ var ContextEngine = class _ContextEngine {
2792
3533
  if (!fragment2.codec) {
2793
3534
  throw new Error(`Fragment "${fragment2.name}" is missing codec.`);
2794
3535
  }
2795
- return fragment2.codec.encode();
3536
+ return requireUIMessage(
3537
+ fragment2.codec.encode(),
3538
+ `Pending fragment "${fragment2.name}"`
3539
+ );
2796
3540
  }
2797
3541
  return void 0;
2798
3542
  }
@@ -2835,7 +3579,7 @@ var ContextEngine = class _ContextEngine {
2835
3579
  this.#branch.headMessageId
2836
3580
  );
2837
3581
  for (const msg of chain) {
2838
- const data = msg.data;
3582
+ const data = requireUIMessage(msg.data, `Stored message "${msg.id}"`);
2839
3583
  if (isEmptyAssistantPlaceholder(data)) continue;
2840
3584
  messages.push(data);
2841
3585
  }
@@ -2844,7 +3588,10 @@ var ContextEngine = class _ContextEngine {
2844
3588
  if (!fragment2.codec) {
2845
3589
  throw new Error(`Fragment "${fragment2.name}" is missing codec.`);
2846
3590
  }
2847
- const encoded = fragment2.codec.encode();
3591
+ const encoded = requireUIMessage(
3592
+ fragment2.codec.encode(),
3593
+ `Pending fragment "${fragment2.name}"`
3594
+ );
2848
3595
  if (isEmptyAssistantPlaceholder(encoded)) continue;
2849
3596
  messages.push(encoded);
2850
3597
  }
@@ -2936,6 +3683,11 @@ var ContextEngine = class _ContextEngine {
2936
3683
  */
2937
3684
  async resolve(options) {
2938
3685
  await this.#ensureInitialized();
3686
+ await this.#loaderResolver.resolve(this.#fragments, {
3687
+ sandbox: options.sandbox,
3688
+ context: this,
3689
+ signal: options.signal
3690
+ });
2939
3691
  const systemPrompt = options.renderer.render(this.#renderableFragments);
2940
3692
  const messages = await this.getMessages();
2941
3693
  return { systemPrompt, messages };
@@ -2959,114 +3711,61 @@ var ContextEngine = class _ContextEngine {
2959
3711
  if (this.#pendingMessages.length === 0) {
2960
3712
  return { headMessageId: this.#branch?.headMessageId ?? void 0 };
2961
3713
  }
2962
- const shouldBranch = options?.branch ?? true;
2963
- if (shouldBranch) {
2964
- for (const fragment2 of this.#pendingMessages) {
2965
- if (fragment2.id) {
2966
- const existing = await this.#store.getMessage(fragment2.id);
2967
- if (existing && existing.parentId) {
2968
- await this.#rewindForUpdate(existing.parentId);
2969
- fragment2.id = crypto.randomUUID();
2970
- break;
2971
- }
3714
+ const pipeline = new SavePipeline(
3715
+ this.#asSavePipelineEngine(),
3716
+ this.#pendingMessages,
3717
+ this.#fragments
3718
+ );
3719
+ await pipeline.applyUpdateBranching(options?.branch ?? true);
3720
+ await pipeline.evaluateReminders(this.#reminderHandlers);
3721
+ const result = await pipeline.persist();
3722
+ this.#pendingMessages = [];
3723
+ return result;
3724
+ }
3725
+ #reminderHandlers = [
3726
+ new UserTargetHandler(),
3727
+ new ToolOutputTargetHandler()
3728
+ ];
3729
+ #asSavePipelineEngine() {
3730
+ return {
3731
+ store: this.#store,
3732
+ chatId: this.#chatId,
3733
+ branchName: this.#branchName,
3734
+ getActiveBranch: () => ({
3735
+ id: this.#activeBranch.id,
3736
+ headMessageId: this.#activeBranch.headMessageId
3737
+ }),
3738
+ commitHead: async (headMessageId) => {
3739
+ await this.#store.updateBranchHead(
3740
+ this.#activeBranch.id,
3741
+ headMessageId
3742
+ );
3743
+ this.#activeBranch.headMessageId = headMessageId;
3744
+ },
3745
+ rewindForUpdate: (parentId) => this.#rewindForUpdate(parentId),
3746
+ getChainSummary: () => this.#getChainContext(),
3747
+ buildBaseWhenCtx: (chain) => {
3748
+ const rawUsage = this.#chatData?.metadata?.usage;
3749
+ const usage = isLanguageModelUsage(rawUsage) ? rawUsage : void 0;
3750
+ const elapsed = chain.lastMessageAt !== void 0 ? Date.now() - chain.lastMessageAt : void 0;
3751
+ const chatData = this.#chatData;
3752
+ if (!chatData) {
3753
+ throw new Error(
3754
+ "ContextEngine must be initialized before reminders run"
3755
+ );
2972
3756
  }
3757
+ return {
3758
+ turn: chain.turn,
3759
+ messageCount: chain.messageCount,
3760
+ lastMessageAt: chain.lastMessageAt,
3761
+ lastMessage: chain.lastMessage,
3762
+ chat: chatData,
3763
+ usage,
3764
+ branch: this.#branchName,
3765
+ elapsed
3766
+ };
2973
3767
  }
2974
- }
2975
- const conditionalReminders = this.#fragments.filter(isConditionalReminder);
2976
- if (conditionalReminders.length > 0) {
2977
- let lastUserIndex = -1;
2978
- for (let i = this.#pendingMessages.length - 1; i >= 0; i--) {
2979
- if (this.#pendingMessages[i].name === "user") {
2980
- lastUserIndex = i;
2981
- break;
2982
- }
2983
- }
2984
- if (lastUserIndex >= 0) {
2985
- const lastUserFragment = this.#pendingMessages[lastUserIndex];
2986
- if (lastUserFragment.codec) {
2987
- const {
2988
- turn,
2989
- messageCount,
2990
- lastMessageAt,
2991
- lastMessage,
2992
- lastAssistantMessage
2993
- } = await this.#getChainContext();
2994
- const original = lastUserFragment.codec.encode();
2995
- const plainText = extractPlainText(original);
2996
- const rawUsage = this.#chatData?.metadata?.usage;
2997
- const usage = isLanguageModelUsage(rawUsage) ? rawUsage : void 0;
2998
- const elapsed = lastMessageAt !== void 0 ? Date.now() - lastMessageAt : void 0;
2999
- const whenCtx = {
3000
- turn,
3001
- content: plainText,
3002
- lastMessageAt,
3003
- lastMessage,
3004
- currentMessage: original,
3005
- chat: this.#chatData,
3006
- usage,
3007
- branch: this.#branchName,
3008
- elapsed,
3009
- messageCount,
3010
- lastAssistantMessage
3011
- };
3012
- const configs = conditionalReminders.map(
3013
- (it) => it.metadata.reminder
3014
- );
3015
- const whenResults = await Promise.all(
3016
- configs.map((it) => it.when(whenCtx))
3017
- );
3018
- const firedReminders = configs.filter((_, i) => whenResults[i]);
3019
- if (firedReminders.length > 0) {
3020
- const resolvedOrNull = await Promise.all(
3021
- firedReminders.map((it) => resolveReminderAsync(it, whenCtx))
3022
- );
3023
- const reminders = resolvedOrNull.flatMap((resolved, i) => {
3024
- if (!resolved) return [];
3025
- return [
3026
- {
3027
- text: resolved.text,
3028
- asPart: firedReminders[i].asPart,
3029
- metadata: resolved.metadata
3030
- }
3031
- ];
3032
- });
3033
- const recreated = user(original, ...reminders);
3034
- recreated.id = lastUserFragment.id;
3035
- this.#pendingMessages[lastUserIndex] = recreated;
3036
- }
3037
- }
3038
- }
3039
- }
3040
- let parentId = this.#activeBranch.headMessageId;
3041
- const now = Date.now();
3042
- for (const fragment2 of this.#pendingMessages) {
3043
- if (!fragment2.codec) {
3044
- throw new Error(`Fragment "${fragment2.name}" is missing codec.`);
3045
- }
3046
- const msgId = fragment2.id ?? crypto.randomUUID();
3047
- let msgParentId = parentId;
3048
- if (!shouldBranch && msgId === parentId) {
3049
- const existing = await this.#store.getMessage(msgId);
3050
- if (existing) {
3051
- msgParentId = existing.parentId;
3052
- }
3053
- }
3054
- const messageData = {
3055
- id: msgId,
3056
- chatId: this.#chatId,
3057
- parentId: msgParentId,
3058
- name: fragment2.name,
3059
- type: fragment2.type,
3060
- data: fragment2.codec.encode(),
3061
- createdAt: now
3062
- };
3063
- await this.#store.addMessage(messageData);
3064
- parentId = messageData.id;
3065
- }
3066
- await this.#store.updateBranchHead(this.#activeBranch.id, parentId);
3067
- this.#activeBranch.headMessageId = parentId;
3068
- this.#pendingMessages = [];
3069
- return { headMessageId: this.#activeBranch.headMessageId ?? void 0 };
3768
+ };
3070
3769
  }
3071
3770
  /**
3072
3771
  * Estimate token count and cost for the full context.
@@ -3082,6 +3781,11 @@ var ContextEngine = class _ContextEngine {
3082
3781
  */
3083
3782
  async estimate(modelId, options = {}) {
3084
3783
  await this.#ensureInitialized();
3784
+ await this.#loaderResolver.resolve(this.#fragments, {
3785
+ sandbox: options.sandbox,
3786
+ context: this,
3787
+ signal: options.signal
3788
+ });
3085
3789
  const renderer = options.renderer ?? new XmlRenderer();
3086
3790
  const registry = getModelsRegistry();
3087
3791
  await registry.load();
@@ -3354,13 +4058,9 @@ var ContextEngine = class _ContextEngine {
3354
4058
  async trackUsage(usage) {
3355
4059
  await this.#ensureInitialized();
3356
4060
  const freshChatData = await this.#store.getChat(this.#chatId);
3357
- const currentUsage = freshChatData?.metadata?.usage ?? {};
3358
- const updatedUsage = mergeWith(
3359
- {},
3360
- currentUsage,
3361
- usage,
3362
- (a, b) => typeof a === "number" || typeof b === "number" ? (a ?? 0) + (b ?? 0) : void 0
3363
- );
4061
+ const storedUsage = freshChatData?.metadata?.usage;
4062
+ const currentUsage = isLanguageModelUsage(storedUsage) ? storedUsage : void 0;
4063
+ const updatedUsage = mergeLanguageModelUsage(currentUsage, usage);
3364
4064
  this.#chatData = await this.#store.updateChat(this.#chatId, {
3365
4065
  metadata: {
3366
4066
  ...freshChatData?.metadata,
@@ -3415,8 +4115,9 @@ var ContextEngine = class _ContextEngine {
3415
4115
  */
3416
4116
  getSkillMounts() {
3417
4117
  for (const fragment2 of this.#fragments) {
3418
- if (fragment2.name === "available_skills" && fragment2.metadata?.mounts) {
3419
- return { mounts: fragment2.metadata.mounts };
4118
+ const mounts = fragment2.metadata?.mounts;
4119
+ if (fragment2.name === "available_skills" && Array.isArray(mounts) && mounts.every(isSkillPathMapping)) {
4120
+ return { mounts };
3420
4121
  }
3421
4122
  }
3422
4123
  return { mounts: [] };
@@ -3443,7 +4144,11 @@ var ContextEngine = class _ContextEngine {
3443
4144
  async inspect(options) {
3444
4145
  await this.#ensureInitialized();
3445
4146
  const { renderer } = options;
3446
- const estimateResult = await this.estimate(options.modelId, { renderer });
4147
+ const estimateResult = await this.estimate(options.modelId, {
4148
+ renderer,
4149
+ sandbox: options.sandbox,
4150
+ signal: options.signal
4151
+ });
3447
4152
  const rendered = renderer.render(this.#renderableFragments);
3448
4153
  const persistedMessages = [];
3449
4154
  if (this.#branch?.headMessageId) {
@@ -4515,6 +5220,32 @@ function or(...predicates) {
4515
5220
  function not(predicate) {
4516
5221
  return async (ctx) => !await predicate(ctx);
4517
5222
  }
5223
+ function withinLastN(n, predicate) {
5224
+ return async (ctx) => {
5225
+ if (n <= 0) return false;
5226
+ const candidates = (ctx.lastAssistantMessages ?? []).slice(-n);
5227
+ for (const message2 of candidates) {
5228
+ if (await predicate({ ...ctx, lastAssistantMessage: message2 })) {
5229
+ return true;
5230
+ }
5231
+ }
5232
+ return false;
5233
+ };
5234
+ }
5235
+ function everyOfLastN(n, predicate) {
5236
+ return async (ctx) => {
5237
+ if (n <= 0) return false;
5238
+ const history = ctx.lastAssistantMessages ?? [];
5239
+ if (history.length < n) return false;
5240
+ const candidates = history.slice(-n);
5241
+ for (const message2 of candidates) {
5242
+ if (!await predicate({ ...ctx, lastAssistantMessage: message2 })) {
5243
+ return false;
5244
+ }
5245
+ }
5246
+ return true;
5247
+ };
5248
+ }
4518
5249
 
4519
5250
  // packages/context/src/lib/fragments/reminders/content-predicates.ts
4520
5251
  function contentMatches(topics, options) {
@@ -4540,6 +5271,37 @@ function contentPattern(pattern) {
4540
5271
  };
4541
5272
  }
4542
5273
 
5274
+ // packages/context/src/lib/fragments/reminders/message-predicates.ts
5275
+ function assertCountSpec(spec) {
5276
+ const hasEq = spec.eq !== void 0;
5277
+ const hasRange = spec.gte !== void 0 || spec.lte !== void 0;
5278
+ if (!hasEq && !hasRange) {
5279
+ throw new Error("CountSpec must include at least one of gte/lte/eq");
5280
+ }
5281
+ if (hasEq && hasRange) {
5282
+ throw new Error("CountSpec.eq cannot be combined with gte/lte");
5283
+ }
5284
+ }
5285
+ function checkCount(count, spec) {
5286
+ if (spec.eq !== void 0) return count === spec.eq;
5287
+ if (spec.gte !== void 0 && count < spec.gte) return false;
5288
+ if (spec.lte !== void 0 && count > spec.lte) return false;
5289
+ return true;
5290
+ }
5291
+ function lastAssistantLength(spec) {
5292
+ assertCountSpec(spec);
5293
+ return (ctx) => {
5294
+ const message2 = ctx.lastAssistantMessage;
5295
+ const text = message2 ? extractPlainText(message2) : "";
5296
+ return checkCount(text.length, spec);
5297
+ };
5298
+ }
5299
+
5300
+ // packages/context/src/lib/fragments/reminders/temporal/elapsed.ts
5301
+ function elapsedExceeds(ms) {
5302
+ return (ctx) => (ctx.elapsed ?? 0) >= ms;
5303
+ }
5304
+
4543
5305
  // packages/context/src/lib/fragments/reminders/temporal/predicates.ts
4544
5306
  var LOCALE_METADATA_KEY = "locale";
4545
5307
  function getLocaleFromMessage(message2) {
@@ -4811,6 +5573,73 @@ function temporalReminder(options) {
4811
5573
  ];
4812
5574
  }
4813
5575
 
5576
+ // packages/context/src/lib/fragments/reminders/tool-predicates.ts
5577
+ import { isStaticToolUIPart as isStaticToolUIPart2 } from "ai";
5578
+ var COMPLETED_STATES = /* @__PURE__ */ new Set([
5579
+ "input-available",
5580
+ "output-available",
5581
+ "output-error"
5582
+ ]);
5583
+ function matchesName(spec, name) {
5584
+ return typeof spec === "function" ? spec(name) : spec === name;
5585
+ }
5586
+ function toolNameOf(part) {
5587
+ return part.type.slice("tool-".length);
5588
+ }
5589
+ function toolPartsOf(message2) {
5590
+ if (!message2) return [];
5591
+ return message2.parts.filter(isStaticToolUIPart2);
5592
+ }
5593
+ function toolCall(options) {
5594
+ return (ctx) => {
5595
+ const parts = toolPartsOf(ctx.lastAssistantMessage);
5596
+ return parts.some((part) => {
5597
+ if (options.state) {
5598
+ if (part.state !== options.state) return false;
5599
+ } else if (!COMPLETED_STATES.has(part.state)) {
5600
+ return false;
5601
+ }
5602
+ if (options.name !== void 0 && !matchesName(options.name, toolNameOf(part))) {
5603
+ return false;
5604
+ }
5605
+ if (options.input && !options.input(part.input)) {
5606
+ return false;
5607
+ }
5608
+ if (options.output) {
5609
+ if (part.state !== "output-available") return false;
5610
+ if (!options.output(part.output))
5611
+ return false;
5612
+ }
5613
+ if (options.errorText) {
5614
+ if (part.state !== "output-error") return false;
5615
+ const text = part.errorText ?? "";
5616
+ if (!options.errorText(text)) return false;
5617
+ }
5618
+ return true;
5619
+ });
5620
+ };
5621
+ }
5622
+ function toolCalled(name) {
5623
+ return toolCall({ name });
5624
+ }
5625
+ function toolFailed(name) {
5626
+ return toolCall({ name, state: "output-error" });
5627
+ }
5628
+ function anyToolCalled() {
5629
+ return (ctx) => toolPartsOf(ctx.lastAssistantMessage).some(
5630
+ (part) => COMPLETED_STATES.has(part.state)
5631
+ );
5632
+ }
5633
+ function toolCallCount(name, spec) {
5634
+ assertCountSpec(spec);
5635
+ return (ctx) => {
5636
+ const count = toolPartsOf(ctx.lastAssistantMessage).filter(
5637
+ (part) => COMPLETED_STATES.has(part.state) && matchesName(name, toolNameOf(part))
5638
+ ).length;
5639
+ return checkCount(count, spec);
5640
+ };
5641
+ }
5642
+
4814
5643
  // packages/context/src/lib/fragments/reminders/turn-predicates.ts
4815
5644
  function everyNTurns(n) {
4816
5645
  return ({ turn }) => turn % n === 0;
@@ -4825,6 +5654,11 @@ function afterTurn(n) {
4825
5654
  return ({ turn }) => turn > n;
4826
5655
  }
4827
5656
 
5657
+ // packages/context/src/lib/fragments/reminders/usage-predicates.ts
5658
+ function usageExceeds(totalTokens) {
5659
+ return (ctx) => (ctx.usage?.totalTokens ?? 0) >= totalTokens;
5660
+ }
5661
+
4828
5662
  // packages/context/src/lib/fragments/socratic.ts
4829
5663
  function socraticPrompting() {
4830
5664
  return [
@@ -5096,44 +5930,6 @@ async function useAgentOsSandbox(options, fn) {
5096
5930
  }
5097
5931
  }
5098
5932
 
5099
- // packages/context/src/lib/sandbox/ast-utils.ts
5100
- function asStaticWordText(word) {
5101
- if (!word) {
5102
- return null;
5103
- }
5104
- return asStaticWordPartText(
5105
- word.parts
5106
- );
5107
- }
5108
- function asStaticWordPartText(parts) {
5109
- let text = "";
5110
- for (const part of parts) {
5111
- const type = part.type;
5112
- if (type === "Literal" || type === "SingleQuoted" || type === "Escaped") {
5113
- if (typeof part.value !== "string") {
5114
- return null;
5115
- }
5116
- text += part.value;
5117
- continue;
5118
- }
5119
- if (type === "DoubleQuoted") {
5120
- if (!Array.isArray(part.parts)) {
5121
- return null;
5122
- }
5123
- const inner = asStaticWordPartText(
5124
- part.parts
5125
- );
5126
- if (inner == null) {
5127
- return null;
5128
- }
5129
- text += inner;
5130
- continue;
5131
- }
5132
- return null;
5133
- }
5134
- return text;
5135
- }
5136
-
5137
5933
  // packages/context/src/lib/sandbox/bash-exception.ts
5138
5934
  var BashException = class extends Error {
5139
5935
  };
@@ -5276,10 +6072,9 @@ function joinSandbox(base, relative3) {
5276
6072
  const normalizedBase = base.endsWith("/") ? base.slice(0, -1) : base;
5277
6073
  return `${normalizedBase}/${relative3}`;
5278
6074
  }
5279
- async function uploadSkills(sandbox, inputs) {
6075
+ function discoverSkillMappings(inputs) {
5280
6076
  if (inputs.length === 0) return [];
5281
6077
  const discoveredByName = /* @__PURE__ */ new Map();
5282
- const filesToUpload = [];
5283
6078
  for (const { host, sandbox: sandboxBase } of inputs) {
5284
6079
  const absoluteHost = path3.resolve(expandHome2(host));
5285
6080
  for (const skill of discoverSkillsInDirectory(host)) {
@@ -5292,6 +6087,14 @@ async function uploadSkills(sandbox, inputs) {
5292
6087
  sandbox: joinSandbox(sandboxBase, relative3)
5293
6088
  });
5294
6089
  }
6090
+ }
6091
+ return Array.from(discoveredByName.values());
6092
+ }
6093
+ async function uploadSkills(sandbox, inputs) {
6094
+ if (inputs.length === 0) return [];
6095
+ const skills2 = discoverSkillMappings(inputs);
6096
+ const filesToUpload = [];
6097
+ for (const { host, sandbox: sandboxBase } of inputs) {
5295
6098
  const walked = await walkDirectory(host);
5296
6099
  for (const file of walked) {
5297
6100
  filesToUpload.push({
@@ -5303,7 +6106,7 @@ async function uploadSkills(sandbox, inputs) {
5303
6106
  if (filesToUpload.length > 0) {
5304
6107
  await sandbox.writeFiles(filesToUpload);
5305
6108
  }
5306
- return Array.from(discoveredByName.values());
6109
+ return skills2;
5307
6110
  }
5308
6111
 
5309
6112
  // packages/context/src/lib/sandbox/bash-tool.ts
@@ -5339,7 +6142,16 @@ async function createBashTool(options = {}) {
5339
6142
  throw new Error("bash tool execution is not available");
5340
6143
  }
5341
6144
  return runWithBashMeta(async () => {
5342
- const result = await originalExecute({ command }, execOptions);
6145
+ let result;
6146
+ try {
6147
+ result = await originalExecute({ command }, execOptions);
6148
+ } catch (err) {
6149
+ if (err instanceof BashException) {
6150
+ result = err.format();
6151
+ } else {
6152
+ throw err;
6153
+ }
6154
+ }
5343
6155
  const state = readBashMeta();
5344
6156
  if (!state) return result;
5345
6157
  const hasHidden = Object.keys(state.hidden).length > 0;
@@ -5373,43 +6185,6 @@ async function createBashTool(options = {}) {
5373
6185
  };
5374
6186
  }
5375
6187
 
5376
- // packages/context/src/lib/sandbox/extension.ts
5377
- var DuplicateCommandError = class extends Error {
5378
- commandName;
5379
- constructor(commandName) {
5380
- super(`Duplicate extension command name: "${commandName}"`);
5381
- this.name = "DuplicateCommandError";
5382
- this.commandName = commandName;
5383
- }
5384
- };
5385
- function chainHooks(...hooks) {
5386
- return async (value) => {
5387
- let current = value;
5388
- for (const hook of hooks) {
5389
- current = await hook(current);
5390
- }
5391
- return current;
5392
- };
5393
- }
5394
- function mergeExtensions(...extensions) {
5395
- const commands = [];
5396
- const seen = /* @__PURE__ */ new Set();
5397
- for (const ext of extensions) {
5398
- for (const cmd of ext.commands ?? []) {
5399
- if (seen.has(cmd.name)) throw new DuplicateCommandError(cmd.name);
5400
- seen.add(cmd.name);
5401
- commands.push(cmd);
5402
- }
5403
- }
5404
- const hooks = extensions.map((e) => e.onBeforeBashCall).filter((h) => !!h);
5405
- return {
5406
- commands,
5407
- plugins: extensions.flatMap((e) => e.plugins ?? []),
5408
- env: Object.assign({}, ...extensions.map((e) => e.env ?? {})),
5409
- onBeforeBashCall: hooks.length > 0 ? chainHooks(...hooks) : void 0
5410
- };
5411
- }
5412
-
5413
6188
  // packages/context/src/lib/sandbox/file-events.ts
5414
6189
  var ObservedFs = class {
5415
6190
  #base;
@@ -5655,9 +6430,11 @@ function resolveRealCwd(ctx) {
5655
6430
 
5656
6431
  // packages/context/src/lib/sandbox/docker-sandbox.ts
5657
6432
  import "bash-tool";
5658
- import spawn2 from "nano-spawn";
6433
+ import spawn3 from "nano-spawn";
5659
6434
  import { createHash } from "node:crypto";
5660
6435
  import { existsSync as existsSync3, readFileSync as readFileSync2 } from "node:fs";
6436
+
6437
+ // packages/context/src/lib/sandbox/docker-sandbox-errors.ts
5661
6438
  var DockerSandboxError = class extends Error {
5662
6439
  containerId;
5663
6440
  constructor(message2, containerId) {
@@ -5699,19 +6476,33 @@ var PackageInstallError = class extends DockerSandboxError {
5699
6476
  this.stderr = stderr;
5700
6477
  }
5701
6478
  };
5702
- var BinaryInstallError = class extends DockerSandboxError {
5703
- binaryName;
5704
- url;
6479
+ var InstallError = class extends DockerSandboxError {
6480
+ target;
6481
+ source;
5705
6482
  reason;
5706
- constructor(binaryName, url, reason, containerId) {
6483
+ url;
6484
+ constructor(opts) {
6485
+ const where = opts.url ? `${opts.source} (${opts.url})` : opts.source;
5707
6486
  super(
5708
- `Failed to install binary "${binaryName}" from ${url}: ${reason}`,
5709
- containerId
6487
+ `Failed to install "${opts.target}" via ${where}: ${opts.reason}`,
6488
+ opts.containerId
5710
6489
  );
5711
- this.name = "BinaryInstallError";
5712
- this.binaryName = binaryName;
5713
- this.url = url;
5714
- this.reason = reason;
6490
+ this.name = "InstallError";
6491
+ this.target = opts.target;
6492
+ this.source = opts.source;
6493
+ this.reason = opts.reason;
6494
+ this.url = opts.url;
6495
+ }
6496
+ };
6497
+ var MissingRuntimeError = class extends DockerSandboxError {
6498
+ runtime;
6499
+ required;
6500
+ constructor(runtime, required, details, containerId) {
6501
+ const base = `Required runtime "${runtime}" is not installed (needs: ${required.join(", ")}).`;
6502
+ super(details ? `${base} ${details}` : base, containerId);
6503
+ this.name = "MissingRuntimeError";
6504
+ this.runtime = runtime;
6505
+ this.required = required;
5715
6506
  }
5716
6507
  };
5717
6508
  var MountPathError = class extends DockerSandboxError {
@@ -5744,12 +6535,108 @@ var ComposeStartError = class extends DockerSandboxError {
5744
6535
  this.stderr = stderr;
5745
6536
  }
5746
6537
  };
6538
+
6539
+ // packages/context/src/lib/sandbox/installers/installer.ts
6540
+ import "bash-tool";
6541
+ import spawn2 from "nano-spawn";
6542
+ var Installer = class {
6543
+ };
5747
6544
  function isDebianBased(image) {
5748
6545
  const lower = image.toLowerCase();
5749
6546
  if (lower.includes("alpine")) return false;
5750
6547
  const debianPatterns = ["debian", "ubuntu", "node", "python"];
5751
6548
  return debianPatterns.some((pattern) => lower.includes(pattern));
5752
6549
  }
6550
+ function shellQuote(s) {
6551
+ return `'${s.replace(/'/g, `'\\''`)}'`;
6552
+ }
6553
+ function createInstallerContext(containerId, image) {
6554
+ const packageManager = isDebianBased(image) ? "apt-get" : "apk";
6555
+ let archPromise = null;
6556
+ const ensuredTools = /* @__PURE__ */ new Set();
6557
+ let aptUpdated = false;
6558
+ const exec = async (command) => {
6559
+ try {
6560
+ const result = await spawn2("docker", [
6561
+ "exec",
6562
+ containerId,
6563
+ "sh",
6564
+ "-c",
6565
+ command
6566
+ ]);
6567
+ return { stdout: result.stdout, stderr: result.stderr, exitCode: 0 };
6568
+ } catch (error) {
6569
+ const err = error;
6570
+ return {
6571
+ stdout: err.stdout ?? "",
6572
+ stderr: err.stderr ?? err.message ?? "",
6573
+ exitCode: err.exitCode ?? 1
6574
+ };
6575
+ }
6576
+ };
6577
+ const arch = async () => {
6578
+ if (!archPromise) {
6579
+ const attempt = (async () => {
6580
+ const result = await exec("uname -m");
6581
+ if (result.exitCode !== 0) {
6582
+ throw new DockerSandboxError(
6583
+ `Failed to detect container architecture: ${result.stderr}`,
6584
+ containerId
6585
+ );
6586
+ }
6587
+ return result.stdout.trim();
6588
+ })();
6589
+ archPromise = attempt.catch((err) => {
6590
+ archPromise = null;
6591
+ throw err;
6592
+ });
6593
+ }
6594
+ return archPromise;
6595
+ };
6596
+ const installPackages = async (packages) => {
6597
+ if (packages.length === 0) return;
6598
+ const quoted = packages.map(shellQuote).join(" ");
6599
+ let cmd;
6600
+ if (packageManager === "apt-get") {
6601
+ cmd = aptUpdated ? `apt-get install -y ${quoted}` : `apt-get update && apt-get install -y ${quoted}`;
6602
+ } else {
6603
+ cmd = `apk add --no-cache ${quoted}`;
6604
+ }
6605
+ const result = await exec(cmd);
6606
+ if (result.exitCode !== 0) {
6607
+ throw new PackageInstallError(
6608
+ packages,
6609
+ image,
6610
+ packageManager,
6611
+ result.stderr,
6612
+ containerId
6613
+ );
6614
+ }
6615
+ if (packageManager === "apt-get") aptUpdated = true;
6616
+ };
6617
+ const ensureTool = async (checkName, installName) => {
6618
+ const cacheKey = installName ?? checkName;
6619
+ if (ensuredTools.has(cacheKey)) return;
6620
+ const check = await exec(`which ${shellQuote(checkName)}`);
6621
+ if (check.exitCode === 0) {
6622
+ ensuredTools.add(cacheKey);
6623
+ return;
6624
+ }
6625
+ await installPackages([installName ?? checkName]);
6626
+ ensuredTools.add(cacheKey);
6627
+ };
6628
+ return {
6629
+ containerId,
6630
+ image,
6631
+ packageManager,
6632
+ arch,
6633
+ exec,
6634
+ installPackages,
6635
+ ensureTool
6636
+ };
6637
+ }
6638
+
6639
+ // packages/context/src/lib/sandbox/docker-sandbox.ts
5753
6640
  function isDockerfileOptions(opts) {
5754
6641
  return "dockerfile" in opts;
5755
6642
  }
@@ -5761,7 +6648,8 @@ var DockerSandboxStrategy = class {
5761
6648
  mounts;
5762
6649
  resources;
5763
6650
  env;
5764
- constructor(mounts = [], resources = {}, env = {}) {
6651
+ constructor(args = {}) {
6652
+ const { mounts = [], resources = {}, env = {} } = args;
5765
6653
  for (const key of Object.keys(env)) {
5766
6654
  if (key.length === 0 || key.includes("=")) {
5767
6655
  throw new DockerSandboxError(
@@ -5773,16 +6661,6 @@ var DockerSandboxStrategy = class {
5773
6661
  this.resources = resources;
5774
6662
  this.env = env;
5775
6663
  }
5776
- /**
5777
- * Template method - defines the algorithm skeleton for creating a sandbox.
5778
- *
5779
- * Steps:
5780
- * 1. Validate mount paths exist on host
5781
- * 2. Get/build the Docker image (strategy-specific)
5782
- * 3. Start the container
5783
- * 4. Configure the container (strategy-specific)
5784
- * 5. Create and return sandbox methods
5785
- */
5786
6664
  async create() {
5787
6665
  this.validateMounts();
5788
6666
  const image = await this.getImage();
@@ -5796,12 +6674,6 @@ var DockerSandboxStrategy = class {
5796
6674
  }
5797
6675
  return this.createSandboxMethods();
5798
6676
  }
5799
- // ─────────────────────────────────────────────────────────────────────────
5800
- // Common implementations (shared by all strategies)
5801
- // ─────────────────────────────────────────────────────────────────────────
5802
- /**
5803
- * Validates that all mount paths exist on the host filesystem.
5804
- */
5805
6677
  validateMounts() {
5806
6678
  for (const mount of this.mounts) {
5807
6679
  if (!existsSync3(mount.hostPath)) {
@@ -5809,24 +6681,18 @@ var DockerSandboxStrategy = class {
5809
6681
  }
5810
6682
  }
5811
6683
  }
5812
- /**
5813
- * Builds the docker run command arguments.
5814
- */
5815
6684
  buildDockerArgs(image, containerId) {
5816
6685
  const { memory = "1g", cpus = 2 } = this.resources;
5817
6686
  const args = [
5818
6687
  "run",
5819
6688
  "-d",
5820
- // Detached mode
5821
6689
  "--rm",
5822
- // Remove container when stopped
5823
6690
  "--name",
5824
6691
  containerId,
5825
6692
  `--memory=${memory}`,
5826
6693
  `--cpus=${cpus}`,
5827
6694
  "-w",
5828
6695
  "/workspace"
5829
- // Set working directory
5830
6696
  ];
5831
6697
  for (const [key, value] of Object.entries(this.env)) {
5832
6698
  args.push("-e", `${key}=${value}`);
@@ -5838,14 +6704,11 @@ var DockerSandboxStrategy = class {
5838
6704
  args.push(image, "tail", "-f", "/dev/null");
5839
6705
  return args;
5840
6706
  }
5841
- /**
5842
- * Starts a Docker container with the given image.
5843
- */
5844
6707
  async startContainer(image) {
5845
6708
  const containerId = `sandbox-${crypto.randomUUID().slice(0, 8)}`;
5846
6709
  const args = this.buildDockerArgs(image, containerId);
5847
6710
  try {
5848
- await spawn2("docker", args);
6711
+ await spawn3("docker", args);
5849
6712
  } catch (error) {
5850
6713
  const err = error;
5851
6714
  if (err.message?.includes("Cannot connect") || err.message?.includes("docker daemon") || err.stderr?.includes("Cannot connect")) {
@@ -5855,21 +6718,15 @@ var DockerSandboxStrategy = class {
5855
6718
  }
5856
6719
  return containerId;
5857
6720
  }
5858
- /**
5859
- * Stops a Docker container.
5860
- */
5861
6721
  async stopContainer(containerId) {
5862
6722
  try {
5863
- await spawn2("docker", ["stop", containerId]);
6723
+ await spawn3("docker", ["stop", containerId]);
5864
6724
  } catch {
5865
6725
  }
5866
6726
  }
5867
- /**
5868
- * Executes a command in the container.
5869
- */
5870
6727
  async exec(command) {
5871
6728
  try {
5872
- const result = await spawn2("docker", [
6729
+ const result = await spawn3("docker", [
5873
6730
  "exec",
5874
6731
  this.context.containerId,
5875
6732
  "sh",
@@ -5890,9 +6747,6 @@ var DockerSandboxStrategy = class {
5890
6747
  };
5891
6748
  }
5892
6749
  }
5893
- /**
5894
- * Creates the DockerSandbox interface with all methods.
5895
- */
5896
6750
  createSandboxMethods() {
5897
6751
  const { containerId } = this.context;
5898
6752
  const sandbox = {
@@ -5932,229 +6786,64 @@ var DockerSandboxStrategy = class {
5932
6786
  };
5933
6787
  var RuntimeStrategy = class extends DockerSandboxStrategy {
5934
6788
  image;
5935
- packages;
5936
- binaries;
5937
- constructor(image = "alpine:latest", packages = [], binaries = [], mounts, resources, env) {
5938
- super(mounts, resources, env);
5939
- this.image = image;
5940
- this.packages = packages;
5941
- this.binaries = binaries;
6789
+ installers;
6790
+ constructor(args = {}) {
6791
+ super({ mounts: args.mounts, resources: args.resources, env: args.env });
6792
+ this.image = args.image ?? "alpine:latest";
6793
+ this.installers = args.installers ?? [];
5942
6794
  }
5943
6795
  async getImage() {
5944
6796
  return this.image;
5945
6797
  }
5946
6798
  async configure() {
5947
- await this.installPackages();
5948
- await this.installBinaries();
6799
+ const ctx = createInstallerContext(this.context.containerId, this.image);
6800
+ for (const installer of this.installers) {
6801
+ await installer.install(ctx);
6802
+ }
5949
6803
  }
5950
- /**
5951
- * Installs packages using the appropriate package manager (apk/apt-get).
5952
- */
5953
- async installPackages() {
5954
- if (this.packages.length === 0) return;
5955
- const useApt = isDebianBased(this.image);
5956
- const installCmd = useApt ? `apt-get update && apt-get install -y ${this.packages.join(" ")}` : `apk add --no-cache ${this.packages.join(" ")}`;
5957
- try {
5958
- await spawn2("docker", [
5959
- "exec",
5960
- this.context.containerId,
5961
- "sh",
5962
- "-c",
5963
- installCmd
5964
- ]);
5965
- } catch (error) {
5966
- const err = error;
5967
- throw new PackageInstallError(
5968
- this.packages,
5969
- this.image,
5970
- useApt ? "apt-get" : "apk",
5971
- err.stderr || err.message,
5972
- this.context.containerId
5973
- );
6804
+ };
6805
+ var DockerfileStrategy = class extends DockerSandboxStrategy {
6806
+ imageTag;
6807
+ dockerfile;
6808
+ dockerContext;
6809
+ constructor(args) {
6810
+ super({ mounts: args.mounts, resources: args.resources, env: args.env });
6811
+ this.dockerfile = args.dockerfile;
6812
+ this.dockerContext = args.context ?? ".";
6813
+ this.imageTag = this.computeImageTag();
6814
+ }
6815
+ computeImageTag() {
6816
+ const content = this.isInlineDockerfile() ? this.dockerfile : readFileSync2(this.dockerfile, "utf-8");
6817
+ const hash = createHash("sha256").update(content).digest("hex").slice(0, 12);
6818
+ return `sandbox-${hash}`;
6819
+ }
6820
+ isInlineDockerfile() {
6821
+ return this.dockerfile.includes("\n");
6822
+ }
6823
+ async getImage() {
6824
+ const exists = await this.imageExists();
6825
+ if (!exists) {
6826
+ await this.buildImage();
5974
6827
  }
6828
+ return this.imageTag;
5975
6829
  }
5976
- /**
5977
- * Installs binaries from URLs.
5978
- */
5979
- async installBinaries() {
5980
- if (this.binaries.length === 0) return;
5981
- await this.ensureCurl();
5982
- const arch = await this.detectArchitecture();
5983
- for (const binary of this.binaries) {
5984
- await this.installBinary(binary, arch);
6830
+ async configure() {
6831
+ }
6832
+ async imageExists() {
6833
+ try {
6834
+ await spawn3("docker", ["image", "inspect", this.imageTag]);
6835
+ return true;
6836
+ } catch {
6837
+ return false;
5985
6838
  }
5986
6839
  }
5987
- /**
5988
- * Ensures curl is installed in the container.
5989
- */
5990
- async ensureCurl() {
5991
- const checkResult = await spawn2("docker", [
5992
- "exec",
5993
- this.context.containerId,
5994
- "which",
5995
- "curl"
5996
- ]).catch(() => null);
5997
- if (checkResult) return;
5998
- const useApt = isDebianBased(this.image);
5999
- const curlInstallCmd = useApt ? "apt-get update && apt-get install -y curl" : "apk add --no-cache curl";
6000
- try {
6001
- await spawn2("docker", [
6002
- "exec",
6003
- this.context.containerId,
6004
- "sh",
6005
- "-c",
6006
- curlInstallCmd
6007
- ]);
6008
- } catch (error) {
6009
- const err = error;
6010
- throw new BinaryInstallError(
6011
- "curl",
6012
- "package-manager",
6013
- `Required for binary downloads: ${err.stderr || err.message}`,
6014
- this.context.containerId
6015
- );
6016
- }
6017
- }
6018
- /**
6019
- * Detects the container's CPU architecture.
6020
- */
6021
- async detectArchitecture() {
6022
- try {
6023
- const result = await spawn2("docker", [
6024
- "exec",
6025
- this.context.containerId,
6026
- "uname",
6027
- "-m"
6028
- ]);
6029
- return result.stdout.trim();
6030
- } catch (error) {
6031
- const err = error;
6032
- throw new DockerSandboxError(
6033
- `Failed to detect container architecture: ${err.stderr || err.message}`,
6034
- this.context.containerId
6035
- );
6036
- }
6037
- }
6038
- /**
6039
- * Installs a single binary from URL.
6040
- */
6041
- async installBinary(binary, arch) {
6042
- let url;
6043
- if (typeof binary.url === "string") {
6044
- url = binary.url;
6045
- } else {
6046
- const archUrl = binary.url[arch];
6047
- if (!archUrl) {
6048
- throw new BinaryInstallError(
6049
- binary.name,
6050
- `arch:${arch}`,
6051
- `No URL provided for architecture "${arch}". Available: ${Object.keys(binary.url).join(", ")}`,
6052
- this.context.containerId
6053
- );
6054
- }
6055
- url = archUrl;
6056
- }
6057
- const isTarGz = url.endsWith(".tar.gz") || url.endsWith(".tgz");
6058
- let installCmd;
6059
- if (isTarGz) {
6060
- const binaryPathInArchive = binary.binaryPath || binary.name;
6061
- installCmd = `
6062
- set -e
6063
- TMPDIR=$(mktemp -d)
6064
- cd "$TMPDIR"
6065
- curl -fsSL "${url}" -o archive.tar.gz
6066
- tar -xzf archive.tar.gz
6067
- BINARY_FILE=$(find . -name "${binaryPathInArchive}" -o -name "${binary.name}" | head -1)
6068
- if [ -z "$BINARY_FILE" ]; then
6069
- echo "Binary not found in archive. Contents:" >&2
6070
- find . -type f >&2
6071
- exit 1
6072
- fi
6073
- chmod +x "$BINARY_FILE"
6074
- mv "$BINARY_FILE" /usr/local/bin/${binary.name}
6075
- cd /
6076
- rm -rf "$TMPDIR"
6077
- `;
6078
- } else {
6079
- installCmd = `
6080
- curl -fsSL "${url}" -o /usr/local/bin/${binary.name}
6081
- chmod +x /usr/local/bin/${binary.name}
6082
- `;
6083
- }
6084
- try {
6085
- await spawn2("docker", [
6086
- "exec",
6087
- this.context.containerId,
6088
- "sh",
6089
- "-c",
6090
- installCmd
6091
- ]);
6092
- } catch (error) {
6093
- const err = error;
6094
- throw new BinaryInstallError(
6095
- binary.name,
6096
- url,
6097
- err.stderr || err.message,
6098
- this.context.containerId
6099
- );
6100
- }
6101
- }
6102
- };
6103
- var DockerfileStrategy = class extends DockerSandboxStrategy {
6104
- imageTag;
6105
- dockerfile;
6106
- dockerContext;
6107
- constructor(dockerfile, dockerContext = ".", mounts, resources, env) {
6108
- super(mounts, resources, env);
6109
- this.dockerfile = dockerfile;
6110
- this.dockerContext = dockerContext;
6111
- this.imageTag = this.computeImageTag();
6112
- }
6113
- /**
6114
- * Computes a deterministic image tag based on Dockerfile content.
6115
- * Same Dockerfile → same tag → Docker skips rebuild if image exists.
6116
- */
6117
- computeImageTag() {
6118
- const content = this.isInlineDockerfile() ? this.dockerfile : readFileSync2(this.dockerfile, "utf-8");
6119
- const hash = createHash("sha256").update(content).digest("hex").slice(0, 12);
6120
- return `sandbox-${hash}`;
6121
- }
6122
- /**
6123
- * Checks if the dockerfile property is inline content or a file path.
6124
- */
6125
- isInlineDockerfile() {
6126
- return this.dockerfile.includes("\n");
6127
- }
6128
- async getImage() {
6129
- const exists = await this.imageExists();
6130
- if (!exists) {
6131
- await this.buildImage();
6132
- }
6133
- return this.imageTag;
6134
- }
6135
- async configure() {
6136
- }
6137
- /**
6138
- * Checks if the image already exists locally.
6139
- */
6140
- async imageExists() {
6141
- try {
6142
- await spawn2("docker", ["image", "inspect", this.imageTag]);
6143
- return true;
6144
- } catch {
6145
- return false;
6146
- }
6147
- }
6148
- /**
6149
- * Builds the Docker image from the Dockerfile.
6150
- */
6151
- async buildImage() {
6840
+ async buildImage() {
6152
6841
  try {
6153
6842
  if (this.isInlineDockerfile()) {
6154
6843
  const buildCmd = `echo '${this.dockerfile.replace(/'/g, "'\\''")}' | docker build -t ${this.imageTag} -f - ${this.dockerContext}`;
6155
- await spawn2("sh", ["-c", buildCmd]);
6844
+ await spawn3("sh", ["-c", buildCmd]);
6156
6845
  } else {
6157
- await spawn2("docker", [
6846
+ await spawn3("docker", [
6158
6847
  "build",
6159
6848
  "-t",
6160
6849
  this.imageTag,
@@ -6173,33 +6862,23 @@ var ComposeStrategy = class extends DockerSandboxStrategy {
6173
6862
  projectName;
6174
6863
  composeFile;
6175
6864
  service;
6176
- constructor(composeFile, service, resources) {
6177
- super([], resources);
6178
- this.composeFile = composeFile;
6179
- this.service = service;
6865
+ constructor(args) {
6866
+ super({ resources: args.resources });
6867
+ this.composeFile = args.compose;
6868
+ this.service = args.service;
6180
6869
  this.projectName = this.computeProjectName();
6181
6870
  }
6182
- /**
6183
- * Deterministic project name based on compose file content for caching.
6184
- * Same compose file → same project name → faster subsequent startups.
6185
- */
6186
6871
  computeProjectName() {
6187
6872
  const content = readFileSync2(this.composeFile, "utf-8");
6188
6873
  const hash = createHash("sha256").update(content).digest("hex").slice(0, 8);
6189
6874
  return `sandbox-${hash}`;
6190
6875
  }
6191
- /**
6192
- * Override: No image to get - compose manages its own images.
6193
- */
6194
6876
  async getImage() {
6195
6877
  return "";
6196
6878
  }
6197
- /**
6198
- * Override: Start all services with docker compose up.
6199
- */
6200
6879
  async startContainer(_image) {
6201
6880
  try {
6202
- await spawn2("docker", [
6881
+ await spawn3("docker", [
6203
6882
  "compose",
6204
6883
  "-f",
6205
6884
  this.composeFile,
@@ -6219,12 +6898,9 @@ var ComposeStrategy = class extends DockerSandboxStrategy {
6219
6898
  }
6220
6899
  async configure() {
6221
6900
  }
6222
- /**
6223
- * Override: Execute commands in the target service.
6224
- */
6225
6901
  async exec(command) {
6226
6902
  try {
6227
- const result = await spawn2("docker", [
6903
+ const result = await spawn3("docker", [
6228
6904
  "compose",
6229
6905
  "-f",
6230
6906
  this.composeFile,
@@ -6232,7 +6908,6 @@ var ComposeStrategy = class extends DockerSandboxStrategy {
6232
6908
  this.projectName,
6233
6909
  "exec",
6234
6910
  "-T",
6235
- // -T disables pseudo-TTY
6236
6911
  this.service,
6237
6912
  "sh",
6238
6913
  "-c",
@@ -6248,12 +6923,9 @@ var ComposeStrategy = class extends DockerSandboxStrategy {
6248
6923
  };
6249
6924
  }
6250
6925
  }
6251
- /**
6252
- * Override: Stop all services with docker compose down.
6253
- */
6254
6926
  async stopContainer(_containerId) {
6255
6927
  try {
6256
- await spawn2("docker", [
6928
+ await spawn3("docker", [
6257
6929
  "compose",
6258
6930
  "-f",
6259
6931
  this.composeFile,
@@ -6268,28 +6940,27 @@ var ComposeStrategy = class extends DockerSandboxStrategy {
6268
6940
  async function createDockerSandbox(options = {}) {
6269
6941
  let strategy;
6270
6942
  if (isComposeOptions(options)) {
6271
- strategy = new ComposeStrategy(
6272
- options.compose,
6273
- options.service,
6274
- options.resources
6275
- );
6943
+ strategy = new ComposeStrategy({
6944
+ compose: options.compose,
6945
+ service: options.service,
6946
+ resources: options.resources
6947
+ });
6276
6948
  } else if (isDockerfileOptions(options)) {
6277
- strategy = new DockerfileStrategy(
6278
- options.dockerfile,
6279
- options.context,
6280
- options.mounts,
6281
- options.resources,
6282
- options.env
6283
- );
6949
+ strategy = new DockerfileStrategy({
6950
+ dockerfile: options.dockerfile,
6951
+ context: options.context,
6952
+ mounts: options.mounts,
6953
+ resources: options.resources,
6954
+ env: options.env
6955
+ });
6284
6956
  } else {
6285
- strategy = new RuntimeStrategy(
6286
- options.image,
6287
- options.packages,
6288
- options.binaries,
6289
- options.mounts,
6290
- options.resources,
6291
- options.env
6292
- );
6957
+ strategy = new RuntimeStrategy({
6958
+ image: options.image,
6959
+ installers: options.installers,
6960
+ mounts: options.mounts,
6961
+ resources: options.resources,
6962
+ env: options.env
6963
+ });
6293
6964
  }
6294
6965
  return strategy.create();
6295
6966
  }
@@ -6328,15 +6999,14 @@ async function createContainerTool(options = {}) {
6328
6999
  } else {
6329
7000
  const {
6330
7001
  image,
6331
- packages,
6332
- binaries,
7002
+ installers,
6333
7003
  mounts,
6334
7004
  resources,
6335
7005
  env,
6336
7006
  skills: skills2 = [],
6337
7007
  ...rest
6338
7008
  } = options;
6339
- sandboxOptions = { image, packages, binaries, mounts, resources, env };
7009
+ sandboxOptions = { image, installers, mounts, resources, env };
6340
7010
  bashOptions = rest;
6341
7011
  skillInputs = skills2;
6342
7012
  }
@@ -6354,215 +7024,252 @@ async function createContainerTool(options = {}) {
6354
7024
  };
6355
7025
  }
6356
7026
 
6357
- // packages/context/src/lib/sandbox/routing-sandbox.ts
6358
- import {
6359
- Bash,
6360
- defineCommand as defineCommand2,
6361
- parse
6362
- } from "just-bash";
6363
- function isInstallable(s) {
6364
- return typeof s.install === "function";
6365
- }
6366
- async function createVirtualSandbox(options) {
6367
- let bash = null;
6368
- const ensureInstalled = (op) => {
6369
- if (!bash) {
6370
- throw new Error(
6371
- `createVirtualSandbox: ${op} called before install(). Wrap this sandbox with createRoutingSandbox before use.`
6372
- );
6373
- }
6374
- return bash;
6375
- };
6376
- const sandbox = {
6377
- async install(ext) {
6378
- if (bash) {
6379
- throw new Error(
6380
- "createVirtualSandbox: install() called twice (extensions are install-once)"
6381
- );
6382
- }
6383
- const adapted = ext.commands.map(
6384
- (cmd) => adaptExtensionCommandForBash(cmd, () => sandbox)
6385
- );
6386
- bash = new Bash({
6387
- fs: options.fs,
6388
- cwd: options.cwd,
6389
- env: { ...options.env, ...ext.env },
6390
- customCommands: adapted
6391
- });
6392
- for (const plugin of ext.plugins) {
6393
- bash.registerTransformPlugin(plugin);
6394
- }
6395
- },
6396
- async executeCommand(command) {
6397
- const instance = ensureInstalled("executeCommand");
6398
- const result = await instance.exec(command);
6399
- return {
6400
- stdout: result.stdout,
6401
- stderr: result.stderr,
6402
- exitCode: result.exitCode
6403
- };
6404
- },
6405
- async readFile(path5) {
6406
- return ensureInstalled("readFile").readFile(path5);
6407
- },
6408
- async writeFiles(files) {
6409
- const instance = ensureInstalled("writeFiles");
6410
- for (const f of files) {
6411
- await instance.writeFile(
6412
- f.path,
6413
- typeof f.content === "string" ? f.content : Buffer.from(f.content).toString("utf-8")
6414
- );
6415
- }
6416
- }
6417
- };
6418
- return sandbox;
7027
+ // packages/context/src/lib/sandbox/installers/package-manager.ts
7028
+ var PackageInstaller = class extends Installer {
7029
+ kind;
7030
+ packages;
7031
+ constructor(packages) {
7032
+ super();
7033
+ this.packages = packages;
7034
+ this.kind = packages.length === 0 ? "pkg:<empty>" : `pkg:${packages.join(",")}`;
7035
+ }
7036
+ async install(ctx) {
7037
+ if (this.packages.length === 0) return;
7038
+ await ctx.installPackages([...this.packages]);
7039
+ }
7040
+ };
7041
+ function pkg(packages) {
7042
+ return new PackageInstaller(packages);
6419
7043
  }
6420
- async function createRoutingSandbox(opts) {
6421
- const merged = mergeExtensions(...opts.hostExtensions);
6422
- if (isInstallable(opts.backend)) {
6423
- await opts.backend.install(merged);
6424
- return wrapPreCallHook(opts.backend, merged.onBeforeBashCall);
7044
+
7045
+ // packages/context/src/lib/sandbox/installers/url-binary.ts
7046
+ var UrlBinaryInstaller = class extends Installer {
7047
+ kind;
7048
+ options;
7049
+ constructor(options) {
7050
+ super();
7051
+ this.options = options;
7052
+ this.kind = `url-binary:${options.name}`;
7053
+ }
7054
+ async install(ctx) {
7055
+ const url = await resolveUrl(ctx, this.options);
7056
+ await downloadAndInstall(
7057
+ ctx,
7058
+ this.options.name,
7059
+ url,
7060
+ this.options.binaryPath
7061
+ );
7062
+ }
7063
+ };
7064
+ function urlBinary(options) {
7065
+ return new UrlBinaryInstaller(options);
7066
+ }
7067
+ async function resolveUrl(ctx, options) {
7068
+ if (typeof options.url === "string") return options.url;
7069
+ const arch = await ctx.arch();
7070
+ const archUrl = options.url[arch];
7071
+ if (!archUrl) {
7072
+ throw new InstallError({
7073
+ target: options.name,
7074
+ source: "url",
7075
+ reason: `No URL provided for architecture "${arch}". Available: ${Object.keys(options.url).join(", ")}`,
7076
+ containerId: ctx.containerId
7077
+ });
7078
+ }
7079
+ return archUrl;
7080
+ }
7081
+ async function downloadAndInstall(ctx, name, url, binaryPath, source = "url") {
7082
+ await ctx.ensureTool("curl");
7083
+ const isTarGz = url.endsWith(".tar.gz") || url.endsWith(".tgz");
7084
+ const installCmd = isTarGz ? buildTarGzInstallCmd(name, url, binaryPath ?? name) : buildRawInstallCmd(name, url);
7085
+ const result = await ctx.exec(installCmd);
7086
+ if (result.exitCode !== 0) {
7087
+ throw new InstallError({
7088
+ target: name,
7089
+ source,
7090
+ url,
7091
+ reason: result.stderr,
7092
+ containerId: ctx.containerId
7093
+ });
6425
7094
  }
6426
- return createShallowRouter(opts.backend, merged, opts.cwd ?? "/");
6427
7095
  }
6428
- function wrapPreCallHook(backend, hook) {
6429
- if (!hook) return backend;
6430
- return {
6431
- executeCommand: async (raw) => {
6432
- const { command } = await hook({ command: raw });
6433
- return backend.executeCommand(command);
6434
- },
6435
- readFile: (path5) => backend.readFile(path5),
6436
- writeFiles: (files) => backend.writeFiles(files)
6437
- };
7096
+ function buildTarGzInstallCmd(name, url, binaryPathInArchive) {
7097
+ return `
7098
+ set -e
7099
+ NAME=${shellQuote(name)}
7100
+ URL=${shellQuote(url)}
7101
+ BIN_IN_ARCHIVE=${shellQuote(binaryPathInArchive)}
7102
+ TMPDIR=$(mktemp -d)
7103
+ cd "$TMPDIR"
7104
+ curl -fsSL "$URL" -o archive.tar.gz
7105
+ tar -xzf archive.tar.gz
7106
+ BINARY_FILE=$(find . -name "$BIN_IN_ARCHIVE" -o -name "$NAME" | head -1)
7107
+ if [ -z "$BINARY_FILE" ]; then
7108
+ echo "Binary not found in archive. Contents:" >&2
7109
+ find . -type f >&2
7110
+ exit 1
7111
+ fi
7112
+ chmod +x "$BINARY_FILE"
7113
+ mv "$BINARY_FILE" "/usr/local/bin/$NAME"
7114
+ cd /
7115
+ rm -rf "$TMPDIR"
7116
+ `;
7117
+ }
7118
+ function buildRawInstallCmd(name, url) {
7119
+ return `
7120
+ NAME=${shellQuote(name)}
7121
+ URL=${shellQuote(url)}
7122
+ curl -fsSL "$URL" -o "/usr/local/bin/$NAME"
7123
+ chmod +x "/usr/local/bin/$NAME"
7124
+ `;
6438
7125
  }
6439
- function createShallowRouter(backend, ext, cwd) {
6440
- const byName = new Map(ext.commands.map((c) => [c.name, c]));
6441
- const transformer = new Bash({});
6442
- for (const plugin of ext.plugins) {
6443
- transformer.registerTransformPlugin(plugin);
6444
- }
6445
- return {
6446
- readFile: (path5) => backend.readFile(path5),
6447
- writeFiles: (files) => backend.writeFiles(files),
6448
- executeCommand: async (raw) => {
6449
- const preHook = ext.onBeforeBashCall ? (await ext.onBeforeBashCall({ command: raw })).command : raw;
6450
- let transformed;
6451
- let dispatch;
6452
- try {
6453
- transformed = ext.plugins.length > 0 ? transformer.transform(preHook).script : preHook;
6454
- dispatch = extractDispatchTarget(parse(transformed), cwd);
6455
- } catch (err) {
6456
- return {
6457
- stdout: "",
6458
- stderr: `parse error: ${err.message}
6459
- `,
6460
- exitCode: 2
6461
- };
6462
- }
6463
- const [name, ...args] = dispatch?.tokens ?? [];
6464
- const cmd = name ? byName.get(name) : void 0;
6465
- if (cmd) {
6466
- return cmd.handler(args, {
6467
- sandbox: backend,
6468
- cwd: dispatch?.cwd ?? cwd,
6469
- env: ext.env,
6470
- stdin: ""
6471
- });
6472
- }
6473
- return backend.executeCommand(transformed);
7126
+
7127
+ // packages/context/src/lib/sandbox/installers/npm.ts
7128
+ var NODE_BINARIES = ["node", "npm"];
7129
+ var ENSURE_RUNTIME_HINT = "Pass `{ ensureRuntime: true }` to auto-install, or add it via `pkg([...])`.";
7130
+ var NpmInstaller = class extends Installer {
7131
+ kind;
7132
+ packageName;
7133
+ options;
7134
+ constructor(packageName, options = {}) {
7135
+ super();
7136
+ this.packageName = packageName;
7137
+ this.options = options;
7138
+ this.kind = `npm:${packageName}`;
7139
+ }
7140
+ async install(ctx) {
7141
+ await ensureNodeRuntime(ctx, this.options.ensureRuntime ?? false);
7142
+ const spec = this.options.version ? `${this.packageName}@${this.options.version}` : this.packageName;
7143
+ const result = await ctx.exec(`npm install -g ${spec}`);
7144
+ if (result.exitCode !== 0) {
7145
+ throw new InstallError({
7146
+ target: this.packageName,
7147
+ source: "npm",
7148
+ reason: result.stderr,
7149
+ containerId: ctx.containerId
7150
+ });
6474
7151
  }
6475
- };
6476
- }
6477
- function extractDispatchTarget(ast, defaultCwd) {
6478
- if (ast.statements.length !== 1) return null;
6479
- const statement = ast.statements[0];
6480
- if (statement.background) return null;
6481
- if (statement.operators.length === 0 && statement.pipelines.length === 1) {
6482
- const tokens = tokenizeSimplePipeline(statement.pipelines[0]);
6483
- return tokens ? { tokens, cwd: defaultCwd } : null;
6484
- }
6485
- if (statement.operators.length === 1 && statement.operators[0] === "&&" && statement.pipelines.length === 2) {
6486
- const cd = simpleCommandFromPipeline(statement.pipelines[0]);
6487
- const cdCwd = readCdTarget(cd);
6488
- if (!cdCwd) return null;
6489
- const tokens = tokenizeSimplePipeline(statement.pipelines[1]);
6490
- return tokens ? { tokens, cwd: cdCwd } : null;
6491
7152
  }
6492
- return null;
7153
+ };
7154
+ function npm(packageName, options) {
7155
+ return new NpmInstaller(packageName, options);
6493
7156
  }
6494
- function simpleCommandFromPipeline(pipeline) {
6495
- if (!pipeline) return null;
6496
- if (pipeline.negated || pipeline.timed || pipeline.commands.length !== 1) {
6497
- return null;
7157
+ async function ensureNodeRuntime(ctx, ensure) {
7158
+ if (ensure) {
7159
+ await ctx.ensureTool("node", "nodejs");
7160
+ await ctx.ensureTool("npm");
7161
+ return;
7162
+ }
7163
+ const check = await ctx.exec("which node && which npm");
7164
+ if (check.exitCode !== 0) {
7165
+ throw new MissingRuntimeError(
7166
+ "npm",
7167
+ NODE_BINARIES,
7168
+ ENSURE_RUNTIME_HINT,
7169
+ ctx.containerId
7170
+ );
6498
7171
  }
6499
- const command = pipeline.commands[0];
6500
- return command?.type === "SimpleCommand" ? command : null;
6501
7172
  }
6502
- function readCdTarget(command) {
6503
- if (!command) return null;
6504
- if (command.redirections.length > 0 || command.args.length !== 1) {
6505
- return null;
7173
+
7174
+ // packages/context/src/lib/sandbox/installers/pip.ts
7175
+ var PYTHON_BINARIES = ["python3", "pip3"];
7176
+ var PipInstaller = class extends Installer {
7177
+ kind;
7178
+ packageName;
7179
+ options;
7180
+ constructor(packageName, options = {}) {
7181
+ super();
7182
+ this.packageName = packageName;
7183
+ this.options = options;
7184
+ this.kind = `pip:${packageName}`;
7185
+ }
7186
+ async install(ctx) {
7187
+ await ensurePythonRuntime(ctx, this.options.ensureRuntime ?? false);
7188
+ const spec = this.options.version ? `${this.packageName}${formatVersion(this.options.version)}` : this.packageName;
7189
+ const flags = this.options.breakSystemPackages ?? true ? "--break-system-packages" : "";
7190
+ const result = await ctx.exec(`pip3 install ${flags} ${spec}`.trim());
7191
+ if (result.exitCode !== 0) {
7192
+ throw new InstallError({
7193
+ target: this.packageName,
7194
+ source: "pypi",
7195
+ reason: result.stderr,
7196
+ containerId: ctx.containerId
7197
+ });
7198
+ }
7199
+ }
7200
+ };
7201
+ function pip(packageName, options) {
7202
+ return new PipInstaller(packageName, options);
7203
+ }
7204
+ async function ensurePythonRuntime(ctx, ensure) {
7205
+ if (ensure) {
7206
+ const pipPackage = ctx.packageManager === "apk" ? "py3-pip" : "python3-pip";
7207
+ await ctx.ensureTool("python3");
7208
+ await ctx.ensureTool("pip3", pipPackage);
7209
+ return;
7210
+ }
7211
+ const check = await ctx.exec("which python3 && which pip3");
7212
+ if (check.exitCode !== 0) {
7213
+ throw new MissingRuntimeError(
7214
+ "pip",
7215
+ PYTHON_BINARIES,
7216
+ "Pass `{ ensureRuntime: true }` to auto-install, or add via `pkg([...])` (Alpine: `python3 py3-pip`; Debian: `python3 python3-pip`).",
7217
+ ctx.containerId
7218
+ );
6506
7219
  }
6507
- const name = asStaticWordText(command.name);
6508
- if (name !== "cd") return null;
6509
- return asStaticWordText(command.args[0]);
6510
- }
6511
- function tokenizeSimplePipeline(pipeline) {
6512
- const command = simpleCommandFromPipeline(pipeline);
6513
- if (!command || command.redirections.length > 0) return null;
6514
- const name = asStaticWordText(command.name);
6515
- if (!name) return null;
6516
- const args = [];
6517
- for (const arg of command.args) {
6518
- const text = asStaticWordText(arg);
6519
- if (text == null) return null;
6520
- args.push(text);
6521
- }
6522
- return [name, ...args];
6523
- }
6524
- function adaptExtensionCommandForBash(ext, getSandbox) {
6525
- return defineCommand2(ext.name, async (args, bashCtx) => {
6526
- return ext.handler(args, {
6527
- sandbox: getSandbox(),
6528
- cwd: bashCtx.cwd,
6529
- env: Object.fromEntries(bashCtx.env),
6530
- stdin: bashCtx.stdin,
6531
- signal: bashCtx.signal
6532
- });
6533
- });
7220
+ }
7221
+ function formatVersion(version) {
7222
+ return /^[<>=!~]/.test(version) ? version : `==${version}`;
6534
7223
  }
6535
7224
 
6536
- // packages/context/src/lib/sandbox/openapi/extension.ts
6537
- import { rpc } from "@sdk-it/rpc";
6538
- import {
6539
- forEachOperation,
6540
- loadSpec,
6541
- toIR
6542
- } from "@sdk-it/spec";
7225
+ // packages/context/src/lib/sandbox/installers/github-release.ts
7226
+ var GithubReleaseInstaller = class extends Installer {
7227
+ kind;
7228
+ options;
7229
+ constructor(options) {
7230
+ super();
7231
+ this.options = options;
7232
+ this.kind = `github-release:${options.owner}/${options.repo}@${options.version}`;
7233
+ }
7234
+ async install(ctx) {
7235
+ const arch = await ctx.arch();
7236
+ const assetName = this.options.asset(arch);
7237
+ const url = `https://github.com/${this.options.owner}/${this.options.repo}/releases/download/${this.options.version}/${assetName}`;
7238
+ await downloadAndInstall(
7239
+ ctx,
7240
+ this.options.name,
7241
+ url,
7242
+ this.options.binaryPath,
7243
+ "github-release"
7244
+ );
7245
+ }
7246
+ };
7247
+ function githubRelease(options) {
7248
+ return new GithubReleaseInstaller(options);
7249
+ }
6543
7250
 
6544
7251
  // packages/context/src/lib/sandbox/subcommand.ts
6545
- import { parse as parse2 } from "just-bash";
7252
+ import {
7253
+ defineCommand as defineCommand2,
7254
+ parse
7255
+ } from "just-bash";
6546
7256
  function defineSubcommandGroup(name, subcommands) {
6547
7257
  const usageLines = Object.entries(subcommands).map(([, def]) => ` ${name} ${def.usage.padEnd(30)} ${def.description}`).join("\n");
6548
- return {
6549
- name,
6550
- handler: async (args, ctx) => {
6551
- const subcommand = args[0];
6552
- const restArgs = args.slice(1);
6553
- if (subcommand && subcommand in subcommands) {
6554
- return subcommands[subcommand].handler(restArgs, ctx);
6555
- }
6556
- return {
6557
- stdout: "",
6558
- stderr: `${name}: ${subcommand ? `unknown subcommand '${subcommand}'` : "missing subcommand"}
7258
+ return defineCommand2(name, async (args, ctx) => {
7259
+ const subcommand = args[0];
7260
+ const restArgs = args.slice(1);
7261
+ if (subcommand && subcommand in subcommands) {
7262
+ return subcommands[subcommand].handler(restArgs, ctx);
7263
+ }
7264
+ return {
7265
+ stdout: "",
7266
+ stderr: `${name}: ${subcommand ? `unknown subcommand '${subcommand}'` : "missing subcommand"}
6559
7267
 
6560
7268
  Usage:
6561
7269
  ${usageLines}`,
6562
- exitCode: 1
6563
- };
6564
- }
6565
- };
7270
+ exitCode: 1
7271
+ };
7272
+ });
6566
7273
  }
6567
7274
  function escapeRegExp(s) {
6568
7275
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -6593,7 +7300,7 @@ function buildSubcommandRepair(name, subcommands) {
6593
7300
  const match = raw.match(pattern);
6594
7301
  if (!match) return raw;
6595
7302
  try {
6596
- parse2(raw);
7303
+ parse(raw);
6597
7304
  return raw;
6598
7305
  } catch {
6599
7306
  }
@@ -6605,7 +7312,7 @@ function buildSubcommandRepair(name, subcommands) {
6605
7312
  if (repairedArgs == null) return raw;
6606
7313
  const repaired = `${prefix} ${repairedArgs}`;
6607
7314
  try {
6608
- parse2(repaired);
7315
+ parse(repaired);
6609
7316
  return repaired;
6610
7317
  } catch {
6611
7318
  return raw;
@@ -6613,294 +7320,51 @@ function buildSubcommandRepair(name, subcommands) {
6613
7320
  };
6614
7321
  }
6615
7322
 
6616
- // packages/context/src/lib/sandbox/openapi/format.ts
6617
- function formatResponse(value) {
6618
- const payload = unwrapApiResponse(value);
6619
- if (Array.isArray(payload)) {
6620
- const stdout = payload.length === 0 ? "" : payload.map((item) => JSON.stringify(item)).join("\n") + "\n";
6621
- return { stdout, stderr: "", exitCode: 0 };
6622
- }
6623
- return { stdout: JSON.stringify(payload) + "\n", stderr: "", exitCode: 0 };
6624
- }
6625
- function unwrapApiResponse(value) {
6626
- if (value !== null && typeof value === "object" && "status" in value && "data" in value) {
6627
- return value.data;
6628
- }
6629
- return value;
6630
- }
6631
- function formatError(details) {
6632
- const payload = { ...details.extra ?? {} };
6633
- payload.ok = false;
6634
- payload.group = details.group;
6635
- payload.code = details.code;
6636
- payload.message = details.message;
6637
- if (details.operation) payload.operation = details.operation;
6638
- return {
6639
- stdout: "",
6640
- stderr: JSON.stringify(payload) + "\n",
6641
- exitCode: 1
6642
- };
6643
- }
6644
-
6645
- // packages/context/src/lib/sandbox/openapi/schema.ts
6646
- import { zodToJsonSchema } from "zod-to-json-schema";
6647
- function buildSchemaSubcommand(group, client, entries) {
7323
+ // packages/context/src/lib/sandbox/virtual-sandbox.ts
7324
+ import { Bash } from "just-bash";
7325
+ async function createVirtualSandbox(options) {
7326
+ const bash = new Bash({
7327
+ fs: options.fs,
7328
+ cwd: options.cwd,
7329
+ env: options.env,
7330
+ customCommands: options.customCommands
7331
+ });
6648
7332
  return {
6649
- usage: "schema",
6650
- description: "Dump JSON Schema for every operation in this group.",
6651
- handler: async () => {
6652
- try {
6653
- const operations = entries.map((entry) => {
6654
- const [method, path5] = entry.endpoint.split(" ");
6655
- const zodSchema = client.schemas[entry.endpoint]?.schema;
6656
- const input = zodSchema ? zodToJsonSchema(zodSchema, {
6657
- target: "jsonSchema7",
6658
- $refStrategy: "none"
6659
- }) : {};
6660
- return entry.summary ? {
6661
- operationId: entry.operationId,
6662
- method,
6663
- path: path5,
6664
- summary: entry.summary,
6665
- input
6666
- } : { operationId: entry.operationId, method, path: path5, input };
6667
- });
6668
- return formatResponse({ group, operations });
6669
- } catch (error) {
6670
- return formatError({
6671
- group,
6672
- operation: "schema",
6673
- code: "schema_dump_failed",
6674
- message: error instanceof Error ? error.message : String(error)
6675
- });
7333
+ async executeCommand(command) {
7334
+ const result = await bash.exec(command);
7335
+ return {
7336
+ stdout: result.stdout,
7337
+ stderr: result.stderr,
7338
+ exitCode: result.exitCode
7339
+ };
7340
+ },
7341
+ async readFile(path5) {
7342
+ return bash.readFile(path5);
7343
+ },
7344
+ async writeFiles(files) {
7345
+ for (const file of files) {
7346
+ await bash.writeFile(
7347
+ file.path,
7348
+ typeof file.content === "string" ? file.content : Buffer.from(file.content).toString("utf-8")
7349
+ );
6676
7350
  }
6677
7351
  }
6678
7352
  };
6679
7353
  }
6680
7354
 
6681
- // packages/context/src/lib/sandbox/openapi/validate.ts
6682
- function extractPathParams(path5) {
6683
- const names = [];
6684
- const re = /{([^}]+)}/g;
6685
- let match;
6686
- while ((match = re.exec(path5)) !== null) {
6687
- names.push(match[1]);
6688
- }
6689
- return names;
6690
- }
6691
- var UNSAFE_CHARS = /[/\\?#&]/;
6692
- var CONTROL = /[\x00-\x1f\x7f]/;
6693
- var PRE_ENCODED = /%[0-9a-fA-F]{2}/;
6694
- function findUnsafePathParam(input, names) {
6695
- if (input === null || typeof input !== "object" || Array.isArray(input)) {
6696
- return null;
7355
+ // packages/context/src/lib/skills/fragments.ts
7356
+ import dedent from "dedent";
7357
+ function skills(sandbox) {
7358
+ const mounts = sandbox.skills ?? [];
7359
+ if (mounts.length === 0) {
7360
+ return { name: "available_skills", data: [], metadata: { mounts: [] } };
6697
7361
  }
6698
- const record = input;
6699
- for (const name of names) {
6700
- const value = record[name];
6701
- if (typeof value !== "string") continue;
6702
- if (value.length === 0) {
6703
- return { field: name, reason: "is empty" };
6704
- }
6705
- if (value === "." || value === "..") {
6706
- return {
6707
- field: name,
6708
- reason: `resolves to '${value}' path segment`
6709
- };
6710
- }
6711
- const unsafeChar = value.match(UNSAFE_CHARS);
6712
- if (unsafeChar) {
6713
- return {
6714
- field: name,
6715
- reason: `contains disallowed character '${unsafeChar[0]}'`
6716
- };
6717
- }
6718
- if (CONTROL.test(value)) {
6719
- return { field: name, reason: "contains a control character" };
6720
- }
6721
- const encoded = value.match(PRE_ENCODED);
6722
- if (encoded) {
6723
- return {
6724
- field: name,
6725
- reason: `contains pre-encoded sequence '${encoded[0]}'`
6726
- };
6727
- }
6728
- }
6729
- return null;
6730
- }
6731
-
6732
- // packages/context/src/lib/sandbox/openapi/extension.ts
6733
- var RESERVED_SUBCOMMAND = "schema";
6734
- function serializeError(err) {
6735
- if (err === null || err === void 0) return err;
6736
- if (typeof err !== "object") return String(err);
6737
- if (err instanceof Error) {
6738
- const out = {
6739
- name: err.name,
6740
- message: err.message
6741
- };
6742
- for (const key of Object.getOwnPropertyNames(err)) {
6743
- if (key === "stack") continue;
6744
- const value = err[key];
6745
- out[key] = key === "cause" ? serializeError(value) : value;
6746
- }
6747
- return out;
6748
- }
6749
- return err;
6750
- }
6751
- async function createOpenAPIExtension(options) {
6752
- const { name, openapi, includeOperation, ...clientOptions } = options;
6753
- const ir = toIR({
6754
- spec: await loadSpec(openapi),
6755
- responses: { flattenErrorResponses: true }
6756
- });
6757
- if ((ir.servers?.length ?? 0) === 0 && !clientOptions.baseUrl) {
6758
- throw new Error(
6759
- `createOpenAPIExtension("${name}"): openapi spec has no servers and no baseUrl was provided`
6760
- );
6761
- }
6762
- const planned = [];
6763
- const registered = /* @__PURE__ */ new Map();
6764
- forEachOperation(ir, (entry, operation) => {
6765
- if (includeOperation && !includeOperation(entry, operation)) return;
6766
- const subName = operation["x-fn-name"] || operation.operationId;
6767
- const endpoint = `${entry.method.toUpperCase()} ${entry.path}`;
6768
- if (!subName) {
6769
- throw new Error(
6770
- `createOpenAPIExtension("${name}"): operation ${endpoint} has no x-fn-name or operationId`
6771
- );
6772
- }
6773
- if (subName === RESERVED_SUBCOMMAND) {
6774
- throw new Error(
6775
- `createOpenAPIExtension("${name}"): operation name "${RESERVED_SUBCOMMAND}" is reserved for introspection`
6776
- );
6777
- }
6778
- const existing = registered.get(subName);
6779
- if (existing) {
6780
- throw new Error(
6781
- `createOpenAPIExtension("${name}"): duplicate subcommand "${subName}" (from ${existing} and ${endpoint})`
6782
- );
6783
- }
6784
- registered.set(subName, endpoint);
6785
- planned.push({
6786
- subName,
6787
- endpoint,
6788
- description: operation.summary || operation.description || endpoint,
6789
- summary: operation.summary,
6790
- pathParamNames: extractPathParams(entry.path)
6791
- });
6792
- });
6793
- if (planned.length === 0) {
6794
- throw new Error(
6795
- `createOpenAPIExtension("${name}"): no operations matched; spec has no operations or includeOperation filtered all of them out`
6796
- );
6797
- }
6798
- const client = await rpc(openapi, clientOptions);
6799
- const subcommands = {};
6800
- const schemaEntries = [];
6801
- for (const {
6802
- subName,
6803
- endpoint,
6804
- description,
6805
- summary,
6806
- pathParamNames
6807
- } of planned) {
6808
- const schema = client.schemas[endpoint]?.schema;
6809
- if (!schema) {
6810
- throw new Error(
6811
- `createOpenAPIExtension("${name}"): no input schema for ${endpoint}`
6812
- );
6813
- }
6814
- schemaEntries.push({ endpoint, operationId: subName, summary });
6815
- subcommands[subName] = {
6816
- usage: `${subName} '<json>'`,
6817
- description,
6818
- repair: repairQuotedArg,
6819
- handler: async (args, ctx) => {
6820
- const raw = args.join(" ").trim();
6821
- if (!raw) {
6822
- return formatError({
6823
- group: name,
6824
- operation: subName,
6825
- code: "missing_input",
6826
- message: "no input provided"
6827
- });
6828
- }
6829
- let input;
6830
- try {
6831
- input = JSON.parse(raw);
6832
- } catch (error) {
6833
- return formatError({
6834
- group: name,
6835
- operation: subName,
6836
- code: "invalid_json",
6837
- message: error instanceof Error ? error.message : String(error)
6838
- });
6839
- }
6840
- const parsed = schema.safeParse(input);
6841
- if (!parsed.success) {
6842
- return formatError({
6843
- group: name,
6844
- operation: subName,
6845
- code: "schema_validation",
6846
- message: parsed.error.message,
6847
- extra: { issues: parsed.error.issues }
6848
- });
6849
- }
6850
- const unsafe = findUnsafePathParam(parsed.data, pathParamNames);
6851
- if (unsafe) {
6852
- return formatError({
6853
- group: name,
6854
- operation: subName,
6855
- code: "path_param_unsafe",
6856
- message: `path parameter '${unsafe.field}' ${unsafe.reason}`,
6857
- extra: { field: unsafe.field }
6858
- });
6859
- }
6860
- try {
6861
- const response = await client.request(endpoint, parsed.data, {
6862
- signal: ctx.signal
6863
- });
6864
- return formatResponse(response);
6865
- } catch (error) {
6866
- return formatError({
6867
- group: name,
6868
- operation: subName,
6869
- code: "request_failed",
6870
- message: error instanceof Error ? error.message : String(error),
6871
- extra: { error: serializeError(error) }
6872
- });
6873
- }
6874
- }
6875
- };
6876
- }
6877
- subcommands[RESERVED_SUBCOMMAND] = buildSchemaSubcommand(
6878
- name,
6879
- client,
6880
- schemaEntries
6881
- );
6882
- const command = defineSubcommandGroup(name, subcommands);
6883
- const repair = buildSubcommandRepair(name, subcommands);
6884
- return {
6885
- commands: [command],
6886
- onBeforeBashCall: ({ command: raw }) => ({ command: repair(raw) }),
6887
- client
6888
- };
6889
- }
6890
-
6891
- // packages/context/src/lib/skills/fragments.ts
6892
- import dedent from "dedent";
6893
- function skills(sandbox) {
6894
- const mounts = sandbox.skills ?? [];
6895
- if (mounts.length === 0) {
6896
- return { name: "available_skills", data: [], metadata: { mounts: [] } };
6897
- }
6898
- const skillFragments = mounts.map((mount) => ({
6899
- name: "skill",
6900
- data: {
6901
- name: mount.name,
6902
- path: mount.sandbox,
6903
- description: mount.description
7362
+ const skillFragments = mounts.map((mount) => ({
7363
+ name: "skill",
7364
+ data: {
7365
+ name: mount.name,
7366
+ path: mount.sandbox,
7367
+ description: mount.description
6904
7368
  }
6905
7369
  }));
6906
7370
  return {
@@ -8549,129 +9013,931 @@ async function persistedWriter(options) {
8549
9013
  }
8550
9014
  }
8551
9015
  }
8552
- const wrappedWriter = {
8553
- onError: writer.onError,
8554
- async write(part) {
8555
- await persistChunk(makeChunk(part));
8556
- writer.write(part);
8557
- },
8558
- merge(stream) {
8559
- const transform = new TransformStream({
8560
- async transform(chunk, controller) {
8561
- await persistChunk(makeChunk(chunk));
8562
- controller.enqueue(chunk);
8563
- }
8564
- });
8565
- writer.merge(stream.pipeThrough(transform));
9016
+ const wrappedWriter = {
9017
+ onError: writer.onError,
9018
+ async write(part) {
9019
+ await persistChunk(makeChunk(part));
9020
+ writer.write(part);
9021
+ },
9022
+ merge(stream) {
9023
+ const transform = new TransformStream({
9024
+ async transform(chunk, controller) {
9025
+ await persistChunk(makeChunk(chunk));
9026
+ controller.enqueue(chunk);
9027
+ }
9028
+ });
9029
+ writer.merge(stream.pipeThrough(transform));
9030
+ }
9031
+ };
9032
+ return {
9033
+ writer: wrappedWriter,
9034
+ streamId,
9035
+ flush,
9036
+ async complete() {
9037
+ await flush();
9038
+ await store2.updateStreamStatus(streamId, "completed");
9039
+ },
9040
+ async fail(error) {
9041
+ await flush();
9042
+ await store2.updateStreamStatus(streamId, "failed", { error });
9043
+ },
9044
+ async cleanup() {
9045
+ await store2.deleteStream(streamId);
9046
+ }
9047
+ };
9048
+ }
9049
+
9050
+ // packages/context/src/lib/stream/polling-change-source.ts
9051
+ import { setTimeout as delay } from "node:timers/promises";
9052
+
9053
+ // packages/context/src/lib/stream/polling-policy.ts
9054
+ var DEFAULT_WATCH_POLLING = {
9055
+ minMs: 25,
9056
+ maxMs: 500,
9057
+ multiplier: 2,
9058
+ jitterRatio: 0.15,
9059
+ statusCheckEvery: 3
9060
+ };
9061
+ var DEFAULT_CANCEL_POLLING = {
9062
+ minMs: 50,
9063
+ maxMs: 500,
9064
+ multiplier: 2,
9065
+ jitterRatio: 0.15
9066
+ };
9067
+ function normalizeWatchPolling(polling, fallback = DEFAULT_WATCH_POLLING) {
9068
+ const merged = {
9069
+ ...fallback,
9070
+ ...polling
9071
+ };
9072
+ const normalizedBase = normalizeAdaptivePolling(merged, fallback);
9073
+ return {
9074
+ ...normalizedBase,
9075
+ statusCheckEvery: clampInt(merged.statusCheckEvery, 1, 1e4)
9076
+ };
9077
+ }
9078
+ function normalizeCancelPolling(polling, fallback = DEFAULT_CANCEL_POLLING) {
9079
+ return normalizeAdaptivePolling(polling, fallback);
9080
+ }
9081
+ function createAdaptivePollingState(config) {
9082
+ return {
9083
+ config,
9084
+ currentMs: config.minMs
9085
+ };
9086
+ }
9087
+ function resetAdaptivePolling(state) {
9088
+ state.currentMs = state.config.minMs;
9089
+ }
9090
+ function nextAdaptivePollingDelay(state) {
9091
+ const current = clampInt(
9092
+ state.currentMs,
9093
+ state.config.minMs,
9094
+ state.config.maxMs
9095
+ );
9096
+ const delay2 = applyJitter(
9097
+ current,
9098
+ state.config.jitterRatio,
9099
+ state.config.minMs,
9100
+ state.config.maxMs
9101
+ );
9102
+ state.currentMs = clampInt(
9103
+ Math.ceil(current * state.config.multiplier),
9104
+ state.config.minMs,
9105
+ state.config.maxMs
9106
+ );
9107
+ return delay2;
9108
+ }
9109
+ function normalizeAdaptivePolling(polling, fallback) {
9110
+ const merged = {
9111
+ ...fallback,
9112
+ ...polling
9113
+ };
9114
+ const minMs = clampInt(merged.minMs, 1, 6e4);
9115
+ const maxMs = clampInt(merged.maxMs, minMs, 6e4);
9116
+ return {
9117
+ minMs,
9118
+ maxMs,
9119
+ multiplier: clampFloat(merged.multiplier, 1, 10),
9120
+ jitterRatio: clampFloat(merged.jitterRatio, 0, 1)
9121
+ };
9122
+ }
9123
+ function applyJitter(value, jitterRatio, min, max) {
9124
+ if (jitterRatio <= 0) return value;
9125
+ const radius = value * jitterRatio;
9126
+ const lowerBound = Math.max(0, value - radius);
9127
+ const upperBound = value + radius;
9128
+ const jittered = Math.round(
9129
+ lowerBound + Math.random() * (upperBound - lowerBound)
9130
+ );
9131
+ return clampInt(jittered, min, max);
9132
+ }
9133
+ function clampInt(value, min, max) {
9134
+ if (!Number.isFinite(value)) return min;
9135
+ return Math.min(max, Math.max(min, Math.round(value)));
9136
+ }
9137
+ function clampFloat(value, min, max) {
9138
+ if (!Number.isFinite(value)) return min;
9139
+ return Math.min(max, Math.max(min, value));
9140
+ }
9141
+
9142
+ // packages/context/src/lib/stream/polling-change-source.ts
9143
+ var PollingChangeSource = class {
9144
+ #reads;
9145
+ #config;
9146
+ #onPoll;
9147
+ constructor(options) {
9148
+ this.#reads = options.reads;
9149
+ this.#config = normalizeWatchPolling(options.config, DEFAULT_WATCH_POLLING);
9150
+ this.#onPoll = options.onPoll;
9151
+ }
9152
+ async *subscribe(streamId, signal) {
9153
+ const initialStatus = await this.#reads.getStreamStatus(streamId);
9154
+ if (initialStatus === void 0) {
9155
+ throw new Error(`Stream "${streamId}" not found`);
9156
+ }
9157
+ yield { kind: "tick" };
9158
+ if (isTerminal(initialStatus)) return;
9159
+ const delayState = createAdaptivePollingState(this.#config);
9160
+ let pollsSinceStatus = 0;
9161
+ let lastStatus = initialStatus;
9162
+ while (!signal.aborted) {
9163
+ const delayMs = nextAdaptivePollingDelay(delayState);
9164
+ this.#emit({ type: "idle", streamId, delayMs });
9165
+ if (!await waitForDelay(delayMs, signal)) return;
9166
+ pollsSinceStatus += 1;
9167
+ const shouldCheckStatus = pollsSinceStatus >= this.#config.statusCheckEvery;
9168
+ if (!shouldCheckStatus) {
9169
+ yield { kind: "chunks" };
9170
+ continue;
9171
+ }
9172
+ pollsSinceStatus = 0;
9173
+ const status = await this.#reads.getStreamStatus(streamId);
9174
+ this.#emit({
9175
+ type: "poll",
9176
+ streamId,
9177
+ delayMs,
9178
+ status: status ?? "missing"
9179
+ });
9180
+ if (status === void 0) {
9181
+ yield { kind: "status" };
9182
+ return;
9183
+ }
9184
+ if (status === "running" && lastStatus !== "running") {
9185
+ resetAdaptivePolling(delayState);
9186
+ }
9187
+ lastStatus = status;
9188
+ if (isTerminal(status)) {
9189
+ yield { kind: "status" };
9190
+ return;
9191
+ }
9192
+ yield { kind: "tick" };
9193
+ }
9194
+ }
9195
+ #emit(event) {
9196
+ if (!this.#onPoll) return;
9197
+ try {
9198
+ this.#onPoll(event);
9199
+ } catch {
9200
+ }
9201
+ }
9202
+ };
9203
+ function isTerminal(status) {
9204
+ return status !== "queued" && status !== "running";
9205
+ }
9206
+ function waitForDelay(ms, signal) {
9207
+ return delay(ms, true, { signal }).catch((err) => {
9208
+ if (signal.aborted) return false;
9209
+ throw err;
9210
+ });
9211
+ }
9212
+
9213
+ // packages/context/src/lib/sandbox/ast-utils.ts
9214
+ import { serialize } from "just-bash";
9215
+ function asStaticWordText(word, options = {}) {
9216
+ if (!word) {
9217
+ return null;
9218
+ }
9219
+ return asStaticWordPartText(
9220
+ word.parts,
9221
+ options
9222
+ );
9223
+ }
9224
+ function asStaticWordPartText(parts, options = {}) {
9225
+ let text = "";
9226
+ for (const part of parts) {
9227
+ const type = part.type;
9228
+ if (type === "Literal" || type === "SingleQuoted" || type === "Escaped") {
9229
+ if (typeof part.value !== "string") {
9230
+ return null;
9231
+ }
9232
+ text += part.value;
9233
+ continue;
9234
+ }
9235
+ if (type === "DoubleQuoted") {
9236
+ if (!Array.isArray(part.parts)) {
9237
+ return null;
9238
+ }
9239
+ const inner = asStaticWordPartText(
9240
+ part.parts,
9241
+ options
9242
+ );
9243
+ if (inner == null) {
9244
+ return null;
9245
+ }
9246
+ text += inner;
9247
+ continue;
9248
+ }
9249
+ if (options.preserveLegacyBackticks && type === "CommandSubstitution" && part.legacy === true) {
9250
+ text += "`" + serialize(part.body).trim() + "`";
9251
+ continue;
9252
+ }
9253
+ return null;
9254
+ }
9255
+ return text;
9256
+ }
9257
+
9258
+ // packages/context/src/lib/stream/postgres-notify-change-source.ts
9259
+ import { createRequire as createRequire3 } from "node:module";
9260
+
9261
+ // packages/context/src/lib/stream/ddl.stream.postgres-notify.ts
9262
+ var DEFAULT_POSTGRES_STREAM_CHANGES_CHANNEL = "deepagents_stream_changes";
9263
+ function postgresStreamNotifyDDL(schema, channel = DEFAULT_POSTGRES_STREAM_CHANGES_CHANNEL) {
9264
+ return `
9265
+ CREATE SCHEMA IF NOT EXISTS "${schema}";
9266
+
9267
+ CREATE OR REPLACE FUNCTION "${schema}"."notify_stream_chunks_insert"()
9268
+ RETURNS TRIGGER AS $$
9269
+ DECLARE
9270
+ changed_stream_id TEXT;
9271
+ BEGIN
9272
+ FOR changed_stream_id IN
9273
+ SELECT DISTINCT stream_id FROM new_rows
9274
+ LOOP
9275
+ PERFORM pg_notify(
9276
+ '${channel}',
9277
+ json_build_object(
9278
+ 'schema', TG_TABLE_SCHEMA,
9279
+ 'streamId', changed_stream_id,
9280
+ 'kind', 'chunks'
9281
+ )::text
9282
+ );
9283
+ END LOOP;
9284
+ RETURN NULL;
9285
+ END;
9286
+ $$ LANGUAGE plpgsql;
9287
+
9288
+ CREATE OR REPLACE TRIGGER "stream_chunks_notify_insert"
9289
+ AFTER INSERT ON "${schema}"."stream_chunks"
9290
+ REFERENCING NEW TABLE AS new_rows
9291
+ FOR EACH STATEMENT
9292
+ EXECUTE FUNCTION "${schema}"."notify_stream_chunks_insert"();
9293
+
9294
+ CREATE OR REPLACE FUNCTION "${schema}"."notify_stream_status_update"()
9295
+ RETURNS TRIGGER AS $$
9296
+ BEGIN
9297
+ IF OLD.status IS DISTINCT FROM NEW.status THEN
9298
+ PERFORM pg_notify(
9299
+ '${channel}',
9300
+ json_build_object(
9301
+ 'schema', TG_TABLE_SCHEMA,
9302
+ 'streamId', NEW.id,
9303
+ 'kind', 'status'
9304
+ )::text
9305
+ );
9306
+ END IF;
9307
+ RETURN NEW;
9308
+ END;
9309
+ $$ LANGUAGE plpgsql;
9310
+
9311
+ CREATE OR REPLACE TRIGGER "streams_notify_status_update"
9312
+ AFTER UPDATE OF status ON "${schema}"."streams"
9313
+ FOR EACH ROW
9314
+ EXECUTE FUNCTION "${schema}"."notify_stream_status_update"();
9315
+ `;
9316
+ }
9317
+
9318
+ // packages/context/src/lib/stream/postgres-notify-change-source.ts
9319
+ var PostgresNotifyChangeSource = class _PostgresNotifyChangeSource {
9320
+ #pool;
9321
+ #schema;
9322
+ #channel;
9323
+ #ownsPool;
9324
+ #listener;
9325
+ #listenPromise;
9326
+ #isInitialized = false;
9327
+ #isClosing = false;
9328
+ #isClosed = false;
9329
+ #subscribers = /* @__PURE__ */ new Map();
9330
+ #onNotification = (message2) => {
9331
+ this.#handleNotification(message2);
9332
+ };
9333
+ #onListenerError = (error) => {
9334
+ this.#failListener(error);
9335
+ };
9336
+ #onListenerEnd = () => {
9337
+ if (!this.#isClosing) {
9338
+ this.#failListener(
9339
+ new Error("PostgreSQL stream notification listener ended")
9340
+ );
9341
+ }
9342
+ };
9343
+ constructor(options) {
9344
+ const schema = options.schema ?? "public";
9345
+ const channel = options.channel ?? DEFAULT_POSTGRES_STREAM_CHANGES_CHANNEL;
9346
+ assertIdentifier(schema, "schema");
9347
+ assertIdentifier(channel, "channel");
9348
+ this.#schema = schema;
9349
+ this.#channel = channel;
9350
+ const pg = _PostgresNotifyChangeSource.#requirePg();
9351
+ if (options.pool instanceof pg.Pool) {
9352
+ this.#pool = options.pool;
9353
+ this.#ownsPool = false;
9354
+ } else {
9355
+ this.#pool = typeof options.pool === "string" ? new pg.Pool({ connectionString: options.pool }) : new pg.Pool(options.pool);
9356
+ this.#ownsPool = true;
9357
+ }
9358
+ }
9359
+ static #requirePg() {
9360
+ try {
9361
+ const require2 = createRequire3(import.meta.url);
9362
+ return require2("pg");
9363
+ } catch {
9364
+ throw new Error(
9365
+ 'PostgresNotifyChangeSource requires the "pg" package. Install it with: npm install pg'
9366
+ );
9367
+ }
9368
+ }
9369
+ async initialize() {
9370
+ await this.#pool.query(
9371
+ postgresStreamNotifyDDL(this.#schema, this.#channel)
9372
+ );
9373
+ this.#isInitialized = true;
9374
+ }
9375
+ async *subscribe(streamId, signal) {
9376
+ this.#ensureInitialized();
9377
+ this.#ensureOpen();
9378
+ const queue = new ChangeQueue();
9379
+ this.#addSubscriber(streamId, queue);
9380
+ const cleanup = () => {
9381
+ queue.close();
9382
+ this.#removeSubscriber(streamId, queue);
9383
+ };
9384
+ signal.addEventListener("abort", cleanup, { once: true });
9385
+ try {
9386
+ await this.#ensureListening();
9387
+ if (signal.aborted || this.#isClosed) return;
9388
+ yield { kind: "tick" };
9389
+ while (!signal.aborted && !this.#isClosed) {
9390
+ const change = await queue.next(signal);
9391
+ if (!change) return;
9392
+ yield change;
9393
+ }
9394
+ } finally {
9395
+ signal.removeEventListener("abort", cleanup);
9396
+ cleanup();
9397
+ await this.#releaseListenerIfIdle();
9398
+ }
9399
+ }
9400
+ async close() {
9401
+ if (this.#isClosed) return;
9402
+ this.#isClosed = true;
9403
+ this.#isClosing = true;
9404
+ for (const subscribers of this.#subscribers.values()) {
9405
+ for (const subscriber of subscribers) {
9406
+ subscriber.close();
9407
+ }
9408
+ }
9409
+ this.#subscribers.clear();
9410
+ if (this.#listenPromise) {
9411
+ try {
9412
+ await this.#listenPromise;
9413
+ } catch {
9414
+ }
9415
+ }
9416
+ await this.#releaseListener();
9417
+ if (this.#ownsPool) {
9418
+ await this.#pool.end();
9419
+ }
9420
+ }
9421
+ #ensureInitialized() {
9422
+ if (!this.#isInitialized) {
9423
+ throw new Error(
9424
+ "PostgresNotifyChangeSource not initialized. Call await source.initialize() after construction."
9425
+ );
9426
+ }
9427
+ }
9428
+ #ensureOpen() {
9429
+ if (this.#isClosed) {
9430
+ throw new Error("PostgresNotifyChangeSource is closed.");
9431
+ }
9432
+ }
9433
+ async #ensureListening() {
9434
+ this.#ensureOpen();
9435
+ if (this.#listener) return;
9436
+ if (this.#listenPromise) return this.#listenPromise;
9437
+ this.#listenPromise = (async () => {
9438
+ const listener = await this.#pool.connect();
9439
+ let success = false;
9440
+ try {
9441
+ listener.on("notification", this.#onNotification);
9442
+ listener.on("error", this.#onListenerError);
9443
+ listener.on("end", this.#onListenerEnd);
9444
+ await listener.query(`LISTEN ${quoteIdentifier(this.#channel)}`);
9445
+ if (this.#isClosed) {
9446
+ return;
9447
+ }
9448
+ this.#listener = listener;
9449
+ success = true;
9450
+ } finally {
9451
+ this.#listenPromise = void 0;
9452
+ if (!success) {
9453
+ listener.off("notification", this.#onNotification);
9454
+ listener.off("error", this.#onListenerError);
9455
+ listener.off("end", this.#onListenerEnd);
9456
+ listener.release(true);
9457
+ }
9458
+ }
9459
+ })();
9460
+ return this.#listenPromise;
9461
+ }
9462
+ #handleNotification(message2) {
9463
+ if (message2.channel !== this.#channel || !message2.payload) return;
9464
+ const payload = parsePayload(message2.payload);
9465
+ if (!payload) return;
9466
+ if (payload.schema !== this.#schema) return;
9467
+ if (payload.kind !== "chunks" && payload.kind !== "status") return;
9468
+ const subscribers = this.#subscribers.get(payload.streamId);
9469
+ if (!subscribers) return;
9470
+ const change = payload.kind === "chunks" ? { kind: "chunks" } : { kind: "status" };
9471
+ for (const subscriber of subscribers) {
9472
+ subscriber.push(change);
9473
+ }
9474
+ }
9475
+ #failListener(error) {
9476
+ const listener = this.#listener;
9477
+ this.#listener = void 0;
9478
+ this.#listenPromise = void 0;
9479
+ if (listener) {
9480
+ listener.off("notification", this.#onNotification);
9481
+ listener.off("error", this.#onListenerError);
9482
+ listener.off("end", this.#onListenerEnd);
9483
+ listener.release(error);
9484
+ }
9485
+ for (const subscribers of this.#subscribers.values()) {
9486
+ for (const subscriber of subscribers) {
9487
+ subscriber.fail(error);
9488
+ }
9489
+ }
9490
+ this.#subscribers.clear();
9491
+ }
9492
+ async #releaseListenerIfIdle() {
9493
+ if (this.#subscribers.size > 0) return;
9494
+ await this.#releaseListener();
9495
+ }
9496
+ async #releaseListener() {
9497
+ const listener = this.#listener;
9498
+ if (!listener) return;
9499
+ this.#listener = void 0;
9500
+ listener.off("notification", this.#onNotification);
9501
+ listener.off("error", this.#onListenerError);
9502
+ listener.off("end", this.#onListenerEnd);
9503
+ try {
9504
+ await listener.query(`UNLISTEN ${quoteIdentifier(this.#channel)}`);
9505
+ } finally {
9506
+ listener.release();
9507
+ }
9508
+ }
9509
+ #addSubscriber(streamId, subscriber) {
9510
+ let subscribers = this.#subscribers.get(streamId);
9511
+ if (!subscribers) {
9512
+ subscribers = /* @__PURE__ */ new Set();
9513
+ this.#subscribers.set(streamId, subscribers);
9514
+ }
9515
+ subscribers.add(subscriber);
9516
+ }
9517
+ #removeSubscriber(streamId, subscriber) {
9518
+ const subscribers = this.#subscribers.get(streamId);
9519
+ if (!subscribers) return;
9520
+ subscribers.delete(subscriber);
9521
+ if (subscribers.size === 0) {
9522
+ this.#subscribers.delete(streamId);
9523
+ }
9524
+ }
9525
+ };
9526
+ var ChangeQueue = class {
9527
+ #values = [];
9528
+ #waiters = /* @__PURE__ */ new Set();
9529
+ #closed = false;
9530
+ #error;
9531
+ push(value) {
9532
+ if (this.#closed) return;
9533
+ const waiter = this.#waiters.values().next().value;
9534
+ if (waiter) {
9535
+ this.#waiters.delete(waiter);
9536
+ waiter.resolve(value);
9537
+ return;
9538
+ }
9539
+ this.#values.push(value);
9540
+ }
9541
+ fail(error) {
9542
+ if (this.#closed) return;
9543
+ this.#closed = true;
9544
+ this.#error = error;
9545
+ this.#values = [];
9546
+ for (const waiter of this.#waiters) {
9547
+ waiter.reject(error);
9548
+ }
9549
+ this.#waiters.clear();
9550
+ }
9551
+ close() {
9552
+ if (this.#closed) return;
9553
+ this.#closed = true;
9554
+ this.#values = [];
9555
+ for (const waiter of this.#waiters) {
9556
+ waiter.resolve(void 0);
9557
+ }
9558
+ this.#waiters.clear();
9559
+ }
9560
+ async next(signal) {
9561
+ if (this.#error) throw this.#error;
9562
+ if (this.#closed || signal.aborted) return void 0;
9563
+ if (this.#values.length > 0) {
9564
+ return this.#values.shift();
9565
+ }
9566
+ return new Promise((resolve4, reject) => {
9567
+ const waiter = { resolve: resolve4, reject };
9568
+ const cleanup = () => {
9569
+ this.#waiters.delete(waiter);
9570
+ signal.removeEventListener("abort", onAbort);
9571
+ };
9572
+ const onAbort = () => {
9573
+ cleanup();
9574
+ resolve4(void 0);
9575
+ };
9576
+ waiter.resolve = (value) => {
9577
+ cleanup();
9578
+ resolve4(value);
9579
+ };
9580
+ waiter.reject = (error) => {
9581
+ cleanup();
9582
+ reject(error);
9583
+ };
9584
+ this.#waiters.add(waiter);
9585
+ signal.addEventListener("abort", onAbort, { once: true });
9586
+ });
9587
+ }
9588
+ };
9589
+ function parsePayload(payload) {
9590
+ try {
9591
+ const parsed = JSON.parse(payload);
9592
+ if (!parsed || typeof parsed !== "object") return void 0;
9593
+ const candidate = parsed;
9594
+ if (typeof candidate.schema !== "string" || typeof candidate.streamId !== "string" || typeof candidate.kind !== "string") {
9595
+ return void 0;
9596
+ }
9597
+ return {
9598
+ schema: candidate.schema,
9599
+ streamId: candidate.streamId,
9600
+ kind: candidate.kind
9601
+ };
9602
+ } catch {
9603
+ return void 0;
9604
+ }
9605
+ }
9606
+ function quoteIdentifier(value) {
9607
+ return `"${value.replaceAll('"', '""')}"`;
9608
+ }
9609
+ function assertIdentifier(value, label) {
9610
+ if (!/^[a-zA-Z_]\w*$/.test(value)) {
9611
+ throw new Error(`Invalid ${label} name: "${value}"`);
9612
+ }
9613
+ }
9614
+
9615
+ // packages/context/src/lib/stream/postgres.stream-store.ts
9616
+ import { createRequire as createRequire4 } from "node:module";
9617
+
9618
+ // packages/context/src/lib/stream/ddl.stream.postgres.ts
9619
+ function postgresStreamDDL(schema) {
9620
+ return `
9621
+ CREATE SCHEMA IF NOT EXISTS "${schema}";
9622
+
9623
+ CREATE TABLE IF NOT EXISTS "${schema}"."streams" (
9624
+ id TEXT PRIMARY KEY,
9625
+ status TEXT NOT NULL DEFAULT 'queued'
9626
+ CHECK (status IN ('queued', 'running', 'completed', 'failed', 'cancelled')),
9627
+ created_at BIGINT NOT NULL,
9628
+ started_at BIGINT,
9629
+ finished_at BIGINT,
9630
+ cancel_requested_at BIGINT,
9631
+ error TEXT
9632
+ );
9633
+
9634
+ CREATE TABLE IF NOT EXISTS "${schema}"."stream_chunks" (
9635
+ stream_id TEXT NOT NULL,
9636
+ seq INTEGER NOT NULL,
9637
+ data JSONB NOT NULL,
9638
+ created_at BIGINT NOT NULL,
9639
+ PRIMARY KEY (stream_id, seq),
9640
+ FOREIGN KEY (stream_id) REFERENCES "${schema}"."streams"(id) ON DELETE CASCADE
9641
+ );
9642
+
9643
+ CREATE INDEX IF NOT EXISTS "idx_${schema}_streams_created_at_id"
9644
+ ON "${schema}"."streams"(created_at, id);
9645
+
9646
+ CREATE INDEX IF NOT EXISTS "idx_${schema}_streams_status_created_at_id"
9647
+ ON "${schema}"."streams"(status, created_at, id);
9648
+ `;
9649
+ }
9650
+
9651
+ // packages/context/src/lib/stream/stream-store.ts
9652
+ var StreamStore = class {
9653
+ async listRunningStreamIds() {
9654
+ return this.listStreamIds({ status: "running" });
9655
+ }
9656
+ };
9657
+
9658
+ // packages/context/src/lib/stream/postgres.stream-store.ts
9659
+ var PostgresStreamStore = class _PostgresStreamStore extends StreamStore {
9660
+ #pool;
9661
+ #schema;
9662
+ #ownsPool;
9663
+ #isInitialized = false;
9664
+ #isClosed = false;
9665
+ constructor(options) {
9666
+ super();
9667
+ const schema = options.schema ?? "public";
9668
+ assertIdentifier2(schema, "schema");
9669
+ this.#schema = schema;
9670
+ const pg = _PostgresStreamStore.#requirePg();
9671
+ if (options.pool instanceof pg.Pool) {
9672
+ this.#pool = options.pool;
9673
+ this.#ownsPool = false;
9674
+ } else {
9675
+ this.#pool = typeof options.pool === "string" ? new pg.Pool({ connectionString: options.pool }) : new pg.Pool(options.pool);
9676
+ this.#ownsPool = true;
9677
+ }
9678
+ }
9679
+ static #requirePg() {
9680
+ try {
9681
+ const require2 = createRequire4(import.meta.url);
9682
+ return require2("pg");
9683
+ } catch {
9684
+ throw new Error(
9685
+ 'PostgresStreamStore requires the "pg" package. Install it with: npm install pg'
9686
+ );
9687
+ }
9688
+ }
9689
+ #t(name) {
9690
+ return `"${this.#schema}"."${name}"`;
9691
+ }
9692
+ async initialize() {
9693
+ await this.#pool.query(postgresStreamDDL(this.#schema));
9694
+ this.#isInitialized = true;
9695
+ }
9696
+ async close() {
9697
+ if (this.#isClosed) return;
9698
+ this.#isClosed = true;
9699
+ if (this.#ownsPool) {
9700
+ await this.#pool.end();
9701
+ }
9702
+ }
9703
+ #ensureInitialized() {
9704
+ if (!this.#isInitialized) {
9705
+ throw new Error(
9706
+ "PostgresStreamStore not initialized. Call await store.initialize() after construction."
9707
+ );
9708
+ }
9709
+ }
9710
+ async #query(sql, params) {
9711
+ this.#ensureInitialized();
9712
+ const result = await this.#pool.query(sql, params);
9713
+ return result.rows;
9714
+ }
9715
+ async #useTransaction(fn) {
9716
+ this.#ensureInitialized();
9717
+ const client = await this.#pool.connect();
9718
+ try {
9719
+ await client.query("BEGIN");
9720
+ const result = await fn(client);
9721
+ await client.query("COMMIT");
9722
+ return result;
9723
+ } catch (error) {
9724
+ await client.query("ROLLBACK");
9725
+ throw error;
9726
+ } finally {
9727
+ client.release();
9728
+ }
9729
+ }
9730
+ async createStream(stream) {
9731
+ await this.#query(
9732
+ `INSERT INTO ${this.#t("streams")}
9733
+ (id, status, created_at, started_at, finished_at, cancel_requested_at, error)
9734
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`,
9735
+ streamParams(stream)
9736
+ );
9737
+ }
9738
+ async upsertStream(stream) {
9739
+ const rows = await this.#query(
9740
+ `INSERT INTO ${this.#t("streams")}
9741
+ (id, status, created_at, started_at, finished_at, cancel_requested_at, error)
9742
+ VALUES ($1, $2, $3, $4, $5, $6, $7)
9743
+ ON CONFLICT(id) DO NOTHING
9744
+ RETURNING *`,
9745
+ streamParams(stream)
9746
+ );
9747
+ if (rows[0]) {
9748
+ return { stream: rowToStream(rows[0]), created: true };
9749
+ }
9750
+ const existing = await this.getStream(stream.id);
9751
+ if (!existing) {
9752
+ throw new Error(
9753
+ `Stream "${stream.id}" disappeared between upsert and fetch`
9754
+ );
9755
+ }
9756
+ return { stream: existing, created: false };
9757
+ }
9758
+ async getStream(streamId) {
9759
+ const rows = await this.#query(
9760
+ `SELECT * FROM ${this.#t("streams")} WHERE id = $1`,
9761
+ [streamId]
9762
+ );
9763
+ return rows[0] ? rowToStream(rows[0]) : void 0;
9764
+ }
9765
+ async getStreamStatus(streamId) {
9766
+ const rows = await this.#query(
9767
+ `SELECT status FROM ${this.#t("streams")} WHERE id = $1`,
9768
+ [streamId]
9769
+ );
9770
+ return rows[0]?.status;
9771
+ }
9772
+ async listStreamIds(options) {
9773
+ const params = [];
9774
+ let sql = `SELECT id FROM ${this.#t("streams")}`;
9775
+ if (options?.status) {
9776
+ params.push(options.status);
9777
+ sql += ` WHERE status = $${params.length}`;
9778
+ }
9779
+ sql += " ORDER BY created_at ASC, id ASC";
9780
+ const rows = await this.#query(sql, params);
9781
+ return rows.map((row) => row.id);
9782
+ }
9783
+ async updateStreamStatus(streamId, status, options) {
9784
+ const now = Date.now();
9785
+ switch (status) {
9786
+ case "running":
9787
+ await this.#query(
9788
+ `UPDATE ${this.#t("streams")}
9789
+ SET status = $1, started_at = $2
9790
+ WHERE id = $3`,
9791
+ [status, now, streamId]
9792
+ );
9793
+ break;
9794
+ case "completed":
9795
+ await this.#query(
9796
+ `UPDATE ${this.#t("streams")}
9797
+ SET status = $1, finished_at = $2
9798
+ WHERE id = $3`,
9799
+ [status, now, streamId]
9800
+ );
9801
+ break;
9802
+ case "failed":
9803
+ await this.#query(
9804
+ `UPDATE ${this.#t("streams")}
9805
+ SET status = $1, finished_at = $2, error = $3
9806
+ WHERE id = $4`,
9807
+ [status, now, options?.error ?? null, streamId]
9808
+ );
9809
+ break;
9810
+ case "cancelled":
9811
+ await this.#query(
9812
+ `UPDATE ${this.#t("streams")}
9813
+ SET status = $1, cancel_requested_at = $2, finished_at = $3
9814
+ WHERE id = $4`,
9815
+ [status, now, now, streamId]
9816
+ );
9817
+ break;
9818
+ default:
9819
+ await this.#query(
9820
+ `UPDATE ${this.#t("streams")}
9821
+ SET status = $1
9822
+ WHERE id = $2`,
9823
+ [status, streamId]
9824
+ );
9825
+ }
9826
+ }
9827
+ async appendChunks(chunks) {
9828
+ if (chunks.length === 0) return;
9829
+ const rows = chunks.map((chunk) => ({
9830
+ stream_id: chunk.streamId,
9831
+ seq: chunk.seq,
9832
+ data: chunk.data,
9833
+ created_at: chunk.createdAt
9834
+ }));
9835
+ await this.#query(
9836
+ `INSERT INTO ${this.#t("stream_chunks")} (stream_id, seq, data, created_at)
9837
+ SELECT stream_id, seq, data, created_at
9838
+ FROM jsonb_to_recordset($1::jsonb)
9839
+ AS rows(stream_id TEXT, seq INTEGER, data JSONB, created_at BIGINT)`,
9840
+ [JSON.stringify(rows)]
9841
+ );
9842
+ }
9843
+ async getChunks(streamId, fromSeq, limit) {
9844
+ const params = [streamId];
9845
+ let sql = `SELECT * FROM ${this.#t("stream_chunks")} WHERE stream_id = $1`;
9846
+ if (fromSeq !== void 0) {
9847
+ params.push(fromSeq);
9848
+ sql += ` AND seq >= $${params.length}`;
8566
9849
  }
8567
- };
8568
- return {
8569
- writer: wrappedWriter,
8570
- streamId,
8571
- flush,
8572
- async complete() {
8573
- await flush();
8574
- await store2.updateStreamStatus(streamId, "completed");
8575
- },
8576
- async fail(error) {
8577
- await flush();
8578
- await store2.updateStreamStatus(streamId, "failed", { error });
8579
- },
8580
- async cleanup() {
8581
- await store2.deleteStream(streamId);
9850
+ sql += " ORDER BY seq ASC";
9851
+ if (limit !== void 0) {
9852
+ params.push(limit);
9853
+ sql += ` LIMIT $${params.length}`;
8582
9854
  }
8583
- };
8584
- }
8585
-
8586
- // packages/context/src/lib/stream/polling-policy.ts
8587
- var DEFAULT_WATCH_POLLING = {
8588
- minMs: 25,
8589
- maxMs: 500,
8590
- multiplier: 2,
8591
- jitterRatio: 0.15,
8592
- statusCheckEvery: 3,
8593
- chunkPageSize: 128
8594
- };
8595
- var DEFAULT_CANCEL_POLLING = {
8596
- minMs: 50,
8597
- maxMs: 500,
8598
- multiplier: 2,
8599
- jitterRatio: 0.15
9855
+ const rows = await this.#query(sql, params);
9856
+ return rows.map((row) => ({
9857
+ streamId: row.stream_id,
9858
+ seq: row.seq,
9859
+ data: row.data,
9860
+ createdAt: toNumber(row.created_at)
9861
+ }));
9862
+ }
9863
+ async deleteStream(streamId) {
9864
+ await this.#query(`DELETE FROM ${this.#t("streams")} WHERE id = $1`, [
9865
+ streamId
9866
+ ]);
9867
+ }
9868
+ async reopenStream(streamId) {
9869
+ return this.#useTransaction(async (client) => {
9870
+ const result = await client.query(
9871
+ `SELECT * FROM ${this.#t("streams")} WHERE id = $1 FOR UPDATE`,
9872
+ [streamId]
9873
+ );
9874
+ const row = result.rows[0];
9875
+ if (!row) {
9876
+ throw new Error(`Stream "${streamId}" not found`);
9877
+ }
9878
+ if (!isTerminal2(row.status)) {
9879
+ throw new Error(
9880
+ `Cannot reopen stream "${streamId}" with status "${row.status}". Only terminal streams can be reopened.`
9881
+ );
9882
+ }
9883
+ await client.query(`DELETE FROM ${this.#t("streams")} WHERE id = $1`, [
9884
+ streamId
9885
+ ]);
9886
+ const now = Date.now();
9887
+ const stream = {
9888
+ id: streamId,
9889
+ status: "queued",
9890
+ createdAt: now,
9891
+ startedAt: null,
9892
+ finishedAt: null,
9893
+ cancelRequestedAt: null,
9894
+ error: null
9895
+ };
9896
+ await client.query(
9897
+ `INSERT INTO ${this.#t("streams")}
9898
+ (id, status, created_at, started_at, finished_at, cancel_requested_at, error)
9899
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`,
9900
+ streamParams(stream)
9901
+ );
9902
+ return stream;
9903
+ });
9904
+ }
8600
9905
  };
8601
- function normalizeWatchPolling(polling, fallback = DEFAULT_WATCH_POLLING) {
8602
- const merged = {
8603
- ...fallback,
8604
- ...polling
8605
- };
8606
- const normalizedBase = normalizeAdaptivePolling(merged, fallback);
8607
- return {
8608
- ...normalizedBase,
8609
- statusCheckEvery: clampInt(merged.statusCheckEvery, 1, 1e4),
8610
- chunkPageSize: clampInt(merged.chunkPageSize, 1, 1e4)
8611
- };
8612
- }
8613
- function normalizeCancelPolling(polling, fallback = DEFAULT_CANCEL_POLLING) {
8614
- return normalizeAdaptivePolling(polling, fallback);
9906
+ function streamParams(stream) {
9907
+ return [
9908
+ stream.id,
9909
+ stream.status,
9910
+ stream.createdAt,
9911
+ stream.startedAt,
9912
+ stream.finishedAt,
9913
+ stream.cancelRequestedAt,
9914
+ stream.error
9915
+ ];
8615
9916
  }
8616
- function createAdaptivePollingState(config) {
9917
+ function rowToStream(row) {
8617
9918
  return {
8618
- config,
8619
- currentMs: config.minMs
9919
+ id: row.id,
9920
+ status: row.status,
9921
+ createdAt: toNumber(row.created_at),
9922
+ startedAt: toNullableNumber(row.started_at),
9923
+ finishedAt: toNullableNumber(row.finished_at),
9924
+ cancelRequestedAt: toNullableNumber(row.cancel_requested_at),
9925
+ error: row.error
8620
9926
  };
8621
9927
  }
8622
- function resetAdaptivePolling(state) {
8623
- state.currentMs = state.config.minMs;
8624
- }
8625
- function nextAdaptivePollingDelay(state) {
8626
- const current = clampInt(
8627
- state.currentMs,
8628
- state.config.minMs,
8629
- state.config.maxMs
8630
- );
8631
- const delay = applyJitter(
8632
- current,
8633
- state.config.jitterRatio,
8634
- state.config.minMs,
8635
- state.config.maxMs
8636
- );
8637
- state.currentMs = clampInt(
8638
- Math.ceil(current * state.config.multiplier),
8639
- state.config.minMs,
8640
- state.config.maxMs
8641
- );
8642
- return delay;
8643
- }
8644
- function normalizeAdaptivePolling(polling, fallback) {
8645
- const merged = {
8646
- ...fallback,
8647
- ...polling
8648
- };
8649
- const minMs = clampInt(merged.minMs, 1, 6e4);
8650
- const maxMs = clampInt(merged.maxMs, minMs, 6e4);
8651
- return {
8652
- minMs,
8653
- maxMs,
8654
- multiplier: clampFloat(merged.multiplier, 1, 10),
8655
- jitterRatio: clampFloat(merged.jitterRatio, 0, 1)
8656
- };
9928
+ function toNumber(value) {
9929
+ return typeof value === "number" ? value : Number(value);
8657
9930
  }
8658
- function applyJitter(value, jitterRatio, min, max) {
8659
- if (jitterRatio <= 0) return value;
8660
- const radius = value * jitterRatio;
8661
- const lowerBound = Math.max(0, value - radius);
8662
- const upperBound = value + radius;
8663
- const jittered = Math.round(
8664
- lowerBound + Math.random() * (upperBound - lowerBound)
8665
- );
8666
- return clampInt(jittered, min, max);
9931
+ function toNullableNumber(value) {
9932
+ return value == null ? null : toNumber(value);
8667
9933
  }
8668
- function clampInt(value, min, max) {
8669
- if (!Number.isFinite(value)) return min;
8670
- return Math.min(max, Math.max(min, Math.round(value)));
9934
+ function isTerminal2(status) {
9935
+ return status !== "queued" && status !== "running";
8671
9936
  }
8672
- function clampFloat(value, min, max) {
8673
- if (!Number.isFinite(value)) return min;
8674
- return Math.min(max, Math.max(min, value));
9937
+ function assertIdentifier2(value, label) {
9938
+ if (!/^[a-zA-Z_]\w*$/.test(value)) {
9939
+ throw new Error(`Invalid ${label} name: "${value}"`);
9940
+ }
8675
9941
  }
8676
9942
 
8677
9943
  // packages/context/src/lib/stream/sqlite.stream-store.ts
@@ -8680,13 +9946,6 @@ import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
8680
9946
  // packages/context/src/lib/stream/ddl.stream.sqlite.sql
8681
9947
  var ddl_stream_sqlite_default = "PRAGMA journal_mode = WAL;\nPRAGMA synchronous = NORMAL;\nPRAGMA foreign_keys = ON;\n\nCREATE TABLE IF NOT EXISTS streams (\n id TEXT PRIMARY KEY,\n status TEXT NOT NULL DEFAULT 'queued'\n CHECK(status IN ('queued','running','completed','failed','cancelled')),\n createdAt INTEGER NOT NULL,\n startedAt INTEGER,\n finishedAt INTEGER,\n cancelRequestedAt INTEGER,\n error TEXT\n);\n\nCREATE TABLE IF NOT EXISTS stream_chunks (\n streamId TEXT NOT NULL,\n seq INTEGER NOT NULL,\n data TEXT NOT NULL,\n createdAt INTEGER NOT NULL,\n PRIMARY KEY (streamId, seq),\n FOREIGN KEY (streamId) REFERENCES streams(id) ON DELETE CASCADE\n);\n\n-- Supports ordered listing across all streams.\nCREATE INDEX IF NOT EXISTS idx_streams_created_at_id\n ON streams(createdAt, id);\n\n-- Supports status-filtered ordered listing (e.g. running streams).\nCREATE INDEX IF NOT EXISTS idx_streams_status_created_at_id\n ON streams(status, createdAt, id);\n";
8682
9948
 
8683
- // packages/context/src/lib/stream/stream-store.ts
8684
- var StreamStore = class {
8685
- async listRunningStreamIds() {
8686
- return this.listStreamIds({ status: "running" });
8687
- }
8688
- };
8689
-
8690
9949
  // packages/context/src/lib/stream/sqlite.stream-store.ts
8691
9950
  var SqliteStreamStore = class extends StreamStore {
8692
9951
  #db;
@@ -8913,26 +10172,20 @@ var SqliteStreamStore = class extends StreamStore {
8913
10172
 
8914
10173
  // packages/context/src/lib/stream/stream-manager.ts
8915
10174
  import { createUIMessageStream as createUIMessageStream3 } from "ai";
8916
- import { setTimeout } from "node:timers/promises";
8917
- function isTerminal(status) {
10175
+ function isTerminal3(status) {
8918
10176
  return status !== "queued" && status !== "running";
8919
10177
  }
10178
+ var DEFAULT_CHUNK_PAGE_SIZE = 128;
8920
10179
  var StreamManager = class {
8921
10180
  #store;
8922
- #watchPollingDefaults;
8923
- #cancelPollingDefaults;
8924
- #onPollingEvent;
10181
+ #changeSource;
10182
+ #chunkPageSize;
10183
+ #onWatchEvent;
8925
10184
  constructor(options) {
8926
10185
  this.#store = options.store;
8927
- this.#watchPollingDefaults = normalizeWatchPolling(
8928
- options.watchPolling,
8929
- DEFAULT_WATCH_POLLING
8930
- );
8931
- this.#cancelPollingDefaults = normalizeCancelPolling(
8932
- options.cancelPolling,
8933
- DEFAULT_CANCEL_POLLING
8934
- );
8935
- this.#onPollingEvent = options.onPollingEvent;
10186
+ this.#changeSource = options.changeSource;
10187
+ this.#chunkPageSize = options.chunkPageSize ?? DEFAULT_CHUNK_PAGE_SIZE;
10188
+ this.#onWatchEvent = options.onWatchEvent;
8936
10189
  }
8937
10190
  get store() {
8938
10191
  return this.#store;
@@ -8956,51 +10209,12 @@ var StreamManager = class {
8956
10209
  }
8957
10210
  async persist(stream, streamId, options) {
8958
10211
  const existing = await this.#store.getStream(streamId);
8959
- if (existing && isTerminal(existing.status)) {
10212
+ if (existing && isTerminal3(existing.status)) {
8960
10213
  return { streamId };
8961
10214
  }
8962
10215
  await this.#store.updateStreamStatus(streamId, "running");
8963
10216
  const ac = new AbortController();
8964
- const cancelPolling = normalizeCancelPolling(
8965
- options?.cancelPolling,
8966
- this.#cancelPollingDefaults
8967
- );
8968
- const pollState = createAdaptivePollingState(cancelPolling);
8969
- const pollCancel = (async () => {
8970
- while (!ac.signal.aborted) {
8971
- const delayMs = nextAdaptivePollingDelay(pollState);
8972
- const continued = await waitForDelay(delayMs, ac.signal);
8973
- if (!continued || ac.signal.aborted) break;
8974
- const status = await this.#store.getStreamStatus(streamId);
8975
- this.#emitPolling({
8976
- type: "persist:cancel-poll",
8977
- streamId,
8978
- delayMs,
8979
- status: status ?? "missing"
8980
- });
8981
- if (status === void 0) {
8982
- ac.abort();
8983
- break;
8984
- }
8985
- if (status === "cancelled") {
8986
- const current = await this.#store.getStream(streamId);
8987
- const latencyMs = current?.cancelRequestedAt != null ? Math.max(0, Date.now() - current.cancelRequestedAt) : null;
8988
- this.#emitPolling({
8989
- type: "persist:cancel-detected",
8990
- streamId,
8991
- latencyMs
8992
- });
8993
- if (options?.onCancelDetected) {
8994
- try {
8995
- await options.onCancelDetected({ streamId, latencyMs });
8996
- } catch {
8997
- }
8998
- }
8999
- ac.abort();
9000
- break;
9001
- }
9002
- }
9003
- })();
10217
+ const cancelWatcher = this.#runCancelWatcher(streamId, ac, options);
9004
10218
  let pw;
9005
10219
  const sink = createUIMessageStream3({
9006
10220
  execute: async ({ writer }) => {
@@ -9041,142 +10255,131 @@ var StreamManager = class {
9041
10255
  }
9042
10256
  } finally {
9043
10257
  if (!ac.signal.aborted) ac.abort();
9044
- await pollCancel;
10258
+ await cancelWatcher;
9045
10259
  }
9046
10260
  return { streamId: pw?.streamId ?? streamId };
9047
10261
  }
9048
- watch(streamId, options) {
10262
+ async #runCancelWatcher(streamId, ac, options) {
10263
+ try {
10264
+ for await (const change of this.#changeSource.subscribe(
10265
+ streamId,
10266
+ ac.signal
10267
+ )) {
10268
+ if (change.kind === "chunks") continue;
10269
+ const status = await this.#store.getStreamStatus(streamId);
10270
+ if (status === void 0) {
10271
+ ac.abort();
10272
+ return;
10273
+ }
10274
+ if (status === "cancelled") {
10275
+ const current = await this.#store.getStream(streamId);
10276
+ const latencyMs = current?.cancelRequestedAt != null ? Math.max(0, Date.now() - current.cancelRequestedAt) : null;
10277
+ this.#emit({
10278
+ type: "persist:cancel-detected",
10279
+ streamId,
10280
+ latencyMs
10281
+ });
10282
+ if (options?.onCancelDetected) {
10283
+ try {
10284
+ await options.onCancelDetected({ streamId, latencyMs });
10285
+ } catch {
10286
+ }
10287
+ }
10288
+ ac.abort();
10289
+ return;
10290
+ }
10291
+ }
10292
+ } catch {
10293
+ }
10294
+ }
10295
+ watch(streamId) {
9049
10296
  const store2 = this.#store;
9050
- const polling = normalizeWatchPolling(options, this.#watchPollingDefaults);
9051
- const delayState = createAdaptivePollingState(polling);
10297
+ const changeSource = this.#changeSource;
10298
+ const pageSize = this.#chunkPageSize;
10299
+ const emit = this.#emit.bind(this);
9052
10300
  const ac = new AbortController();
9053
10301
  const lastSeqRef = { value: -1 };
9054
- let chunkPollsSinceStatus = 0;
9055
- const emitChunks = (controller, chunks) => {
9056
- for (const chunk of chunks) {
9057
- controller.enqueue(chunk.data);
9058
- lastSeqRef.value = chunk.seq;
9059
- }
9060
- return chunks.length;
10302
+ let iterator;
10303
+ const emitPage = (delivered, lastSeq) => {
10304
+ emit({ type: "watch:chunks", streamId, delivered, lastSeq });
9061
10305
  };
9062
- return new ReadableStream({
9063
- async start() {
10306
+ const finalize = async (controller, reason, knownStatus) => {
10307
+ await drainAvailable(
10308
+ controller,
10309
+ store2,
10310
+ streamId,
10311
+ lastSeqRef,
10312
+ pageSize,
10313
+ emitPage
10314
+ );
10315
+ const finalStatus = knownStatus ?? await store2.getStreamStatus(streamId);
10316
+ if (finalStatus === "failed") {
9064
10317
  const stream = await store2.getStream(streamId);
9065
- if (!stream) {
9066
- throw new Error(`Stream "${streamId}" not found`);
10318
+ if (stream) {
10319
+ const errorText = stream.error || "Stream failed";
10320
+ emit({
10321
+ type: "watch:error-emitted",
10322
+ streamId,
10323
+ errorTextLength: errorText.length
10324
+ });
10325
+ controller.enqueue({ type: "error", errorText });
9067
10326
  }
10327
+ }
10328
+ emit({ type: "watch:closed", streamId, reason });
10329
+ controller.close();
10330
+ ac.abort();
10331
+ };
10332
+ return new ReadableStream({
10333
+ start: () => {
10334
+ iterator = changeSource.subscribe(streamId, ac.signal)[Symbol.asyncIterator]();
9068
10335
  },
9069
10336
  pull: async (controller) => {
10337
+ if (!iterator) return;
9070
10338
  while (!ac.signal.aborted) {
9071
- const fromSeq = lastSeqRef.value + 1;
9072
- const chunks = await store2.getChunks(
9073
- streamId,
9074
- fromSeq,
9075
- polling.chunkPageSize
9076
- );
9077
- let statusChecked = false;
9078
- let currentStatus;
9079
- if (chunks.length === 0) {
9080
- chunkPollsSinceStatus = polling.statusCheckEvery;
9081
- } else {
9082
- chunkPollsSinceStatus += 1;
10339
+ let result;
10340
+ try {
10341
+ result = await iterator.next();
10342
+ } catch (error) {
10343
+ iterator.return?.().catch(() => void 0);
10344
+ if (isAbortError(error)) return;
10345
+ throw error;
9083
10346
  }
9084
- if (chunkPollsSinceStatus >= polling.statusCheckEvery) {
9085
- statusChecked = true;
9086
- chunkPollsSinceStatus = 0;
9087
- currentStatus = await store2.getStreamStatus(streamId);
10347
+ if (result.done) {
10348
+ await finalize(controller, "source-ended");
10349
+ return;
9088
10350
  }
9089
- this.#emitPolling({
9090
- type: "watch:poll",
9091
- streamId,
9092
- fromSeq,
9093
- chunkCount: chunks.length,
9094
- statusChecked
9095
- });
9096
- if (chunks.length > 0) {
9097
- const delivered = emitChunks(controller, chunks);
9098
- this.#emitPolling({
9099
- type: "watch:chunks",
10351
+ const change = result.value;
10352
+ let delivered = 0;
10353
+ if (change.kind !== "status") {
10354
+ delivered = await drainAvailable(
10355
+ controller,
10356
+ store2,
9100
10357
  streamId,
9101
- delivered,
9102
- lastSeq: lastSeqRef.value
9103
- });
9104
- resetAdaptivePolling(delayState);
9105
- if (chunks.length >= polling.chunkPageSize) {
9106
- continue;
9107
- }
9108
- return;
10358
+ lastSeqRef,
10359
+ pageSize,
10360
+ emitPage
10361
+ );
9109
10362
  }
9110
- if (statusChecked) {
9111
- if (currentStatus === void 0) {
9112
- this.#emitPolling({
9113
- type: "watch:closed",
9114
- streamId,
9115
- reason: "missing"
9116
- });
9117
- controller.close();
9118
- ac.abort();
10363
+ if (change.kind !== "chunks") {
10364
+ const status = await store2.getStreamStatus(streamId);
10365
+ if (status === void 0) {
10366
+ await finalize(controller, "missing");
9119
10367
  return;
9120
10368
  }
9121
- if (isTerminal(currentStatus)) {
9122
- const drained = await drainRemainingChunks({
9123
- controller,
9124
- store: store2,
9125
- streamId,
9126
- fromSeq: lastSeqRef.value + 1,
9127
- chunkPageSize: polling.chunkPageSize,
9128
- onChunk: (seq) => {
9129
- lastSeqRef.value = seq;
9130
- }
9131
- });
9132
- if (drained > 0) {
9133
- this.#emitPolling({
9134
- type: "watch:chunks",
9135
- streamId,
9136
- delivered: drained,
9137
- lastSeq: lastSeqRef.value
9138
- });
9139
- }
9140
- if (currentStatus === "failed") {
9141
- const stream = await store2.getStream(streamId);
9142
- if (stream) {
9143
- const errorText = stream.error || "Stream failed";
9144
- this.#emitPolling({
9145
- type: "watch:error-emitted",
9146
- streamId,
9147
- errorTextLength: errorText.length
9148
- });
9149
- controller.enqueue({
9150
- type: "error",
9151
- errorText
9152
- });
9153
- }
9154
- }
9155
- this.#emitPolling({
9156
- type: "watch:closed",
9157
- streamId,
9158
- reason: "terminal"
9159
- });
9160
- controller.close();
9161
- ac.abort();
10369
+ if (isTerminal3(status)) {
10370
+ await finalize(controller, "terminal", status);
9162
10371
  return;
9163
10372
  }
9164
10373
  }
9165
- const delayMs = nextAdaptivePollingDelay(delayState);
9166
- this.#emitPolling({
9167
- type: "watch:empty",
9168
- streamId,
9169
- fromSeq: lastSeqRef.value + 1,
9170
- delayMs
9171
- });
9172
- const continued = await waitForDelay(delayMs, ac.signal);
9173
- if (!continued) {
9174
- return;
9175
- }
10374
+ if (delivered > 0) return;
9176
10375
  }
9177
10376
  },
9178
- cancel() {
10377
+ cancel: () => {
9179
10378
  ac.abort();
10379
+ return iterator?.return?.().then(
10380
+ () => void 0,
10381
+ () => void 0
10382
+ );
9180
10383
  }
9181
10384
  });
9182
10385
  }
@@ -9187,41 +10390,32 @@ var StreamManager = class {
9187
10390
  async cleanup(streamId) {
9188
10391
  await this.#store.deleteStream(streamId);
9189
10392
  }
9190
- #emitPolling(event) {
9191
- if (!this.#onPollingEvent) return;
10393
+ #emit(event) {
10394
+ if (!this.#onWatchEvent) return;
9192
10395
  try {
9193
- this.#onPollingEvent(event);
10396
+ this.#onWatchEvent(event);
9194
10397
  } catch {
9195
10398
  }
9196
10399
  }
9197
10400
  };
9198
- async function drainRemainingChunks(options) {
9199
- const { controller, store: store2, streamId, chunkPageSize, onChunk } = options;
9200
- let fromSeq = options.fromSeq;
9201
- let drained = 0;
10401
+ async function drainAvailable(controller, store2, streamId, lastSeqRef, pageSize, onPage) {
10402
+ let total = 0;
9202
10403
  while (true) {
9203
- const chunks = await store2.getChunks(streamId, fromSeq, chunkPageSize);
10404
+ const chunks = await store2.getChunks(
10405
+ streamId,
10406
+ lastSeqRef.value + 1,
10407
+ pageSize
10408
+ );
9204
10409
  if (chunks.length === 0) break;
9205
10410
  for (const chunk of chunks) {
9206
10411
  controller.enqueue(chunk.data);
9207
- onChunk(chunk.seq);
9208
- drained++;
9209
- fromSeq = chunk.seq + 1;
10412
+ lastSeqRef.value = chunk.seq;
9210
10413
  }
9211
- if (chunks.length < chunkPageSize) {
9212
- break;
9213
- }
9214
- }
9215
- return drained;
9216
- }
9217
- async function waitForDelay(ms, signal) {
9218
- try {
9219
- await setTimeout(ms, void 0, signal ? { signal } : void 0);
9220
- return true;
9221
- } catch (error) {
9222
- if (isAbortError(error)) return false;
9223
- throw error;
10414
+ total += chunks.length;
10415
+ onPage?.(chunks.length, lastSeqRef.value);
10416
+ if (chunks.length < pageSize) break;
9224
10417
  }
10418
+ return total;
9225
10419
  }
9226
10420
  function isAbortError(error) {
9227
10421
  return error instanceof Error && (error.name === "AbortError" || /aborted/i.test(error.message));
@@ -9302,9 +10496,9 @@ export {
9302
10496
  AgentOsCreationError,
9303
10497
  AgentOsNotAvailableError,
9304
10498
  AgentOsSandboxError,
10499
+ AsyncResolver,
9305
10500
  BM25Classifier,
9306
10501
  BashException,
9307
- BinaryInstallError,
9308
10502
  ComposeStartError,
9309
10503
  ComposeStrategy,
9310
10504
  ContainerCreationError,
@@ -9318,15 +10512,29 @@ export {
9318
10512
  DockerSandboxStrategy,
9319
10513
  DockerfileBuildError,
9320
10514
  DockerfileStrategy,
9321
- DuplicateCommandError,
10515
+ FragmentLoaderResolver,
10516
+ FunctionResolver,
10517
+ GeneratorResolver,
10518
+ GithubReleaseInstaller,
9322
10519
  InMemoryContextStore,
10520
+ InstallError,
10521
+ Installer,
10522
+ IterableResolver,
9323
10523
  LOCALE_METADATA_KEY,
9324
10524
  MarkdownRenderer,
10525
+ MissingRuntimeError,
9325
10526
  ModelsRegistry,
9326
10527
  MountPathError,
10528
+ NpmInstaller,
9327
10529
  ObservedFs,
9328
10530
  PackageInstallError,
10531
+ PackageInstaller,
10532
+ PipInstaller,
10533
+ PollingChangeSource,
9329
10534
  PostgresContextStore,
10535
+ PostgresNotifyChangeSource,
10536
+ PostgresStreamStore,
10537
+ PromiseResolver,
9330
10538
  RuntimeStrategy,
9331
10539
  SqlServerContextStore,
9332
10540
  SqliteContextStore,
@@ -9336,6 +10544,7 @@ export {
9336
10544
  TitleGenerator,
9337
10545
  TomlRenderer,
9338
10546
  ToonRenderer,
10547
+ UrlBinaryInstaller,
9339
10548
  XmlRenderer,
9340
10549
  addUsage,
9341
10550
  advisorPreamble,
@@ -9346,17 +10555,19 @@ export {
9346
10555
  alias,
9347
10556
  analogy,
9348
10557
  and,
10558
+ anyToolCalled,
9349
10559
  applyInlineReminder,
9350
10560
  applyPartReminder,
9351
10561
  applyReminderToMessage,
10562
+ applyToolOutputRemindersToMessage,
9352
10563
  asStaticWordPartText,
9353
10564
  asStaticWordText,
10565
+ assertCountSpec,
9354
10566
  assistant,
9355
10567
  assistantText,
9356
- buildSchemaSubcommand,
9357
10568
  buildSubcommandRepair,
9358
- chainHooks,
9359
10569
  chat,
10570
+ checkCount,
9360
10571
  clarification,
9361
10572
  classifies,
9362
10573
  contentIncludes,
@@ -9369,29 +10580,29 @@ export {
9369
10580
  createBinaryBridges,
9370
10581
  createContainerTool,
9371
10582
  createDockerSandbox,
9372
- createOpenAPIExtension,
10583
+ createInstallerContext,
9373
10584
  createRepairToolCall,
9374
- createRoutingSandbox,
9375
10585
  createVirtualSandbox,
9376
10586
  dateReminder,
9377
10587
  dayChanged,
9378
10588
  defaultChatMessageMetadata,
10589
+ defaultResolvers,
9379
10590
  defaultTokenizer,
9380
10591
  defineSubcommandGroup,
10592
+ discoverSkillMappings,
9381
10593
  discoverSkillsInDirectory,
10594
+ elapsedExceeds,
9382
10595
  encodeSerializedValue,
9383
10596
  errorRecoveryGuardrail,
9384
10597
  estimate,
9385
10598
  everyNTurns,
10599
+ everyOfLastN,
9386
10600
  example,
9387
10601
  executorContext,
9388
10602
  explain,
9389
- extractPathParams,
9390
10603
  fail,
9391
- findUnsafePathParam,
10604
+ findSingleOutputAvailableToolPart,
9392
10605
  firstN,
9393
- formatError,
9394
- formatResponse,
9395
10606
  fragment,
9396
10607
  fromFragment,
9397
10608
  getFragmentData,
@@ -9399,6 +10610,7 @@ export {
9399
10610
  getModelsRegistry,
9400
10611
  getReminderRanges,
9401
10612
  getSeason,
10613
+ githubRelease,
9402
10614
  glossary,
9403
10615
  guardrail,
9404
10616
  hint,
@@ -9406,16 +10618,16 @@ export {
9406
10618
  identity,
9407
10619
  isComposeOptions,
9408
10620
  isConditionalReminder,
10621
+ isDebianBased,
9409
10622
  isDockerfileOptions,
9410
10623
  isFragment,
9411
10624
  isFragmentObject,
9412
- isInstallable,
9413
10625
  isMessageFragment,
9414
10626
  isRecord,
10627
+ lastAssistantLength,
9415
10628
  loadSkillMetadata,
9416
10629
  localeReminder,
9417
10630
  mapGenerateErrorToCode,
9418
- mergeExtensions,
9419
10631
  mergeMessageMetadata,
9420
10632
  mergeReminderMetadata,
9421
10633
  message,
@@ -9425,6 +10637,7 @@ export {
9425
10637
  normalizeCancelPolling,
9426
10638
  normalizeWatchPolling,
9427
10639
  not,
10640
+ npm,
9428
10641
  nullUsage,
9429
10642
  once,
9430
10643
  or,
@@ -9432,6 +10645,8 @@ export {
9432
10645
  pass,
9433
10646
  persistedWriter,
9434
10647
  persona,
10648
+ pip,
10649
+ pkg,
9435
10650
  policy,
9436
10651
  preference,
9437
10652
  principle,
@@ -9452,6 +10667,7 @@ export {
9452
10667
  seasonChanged,
9453
10668
  seasonReminder,
9454
10669
  selfCritique,
10670
+ shellQuote,
9455
10671
  skills,
9456
10672
  skillsReminder,
9457
10673
  socraticPrompting,
@@ -9467,13 +10683,20 @@ export {
9467
10683
  timeReminder,
9468
10684
  toFragment,
9469
10685
  toMessageFragment,
10686
+ toolCall,
10687
+ toolCallCount,
10688
+ toolCalled,
10689
+ toolFailed,
9470
10690
  uploadSkills,
10691
+ urlBinary,
10692
+ usageExceeds,
9471
10693
  useAgentOsSandbox,
9472
10694
  useBashMeta,
9473
10695
  useSandbox,
9474
10696
  user,
9475
10697
  visualizeGraph,
9476
10698
  weekChanged,
10699
+ withinLastN,
9477
10700
  workflow,
9478
10701
  yearChanged,
9479
10702
  yearReminder