@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.
- package/README.md +44 -11
- package/dist/browser.js +171 -15
- package/dist/browser.js.map +3 -3
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2584 -1361
- package/dist/index.js.map +4 -4
- package/dist/lib/agent.d.ts +6 -0
- package/dist/lib/agent.d.ts.map +1 -1
- package/dist/lib/chain-summary.d.ts +18 -0
- package/dist/lib/chain-summary.d.ts.map +1 -0
- package/dist/lib/engine.d.ts +25 -6
- package/dist/lib/engine.d.ts.map +1 -1
- package/dist/lib/fragments/message/user.d.ts +22 -2
- package/dist/lib/fragments/message/user.d.ts.map +1 -1
- package/dist/lib/fragments/reminders/combinators.d.ts +23 -0
- package/dist/lib/fragments/reminders/combinators.d.ts.map +1 -1
- package/dist/lib/fragments/reminders/message-predicates.d.ts +10 -0
- package/dist/lib/fragments/reminders/message-predicates.d.ts.map +1 -0
- package/dist/lib/fragments/reminders/temporal/elapsed.d.ts +3 -0
- package/dist/lib/fragments/reminders/temporal/elapsed.d.ts.map +1 -0
- package/dist/lib/fragments/reminders/temporal/index.d.ts +1 -0
- package/dist/lib/fragments/reminders/temporal/index.d.ts.map +1 -1
- package/dist/lib/fragments/reminders/tool-predicates.d.ts +17 -0
- package/dist/lib/fragments/reminders/tool-predicates.d.ts.map +1 -0
- package/dist/lib/fragments/reminders/usage-predicates.d.ts +3 -0
- package/dist/lib/fragments/reminders/usage-predicates.d.ts.map +1 -0
- package/dist/lib/fragments.d.ts +8 -3
- package/dist/lib/fragments.d.ts.map +1 -1
- package/dist/lib/resolvers/async-resolver.d.ts +8 -0
- package/dist/lib/resolvers/async-resolver.d.ts.map +1 -0
- package/dist/lib/resolvers/function-resolver.d.ts +8 -0
- package/dist/lib/resolvers/function-resolver.d.ts.map +1 -0
- package/dist/lib/resolvers/generator-resolver.d.ts +13 -0
- package/dist/lib/resolvers/generator-resolver.d.ts.map +1 -0
- package/dist/lib/resolvers/index.d.ts +10 -0
- package/dist/lib/resolvers/index.d.ts.map +1 -0
- package/dist/lib/resolvers/iterable-resolver.d.ts +12 -0
- package/dist/lib/resolvers/iterable-resolver.d.ts.map +1 -0
- package/dist/lib/resolvers/loader-resolver.d.ts +13 -0
- package/dist/lib/resolvers/loader-resolver.d.ts.map +1 -0
- package/dist/lib/resolvers/promise-resolver.d.ts +7 -0
- package/dist/lib/resolvers/promise-resolver.d.ts.map +1 -0
- package/dist/lib/resolvers/types.d.ts +45 -0
- package/dist/lib/resolvers/types.d.ts.map +1 -0
- package/dist/lib/sandbox/ast-utils.d.ts +5 -2
- package/dist/lib/sandbox/ast-utils.d.ts.map +1 -1
- package/dist/lib/sandbox/bash-tool.d.ts.map +1 -1
- package/dist/lib/sandbox/container-tool.d.ts +32 -8
- package/dist/lib/sandbox/container-tool.d.ts.map +1 -1
- package/dist/lib/sandbox/docker-sandbox-errors.d.ts +58 -0
- package/dist/lib/sandbox/docker-sandbox-errors.d.ts.map +1 -0
- package/dist/lib/sandbox/docker-sandbox.d.ts +69 -357
- package/dist/lib/sandbox/docker-sandbox.d.ts.map +1 -1
- package/dist/lib/sandbox/index.d.ts +2 -7
- package/dist/lib/sandbox/index.d.ts.map +1 -1
- package/dist/lib/sandbox/installers/github-release.d.ts +46 -0
- package/dist/lib/sandbox/installers/github-release.d.ts.map +1 -0
- package/dist/lib/sandbox/installers/index.d.ts +7 -0
- package/dist/lib/sandbox/installers/index.d.ts.map +1 -0
- package/dist/lib/sandbox/installers/installer.d.ts +44 -0
- package/dist/lib/sandbox/installers/installer.d.ts.map +1 -0
- package/dist/lib/sandbox/installers/npm.d.ts +32 -0
- package/dist/lib/sandbox/installers/npm.d.ts.map +1 -0
- package/dist/lib/sandbox/installers/package-manager.d.ts +19 -0
- package/dist/lib/sandbox/installers/package-manager.d.ts.map +1 -0
- package/dist/lib/sandbox/installers/pip.d.ts +37 -0
- package/dist/lib/sandbox/installers/pip.d.ts.map +1 -0
- package/dist/lib/sandbox/installers/url-binary.d.ts +41 -0
- package/dist/lib/sandbox/installers/url-binary.d.ts.map +1 -0
- package/dist/lib/sandbox/subcommand.d.ts +11 -11
- package/dist/lib/sandbox/subcommand.d.ts.map +1 -1
- package/dist/lib/sandbox/upload-skills.d.ts +6 -0
- package/dist/lib/sandbox/upload-skills.d.ts.map +1 -1
- package/dist/lib/sandbox/virtual-sandbox.d.ts +10 -0
- package/dist/lib/sandbox/virtual-sandbox.d.ts.map +1 -0
- package/dist/lib/save/reminder-target-handler.d.ts +36 -0
- package/dist/lib/save/reminder-target-handler.d.ts.map +1 -0
- package/dist/lib/save/save-pipeline.d.ts +28 -0
- package/dist/lib/save/save-pipeline.d.ts.map +1 -0
- package/dist/lib/save/tool-output-target-handler.d.ts +7 -0
- package/dist/lib/save/tool-output-target-handler.d.ts.map +1 -0
- package/dist/lib/save/user-target-handler.d.ts +7 -0
- package/dist/lib/save/user-target-handler.d.ts.map +1 -0
- package/dist/lib/stream/change-source.d.ts +11 -0
- package/dist/lib/stream/change-source.d.ts.map +1 -0
- package/dist/lib/stream/ddl.stream.postgres-notify.d.ts +3 -0
- package/dist/lib/stream/ddl.stream.postgres-notify.d.ts.map +1 -0
- package/dist/lib/stream/ddl.stream.postgres.d.ts +2 -0
- package/dist/lib/stream/ddl.stream.postgres.d.ts.map +1 -0
- package/dist/lib/stream/polling-change-source.d.ts +27 -0
- package/dist/lib/stream/polling-change-source.d.ts.map +1 -0
- package/dist/lib/stream/polling-policy.d.ts +0 -1
- package/dist/lib/stream/polling-policy.d.ts.map +1 -1
- package/dist/lib/stream/postgres-notify-change-source.d.ts +15 -0
- package/dist/lib/stream/postgres-notify-change-source.d.ts.map +1 -0
- package/dist/lib/stream/postgres.stream-store.d.ts +29 -0
- package/dist/lib/stream/postgres.stream-store.d.ts.map +1 -0
- package/dist/lib/stream/stream-manager.d.ts +8 -27
- package/dist/lib/stream/stream-manager.d.ts.map +1 -1
- package/dist/lib/ui-message-guards.d.ts +10 -0
- package/dist/lib/ui-message-guards.d.ts.map +1 -0
- package/package.json +8 -13
- package/dist/lib/sandbox/extension.d.ts +0 -39
- package/dist/lib/sandbox/extension.d.ts.map +0 -1
- package/dist/lib/sandbox/openapi/demo.d.ts +0 -2
- package/dist/lib/sandbox/openapi/demo.d.ts.map +0 -1
- package/dist/lib/sandbox/openapi/extension.d.ts +0 -19
- package/dist/lib/sandbox/openapi/extension.d.ts.map +0 -1
- package/dist/lib/sandbox/openapi/format.d.ts +0 -11
- package/dist/lib/sandbox/openapi/format.d.ts.map +0 -1
- package/dist/lib/sandbox/openapi/schema.d.ts +0 -9
- package/dist/lib/sandbox/openapi/schema.d.ts.map +0 -1
- package/dist/lib/sandbox/openapi/skill/index.d.ts +0 -6
- package/dist/lib/sandbox/openapi/skill/index.d.ts.map +0 -1
- package/dist/lib/sandbox/openapi/skill/index.js +0 -14
- package/dist/lib/sandbox/openapi/skill/index.js.map +0 -7
- package/dist/lib/sandbox/openapi/skill/openapi-cli/SKILL.md +0 -109
- package/dist/lib/sandbox/openapi/validate.d.ts +0 -7
- package/dist/lib/sandbox/openapi/validate.d.ts.map +0 -1
- package/dist/lib/sandbox/routing-sandbox.d.ts +0 -28
- 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
|
-
|
|
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 {
|
|
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
|
|
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
|
|
1219
|
+
const reminderRecords = getReminderMetadataRecords(
|
|
1163
1220
|
isRecord(message2.metadata) ? message2.metadata : void 0
|
|
1164
1221
|
);
|
|
1165
1222
|
const rangesByPartIndex = /* @__PURE__ */ new Map();
|
|
1166
|
-
|
|
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
|
|
1318
|
-
const
|
|
1319
|
-
if (
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
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")}: ${
|
|
1564
|
+
`Debug: ${chalk.yellow("RepairingToolCall")}: ${toolCall2.toolName}`
|
|
1411
1565
|
);
|
|
1412
|
-
const tool3 = tools[
|
|
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 "${
|
|
1419
|
-
JSON.stringify(
|
|
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(
|
|
1575
|
+
JSON.stringify(inputSchema(toolCall2)),
|
|
1422
1576
|
"Please fix the inputs."
|
|
1423
1577
|
].join("\n")
|
|
1424
1578
|
});
|
|
1425
|
-
return { ...
|
|
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
|
|
1474
|
-
if (
|
|
1628
|
+
const toolCall2 = step.toolCalls.at(-1);
|
|
1629
|
+
if (toolCall2) {
|
|
1475
1630
|
console.log(
|
|
1476
|
-
`Debug: ${chalk2.yellow("ToolCalled")}: ${
|
|
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
|
|
1548
|
-
if (
|
|
1703
|
+
const toolCall2 = step.toolCalls.at(-1);
|
|
1704
|
+
if (toolCall2) {
|
|
1549
1705
|
console.log(
|
|
1550
|
-
`Debug: (${runId}) ${chalk2.bold.yellow("ToolCalled")}: ${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2766
|
-
|
|
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")
|
|
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
|
|
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 =
|
|
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
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
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
|
|
3358
|
-
const
|
|
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
|
-
|
|
3419
|
-
|
|
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, {
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
5703
|
-
|
|
5704
|
-
|
|
6479
|
+
var InstallError = class extends DockerSandboxError {
|
|
6480
|
+
target;
|
|
6481
|
+
source;
|
|
5705
6482
|
reason;
|
|
5706
|
-
|
|
6483
|
+
url;
|
|
6484
|
+
constructor(opts) {
|
|
6485
|
+
const where = opts.url ? `${opts.source} (${opts.url})` : opts.source;
|
|
5707
6486
|
super(
|
|
5708
|
-
`Failed to install
|
|
5709
|
-
containerId
|
|
6487
|
+
`Failed to install "${opts.target}" via ${where}: ${opts.reason}`,
|
|
6488
|
+
opts.containerId
|
|
5710
6489
|
);
|
|
5711
|
-
this.name = "
|
|
5712
|
-
this.
|
|
5713
|
-
this.
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
this.
|
|
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
|
-
|
|
5948
|
-
|
|
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
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
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
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
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
|
|
6844
|
+
await spawn3("sh", ["-c", buildCmd]);
|
|
6156
6845
|
} else {
|
|
6157
|
-
await
|
|
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(
|
|
6177
|
-
super(
|
|
6178
|
-
this.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
|
|
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
|
|
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
|
|
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.
|
|
6288
|
-
options.
|
|
6289
|
-
options.
|
|
6290
|
-
options.
|
|
6291
|
-
|
|
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
|
-
|
|
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,
|
|
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/
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
|
|
6362
|
-
|
|
6363
|
-
|
|
6364
|
-
|
|
6365
|
-
}
|
|
6366
|
-
async
|
|
6367
|
-
|
|
6368
|
-
|
|
6369
|
-
|
|
6370
|
-
|
|
6371
|
-
|
|
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
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
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
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
|
|
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
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
6446
|
-
|
|
6447
|
-
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
|
|
6451
|
-
|
|
6452
|
-
|
|
6453
|
-
|
|
6454
|
-
|
|
6455
|
-
|
|
6456
|
-
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
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
|
-
|
|
7153
|
+
};
|
|
7154
|
+
function npm(packageName, options) {
|
|
7155
|
+
return new NpmInstaller(packageName, options);
|
|
6493
7156
|
}
|
|
6494
|
-
function
|
|
6495
|
-
if (
|
|
6496
|
-
|
|
6497
|
-
|
|
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
|
-
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
|
|
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
|
-
|
|
6508
|
-
|
|
6509
|
-
return
|
|
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/
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
|
|
6542
|
-
|
|
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 {
|
|
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
|
-
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
6617
|
-
|
|
6618
|
-
|
|
6619
|
-
|
|
6620
|
-
|
|
6621
|
-
|
|
6622
|
-
|
|
6623
|
-
|
|
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
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
|
|
6655
|
-
|
|
6656
|
-
|
|
6657
|
-
|
|
6658
|
-
|
|
6659
|
-
|
|
6660
|
-
|
|
6661
|
-
|
|
6662
|
-
|
|
6663
|
-
|
|
6664
|
-
|
|
6665
|
-
|
|
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/
|
|
6682
|
-
|
|
6683
|
-
|
|
6684
|
-
const
|
|
6685
|
-
|
|
6686
|
-
|
|
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
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
|
|
6703
|
-
|
|
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
|
-
|
|
8569
|
-
|
|
8570
|
-
|
|
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
|
-
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
|
|
8594
|
-
|
|
8595
|
-
|
|
8596
|
-
|
|
8597
|
-
|
|
8598
|
-
|
|
8599
|
-
|
|
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
|
|
8602
|
-
|
|
8603
|
-
|
|
8604
|
-
|
|
8605
|
-
|
|
8606
|
-
|
|
8607
|
-
|
|
8608
|
-
|
|
8609
|
-
|
|
8610
|
-
|
|
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
|
|
9917
|
+
function rowToStream(row) {
|
|
8617
9918
|
return {
|
|
8618
|
-
|
|
8619
|
-
|
|
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
|
|
8623
|
-
|
|
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
|
|
8659
|
-
|
|
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
|
|
8669
|
-
|
|
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
|
|
8673
|
-
if (
|
|
8674
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
8923
|
-
#
|
|
8924
|
-
#
|
|
10181
|
+
#changeSource;
|
|
10182
|
+
#chunkPageSize;
|
|
10183
|
+
#onWatchEvent;
|
|
8925
10184
|
constructor(options) {
|
|
8926
10185
|
this.#store = options.store;
|
|
8927
|
-
this.#
|
|
8928
|
-
|
|
8929
|
-
|
|
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 &&
|
|
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
|
|
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
|
|
10258
|
+
await cancelWatcher;
|
|
9045
10259
|
}
|
|
9046
10260
|
return { streamId: pw?.streamId ?? streamId };
|
|
9047
10261
|
}
|
|
9048
|
-
|
|
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
|
|
9051
|
-
const
|
|
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
|
|
9055
|
-
const
|
|
9056
|
-
|
|
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
|
-
|
|
9063
|
-
|
|
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 (
|
|
9066
|
-
|
|
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
|
-
|
|
9072
|
-
|
|
9073
|
-
|
|
9074
|
-
|
|
9075
|
-
|
|
9076
|
-
|
|
9077
|
-
|
|
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 (
|
|
9085
|
-
|
|
9086
|
-
|
|
9087
|
-
currentStatus = await store2.getStreamStatus(streamId);
|
|
10347
|
+
if (result.done) {
|
|
10348
|
+
await finalize(controller, "source-ended");
|
|
10349
|
+
return;
|
|
9088
10350
|
}
|
|
9089
|
-
|
|
9090
|
-
|
|
9091
|
-
|
|
9092
|
-
|
|
9093
|
-
|
|
9094
|
-
|
|
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
|
-
|
|
9102
|
-
|
|
9103
|
-
|
|
9104
|
-
|
|
9105
|
-
if (chunks.length >= polling.chunkPageSize) {
|
|
9106
|
-
continue;
|
|
9107
|
-
}
|
|
9108
|
-
return;
|
|
10358
|
+
lastSeqRef,
|
|
10359
|
+
pageSize,
|
|
10360
|
+
emitPage
|
|
10361
|
+
);
|
|
9109
10362
|
}
|
|
9110
|
-
if (
|
|
9111
|
-
|
|
9112
|
-
|
|
9113
|
-
|
|
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 (
|
|
9122
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
9191
|
-
if (!this.#
|
|
10393
|
+
#emit(event) {
|
|
10394
|
+
if (!this.#onWatchEvent) return;
|
|
9192
10395
|
try {
|
|
9193
|
-
this.#
|
|
10396
|
+
this.#onWatchEvent(event);
|
|
9194
10397
|
} catch {
|
|
9195
10398
|
}
|
|
9196
10399
|
}
|
|
9197
10400
|
};
|
|
9198
|
-
async function
|
|
9199
|
-
|
|
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(
|
|
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
|
-
|
|
9208
|
-
drained++;
|
|
9209
|
-
fromSeq = chunk.seq + 1;
|
|
10412
|
+
lastSeqRef.value = chunk.seq;
|
|
9210
10413
|
}
|
|
9211
|
-
|
|
9212
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|