@deepagents/context 0.39.0 → 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 +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2309 -1233
- 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 +21 -2
- package/dist/lib/fragments/message/user.d.ts.map +1 -1
- 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 +2 -7
- 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) {
|
|
@@ -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,
|
|
@@ -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({
|
|
@@ -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,43 +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;
|
|
2747
|
-
const lastAssistantMessages = [];
|
|
3504
|
+
const builder = new ChainSummaryBuilder();
|
|
2748
3505
|
if (this.#branch?.headMessageId) {
|
|
2749
3506
|
const chain = await this.#store.getMessageChain(
|
|
2750
3507
|
this.#branch.headMessageId
|
|
2751
3508
|
);
|
|
2752
|
-
for (const msg of chain)
|
|
2753
|
-
messageCount++;
|
|
2754
|
-
if (msg.name === "assistant") {
|
|
2755
|
-
const assistantMsg = msg.data;
|
|
2756
|
-
lastAssistantMessage = assistantMsg;
|
|
2757
|
-
lastAssistantMessages.push(assistantMsg);
|
|
2758
|
-
}
|
|
2759
|
-
if (msg.name !== "user") {
|
|
2760
|
-
continue;
|
|
2761
|
-
}
|
|
2762
|
-
turn++;
|
|
2763
|
-
lastMessageAt = msg.createdAt;
|
|
2764
|
-
lastMessage = msg.data;
|
|
2765
|
-
}
|
|
2766
|
-
}
|
|
2767
|
-
for (const fragment2 of this.#pendingMessages) {
|
|
2768
|
-
messageCount++;
|
|
2769
|
-
if (fragment2.name === "user") turn++;
|
|
3509
|
+
for (const msg of chain) builder.ingestStored(msg);
|
|
2770
3510
|
}
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
lastMessageAt,
|
|
2775
|
-
lastMessage,
|
|
2776
|
-
lastAssistantMessage,
|
|
2777
|
-
lastAssistantMessages
|
|
2778
|
-
};
|
|
3511
|
+
for (const fragment2 of this.#pendingMessages)
|
|
3512
|
+
builder.ingestPending(fragment2);
|
|
3513
|
+
return builder.build();
|
|
2779
3514
|
}
|
|
2780
3515
|
async getTurnCount() {
|
|
2781
3516
|
const { turn } = await this.#getChainContext();
|
|
@@ -2788,7 +3523,9 @@ var ContextEngine = class _ContextEngine {
|
|
|
2788
3523
|
this.#branch.headMessageId
|
|
2789
3524
|
);
|
|
2790
3525
|
for (const msg of chain) {
|
|
2791
|
-
if (msg.name === "user")
|
|
3526
|
+
if (msg.name === "user") {
|
|
3527
|
+
return requireUIMessage(msg.data, `Stored user message "${msg.id}"`);
|
|
3528
|
+
}
|
|
2792
3529
|
}
|
|
2793
3530
|
}
|
|
2794
3531
|
for (const fragment2 of this.#pendingMessages) {
|
|
@@ -2796,7 +3533,10 @@ var ContextEngine = class _ContextEngine {
|
|
|
2796
3533
|
if (!fragment2.codec) {
|
|
2797
3534
|
throw new Error(`Fragment "${fragment2.name}" is missing codec.`);
|
|
2798
3535
|
}
|
|
2799
|
-
return
|
|
3536
|
+
return requireUIMessage(
|
|
3537
|
+
fragment2.codec.encode(),
|
|
3538
|
+
`Pending fragment "${fragment2.name}"`
|
|
3539
|
+
);
|
|
2800
3540
|
}
|
|
2801
3541
|
return void 0;
|
|
2802
3542
|
}
|
|
@@ -2839,7 +3579,7 @@ var ContextEngine = class _ContextEngine {
|
|
|
2839
3579
|
this.#branch.headMessageId
|
|
2840
3580
|
);
|
|
2841
3581
|
for (const msg of chain) {
|
|
2842
|
-
const data = msg.data;
|
|
3582
|
+
const data = requireUIMessage(msg.data, `Stored message "${msg.id}"`);
|
|
2843
3583
|
if (isEmptyAssistantPlaceholder(data)) continue;
|
|
2844
3584
|
messages.push(data);
|
|
2845
3585
|
}
|
|
@@ -2848,7 +3588,10 @@ var ContextEngine = class _ContextEngine {
|
|
|
2848
3588
|
if (!fragment2.codec) {
|
|
2849
3589
|
throw new Error(`Fragment "${fragment2.name}" is missing codec.`);
|
|
2850
3590
|
}
|
|
2851
|
-
const encoded =
|
|
3591
|
+
const encoded = requireUIMessage(
|
|
3592
|
+
fragment2.codec.encode(),
|
|
3593
|
+
`Pending fragment "${fragment2.name}"`
|
|
3594
|
+
);
|
|
2852
3595
|
if (isEmptyAssistantPlaceholder(encoded)) continue;
|
|
2853
3596
|
messages.push(encoded);
|
|
2854
3597
|
}
|
|
@@ -2940,6 +3683,11 @@ var ContextEngine = class _ContextEngine {
|
|
|
2940
3683
|
*/
|
|
2941
3684
|
async resolve(options) {
|
|
2942
3685
|
await this.#ensureInitialized();
|
|
3686
|
+
await this.#loaderResolver.resolve(this.#fragments, {
|
|
3687
|
+
sandbox: options.sandbox,
|
|
3688
|
+
context: this,
|
|
3689
|
+
signal: options.signal
|
|
3690
|
+
});
|
|
2943
3691
|
const systemPrompt = options.renderer.render(this.#renderableFragments);
|
|
2944
3692
|
const messages = await this.getMessages();
|
|
2945
3693
|
return { systemPrompt, messages };
|
|
@@ -2963,116 +3711,61 @@ var ContextEngine = class _ContextEngine {
|
|
|
2963
3711
|
if (this.#pendingMessages.length === 0) {
|
|
2964
3712
|
return { headMessageId: this.#branch?.headMessageId ?? void 0 };
|
|
2965
3713
|
}
|
|
2966
|
-
const
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
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
|
+
);
|
|
2976
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
|
+
};
|
|
2977
3767
|
}
|
|
2978
|
-
}
|
|
2979
|
-
const conditionalReminders = this.#fragments.filter(isConditionalReminder);
|
|
2980
|
-
if (conditionalReminders.length > 0) {
|
|
2981
|
-
let lastUserIndex = -1;
|
|
2982
|
-
for (let i = this.#pendingMessages.length - 1; i >= 0; i--) {
|
|
2983
|
-
if (this.#pendingMessages[i].name === "user") {
|
|
2984
|
-
lastUserIndex = i;
|
|
2985
|
-
break;
|
|
2986
|
-
}
|
|
2987
|
-
}
|
|
2988
|
-
if (lastUserIndex >= 0) {
|
|
2989
|
-
const lastUserFragment = this.#pendingMessages[lastUserIndex];
|
|
2990
|
-
if (lastUserFragment.codec) {
|
|
2991
|
-
const {
|
|
2992
|
-
turn,
|
|
2993
|
-
messageCount,
|
|
2994
|
-
lastMessageAt,
|
|
2995
|
-
lastMessage,
|
|
2996
|
-
lastAssistantMessage,
|
|
2997
|
-
lastAssistantMessages
|
|
2998
|
-
} = await this.#getChainContext();
|
|
2999
|
-
const original = lastUserFragment.codec.encode();
|
|
3000
|
-
const plainText = extractPlainText(original);
|
|
3001
|
-
const rawUsage = this.#chatData?.metadata?.usage;
|
|
3002
|
-
const usage = isLanguageModelUsage(rawUsage) ? rawUsage : void 0;
|
|
3003
|
-
const elapsed = lastMessageAt !== void 0 ? Date.now() - lastMessageAt : void 0;
|
|
3004
|
-
const whenCtx = {
|
|
3005
|
-
turn,
|
|
3006
|
-
content: plainText,
|
|
3007
|
-
lastMessageAt,
|
|
3008
|
-
lastMessage,
|
|
3009
|
-
currentMessage: original,
|
|
3010
|
-
chat: this.#chatData,
|
|
3011
|
-
usage,
|
|
3012
|
-
branch: this.#branchName,
|
|
3013
|
-
elapsed,
|
|
3014
|
-
messageCount,
|
|
3015
|
-
lastAssistantMessage,
|
|
3016
|
-
lastAssistantMessages
|
|
3017
|
-
};
|
|
3018
|
-
const configs = conditionalReminders.map(
|
|
3019
|
-
(it) => it.metadata.reminder
|
|
3020
|
-
);
|
|
3021
|
-
const whenResults = await Promise.all(
|
|
3022
|
-
configs.map((it) => it.when(whenCtx))
|
|
3023
|
-
);
|
|
3024
|
-
const firedReminders = configs.filter((_, i) => whenResults[i]);
|
|
3025
|
-
if (firedReminders.length > 0) {
|
|
3026
|
-
const resolvedOrNull = await Promise.all(
|
|
3027
|
-
firedReminders.map((it) => resolveReminderAsync(it, whenCtx))
|
|
3028
|
-
);
|
|
3029
|
-
const reminders = resolvedOrNull.flatMap((resolved, i) => {
|
|
3030
|
-
if (!resolved) return [];
|
|
3031
|
-
return [
|
|
3032
|
-
{
|
|
3033
|
-
text: resolved.text,
|
|
3034
|
-
asPart: firedReminders[i].asPart,
|
|
3035
|
-
metadata: resolved.metadata
|
|
3036
|
-
}
|
|
3037
|
-
];
|
|
3038
|
-
});
|
|
3039
|
-
const recreated = user(original, ...reminders);
|
|
3040
|
-
recreated.id = lastUserFragment.id;
|
|
3041
|
-
this.#pendingMessages[lastUserIndex] = recreated;
|
|
3042
|
-
}
|
|
3043
|
-
}
|
|
3044
|
-
}
|
|
3045
|
-
}
|
|
3046
|
-
let parentId = this.#activeBranch.headMessageId;
|
|
3047
|
-
const now = Date.now();
|
|
3048
|
-
for (const fragment2 of this.#pendingMessages) {
|
|
3049
|
-
if (!fragment2.codec) {
|
|
3050
|
-
throw new Error(`Fragment "${fragment2.name}" is missing codec.`);
|
|
3051
|
-
}
|
|
3052
|
-
const msgId = fragment2.id ?? crypto.randomUUID();
|
|
3053
|
-
let msgParentId = parentId;
|
|
3054
|
-
if (!shouldBranch && msgId === parentId) {
|
|
3055
|
-
const existing = await this.#store.getMessage(msgId);
|
|
3056
|
-
if (existing) {
|
|
3057
|
-
msgParentId = existing.parentId;
|
|
3058
|
-
}
|
|
3059
|
-
}
|
|
3060
|
-
const messageData = {
|
|
3061
|
-
id: msgId,
|
|
3062
|
-
chatId: this.#chatId,
|
|
3063
|
-
parentId: msgParentId,
|
|
3064
|
-
name: fragment2.name,
|
|
3065
|
-
type: fragment2.type,
|
|
3066
|
-
data: fragment2.codec.encode(),
|
|
3067
|
-
createdAt: now
|
|
3068
|
-
};
|
|
3069
|
-
await this.#store.addMessage(messageData);
|
|
3070
|
-
parentId = messageData.id;
|
|
3071
|
-
}
|
|
3072
|
-
await this.#store.updateBranchHead(this.#activeBranch.id, parentId);
|
|
3073
|
-
this.#activeBranch.headMessageId = parentId;
|
|
3074
|
-
this.#pendingMessages = [];
|
|
3075
|
-
return { headMessageId: this.#activeBranch.headMessageId ?? void 0 };
|
|
3768
|
+
};
|
|
3076
3769
|
}
|
|
3077
3770
|
/**
|
|
3078
3771
|
* Estimate token count and cost for the full context.
|
|
@@ -3088,6 +3781,11 @@ var ContextEngine = class _ContextEngine {
|
|
|
3088
3781
|
*/
|
|
3089
3782
|
async estimate(modelId, options = {}) {
|
|
3090
3783
|
await this.#ensureInitialized();
|
|
3784
|
+
await this.#loaderResolver.resolve(this.#fragments, {
|
|
3785
|
+
sandbox: options.sandbox,
|
|
3786
|
+
context: this,
|
|
3787
|
+
signal: options.signal
|
|
3788
|
+
});
|
|
3091
3789
|
const renderer = options.renderer ?? new XmlRenderer();
|
|
3092
3790
|
const registry = getModelsRegistry();
|
|
3093
3791
|
await registry.load();
|
|
@@ -3360,13 +4058,9 @@ var ContextEngine = class _ContextEngine {
|
|
|
3360
4058
|
async trackUsage(usage) {
|
|
3361
4059
|
await this.#ensureInitialized();
|
|
3362
4060
|
const freshChatData = await this.#store.getChat(this.#chatId);
|
|
3363
|
-
const
|
|
3364
|
-
const
|
|
3365
|
-
|
|
3366
|
-
currentUsage,
|
|
3367
|
-
usage,
|
|
3368
|
-
(a, b) => typeof a === "number" || typeof b === "number" ? (a ?? 0) + (b ?? 0) : void 0
|
|
3369
|
-
);
|
|
4061
|
+
const storedUsage = freshChatData?.metadata?.usage;
|
|
4062
|
+
const currentUsage = isLanguageModelUsage(storedUsage) ? storedUsage : void 0;
|
|
4063
|
+
const updatedUsage = mergeLanguageModelUsage(currentUsage, usage);
|
|
3370
4064
|
this.#chatData = await this.#store.updateChat(this.#chatId, {
|
|
3371
4065
|
metadata: {
|
|
3372
4066
|
...freshChatData?.metadata,
|
|
@@ -3421,8 +4115,9 @@ var ContextEngine = class _ContextEngine {
|
|
|
3421
4115
|
*/
|
|
3422
4116
|
getSkillMounts() {
|
|
3423
4117
|
for (const fragment2 of this.#fragments) {
|
|
3424
|
-
|
|
3425
|
-
|
|
4118
|
+
const mounts = fragment2.metadata?.mounts;
|
|
4119
|
+
if (fragment2.name === "available_skills" && Array.isArray(mounts) && mounts.every(isSkillPathMapping)) {
|
|
4120
|
+
return { mounts };
|
|
3426
4121
|
}
|
|
3427
4122
|
}
|
|
3428
4123
|
return { mounts: [] };
|
|
@@ -3449,7 +4144,11 @@ var ContextEngine = class _ContextEngine {
|
|
|
3449
4144
|
async inspect(options) {
|
|
3450
4145
|
await this.#ensureInitialized();
|
|
3451
4146
|
const { renderer } = options;
|
|
3452
|
-
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
|
+
});
|
|
3453
4152
|
const rendered = renderer.render(this.#renderableFragments);
|
|
3454
4153
|
const persistedMessages = [];
|
|
3455
4154
|
if (this.#branch?.headMessageId) {
|
|
@@ -4875,7 +5574,7 @@ function temporalReminder(options) {
|
|
|
4875
5574
|
}
|
|
4876
5575
|
|
|
4877
5576
|
// packages/context/src/lib/fragments/reminders/tool-predicates.ts
|
|
4878
|
-
import { isStaticToolUIPart } from "ai";
|
|
5577
|
+
import { isStaticToolUIPart as isStaticToolUIPart2 } from "ai";
|
|
4879
5578
|
var COMPLETED_STATES = /* @__PURE__ */ new Set([
|
|
4880
5579
|
"input-available",
|
|
4881
5580
|
"output-available",
|
|
@@ -4889,7 +5588,7 @@ function toolNameOf(part) {
|
|
|
4889
5588
|
}
|
|
4890
5589
|
function toolPartsOf(message2) {
|
|
4891
5590
|
if (!message2) return [];
|
|
4892
|
-
return message2.parts.filter(
|
|
5591
|
+
return message2.parts.filter(isStaticToolUIPart2);
|
|
4893
5592
|
}
|
|
4894
5593
|
function toolCall(options) {
|
|
4895
5594
|
return (ctx) => {
|
|
@@ -5231,44 +5930,6 @@ async function useAgentOsSandbox(options, fn) {
|
|
|
5231
5930
|
}
|
|
5232
5931
|
}
|
|
5233
5932
|
|
|
5234
|
-
// packages/context/src/lib/sandbox/ast-utils.ts
|
|
5235
|
-
function asStaticWordText(word) {
|
|
5236
|
-
if (!word) {
|
|
5237
|
-
return null;
|
|
5238
|
-
}
|
|
5239
|
-
return asStaticWordPartText(
|
|
5240
|
-
word.parts
|
|
5241
|
-
);
|
|
5242
|
-
}
|
|
5243
|
-
function asStaticWordPartText(parts) {
|
|
5244
|
-
let text = "";
|
|
5245
|
-
for (const part of parts) {
|
|
5246
|
-
const type = part.type;
|
|
5247
|
-
if (type === "Literal" || type === "SingleQuoted" || type === "Escaped") {
|
|
5248
|
-
if (typeof part.value !== "string") {
|
|
5249
|
-
return null;
|
|
5250
|
-
}
|
|
5251
|
-
text += part.value;
|
|
5252
|
-
continue;
|
|
5253
|
-
}
|
|
5254
|
-
if (type === "DoubleQuoted") {
|
|
5255
|
-
if (!Array.isArray(part.parts)) {
|
|
5256
|
-
return null;
|
|
5257
|
-
}
|
|
5258
|
-
const inner = asStaticWordPartText(
|
|
5259
|
-
part.parts
|
|
5260
|
-
);
|
|
5261
|
-
if (inner == null) {
|
|
5262
|
-
return null;
|
|
5263
|
-
}
|
|
5264
|
-
text += inner;
|
|
5265
|
-
continue;
|
|
5266
|
-
}
|
|
5267
|
-
return null;
|
|
5268
|
-
}
|
|
5269
|
-
return text;
|
|
5270
|
-
}
|
|
5271
|
-
|
|
5272
5933
|
// packages/context/src/lib/sandbox/bash-exception.ts
|
|
5273
5934
|
var BashException = class extends Error {
|
|
5274
5935
|
};
|
|
@@ -5411,10 +6072,9 @@ function joinSandbox(base, relative3) {
|
|
|
5411
6072
|
const normalizedBase = base.endsWith("/") ? base.slice(0, -1) : base;
|
|
5412
6073
|
return `${normalizedBase}/${relative3}`;
|
|
5413
6074
|
}
|
|
5414
|
-
|
|
6075
|
+
function discoverSkillMappings(inputs) {
|
|
5415
6076
|
if (inputs.length === 0) return [];
|
|
5416
6077
|
const discoveredByName = /* @__PURE__ */ new Map();
|
|
5417
|
-
const filesToUpload = [];
|
|
5418
6078
|
for (const { host, sandbox: sandboxBase } of inputs) {
|
|
5419
6079
|
const absoluteHost = path3.resolve(expandHome2(host));
|
|
5420
6080
|
for (const skill of discoverSkillsInDirectory(host)) {
|
|
@@ -5427,6 +6087,14 @@ async function uploadSkills(sandbox, inputs) {
|
|
|
5427
6087
|
sandbox: joinSandbox(sandboxBase, relative3)
|
|
5428
6088
|
});
|
|
5429
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) {
|
|
5430
6098
|
const walked = await walkDirectory(host);
|
|
5431
6099
|
for (const file of walked) {
|
|
5432
6100
|
filesToUpload.push({
|
|
@@ -5438,7 +6106,7 @@ async function uploadSkills(sandbox, inputs) {
|
|
|
5438
6106
|
if (filesToUpload.length > 0) {
|
|
5439
6107
|
await sandbox.writeFiles(filesToUpload);
|
|
5440
6108
|
}
|
|
5441
|
-
return
|
|
6109
|
+
return skills2;
|
|
5442
6110
|
}
|
|
5443
6111
|
|
|
5444
6112
|
// packages/context/src/lib/sandbox/bash-tool.ts
|
|
@@ -5474,7 +6142,16 @@ async function createBashTool(options = {}) {
|
|
|
5474
6142
|
throw new Error("bash tool execution is not available");
|
|
5475
6143
|
}
|
|
5476
6144
|
return runWithBashMeta(async () => {
|
|
5477
|
-
|
|
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
|
+
}
|
|
5478
6155
|
const state = readBashMeta();
|
|
5479
6156
|
if (!state) return result;
|
|
5480
6157
|
const hasHidden = Object.keys(state.hidden).length > 0;
|
|
@@ -5508,43 +6185,6 @@ async function createBashTool(options = {}) {
|
|
|
5508
6185
|
};
|
|
5509
6186
|
}
|
|
5510
6187
|
|
|
5511
|
-
// packages/context/src/lib/sandbox/extension.ts
|
|
5512
|
-
var DuplicateCommandError = class extends Error {
|
|
5513
|
-
commandName;
|
|
5514
|
-
constructor(commandName) {
|
|
5515
|
-
super(`Duplicate extension command name: "${commandName}"`);
|
|
5516
|
-
this.name = "DuplicateCommandError";
|
|
5517
|
-
this.commandName = commandName;
|
|
5518
|
-
}
|
|
5519
|
-
};
|
|
5520
|
-
function chainHooks(...hooks) {
|
|
5521
|
-
return async (value) => {
|
|
5522
|
-
let current = value;
|
|
5523
|
-
for (const hook of hooks) {
|
|
5524
|
-
current = await hook(current);
|
|
5525
|
-
}
|
|
5526
|
-
return current;
|
|
5527
|
-
};
|
|
5528
|
-
}
|
|
5529
|
-
function mergeExtensions(...extensions) {
|
|
5530
|
-
const commands = [];
|
|
5531
|
-
const seen = /* @__PURE__ */ new Set();
|
|
5532
|
-
for (const ext of extensions) {
|
|
5533
|
-
for (const cmd of ext.commands ?? []) {
|
|
5534
|
-
if (seen.has(cmd.name)) throw new DuplicateCommandError(cmd.name);
|
|
5535
|
-
seen.add(cmd.name);
|
|
5536
|
-
commands.push(cmd);
|
|
5537
|
-
}
|
|
5538
|
-
}
|
|
5539
|
-
const hooks = extensions.map((e) => e.onBeforeBashCall).filter((h) => !!h);
|
|
5540
|
-
return {
|
|
5541
|
-
commands,
|
|
5542
|
-
plugins: extensions.flatMap((e) => e.plugins ?? []),
|
|
5543
|
-
env: Object.assign({}, ...extensions.map((e) => e.env ?? {})),
|
|
5544
|
-
onBeforeBashCall: hooks.length > 0 ? chainHooks(...hooks) : void 0
|
|
5545
|
-
};
|
|
5546
|
-
}
|
|
5547
|
-
|
|
5548
6188
|
// packages/context/src/lib/sandbox/file-events.ts
|
|
5549
6189
|
var ObservedFs = class {
|
|
5550
6190
|
#base;
|
|
@@ -5790,9 +6430,11 @@ function resolveRealCwd(ctx) {
|
|
|
5790
6430
|
|
|
5791
6431
|
// packages/context/src/lib/sandbox/docker-sandbox.ts
|
|
5792
6432
|
import "bash-tool";
|
|
5793
|
-
import
|
|
6433
|
+
import spawn3 from "nano-spawn";
|
|
5794
6434
|
import { createHash } from "node:crypto";
|
|
5795
6435
|
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "node:fs";
|
|
6436
|
+
|
|
6437
|
+
// packages/context/src/lib/sandbox/docker-sandbox-errors.ts
|
|
5796
6438
|
var DockerSandboxError = class extends Error {
|
|
5797
6439
|
containerId;
|
|
5798
6440
|
constructor(message2, containerId) {
|
|
@@ -5834,19 +6476,33 @@ var PackageInstallError = class extends DockerSandboxError {
|
|
|
5834
6476
|
this.stderr = stderr;
|
|
5835
6477
|
}
|
|
5836
6478
|
};
|
|
5837
|
-
var
|
|
5838
|
-
|
|
5839
|
-
|
|
6479
|
+
var InstallError = class extends DockerSandboxError {
|
|
6480
|
+
target;
|
|
6481
|
+
source;
|
|
5840
6482
|
reason;
|
|
5841
|
-
|
|
6483
|
+
url;
|
|
6484
|
+
constructor(opts) {
|
|
6485
|
+
const where = opts.url ? `${opts.source} (${opts.url})` : opts.source;
|
|
5842
6486
|
super(
|
|
5843
|
-
`Failed to install
|
|
5844
|
-
containerId
|
|
6487
|
+
`Failed to install "${opts.target}" via ${where}: ${opts.reason}`,
|
|
6488
|
+
opts.containerId
|
|
5845
6489
|
);
|
|
5846
|
-
this.name = "
|
|
5847
|
-
this.
|
|
5848
|
-
this.
|
|
5849
|
-
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;
|
|
5850
6506
|
}
|
|
5851
6507
|
};
|
|
5852
6508
|
var MountPathError = class extends DockerSandboxError {
|
|
@@ -5879,12 +6535,108 @@ var ComposeStartError = class extends DockerSandboxError {
|
|
|
5879
6535
|
this.stderr = stderr;
|
|
5880
6536
|
}
|
|
5881
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
|
+
};
|
|
5882
6544
|
function isDebianBased(image) {
|
|
5883
6545
|
const lower = image.toLowerCase();
|
|
5884
6546
|
if (lower.includes("alpine")) return false;
|
|
5885
6547
|
const debianPatterns = ["debian", "ubuntu", "node", "python"];
|
|
5886
6548
|
return debianPatterns.some((pattern) => lower.includes(pattern));
|
|
5887
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
|
|
5888
6640
|
function isDockerfileOptions(opts) {
|
|
5889
6641
|
return "dockerfile" in opts;
|
|
5890
6642
|
}
|
|
@@ -5896,7 +6648,8 @@ var DockerSandboxStrategy = class {
|
|
|
5896
6648
|
mounts;
|
|
5897
6649
|
resources;
|
|
5898
6650
|
env;
|
|
5899
|
-
constructor(
|
|
6651
|
+
constructor(args = {}) {
|
|
6652
|
+
const { mounts = [], resources = {}, env = {} } = args;
|
|
5900
6653
|
for (const key of Object.keys(env)) {
|
|
5901
6654
|
if (key.length === 0 || key.includes("=")) {
|
|
5902
6655
|
throw new DockerSandboxError(
|
|
@@ -5908,16 +6661,6 @@ var DockerSandboxStrategy = class {
|
|
|
5908
6661
|
this.resources = resources;
|
|
5909
6662
|
this.env = env;
|
|
5910
6663
|
}
|
|
5911
|
-
/**
|
|
5912
|
-
* Template method - defines the algorithm skeleton for creating a sandbox.
|
|
5913
|
-
*
|
|
5914
|
-
* Steps:
|
|
5915
|
-
* 1. Validate mount paths exist on host
|
|
5916
|
-
* 2. Get/build the Docker image (strategy-specific)
|
|
5917
|
-
* 3. Start the container
|
|
5918
|
-
* 4. Configure the container (strategy-specific)
|
|
5919
|
-
* 5. Create and return sandbox methods
|
|
5920
|
-
*/
|
|
5921
6664
|
async create() {
|
|
5922
6665
|
this.validateMounts();
|
|
5923
6666
|
const image = await this.getImage();
|
|
@@ -5931,12 +6674,6 @@ var DockerSandboxStrategy = class {
|
|
|
5931
6674
|
}
|
|
5932
6675
|
return this.createSandboxMethods();
|
|
5933
6676
|
}
|
|
5934
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
5935
|
-
// Common implementations (shared by all strategies)
|
|
5936
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
5937
|
-
/**
|
|
5938
|
-
* Validates that all mount paths exist on the host filesystem.
|
|
5939
|
-
*/
|
|
5940
6677
|
validateMounts() {
|
|
5941
6678
|
for (const mount of this.mounts) {
|
|
5942
6679
|
if (!existsSync3(mount.hostPath)) {
|
|
@@ -5944,24 +6681,18 @@ var DockerSandboxStrategy = class {
|
|
|
5944
6681
|
}
|
|
5945
6682
|
}
|
|
5946
6683
|
}
|
|
5947
|
-
/**
|
|
5948
|
-
* Builds the docker run command arguments.
|
|
5949
|
-
*/
|
|
5950
6684
|
buildDockerArgs(image, containerId) {
|
|
5951
6685
|
const { memory = "1g", cpus = 2 } = this.resources;
|
|
5952
6686
|
const args = [
|
|
5953
6687
|
"run",
|
|
5954
6688
|
"-d",
|
|
5955
|
-
// Detached mode
|
|
5956
6689
|
"--rm",
|
|
5957
|
-
// Remove container when stopped
|
|
5958
6690
|
"--name",
|
|
5959
6691
|
containerId,
|
|
5960
6692
|
`--memory=${memory}`,
|
|
5961
6693
|
`--cpus=${cpus}`,
|
|
5962
6694
|
"-w",
|
|
5963
6695
|
"/workspace"
|
|
5964
|
-
// Set working directory
|
|
5965
6696
|
];
|
|
5966
6697
|
for (const [key, value] of Object.entries(this.env)) {
|
|
5967
6698
|
args.push("-e", `${key}=${value}`);
|
|
@@ -5973,14 +6704,11 @@ var DockerSandboxStrategy = class {
|
|
|
5973
6704
|
args.push(image, "tail", "-f", "/dev/null");
|
|
5974
6705
|
return args;
|
|
5975
6706
|
}
|
|
5976
|
-
/**
|
|
5977
|
-
* Starts a Docker container with the given image.
|
|
5978
|
-
*/
|
|
5979
6707
|
async startContainer(image) {
|
|
5980
6708
|
const containerId = `sandbox-${crypto.randomUUID().slice(0, 8)}`;
|
|
5981
6709
|
const args = this.buildDockerArgs(image, containerId);
|
|
5982
6710
|
try {
|
|
5983
|
-
await
|
|
6711
|
+
await spawn3("docker", args);
|
|
5984
6712
|
} catch (error) {
|
|
5985
6713
|
const err = error;
|
|
5986
6714
|
if (err.message?.includes("Cannot connect") || err.message?.includes("docker daemon") || err.stderr?.includes("Cannot connect")) {
|
|
@@ -5990,21 +6718,15 @@ var DockerSandboxStrategy = class {
|
|
|
5990
6718
|
}
|
|
5991
6719
|
return containerId;
|
|
5992
6720
|
}
|
|
5993
|
-
/**
|
|
5994
|
-
* Stops a Docker container.
|
|
5995
|
-
*/
|
|
5996
6721
|
async stopContainer(containerId) {
|
|
5997
6722
|
try {
|
|
5998
|
-
await
|
|
6723
|
+
await spawn3("docker", ["stop", containerId]);
|
|
5999
6724
|
} catch {
|
|
6000
6725
|
}
|
|
6001
6726
|
}
|
|
6002
|
-
/**
|
|
6003
|
-
* Executes a command in the container.
|
|
6004
|
-
*/
|
|
6005
6727
|
async exec(command) {
|
|
6006
6728
|
try {
|
|
6007
|
-
const result = await
|
|
6729
|
+
const result = await spawn3("docker", [
|
|
6008
6730
|
"exec",
|
|
6009
6731
|
this.context.containerId,
|
|
6010
6732
|
"sh",
|
|
@@ -6025,9 +6747,6 @@ var DockerSandboxStrategy = class {
|
|
|
6025
6747
|
};
|
|
6026
6748
|
}
|
|
6027
6749
|
}
|
|
6028
|
-
/**
|
|
6029
|
-
* Creates the DockerSandbox interface with all methods.
|
|
6030
|
-
*/
|
|
6031
6750
|
createSandboxMethods() {
|
|
6032
6751
|
const { containerId } = this.context;
|
|
6033
6752
|
const sandbox = {
|
|
@@ -6067,171 +6786,19 @@ var DockerSandboxStrategy = class {
|
|
|
6067
6786
|
};
|
|
6068
6787
|
var RuntimeStrategy = class extends DockerSandboxStrategy {
|
|
6069
6788
|
image;
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
this.
|
|
6075
|
-
this.packages = packages;
|
|
6076
|
-
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 ?? [];
|
|
6077
6794
|
}
|
|
6078
6795
|
async getImage() {
|
|
6079
6796
|
return this.image;
|
|
6080
6797
|
}
|
|
6081
6798
|
async configure() {
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
/**
|
|
6086
|
-
* Installs packages using the appropriate package manager (apk/apt-get).
|
|
6087
|
-
*/
|
|
6088
|
-
async installPackages() {
|
|
6089
|
-
if (this.packages.length === 0) return;
|
|
6090
|
-
const useApt = isDebianBased(this.image);
|
|
6091
|
-
const installCmd = useApt ? `apt-get update && apt-get install -y ${this.packages.join(" ")}` : `apk add --no-cache ${this.packages.join(" ")}`;
|
|
6092
|
-
try {
|
|
6093
|
-
await spawn2("docker", [
|
|
6094
|
-
"exec",
|
|
6095
|
-
this.context.containerId,
|
|
6096
|
-
"sh",
|
|
6097
|
-
"-c",
|
|
6098
|
-
installCmd
|
|
6099
|
-
]);
|
|
6100
|
-
} catch (error) {
|
|
6101
|
-
const err = error;
|
|
6102
|
-
throw new PackageInstallError(
|
|
6103
|
-
this.packages,
|
|
6104
|
-
this.image,
|
|
6105
|
-
useApt ? "apt-get" : "apk",
|
|
6106
|
-
err.stderr || err.message,
|
|
6107
|
-
this.context.containerId
|
|
6108
|
-
);
|
|
6109
|
-
}
|
|
6110
|
-
}
|
|
6111
|
-
/**
|
|
6112
|
-
* Installs binaries from URLs.
|
|
6113
|
-
*/
|
|
6114
|
-
async installBinaries() {
|
|
6115
|
-
if (this.binaries.length === 0) return;
|
|
6116
|
-
await this.ensureCurl();
|
|
6117
|
-
const arch = await this.detectArchitecture();
|
|
6118
|
-
for (const binary of this.binaries) {
|
|
6119
|
-
await this.installBinary(binary, arch);
|
|
6120
|
-
}
|
|
6121
|
-
}
|
|
6122
|
-
/**
|
|
6123
|
-
* Ensures curl is installed in the container.
|
|
6124
|
-
*/
|
|
6125
|
-
async ensureCurl() {
|
|
6126
|
-
const checkResult = await spawn2("docker", [
|
|
6127
|
-
"exec",
|
|
6128
|
-
this.context.containerId,
|
|
6129
|
-
"which",
|
|
6130
|
-
"curl"
|
|
6131
|
-
]).catch(() => null);
|
|
6132
|
-
if (checkResult) return;
|
|
6133
|
-
const useApt = isDebianBased(this.image);
|
|
6134
|
-
const curlInstallCmd = useApt ? "apt-get update && apt-get install -y curl" : "apk add --no-cache curl";
|
|
6135
|
-
try {
|
|
6136
|
-
await spawn2("docker", [
|
|
6137
|
-
"exec",
|
|
6138
|
-
this.context.containerId,
|
|
6139
|
-
"sh",
|
|
6140
|
-
"-c",
|
|
6141
|
-
curlInstallCmd
|
|
6142
|
-
]);
|
|
6143
|
-
} catch (error) {
|
|
6144
|
-
const err = error;
|
|
6145
|
-
throw new BinaryInstallError(
|
|
6146
|
-
"curl",
|
|
6147
|
-
"package-manager",
|
|
6148
|
-
`Required for binary downloads: ${err.stderr || err.message}`,
|
|
6149
|
-
this.context.containerId
|
|
6150
|
-
);
|
|
6151
|
-
}
|
|
6152
|
-
}
|
|
6153
|
-
/**
|
|
6154
|
-
* Detects the container's CPU architecture.
|
|
6155
|
-
*/
|
|
6156
|
-
async detectArchitecture() {
|
|
6157
|
-
try {
|
|
6158
|
-
const result = await spawn2("docker", [
|
|
6159
|
-
"exec",
|
|
6160
|
-
this.context.containerId,
|
|
6161
|
-
"uname",
|
|
6162
|
-
"-m"
|
|
6163
|
-
]);
|
|
6164
|
-
return result.stdout.trim();
|
|
6165
|
-
} catch (error) {
|
|
6166
|
-
const err = error;
|
|
6167
|
-
throw new DockerSandboxError(
|
|
6168
|
-
`Failed to detect container architecture: ${err.stderr || err.message}`,
|
|
6169
|
-
this.context.containerId
|
|
6170
|
-
);
|
|
6171
|
-
}
|
|
6172
|
-
}
|
|
6173
|
-
/**
|
|
6174
|
-
* Installs a single binary from URL.
|
|
6175
|
-
*/
|
|
6176
|
-
async installBinary(binary, arch) {
|
|
6177
|
-
let url;
|
|
6178
|
-
if (typeof binary.url === "string") {
|
|
6179
|
-
url = binary.url;
|
|
6180
|
-
} else {
|
|
6181
|
-
const archUrl = binary.url[arch];
|
|
6182
|
-
if (!archUrl) {
|
|
6183
|
-
throw new BinaryInstallError(
|
|
6184
|
-
binary.name,
|
|
6185
|
-
`arch:${arch}`,
|
|
6186
|
-
`No URL provided for architecture "${arch}". Available: ${Object.keys(binary.url).join(", ")}`,
|
|
6187
|
-
this.context.containerId
|
|
6188
|
-
);
|
|
6189
|
-
}
|
|
6190
|
-
url = archUrl;
|
|
6191
|
-
}
|
|
6192
|
-
const isTarGz = url.endsWith(".tar.gz") || url.endsWith(".tgz");
|
|
6193
|
-
let installCmd;
|
|
6194
|
-
if (isTarGz) {
|
|
6195
|
-
const binaryPathInArchive = binary.binaryPath || binary.name;
|
|
6196
|
-
installCmd = `
|
|
6197
|
-
set -e
|
|
6198
|
-
TMPDIR=$(mktemp -d)
|
|
6199
|
-
cd "$TMPDIR"
|
|
6200
|
-
curl -fsSL "${url}" -o archive.tar.gz
|
|
6201
|
-
tar -xzf archive.tar.gz
|
|
6202
|
-
BINARY_FILE=$(find . -name "${binaryPathInArchive}" -o -name "${binary.name}" | head -1)
|
|
6203
|
-
if [ -z "$BINARY_FILE" ]; then
|
|
6204
|
-
echo "Binary not found in archive. Contents:" >&2
|
|
6205
|
-
find . -type f >&2
|
|
6206
|
-
exit 1
|
|
6207
|
-
fi
|
|
6208
|
-
chmod +x "$BINARY_FILE"
|
|
6209
|
-
mv "$BINARY_FILE" /usr/local/bin/${binary.name}
|
|
6210
|
-
cd /
|
|
6211
|
-
rm -rf "$TMPDIR"
|
|
6212
|
-
`;
|
|
6213
|
-
} else {
|
|
6214
|
-
installCmd = `
|
|
6215
|
-
curl -fsSL "${url}" -o /usr/local/bin/${binary.name}
|
|
6216
|
-
chmod +x /usr/local/bin/${binary.name}
|
|
6217
|
-
`;
|
|
6218
|
-
}
|
|
6219
|
-
try {
|
|
6220
|
-
await spawn2("docker", [
|
|
6221
|
-
"exec",
|
|
6222
|
-
this.context.containerId,
|
|
6223
|
-
"sh",
|
|
6224
|
-
"-c",
|
|
6225
|
-
installCmd
|
|
6226
|
-
]);
|
|
6227
|
-
} catch (error) {
|
|
6228
|
-
const err = error;
|
|
6229
|
-
throw new BinaryInstallError(
|
|
6230
|
-
binary.name,
|
|
6231
|
-
url,
|
|
6232
|
-
err.stderr || err.message,
|
|
6233
|
-
this.context.containerId
|
|
6234
|
-
);
|
|
6799
|
+
const ctx = createInstallerContext(this.context.containerId, this.image);
|
|
6800
|
+
for (const installer of this.installers) {
|
|
6801
|
+
await installer.install(ctx);
|
|
6235
6802
|
}
|
|
6236
6803
|
}
|
|
6237
6804
|
};
|
|
@@ -6239,24 +6806,17 @@ var DockerfileStrategy = class extends DockerSandboxStrategy {
|
|
|
6239
6806
|
imageTag;
|
|
6240
6807
|
dockerfile;
|
|
6241
6808
|
dockerContext;
|
|
6242
|
-
constructor(
|
|
6243
|
-
super(mounts, resources, env);
|
|
6244
|
-
this.dockerfile = dockerfile;
|
|
6245
|
-
this.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 ?? ".";
|
|
6246
6813
|
this.imageTag = this.computeImageTag();
|
|
6247
6814
|
}
|
|
6248
|
-
/**
|
|
6249
|
-
* Computes a deterministic image tag based on Dockerfile content.
|
|
6250
|
-
* Same Dockerfile → same tag → Docker skips rebuild if image exists.
|
|
6251
|
-
*/
|
|
6252
6815
|
computeImageTag() {
|
|
6253
6816
|
const content = this.isInlineDockerfile() ? this.dockerfile : readFileSync2(this.dockerfile, "utf-8");
|
|
6254
6817
|
const hash = createHash("sha256").update(content).digest("hex").slice(0, 12);
|
|
6255
6818
|
return `sandbox-${hash}`;
|
|
6256
6819
|
}
|
|
6257
|
-
/**
|
|
6258
|
-
* Checks if the dockerfile property is inline content or a file path.
|
|
6259
|
-
*/
|
|
6260
6820
|
isInlineDockerfile() {
|
|
6261
6821
|
return this.dockerfile.includes("\n");
|
|
6262
6822
|
}
|
|
@@ -6269,27 +6829,21 @@ var DockerfileStrategy = class extends DockerSandboxStrategy {
|
|
|
6269
6829
|
}
|
|
6270
6830
|
async configure() {
|
|
6271
6831
|
}
|
|
6272
|
-
/**
|
|
6273
|
-
* Checks if the image already exists locally.
|
|
6274
|
-
*/
|
|
6275
6832
|
async imageExists() {
|
|
6276
6833
|
try {
|
|
6277
|
-
await
|
|
6834
|
+
await spawn3("docker", ["image", "inspect", this.imageTag]);
|
|
6278
6835
|
return true;
|
|
6279
6836
|
} catch {
|
|
6280
6837
|
return false;
|
|
6281
6838
|
}
|
|
6282
6839
|
}
|
|
6283
|
-
/**
|
|
6284
|
-
* Builds the Docker image from the Dockerfile.
|
|
6285
|
-
*/
|
|
6286
6840
|
async buildImage() {
|
|
6287
6841
|
try {
|
|
6288
6842
|
if (this.isInlineDockerfile()) {
|
|
6289
6843
|
const buildCmd = `echo '${this.dockerfile.replace(/'/g, "'\\''")}' | docker build -t ${this.imageTag} -f - ${this.dockerContext}`;
|
|
6290
|
-
await
|
|
6844
|
+
await spawn3("sh", ["-c", buildCmd]);
|
|
6291
6845
|
} else {
|
|
6292
|
-
await
|
|
6846
|
+
await spawn3("docker", [
|
|
6293
6847
|
"build",
|
|
6294
6848
|
"-t",
|
|
6295
6849
|
this.imageTag,
|
|
@@ -6308,33 +6862,23 @@ var ComposeStrategy = class extends DockerSandboxStrategy {
|
|
|
6308
6862
|
projectName;
|
|
6309
6863
|
composeFile;
|
|
6310
6864
|
service;
|
|
6311
|
-
constructor(
|
|
6312
|
-
super(
|
|
6313
|
-
this.composeFile =
|
|
6314
|
-
this.service = service;
|
|
6865
|
+
constructor(args) {
|
|
6866
|
+
super({ resources: args.resources });
|
|
6867
|
+
this.composeFile = args.compose;
|
|
6868
|
+
this.service = args.service;
|
|
6315
6869
|
this.projectName = this.computeProjectName();
|
|
6316
6870
|
}
|
|
6317
|
-
/**
|
|
6318
|
-
* Deterministic project name based on compose file content for caching.
|
|
6319
|
-
* Same compose file → same project name → faster subsequent startups.
|
|
6320
|
-
*/
|
|
6321
6871
|
computeProjectName() {
|
|
6322
6872
|
const content = readFileSync2(this.composeFile, "utf-8");
|
|
6323
6873
|
const hash = createHash("sha256").update(content).digest("hex").slice(0, 8);
|
|
6324
6874
|
return `sandbox-${hash}`;
|
|
6325
6875
|
}
|
|
6326
|
-
/**
|
|
6327
|
-
* Override: No image to get - compose manages its own images.
|
|
6328
|
-
*/
|
|
6329
6876
|
async getImage() {
|
|
6330
6877
|
return "";
|
|
6331
6878
|
}
|
|
6332
|
-
/**
|
|
6333
|
-
* Override: Start all services with docker compose up.
|
|
6334
|
-
*/
|
|
6335
6879
|
async startContainer(_image) {
|
|
6336
6880
|
try {
|
|
6337
|
-
await
|
|
6881
|
+
await spawn3("docker", [
|
|
6338
6882
|
"compose",
|
|
6339
6883
|
"-f",
|
|
6340
6884
|
this.composeFile,
|
|
@@ -6354,12 +6898,9 @@ var ComposeStrategy = class extends DockerSandboxStrategy {
|
|
|
6354
6898
|
}
|
|
6355
6899
|
async configure() {
|
|
6356
6900
|
}
|
|
6357
|
-
/**
|
|
6358
|
-
* Override: Execute commands in the target service.
|
|
6359
|
-
*/
|
|
6360
6901
|
async exec(command) {
|
|
6361
6902
|
try {
|
|
6362
|
-
const result = await
|
|
6903
|
+
const result = await spawn3("docker", [
|
|
6363
6904
|
"compose",
|
|
6364
6905
|
"-f",
|
|
6365
6906
|
this.composeFile,
|
|
@@ -6367,7 +6908,6 @@ var ComposeStrategy = class extends DockerSandboxStrategy {
|
|
|
6367
6908
|
this.projectName,
|
|
6368
6909
|
"exec",
|
|
6369
6910
|
"-T",
|
|
6370
|
-
// -T disables pseudo-TTY
|
|
6371
6911
|
this.service,
|
|
6372
6912
|
"sh",
|
|
6373
6913
|
"-c",
|
|
@@ -6383,12 +6923,9 @@ var ComposeStrategy = class extends DockerSandboxStrategy {
|
|
|
6383
6923
|
};
|
|
6384
6924
|
}
|
|
6385
6925
|
}
|
|
6386
|
-
/**
|
|
6387
|
-
* Override: Stop all services with docker compose down.
|
|
6388
|
-
*/
|
|
6389
6926
|
async stopContainer(_containerId) {
|
|
6390
6927
|
try {
|
|
6391
|
-
await
|
|
6928
|
+
await spawn3("docker", [
|
|
6392
6929
|
"compose",
|
|
6393
6930
|
"-f",
|
|
6394
6931
|
this.composeFile,
|
|
@@ -6403,28 +6940,27 @@ var ComposeStrategy = class extends DockerSandboxStrategy {
|
|
|
6403
6940
|
async function createDockerSandbox(options = {}) {
|
|
6404
6941
|
let strategy;
|
|
6405
6942
|
if (isComposeOptions(options)) {
|
|
6406
|
-
strategy = new ComposeStrategy(
|
|
6407
|
-
options.compose,
|
|
6408
|
-
options.service,
|
|
6409
|
-
options.resources
|
|
6410
|
-
);
|
|
6943
|
+
strategy = new ComposeStrategy({
|
|
6944
|
+
compose: options.compose,
|
|
6945
|
+
service: options.service,
|
|
6946
|
+
resources: options.resources
|
|
6947
|
+
});
|
|
6411
6948
|
} else if (isDockerfileOptions(options)) {
|
|
6412
|
-
strategy = new DockerfileStrategy(
|
|
6413
|
-
options.dockerfile,
|
|
6414
|
-
options.context,
|
|
6415
|
-
options.mounts,
|
|
6416
|
-
options.resources,
|
|
6417
|
-
options.env
|
|
6418
|
-
);
|
|
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
|
+
});
|
|
6419
6956
|
} else {
|
|
6420
|
-
strategy = new RuntimeStrategy(
|
|
6421
|
-
options.image,
|
|
6422
|
-
options.
|
|
6423
|
-
options.
|
|
6424
|
-
options.
|
|
6425
|
-
options.
|
|
6426
|
-
|
|
6427
|
-
);
|
|
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
|
+
});
|
|
6428
6964
|
}
|
|
6429
6965
|
return strategy.create();
|
|
6430
6966
|
}
|
|
@@ -6463,15 +6999,14 @@ async function createContainerTool(options = {}) {
|
|
|
6463
6999
|
} else {
|
|
6464
7000
|
const {
|
|
6465
7001
|
image,
|
|
6466
|
-
|
|
6467
|
-
binaries,
|
|
7002
|
+
installers,
|
|
6468
7003
|
mounts,
|
|
6469
7004
|
resources,
|
|
6470
7005
|
env,
|
|
6471
7006
|
skills: skills2 = [],
|
|
6472
7007
|
...rest
|
|
6473
7008
|
} = options;
|
|
6474
|
-
sandboxOptions = { image,
|
|
7009
|
+
sandboxOptions = { image, installers, mounts, resources, env };
|
|
6475
7010
|
bashOptions = rest;
|
|
6476
7011
|
skillInputs = skills2;
|
|
6477
7012
|
}
|
|
@@ -6489,215 +7024,252 @@ async function createContainerTool(options = {}) {
|
|
|
6489
7024
|
};
|
|
6490
7025
|
}
|
|
6491
7026
|
|
|
6492
|
-
// packages/context/src/lib/sandbox/
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
}
|
|
6501
|
-
async
|
|
6502
|
-
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
|
|
6506
|
-
|
|
6507
|
-
|
|
6508
|
-
}
|
|
6509
|
-
return bash;
|
|
6510
|
-
};
|
|
6511
|
-
const sandbox = {
|
|
6512
|
-
async install(ext) {
|
|
6513
|
-
if (bash) {
|
|
6514
|
-
throw new Error(
|
|
6515
|
-
"createVirtualSandbox: install() called twice (extensions are install-once)"
|
|
6516
|
-
);
|
|
6517
|
-
}
|
|
6518
|
-
const adapted = ext.commands.map(
|
|
6519
|
-
(cmd) => adaptExtensionCommandForBash(cmd, () => sandbox)
|
|
6520
|
-
);
|
|
6521
|
-
bash = new Bash({
|
|
6522
|
-
fs: options.fs,
|
|
6523
|
-
cwd: options.cwd,
|
|
6524
|
-
env: { ...options.env, ...ext.env },
|
|
6525
|
-
customCommands: adapted
|
|
6526
|
-
});
|
|
6527
|
-
for (const plugin of ext.plugins) {
|
|
6528
|
-
bash.registerTransformPlugin(plugin);
|
|
6529
|
-
}
|
|
6530
|
-
},
|
|
6531
|
-
async executeCommand(command) {
|
|
6532
|
-
const instance = ensureInstalled("executeCommand");
|
|
6533
|
-
const result = await instance.exec(command);
|
|
6534
|
-
return {
|
|
6535
|
-
stdout: result.stdout,
|
|
6536
|
-
stderr: result.stderr,
|
|
6537
|
-
exitCode: result.exitCode
|
|
6538
|
-
};
|
|
6539
|
-
},
|
|
6540
|
-
async readFile(path5) {
|
|
6541
|
-
return ensureInstalled("readFile").readFile(path5);
|
|
6542
|
-
},
|
|
6543
|
-
async writeFiles(files) {
|
|
6544
|
-
const instance = ensureInstalled("writeFiles");
|
|
6545
|
-
for (const f of files) {
|
|
6546
|
-
await instance.writeFile(
|
|
6547
|
-
f.path,
|
|
6548
|
-
typeof f.content === "string" ? f.content : Buffer.from(f.content).toString("utf-8")
|
|
6549
|
-
);
|
|
6550
|
-
}
|
|
6551
|
-
}
|
|
6552
|
-
};
|
|
6553
|
-
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);
|
|
6554
7043
|
}
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
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
|
+
});
|
|
6560
7094
|
}
|
|
6561
|
-
return createShallowRouter(opts.backend, merged, opts.cwd ?? "/");
|
|
6562
7095
|
}
|
|
6563
|
-
function
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
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
|
+
`;
|
|
6573
7125
|
}
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
|
|
6579
|
-
|
|
6580
|
-
|
|
6581
|
-
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
const cmd = name ? byName.get(name) : void 0;
|
|
6600
|
-
if (cmd) {
|
|
6601
|
-
return cmd.handler(args, {
|
|
6602
|
-
sandbox: backend,
|
|
6603
|
-
cwd: dispatch?.cwd ?? cwd,
|
|
6604
|
-
env: ext.env,
|
|
6605
|
-
stdin: ""
|
|
6606
|
-
});
|
|
6607
|
-
}
|
|
6608
|
-
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
|
+
});
|
|
6609
7151
|
}
|
|
6610
|
-
};
|
|
6611
|
-
}
|
|
6612
|
-
function extractDispatchTarget(ast, defaultCwd) {
|
|
6613
|
-
if (ast.statements.length !== 1) return null;
|
|
6614
|
-
const statement = ast.statements[0];
|
|
6615
|
-
if (statement.background) return null;
|
|
6616
|
-
if (statement.operators.length === 0 && statement.pipelines.length === 1) {
|
|
6617
|
-
const tokens = tokenizeSimplePipeline(statement.pipelines[0]);
|
|
6618
|
-
return tokens ? { tokens, cwd: defaultCwd } : null;
|
|
6619
|
-
}
|
|
6620
|
-
if (statement.operators.length === 1 && statement.operators[0] === "&&" && statement.pipelines.length === 2) {
|
|
6621
|
-
const cd = simpleCommandFromPipeline(statement.pipelines[0]);
|
|
6622
|
-
const cdCwd = readCdTarget(cd);
|
|
6623
|
-
if (!cdCwd) return null;
|
|
6624
|
-
const tokens = tokenizeSimplePipeline(statement.pipelines[1]);
|
|
6625
|
-
return tokens ? { tokens, cwd: cdCwd } : null;
|
|
6626
7152
|
}
|
|
6627
|
-
|
|
7153
|
+
};
|
|
7154
|
+
function npm(packageName, options) {
|
|
7155
|
+
return new NpmInstaller(packageName, options);
|
|
6628
7156
|
}
|
|
6629
|
-
function
|
|
6630
|
-
if (
|
|
6631
|
-
|
|
6632
|
-
|
|
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
|
+
);
|
|
6633
7171
|
}
|
|
6634
|
-
const command = pipeline.commands[0];
|
|
6635
|
-
return command?.type === "SimpleCommand" ? command : null;
|
|
6636
7172
|
}
|
|
6637
|
-
|
|
6638
|
-
|
|
6639
|
-
|
|
6640
|
-
|
|
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
|
+
);
|
|
6641
7219
|
}
|
|
6642
|
-
|
|
6643
|
-
|
|
6644
|
-
return
|
|
6645
|
-
}
|
|
6646
|
-
function tokenizeSimplePipeline(pipeline) {
|
|
6647
|
-
const command = simpleCommandFromPipeline(pipeline);
|
|
6648
|
-
if (!command || command.redirections.length > 0) return null;
|
|
6649
|
-
const name = asStaticWordText(command.name);
|
|
6650
|
-
if (!name) return null;
|
|
6651
|
-
const args = [];
|
|
6652
|
-
for (const arg of command.args) {
|
|
6653
|
-
const text = asStaticWordText(arg);
|
|
6654
|
-
if (text == null) return null;
|
|
6655
|
-
args.push(text);
|
|
6656
|
-
}
|
|
6657
|
-
return [name, ...args];
|
|
6658
|
-
}
|
|
6659
|
-
function adaptExtensionCommandForBash(ext, getSandbox) {
|
|
6660
|
-
return defineCommand2(ext.name, async (args, bashCtx) => {
|
|
6661
|
-
return ext.handler(args, {
|
|
6662
|
-
sandbox: getSandbox(),
|
|
6663
|
-
cwd: bashCtx.cwd,
|
|
6664
|
-
env: Object.fromEntries(bashCtx.env),
|
|
6665
|
-
stdin: bashCtx.stdin,
|
|
6666
|
-
signal: bashCtx.signal
|
|
6667
|
-
});
|
|
6668
|
-
});
|
|
7220
|
+
}
|
|
7221
|
+
function formatVersion(version) {
|
|
7222
|
+
return /^[<>=!~]/.test(version) ? version : `==${version}`;
|
|
6669
7223
|
}
|
|
6670
7224
|
|
|
6671
|
-
// packages/context/src/lib/sandbox/
|
|
6672
|
-
|
|
6673
|
-
|
|
6674
|
-
|
|
6675
|
-
|
|
6676
|
-
|
|
6677
|
-
|
|
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
|
+
}
|
|
6678
7250
|
|
|
6679
7251
|
// packages/context/src/lib/sandbox/subcommand.ts
|
|
6680
|
-
import {
|
|
7252
|
+
import {
|
|
7253
|
+
defineCommand as defineCommand2,
|
|
7254
|
+
parse
|
|
7255
|
+
} from "just-bash";
|
|
6681
7256
|
function defineSubcommandGroup(name, subcommands) {
|
|
6682
7257
|
const usageLines = Object.entries(subcommands).map(([, def]) => ` ${name} ${def.usage.padEnd(30)} ${def.description}`).join("\n");
|
|
6683
|
-
return {
|
|
6684
|
-
|
|
6685
|
-
|
|
6686
|
-
|
|
6687
|
-
|
|
6688
|
-
|
|
6689
|
-
|
|
6690
|
-
|
|
6691
|
-
|
|
6692
|
-
stdout: "",
|
|
6693
|
-
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"}
|
|
6694
7267
|
|
|
6695
7268
|
Usage:
|
|
6696
7269
|
${usageLines}`,
|
|
6697
|
-
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
};
|
|
7270
|
+
exitCode: 1
|
|
7271
|
+
};
|
|
7272
|
+
});
|
|
6701
7273
|
}
|
|
6702
7274
|
function escapeRegExp(s) {
|
|
6703
7275
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -6728,7 +7300,7 @@ function buildSubcommandRepair(name, subcommands) {
|
|
|
6728
7300
|
const match = raw.match(pattern);
|
|
6729
7301
|
if (!match) return raw;
|
|
6730
7302
|
try {
|
|
6731
|
-
|
|
7303
|
+
parse(raw);
|
|
6732
7304
|
return raw;
|
|
6733
7305
|
} catch {
|
|
6734
7306
|
}
|
|
@@ -6740,7 +7312,7 @@ function buildSubcommandRepair(name, subcommands) {
|
|
|
6740
7312
|
if (repairedArgs == null) return raw;
|
|
6741
7313
|
const repaired = `${prefix} ${repairedArgs}`;
|
|
6742
7314
|
try {
|
|
6743
|
-
|
|
7315
|
+
parse(repaired);
|
|
6744
7316
|
return repaired;
|
|
6745
7317
|
} catch {
|
|
6746
7318
|
return raw;
|
|
@@ -6748,278 +7320,35 @@ function buildSubcommandRepair(name, subcommands) {
|
|
|
6748
7320
|
};
|
|
6749
7321
|
}
|
|
6750
7322
|
|
|
6751
|
-
// packages/context/src/lib/sandbox/
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
}
|
|
6760
|
-
function unwrapApiResponse(value) {
|
|
6761
|
-
if (value !== null && typeof value === "object" && "status" in value && "data" in value) {
|
|
6762
|
-
return value.data;
|
|
6763
|
-
}
|
|
6764
|
-
return value;
|
|
6765
|
-
}
|
|
6766
|
-
function formatError(details) {
|
|
6767
|
-
const payload = { ...details.extra ?? {} };
|
|
6768
|
-
payload.ok = false;
|
|
6769
|
-
payload.group = details.group;
|
|
6770
|
-
payload.code = details.code;
|
|
6771
|
-
payload.message = details.message;
|
|
6772
|
-
if (details.operation) payload.operation = details.operation;
|
|
6773
|
-
return {
|
|
6774
|
-
stdout: "",
|
|
6775
|
-
stderr: JSON.stringify(payload) + "\n",
|
|
6776
|
-
exitCode: 1
|
|
6777
|
-
};
|
|
6778
|
-
}
|
|
6779
|
-
|
|
6780
|
-
// packages/context/src/lib/sandbox/openapi/schema.ts
|
|
6781
|
-
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
6782
|
-
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
|
+
});
|
|
6783
7332
|
return {
|
|
6784
|
-
|
|
6785
|
-
|
|
6786
|
-
handler: async () => {
|
|
6787
|
-
try {
|
|
6788
|
-
const operations = entries.map((entry) => {
|
|
6789
|
-
const [method, path5] = entry.endpoint.split(" ");
|
|
6790
|
-
const zodSchema = client.schemas[entry.endpoint]?.schema;
|
|
6791
|
-
const input = zodSchema ? zodToJsonSchema(zodSchema, {
|
|
6792
|
-
target: "jsonSchema7",
|
|
6793
|
-
$refStrategy: "none"
|
|
6794
|
-
}) : {};
|
|
6795
|
-
return entry.summary ? {
|
|
6796
|
-
operationId: entry.operationId,
|
|
6797
|
-
method,
|
|
6798
|
-
path: path5,
|
|
6799
|
-
summary: entry.summary,
|
|
6800
|
-
input
|
|
6801
|
-
} : { operationId: entry.operationId, method, path: path5, input };
|
|
6802
|
-
});
|
|
6803
|
-
return formatResponse({ group, operations });
|
|
6804
|
-
} catch (error) {
|
|
6805
|
-
return formatError({
|
|
6806
|
-
group,
|
|
6807
|
-
operation: "schema",
|
|
6808
|
-
code: "schema_dump_failed",
|
|
6809
|
-
message: error instanceof Error ? error.message : String(error)
|
|
6810
|
-
});
|
|
6811
|
-
}
|
|
6812
|
-
}
|
|
6813
|
-
};
|
|
6814
|
-
}
|
|
6815
|
-
|
|
6816
|
-
// packages/context/src/lib/sandbox/openapi/validate.ts
|
|
6817
|
-
function extractPathParams(path5) {
|
|
6818
|
-
const names = [];
|
|
6819
|
-
const re = /{([^}]+)}/g;
|
|
6820
|
-
let match;
|
|
6821
|
-
while ((match = re.exec(path5)) !== null) {
|
|
6822
|
-
names.push(match[1]);
|
|
6823
|
-
}
|
|
6824
|
-
return names;
|
|
6825
|
-
}
|
|
6826
|
-
var UNSAFE_CHARS = /[/\\?#&]/;
|
|
6827
|
-
var CONTROL = /[\x00-\x1f\x7f]/;
|
|
6828
|
-
var PRE_ENCODED = /%[0-9a-fA-F]{2}/;
|
|
6829
|
-
function findUnsafePathParam(input, names) {
|
|
6830
|
-
if (input === null || typeof input !== "object" || Array.isArray(input)) {
|
|
6831
|
-
return null;
|
|
6832
|
-
}
|
|
6833
|
-
const record = input;
|
|
6834
|
-
for (const name of names) {
|
|
6835
|
-
const value = record[name];
|
|
6836
|
-
if (typeof value !== "string") continue;
|
|
6837
|
-
if (value.length === 0) {
|
|
6838
|
-
return { field: name, reason: "is empty" };
|
|
6839
|
-
}
|
|
6840
|
-
if (value === "." || value === "..") {
|
|
6841
|
-
return {
|
|
6842
|
-
field: name,
|
|
6843
|
-
reason: `resolves to '${value}' path segment`
|
|
6844
|
-
};
|
|
6845
|
-
}
|
|
6846
|
-
const unsafeChar = value.match(UNSAFE_CHARS);
|
|
6847
|
-
if (unsafeChar) {
|
|
6848
|
-
return {
|
|
6849
|
-
field: name,
|
|
6850
|
-
reason: `contains disallowed character '${unsafeChar[0]}'`
|
|
6851
|
-
};
|
|
6852
|
-
}
|
|
6853
|
-
if (CONTROL.test(value)) {
|
|
6854
|
-
return { field: name, reason: "contains a control character" };
|
|
6855
|
-
}
|
|
6856
|
-
const encoded = value.match(PRE_ENCODED);
|
|
6857
|
-
if (encoded) {
|
|
7333
|
+
async executeCommand(command) {
|
|
7334
|
+
const result = await bash.exec(command);
|
|
6858
7335
|
return {
|
|
6859
|
-
|
|
6860
|
-
|
|
7336
|
+
stdout: result.stdout,
|
|
7337
|
+
stderr: result.stderr,
|
|
7338
|
+
exitCode: result.exitCode
|
|
6861
7339
|
};
|
|
6862
|
-
}
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
}
|
|
6866
|
-
|
|
6867
|
-
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
|
|
6872
|
-
if (err instanceof Error) {
|
|
6873
|
-
const out = {
|
|
6874
|
-
name: err.name,
|
|
6875
|
-
message: err.message
|
|
6876
|
-
};
|
|
6877
|
-
for (const key of Object.getOwnPropertyNames(err)) {
|
|
6878
|
-
if (key === "stack") continue;
|
|
6879
|
-
const value = err[key];
|
|
6880
|
-
out[key] = key === "cause" ? serializeError(value) : value;
|
|
6881
|
-
}
|
|
6882
|
-
return out;
|
|
6883
|
-
}
|
|
6884
|
-
return err;
|
|
6885
|
-
}
|
|
6886
|
-
async function createOpenAPIExtension(options) {
|
|
6887
|
-
const { name, openapi, includeOperation, ...clientOptions } = options;
|
|
6888
|
-
const ir = toIR({
|
|
6889
|
-
spec: await loadSpec(openapi),
|
|
6890
|
-
responses: { flattenErrorResponses: true }
|
|
6891
|
-
});
|
|
6892
|
-
if ((ir.servers?.length ?? 0) === 0 && !clientOptions.baseUrl) {
|
|
6893
|
-
throw new Error(
|
|
6894
|
-
`createOpenAPIExtension("${name}"): openapi spec has no servers and no baseUrl was provided`
|
|
6895
|
-
);
|
|
6896
|
-
}
|
|
6897
|
-
const planned = [];
|
|
6898
|
-
const registered = /* @__PURE__ */ new Map();
|
|
6899
|
-
forEachOperation(ir, (entry, operation) => {
|
|
6900
|
-
if (includeOperation && !includeOperation(entry, operation)) return;
|
|
6901
|
-
const subName = operation["x-fn-name"] || operation.operationId;
|
|
6902
|
-
const endpoint = `${entry.method.toUpperCase()} ${entry.path}`;
|
|
6903
|
-
if (!subName) {
|
|
6904
|
-
throw new Error(
|
|
6905
|
-
`createOpenAPIExtension("${name}"): operation ${endpoint} has no x-fn-name or operationId`
|
|
6906
|
-
);
|
|
6907
|
-
}
|
|
6908
|
-
if (subName === RESERVED_SUBCOMMAND) {
|
|
6909
|
-
throw new Error(
|
|
6910
|
-
`createOpenAPIExtension("${name}"): operation name "${RESERVED_SUBCOMMAND}" is reserved for introspection`
|
|
6911
|
-
);
|
|
6912
|
-
}
|
|
6913
|
-
const existing = registered.get(subName);
|
|
6914
|
-
if (existing) {
|
|
6915
|
-
throw new Error(
|
|
6916
|
-
`createOpenAPIExtension("${name}"): duplicate subcommand "${subName}" (from ${existing} and ${endpoint})`
|
|
6917
|
-
);
|
|
6918
|
-
}
|
|
6919
|
-
registered.set(subName, endpoint);
|
|
6920
|
-
planned.push({
|
|
6921
|
-
subName,
|
|
6922
|
-
endpoint,
|
|
6923
|
-
description: operation.summary || operation.description || endpoint,
|
|
6924
|
-
summary: operation.summary,
|
|
6925
|
-
pathParamNames: extractPathParams(entry.path)
|
|
6926
|
-
});
|
|
6927
|
-
});
|
|
6928
|
-
if (planned.length === 0) {
|
|
6929
|
-
throw new Error(
|
|
6930
|
-
`createOpenAPIExtension("${name}"): no operations matched; spec has no operations or includeOperation filtered all of them out`
|
|
6931
|
-
);
|
|
6932
|
-
}
|
|
6933
|
-
const client = await rpc(openapi, clientOptions);
|
|
6934
|
-
const subcommands = {};
|
|
6935
|
-
const schemaEntries = [];
|
|
6936
|
-
for (const {
|
|
6937
|
-
subName,
|
|
6938
|
-
endpoint,
|
|
6939
|
-
description,
|
|
6940
|
-
summary,
|
|
6941
|
-
pathParamNames
|
|
6942
|
-
} of planned) {
|
|
6943
|
-
const schema = client.schemas[endpoint]?.schema;
|
|
6944
|
-
if (!schema) {
|
|
6945
|
-
throw new Error(
|
|
6946
|
-
`createOpenAPIExtension("${name}"): no input schema for ${endpoint}`
|
|
6947
|
-
);
|
|
6948
|
-
}
|
|
6949
|
-
schemaEntries.push({ endpoint, operationId: subName, summary });
|
|
6950
|
-
subcommands[subName] = {
|
|
6951
|
-
usage: `${subName} '<json>'`,
|
|
6952
|
-
description,
|
|
6953
|
-
repair: repairQuotedArg,
|
|
6954
|
-
handler: async (args, ctx) => {
|
|
6955
|
-
const raw = args.join(" ").trim();
|
|
6956
|
-
if (!raw) {
|
|
6957
|
-
return formatError({
|
|
6958
|
-
group: name,
|
|
6959
|
-
operation: subName,
|
|
6960
|
-
code: "missing_input",
|
|
6961
|
-
message: "no input provided"
|
|
6962
|
-
});
|
|
6963
|
-
}
|
|
6964
|
-
let input;
|
|
6965
|
-
try {
|
|
6966
|
-
input = JSON.parse(raw);
|
|
6967
|
-
} catch (error) {
|
|
6968
|
-
return formatError({
|
|
6969
|
-
group: name,
|
|
6970
|
-
operation: subName,
|
|
6971
|
-
code: "invalid_json",
|
|
6972
|
-
message: error instanceof Error ? error.message : String(error)
|
|
6973
|
-
});
|
|
6974
|
-
}
|
|
6975
|
-
const parsed = schema.safeParse(input);
|
|
6976
|
-
if (!parsed.success) {
|
|
6977
|
-
return formatError({
|
|
6978
|
-
group: name,
|
|
6979
|
-
operation: subName,
|
|
6980
|
-
code: "schema_validation",
|
|
6981
|
-
message: parsed.error.message,
|
|
6982
|
-
extra: { issues: parsed.error.issues }
|
|
6983
|
-
});
|
|
6984
|
-
}
|
|
6985
|
-
const unsafe = findUnsafePathParam(parsed.data, pathParamNames);
|
|
6986
|
-
if (unsafe) {
|
|
6987
|
-
return formatError({
|
|
6988
|
-
group: name,
|
|
6989
|
-
operation: subName,
|
|
6990
|
-
code: "path_param_unsafe",
|
|
6991
|
-
message: `path parameter '${unsafe.field}' ${unsafe.reason}`,
|
|
6992
|
-
extra: { field: unsafe.field }
|
|
6993
|
-
});
|
|
6994
|
-
}
|
|
6995
|
-
try {
|
|
6996
|
-
const response = await client.request(endpoint, parsed.data, {
|
|
6997
|
-
signal: ctx.signal
|
|
6998
|
-
});
|
|
6999
|
-
return formatResponse(response);
|
|
7000
|
-
} catch (error) {
|
|
7001
|
-
return formatError({
|
|
7002
|
-
group: name,
|
|
7003
|
-
operation: subName,
|
|
7004
|
-
code: "request_failed",
|
|
7005
|
-
message: error instanceof Error ? error.message : String(error),
|
|
7006
|
-
extra: { error: serializeError(error) }
|
|
7007
|
-
});
|
|
7008
|
-
}
|
|
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
|
+
);
|
|
7009
7350
|
}
|
|
7010
|
-
}
|
|
7011
|
-
}
|
|
7012
|
-
subcommands[RESERVED_SUBCOMMAND] = buildSchemaSubcommand(
|
|
7013
|
-
name,
|
|
7014
|
-
client,
|
|
7015
|
-
schemaEntries
|
|
7016
|
-
);
|
|
7017
|
-
const command = defineSubcommandGroup(name, subcommands);
|
|
7018
|
-
const repair = buildSubcommandRepair(name, subcommands);
|
|
7019
|
-
return {
|
|
7020
|
-
commands: [command],
|
|
7021
|
-
onBeforeBashCall: ({ command: raw }) => ({ command: repair(raw) }),
|
|
7022
|
-
client
|
|
7351
|
+
}
|
|
7023
7352
|
};
|
|
7024
7353
|
}
|
|
7025
7354
|
|
|
@@ -8718,14 +9047,16 @@ async function persistedWriter(options) {
|
|
|
8718
9047
|
};
|
|
8719
9048
|
}
|
|
8720
9049
|
|
|
9050
|
+
// packages/context/src/lib/stream/polling-change-source.ts
|
|
9051
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
9052
|
+
|
|
8721
9053
|
// packages/context/src/lib/stream/polling-policy.ts
|
|
8722
9054
|
var DEFAULT_WATCH_POLLING = {
|
|
8723
9055
|
minMs: 25,
|
|
8724
9056
|
maxMs: 500,
|
|
8725
9057
|
multiplier: 2,
|
|
8726
9058
|
jitterRatio: 0.15,
|
|
8727
|
-
statusCheckEvery: 3
|
|
8728
|
-
chunkPageSize: 128
|
|
9059
|
+
statusCheckEvery: 3
|
|
8729
9060
|
};
|
|
8730
9061
|
var DEFAULT_CANCEL_POLLING = {
|
|
8731
9062
|
minMs: 50,
|
|
@@ -8741,8 +9072,7 @@ function normalizeWatchPolling(polling, fallback = DEFAULT_WATCH_POLLING) {
|
|
|
8741
9072
|
const normalizedBase = normalizeAdaptivePolling(merged, fallback);
|
|
8742
9073
|
return {
|
|
8743
9074
|
...normalizedBase,
|
|
8744
|
-
statusCheckEvery: clampInt(merged.statusCheckEvery, 1, 1e4)
|
|
8745
|
-
chunkPageSize: clampInt(merged.chunkPageSize, 1, 1e4)
|
|
9075
|
+
statusCheckEvery: clampInt(merged.statusCheckEvery, 1, 1e4)
|
|
8746
9076
|
};
|
|
8747
9077
|
}
|
|
8748
9078
|
function normalizeCancelPolling(polling, fallback = DEFAULT_CANCEL_POLLING) {
|
|
@@ -8763,7 +9093,7 @@ function nextAdaptivePollingDelay(state) {
|
|
|
8763
9093
|
state.config.minMs,
|
|
8764
9094
|
state.config.maxMs
|
|
8765
9095
|
);
|
|
8766
|
-
const
|
|
9096
|
+
const delay2 = applyJitter(
|
|
8767
9097
|
current,
|
|
8768
9098
|
state.config.jitterRatio,
|
|
8769
9099
|
state.config.minMs,
|
|
@@ -8774,7 +9104,7 @@ function nextAdaptivePollingDelay(state) {
|
|
|
8774
9104
|
state.config.minMs,
|
|
8775
9105
|
state.config.maxMs
|
|
8776
9106
|
);
|
|
8777
|
-
return
|
|
9107
|
+
return delay2;
|
|
8778
9108
|
}
|
|
8779
9109
|
function normalizeAdaptivePolling(polling, fallback) {
|
|
8780
9110
|
const merged = {
|
|
@@ -8809,42 +9139,836 @@ function clampFloat(value, min, max) {
|
|
|
8809
9139
|
return Math.min(max, Math.max(min, value));
|
|
8810
9140
|
}
|
|
8811
9141
|
|
|
8812
|
-
// packages/context/src/lib/stream/
|
|
8813
|
-
|
|
8814
|
-
|
|
8815
|
-
|
|
8816
|
-
|
|
8817
|
-
|
|
8818
|
-
|
|
8819
|
-
|
|
8820
|
-
|
|
8821
|
-
|
|
8822
|
-
|
|
8823
|
-
|
|
8824
|
-
|
|
8825
|
-
|
|
8826
|
-
|
|
8827
|
-
|
|
8828
|
-
|
|
8829
|
-
|
|
8830
|
-
|
|
8831
|
-
let
|
|
8832
|
-
|
|
8833
|
-
|
|
8834
|
-
this.#
|
|
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" };
|
|
8835
9193
|
}
|
|
8836
|
-
return stmt;
|
|
8837
9194
|
}
|
|
8838
|
-
|
|
8839
|
-
if (this.#
|
|
8840
|
-
|
|
8841
|
-
|
|
8842
|
-
|
|
9195
|
+
#emit(event) {
|
|
9196
|
+
if (!this.#onPoll) return;
|
|
9197
|
+
try {
|
|
9198
|
+
this.#onPoll(event);
|
|
9199
|
+
} catch {
|
|
9200
|
+
}
|
|
8843
9201
|
}
|
|
8844
|
-
|
|
8845
|
-
|
|
8846
|
-
|
|
8847
|
-
|
|
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}`;
|
|
9849
|
+
}
|
|
9850
|
+
sql += " ORDER BY seq ASC";
|
|
9851
|
+
if (limit !== void 0) {
|
|
9852
|
+
params.push(limit);
|
|
9853
|
+
sql += ` LIMIT $${params.length}`;
|
|
9854
|
+
}
|
|
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
|
+
}
|
|
9905
|
+
};
|
|
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
|
+
];
|
|
9916
|
+
}
|
|
9917
|
+
function rowToStream(row) {
|
|
9918
|
+
return {
|
|
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
|
|
9926
|
+
};
|
|
9927
|
+
}
|
|
9928
|
+
function toNumber(value) {
|
|
9929
|
+
return typeof value === "number" ? value : Number(value);
|
|
9930
|
+
}
|
|
9931
|
+
function toNullableNumber(value) {
|
|
9932
|
+
return value == null ? null : toNumber(value);
|
|
9933
|
+
}
|
|
9934
|
+
function isTerminal2(status) {
|
|
9935
|
+
return status !== "queued" && status !== "running";
|
|
9936
|
+
}
|
|
9937
|
+
function assertIdentifier2(value, label) {
|
|
9938
|
+
if (!/^[a-zA-Z_]\w*$/.test(value)) {
|
|
9939
|
+
throw new Error(`Invalid ${label} name: "${value}"`);
|
|
9940
|
+
}
|
|
9941
|
+
}
|
|
9942
|
+
|
|
9943
|
+
// packages/context/src/lib/stream/sqlite.stream-store.ts
|
|
9944
|
+
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
9945
|
+
|
|
9946
|
+
// packages/context/src/lib/stream/ddl.stream.sqlite.sql
|
|
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";
|
|
9948
|
+
|
|
9949
|
+
// packages/context/src/lib/stream/sqlite.stream-store.ts
|
|
9950
|
+
var SqliteStreamStore = class extends StreamStore {
|
|
9951
|
+
#db;
|
|
9952
|
+
#statements = /* @__PURE__ */ new Map();
|
|
9953
|
+
#closed = false;
|
|
9954
|
+
#stmt(sql) {
|
|
9955
|
+
let stmt = this.#statements.get(sql);
|
|
9956
|
+
if (!stmt) {
|
|
9957
|
+
stmt = this.#db.prepare(sql);
|
|
9958
|
+
this.#statements.set(sql, stmt);
|
|
9959
|
+
}
|
|
9960
|
+
return stmt;
|
|
9961
|
+
}
|
|
9962
|
+
close() {
|
|
9963
|
+
if (this.#closed) return;
|
|
9964
|
+
this.#closed = true;
|
|
9965
|
+
this.#statements.clear();
|
|
9966
|
+
this.#db.close();
|
|
9967
|
+
}
|
|
9968
|
+
constructor(pathOrDb) {
|
|
9969
|
+
super();
|
|
9970
|
+
this.#db = typeof pathOrDb === "string" ? new DatabaseSync2(pathOrDb) : pathOrDb;
|
|
9971
|
+
this.#db.exec(ddl_stream_sqlite_default);
|
|
8848
9972
|
}
|
|
8849
9973
|
async createStream(stream) {
|
|
8850
9974
|
this.#stmt(
|
|
@@ -9048,26 +10172,20 @@ var SqliteStreamStore = class extends StreamStore {
|
|
|
9048
10172
|
|
|
9049
10173
|
// packages/context/src/lib/stream/stream-manager.ts
|
|
9050
10174
|
import { createUIMessageStream as createUIMessageStream3 } from "ai";
|
|
9051
|
-
|
|
9052
|
-
function isTerminal(status) {
|
|
10175
|
+
function isTerminal3(status) {
|
|
9053
10176
|
return status !== "queued" && status !== "running";
|
|
9054
10177
|
}
|
|
10178
|
+
var DEFAULT_CHUNK_PAGE_SIZE = 128;
|
|
9055
10179
|
var StreamManager = class {
|
|
9056
10180
|
#store;
|
|
9057
|
-
#
|
|
9058
|
-
#
|
|
9059
|
-
#
|
|
10181
|
+
#changeSource;
|
|
10182
|
+
#chunkPageSize;
|
|
10183
|
+
#onWatchEvent;
|
|
9060
10184
|
constructor(options) {
|
|
9061
10185
|
this.#store = options.store;
|
|
9062
|
-
this.#
|
|
9063
|
-
|
|
9064
|
-
|
|
9065
|
-
);
|
|
9066
|
-
this.#cancelPollingDefaults = normalizeCancelPolling(
|
|
9067
|
-
options.cancelPolling,
|
|
9068
|
-
DEFAULT_CANCEL_POLLING
|
|
9069
|
-
);
|
|
9070
|
-
this.#onPollingEvent = options.onPollingEvent;
|
|
10186
|
+
this.#changeSource = options.changeSource;
|
|
10187
|
+
this.#chunkPageSize = options.chunkPageSize ?? DEFAULT_CHUNK_PAGE_SIZE;
|
|
10188
|
+
this.#onWatchEvent = options.onWatchEvent;
|
|
9071
10189
|
}
|
|
9072
10190
|
get store() {
|
|
9073
10191
|
return this.#store;
|
|
@@ -9091,51 +10209,12 @@ var StreamManager = class {
|
|
|
9091
10209
|
}
|
|
9092
10210
|
async persist(stream, streamId, options) {
|
|
9093
10211
|
const existing = await this.#store.getStream(streamId);
|
|
9094
|
-
if (existing &&
|
|
10212
|
+
if (existing && isTerminal3(existing.status)) {
|
|
9095
10213
|
return { streamId };
|
|
9096
10214
|
}
|
|
9097
10215
|
await this.#store.updateStreamStatus(streamId, "running");
|
|
9098
10216
|
const ac = new AbortController();
|
|
9099
|
-
const
|
|
9100
|
-
options?.cancelPolling,
|
|
9101
|
-
this.#cancelPollingDefaults
|
|
9102
|
-
);
|
|
9103
|
-
const pollState = createAdaptivePollingState(cancelPolling);
|
|
9104
|
-
const pollCancel = (async () => {
|
|
9105
|
-
while (!ac.signal.aborted) {
|
|
9106
|
-
const delayMs = nextAdaptivePollingDelay(pollState);
|
|
9107
|
-
const continued = await waitForDelay(delayMs, ac.signal);
|
|
9108
|
-
if (!continued || ac.signal.aborted) break;
|
|
9109
|
-
const status = await this.#store.getStreamStatus(streamId);
|
|
9110
|
-
this.#emitPolling({
|
|
9111
|
-
type: "persist:cancel-poll",
|
|
9112
|
-
streamId,
|
|
9113
|
-
delayMs,
|
|
9114
|
-
status: status ?? "missing"
|
|
9115
|
-
});
|
|
9116
|
-
if (status === void 0) {
|
|
9117
|
-
ac.abort();
|
|
9118
|
-
break;
|
|
9119
|
-
}
|
|
9120
|
-
if (status === "cancelled") {
|
|
9121
|
-
const current = await this.#store.getStream(streamId);
|
|
9122
|
-
const latencyMs = current?.cancelRequestedAt != null ? Math.max(0, Date.now() - current.cancelRequestedAt) : null;
|
|
9123
|
-
this.#emitPolling({
|
|
9124
|
-
type: "persist:cancel-detected",
|
|
9125
|
-
streamId,
|
|
9126
|
-
latencyMs
|
|
9127
|
-
});
|
|
9128
|
-
if (options?.onCancelDetected) {
|
|
9129
|
-
try {
|
|
9130
|
-
await options.onCancelDetected({ streamId, latencyMs });
|
|
9131
|
-
} catch {
|
|
9132
|
-
}
|
|
9133
|
-
}
|
|
9134
|
-
ac.abort();
|
|
9135
|
-
break;
|
|
9136
|
-
}
|
|
9137
|
-
}
|
|
9138
|
-
})();
|
|
10217
|
+
const cancelWatcher = this.#runCancelWatcher(streamId, ac, options);
|
|
9139
10218
|
let pw;
|
|
9140
10219
|
const sink = createUIMessageStream3({
|
|
9141
10220
|
execute: async ({ writer }) => {
|
|
@@ -9176,142 +10255,131 @@ var StreamManager = class {
|
|
|
9176
10255
|
}
|
|
9177
10256
|
} finally {
|
|
9178
10257
|
if (!ac.signal.aborted) ac.abort();
|
|
9179
|
-
await
|
|
10258
|
+
await cancelWatcher;
|
|
9180
10259
|
}
|
|
9181
10260
|
return { streamId: pw?.streamId ?? streamId };
|
|
9182
10261
|
}
|
|
9183
|
-
|
|
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) {
|
|
9184
10296
|
const store2 = this.#store;
|
|
9185
|
-
const
|
|
9186
|
-
const
|
|
10297
|
+
const changeSource = this.#changeSource;
|
|
10298
|
+
const pageSize = this.#chunkPageSize;
|
|
10299
|
+
const emit = this.#emit.bind(this);
|
|
9187
10300
|
const ac = new AbortController();
|
|
9188
10301
|
const lastSeqRef = { value: -1 };
|
|
9189
|
-
let
|
|
9190
|
-
const
|
|
9191
|
-
|
|
9192
|
-
controller.enqueue(chunk.data);
|
|
9193
|
-
lastSeqRef.value = chunk.seq;
|
|
9194
|
-
}
|
|
9195
|
-
return chunks.length;
|
|
10302
|
+
let iterator;
|
|
10303
|
+
const emitPage = (delivered, lastSeq) => {
|
|
10304
|
+
emit({ type: "watch:chunks", streamId, delivered, lastSeq });
|
|
9196
10305
|
};
|
|
9197
|
-
|
|
9198
|
-
|
|
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") {
|
|
9199
10317
|
const stream = await store2.getStream(streamId);
|
|
9200
|
-
if (
|
|
9201
|
-
|
|
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 });
|
|
9202
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]();
|
|
9203
10335
|
},
|
|
9204
10336
|
pull: async (controller) => {
|
|
10337
|
+
if (!iterator) return;
|
|
9205
10338
|
while (!ac.signal.aborted) {
|
|
9206
|
-
|
|
9207
|
-
|
|
9208
|
-
|
|
9209
|
-
|
|
9210
|
-
|
|
9211
|
-
|
|
9212
|
-
|
|
9213
|
-
let currentStatus;
|
|
9214
|
-
if (chunks.length === 0) {
|
|
9215
|
-
chunkPollsSinceStatus = polling.statusCheckEvery;
|
|
9216
|
-
} else {
|
|
9217
|
-
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;
|
|
9218
10346
|
}
|
|
9219
|
-
if (
|
|
9220
|
-
|
|
9221
|
-
|
|
9222
|
-
currentStatus = await store2.getStreamStatus(streamId);
|
|
10347
|
+
if (result.done) {
|
|
10348
|
+
await finalize(controller, "source-ended");
|
|
10349
|
+
return;
|
|
9223
10350
|
}
|
|
9224
|
-
|
|
9225
|
-
|
|
9226
|
-
|
|
9227
|
-
|
|
9228
|
-
|
|
9229
|
-
|
|
9230
|
-
});
|
|
9231
|
-
if (chunks.length > 0) {
|
|
9232
|
-
const delivered = emitChunks(controller, chunks);
|
|
9233
|
-
this.#emitPolling({
|
|
9234
|
-
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,
|
|
9235
10357
|
streamId,
|
|
9236
|
-
|
|
9237
|
-
|
|
9238
|
-
|
|
9239
|
-
|
|
9240
|
-
if (chunks.length >= polling.chunkPageSize) {
|
|
9241
|
-
continue;
|
|
9242
|
-
}
|
|
9243
|
-
return;
|
|
10358
|
+
lastSeqRef,
|
|
10359
|
+
pageSize,
|
|
10360
|
+
emitPage
|
|
10361
|
+
);
|
|
9244
10362
|
}
|
|
9245
|
-
if (
|
|
9246
|
-
|
|
9247
|
-
|
|
9248
|
-
|
|
9249
|
-
streamId,
|
|
9250
|
-
reason: "missing"
|
|
9251
|
-
});
|
|
9252
|
-
controller.close();
|
|
9253
|
-
ac.abort();
|
|
10363
|
+
if (change.kind !== "chunks") {
|
|
10364
|
+
const status = await store2.getStreamStatus(streamId);
|
|
10365
|
+
if (status === void 0) {
|
|
10366
|
+
await finalize(controller, "missing");
|
|
9254
10367
|
return;
|
|
9255
10368
|
}
|
|
9256
|
-
if (
|
|
9257
|
-
|
|
9258
|
-
controller,
|
|
9259
|
-
store: store2,
|
|
9260
|
-
streamId,
|
|
9261
|
-
fromSeq: lastSeqRef.value + 1,
|
|
9262
|
-
chunkPageSize: polling.chunkPageSize,
|
|
9263
|
-
onChunk: (seq) => {
|
|
9264
|
-
lastSeqRef.value = seq;
|
|
9265
|
-
}
|
|
9266
|
-
});
|
|
9267
|
-
if (drained > 0) {
|
|
9268
|
-
this.#emitPolling({
|
|
9269
|
-
type: "watch:chunks",
|
|
9270
|
-
streamId,
|
|
9271
|
-
delivered: drained,
|
|
9272
|
-
lastSeq: lastSeqRef.value
|
|
9273
|
-
});
|
|
9274
|
-
}
|
|
9275
|
-
if (currentStatus === "failed") {
|
|
9276
|
-
const stream = await store2.getStream(streamId);
|
|
9277
|
-
if (stream) {
|
|
9278
|
-
const errorText = stream.error || "Stream failed";
|
|
9279
|
-
this.#emitPolling({
|
|
9280
|
-
type: "watch:error-emitted",
|
|
9281
|
-
streamId,
|
|
9282
|
-
errorTextLength: errorText.length
|
|
9283
|
-
});
|
|
9284
|
-
controller.enqueue({
|
|
9285
|
-
type: "error",
|
|
9286
|
-
errorText
|
|
9287
|
-
});
|
|
9288
|
-
}
|
|
9289
|
-
}
|
|
9290
|
-
this.#emitPolling({
|
|
9291
|
-
type: "watch:closed",
|
|
9292
|
-
streamId,
|
|
9293
|
-
reason: "terminal"
|
|
9294
|
-
});
|
|
9295
|
-
controller.close();
|
|
9296
|
-
ac.abort();
|
|
10369
|
+
if (isTerminal3(status)) {
|
|
10370
|
+
await finalize(controller, "terminal", status);
|
|
9297
10371
|
return;
|
|
9298
10372
|
}
|
|
9299
10373
|
}
|
|
9300
|
-
|
|
9301
|
-
this.#emitPolling({
|
|
9302
|
-
type: "watch:empty",
|
|
9303
|
-
streamId,
|
|
9304
|
-
fromSeq: lastSeqRef.value + 1,
|
|
9305
|
-
delayMs
|
|
9306
|
-
});
|
|
9307
|
-
const continued = await waitForDelay(delayMs, ac.signal);
|
|
9308
|
-
if (!continued) {
|
|
9309
|
-
return;
|
|
9310
|
-
}
|
|
10374
|
+
if (delivered > 0) return;
|
|
9311
10375
|
}
|
|
9312
10376
|
},
|
|
9313
|
-
cancel() {
|
|
10377
|
+
cancel: () => {
|
|
9314
10378
|
ac.abort();
|
|
10379
|
+
return iterator?.return?.().then(
|
|
10380
|
+
() => void 0,
|
|
10381
|
+
() => void 0
|
|
10382
|
+
);
|
|
9315
10383
|
}
|
|
9316
10384
|
});
|
|
9317
10385
|
}
|
|
@@ -9322,41 +10390,32 @@ var StreamManager = class {
|
|
|
9322
10390
|
async cleanup(streamId) {
|
|
9323
10391
|
await this.#store.deleteStream(streamId);
|
|
9324
10392
|
}
|
|
9325
|
-
#
|
|
9326
|
-
if (!this.#
|
|
10393
|
+
#emit(event) {
|
|
10394
|
+
if (!this.#onWatchEvent) return;
|
|
9327
10395
|
try {
|
|
9328
|
-
this.#
|
|
10396
|
+
this.#onWatchEvent(event);
|
|
9329
10397
|
} catch {
|
|
9330
10398
|
}
|
|
9331
10399
|
}
|
|
9332
10400
|
};
|
|
9333
|
-
async function
|
|
9334
|
-
|
|
9335
|
-
let fromSeq = options.fromSeq;
|
|
9336
|
-
let drained = 0;
|
|
10401
|
+
async function drainAvailable(controller, store2, streamId, lastSeqRef, pageSize, onPage) {
|
|
10402
|
+
let total = 0;
|
|
9337
10403
|
while (true) {
|
|
9338
|
-
const chunks = await store2.getChunks(
|
|
10404
|
+
const chunks = await store2.getChunks(
|
|
10405
|
+
streamId,
|
|
10406
|
+
lastSeqRef.value + 1,
|
|
10407
|
+
pageSize
|
|
10408
|
+
);
|
|
9339
10409
|
if (chunks.length === 0) break;
|
|
9340
10410
|
for (const chunk of chunks) {
|
|
9341
10411
|
controller.enqueue(chunk.data);
|
|
9342
|
-
|
|
9343
|
-
drained++;
|
|
9344
|
-
fromSeq = chunk.seq + 1;
|
|
9345
|
-
}
|
|
9346
|
-
if (chunks.length < chunkPageSize) {
|
|
9347
|
-
break;
|
|
10412
|
+
lastSeqRef.value = chunk.seq;
|
|
9348
10413
|
}
|
|
10414
|
+
total += chunks.length;
|
|
10415
|
+
onPage?.(chunks.length, lastSeqRef.value);
|
|
10416
|
+
if (chunks.length < pageSize) break;
|
|
9349
10417
|
}
|
|
9350
|
-
return
|
|
9351
|
-
}
|
|
9352
|
-
async function waitForDelay(ms, signal) {
|
|
9353
|
-
try {
|
|
9354
|
-
await setTimeout(ms, void 0, signal ? { signal } : void 0);
|
|
9355
|
-
return true;
|
|
9356
|
-
} catch (error) {
|
|
9357
|
-
if (isAbortError(error)) return false;
|
|
9358
|
-
throw error;
|
|
9359
|
-
}
|
|
10418
|
+
return total;
|
|
9360
10419
|
}
|
|
9361
10420
|
function isAbortError(error) {
|
|
9362
10421
|
return error instanceof Error && (error.name === "AbortError" || /aborted/i.test(error.message));
|
|
@@ -9437,9 +10496,9 @@ export {
|
|
|
9437
10496
|
AgentOsCreationError,
|
|
9438
10497
|
AgentOsNotAvailableError,
|
|
9439
10498
|
AgentOsSandboxError,
|
|
10499
|
+
AsyncResolver,
|
|
9440
10500
|
BM25Classifier,
|
|
9441
10501
|
BashException,
|
|
9442
|
-
BinaryInstallError,
|
|
9443
10502
|
ComposeStartError,
|
|
9444
10503
|
ComposeStrategy,
|
|
9445
10504
|
ContainerCreationError,
|
|
@@ -9453,15 +10512,29 @@ export {
|
|
|
9453
10512
|
DockerSandboxStrategy,
|
|
9454
10513
|
DockerfileBuildError,
|
|
9455
10514
|
DockerfileStrategy,
|
|
9456
|
-
|
|
10515
|
+
FragmentLoaderResolver,
|
|
10516
|
+
FunctionResolver,
|
|
10517
|
+
GeneratorResolver,
|
|
10518
|
+
GithubReleaseInstaller,
|
|
9457
10519
|
InMemoryContextStore,
|
|
10520
|
+
InstallError,
|
|
10521
|
+
Installer,
|
|
10522
|
+
IterableResolver,
|
|
9458
10523
|
LOCALE_METADATA_KEY,
|
|
9459
10524
|
MarkdownRenderer,
|
|
10525
|
+
MissingRuntimeError,
|
|
9460
10526
|
ModelsRegistry,
|
|
9461
10527
|
MountPathError,
|
|
10528
|
+
NpmInstaller,
|
|
9462
10529
|
ObservedFs,
|
|
9463
10530
|
PackageInstallError,
|
|
10531
|
+
PackageInstaller,
|
|
10532
|
+
PipInstaller,
|
|
10533
|
+
PollingChangeSource,
|
|
9464
10534
|
PostgresContextStore,
|
|
10535
|
+
PostgresNotifyChangeSource,
|
|
10536
|
+
PostgresStreamStore,
|
|
10537
|
+
PromiseResolver,
|
|
9465
10538
|
RuntimeStrategy,
|
|
9466
10539
|
SqlServerContextStore,
|
|
9467
10540
|
SqliteContextStore,
|
|
@@ -9471,6 +10544,7 @@ export {
|
|
|
9471
10544
|
TitleGenerator,
|
|
9472
10545
|
TomlRenderer,
|
|
9473
10546
|
ToonRenderer,
|
|
10547
|
+
UrlBinaryInstaller,
|
|
9474
10548
|
XmlRenderer,
|
|
9475
10549
|
addUsage,
|
|
9476
10550
|
advisorPreamble,
|
|
@@ -9485,14 +10559,13 @@ export {
|
|
|
9485
10559
|
applyInlineReminder,
|
|
9486
10560
|
applyPartReminder,
|
|
9487
10561
|
applyReminderToMessage,
|
|
10562
|
+
applyToolOutputRemindersToMessage,
|
|
9488
10563
|
asStaticWordPartText,
|
|
9489
10564
|
asStaticWordText,
|
|
9490
10565
|
assertCountSpec,
|
|
9491
10566
|
assistant,
|
|
9492
10567
|
assistantText,
|
|
9493
|
-
buildSchemaSubcommand,
|
|
9494
10568
|
buildSubcommandRepair,
|
|
9495
|
-
chainHooks,
|
|
9496
10569
|
chat,
|
|
9497
10570
|
checkCount,
|
|
9498
10571
|
clarification,
|
|
@@ -9507,15 +10580,16 @@ export {
|
|
|
9507
10580
|
createBinaryBridges,
|
|
9508
10581
|
createContainerTool,
|
|
9509
10582
|
createDockerSandbox,
|
|
9510
|
-
|
|
10583
|
+
createInstallerContext,
|
|
9511
10584
|
createRepairToolCall,
|
|
9512
|
-
createRoutingSandbox,
|
|
9513
10585
|
createVirtualSandbox,
|
|
9514
10586
|
dateReminder,
|
|
9515
10587
|
dayChanged,
|
|
9516
10588
|
defaultChatMessageMetadata,
|
|
10589
|
+
defaultResolvers,
|
|
9517
10590
|
defaultTokenizer,
|
|
9518
10591
|
defineSubcommandGroup,
|
|
10592
|
+
discoverSkillMappings,
|
|
9519
10593
|
discoverSkillsInDirectory,
|
|
9520
10594
|
elapsedExceeds,
|
|
9521
10595
|
encodeSerializedValue,
|
|
@@ -9526,12 +10600,9 @@ export {
|
|
|
9526
10600
|
example,
|
|
9527
10601
|
executorContext,
|
|
9528
10602
|
explain,
|
|
9529
|
-
extractPathParams,
|
|
9530
10603
|
fail,
|
|
9531
|
-
|
|
10604
|
+
findSingleOutputAvailableToolPart,
|
|
9532
10605
|
firstN,
|
|
9533
|
-
formatError,
|
|
9534
|
-
formatResponse,
|
|
9535
10606
|
fragment,
|
|
9536
10607
|
fromFragment,
|
|
9537
10608
|
getFragmentData,
|
|
@@ -9539,6 +10610,7 @@ export {
|
|
|
9539
10610
|
getModelsRegistry,
|
|
9540
10611
|
getReminderRanges,
|
|
9541
10612
|
getSeason,
|
|
10613
|
+
githubRelease,
|
|
9542
10614
|
glossary,
|
|
9543
10615
|
guardrail,
|
|
9544
10616
|
hint,
|
|
@@ -9546,17 +10618,16 @@ export {
|
|
|
9546
10618
|
identity,
|
|
9547
10619
|
isComposeOptions,
|
|
9548
10620
|
isConditionalReminder,
|
|
10621
|
+
isDebianBased,
|
|
9549
10622
|
isDockerfileOptions,
|
|
9550
10623
|
isFragment,
|
|
9551
10624
|
isFragmentObject,
|
|
9552
|
-
isInstallable,
|
|
9553
10625
|
isMessageFragment,
|
|
9554
10626
|
isRecord,
|
|
9555
10627
|
lastAssistantLength,
|
|
9556
10628
|
loadSkillMetadata,
|
|
9557
10629
|
localeReminder,
|
|
9558
10630
|
mapGenerateErrorToCode,
|
|
9559
|
-
mergeExtensions,
|
|
9560
10631
|
mergeMessageMetadata,
|
|
9561
10632
|
mergeReminderMetadata,
|
|
9562
10633
|
message,
|
|
@@ -9566,6 +10637,7 @@ export {
|
|
|
9566
10637
|
normalizeCancelPolling,
|
|
9567
10638
|
normalizeWatchPolling,
|
|
9568
10639
|
not,
|
|
10640
|
+
npm,
|
|
9569
10641
|
nullUsage,
|
|
9570
10642
|
once,
|
|
9571
10643
|
or,
|
|
@@ -9573,6 +10645,8 @@ export {
|
|
|
9573
10645
|
pass,
|
|
9574
10646
|
persistedWriter,
|
|
9575
10647
|
persona,
|
|
10648
|
+
pip,
|
|
10649
|
+
pkg,
|
|
9576
10650
|
policy,
|
|
9577
10651
|
preference,
|
|
9578
10652
|
principle,
|
|
@@ -9593,6 +10667,7 @@ export {
|
|
|
9593
10667
|
seasonChanged,
|
|
9594
10668
|
seasonReminder,
|
|
9595
10669
|
selfCritique,
|
|
10670
|
+
shellQuote,
|
|
9596
10671
|
skills,
|
|
9597
10672
|
skillsReminder,
|
|
9598
10673
|
socraticPrompting,
|
|
@@ -9613,6 +10688,7 @@ export {
|
|
|
9613
10688
|
toolCalled,
|
|
9614
10689
|
toolFailed,
|
|
9615
10690
|
uploadSkills,
|
|
10691
|
+
urlBinary,
|
|
9616
10692
|
usageExceeds,
|
|
9617
10693
|
useAgentOsSandbox,
|
|
9618
10694
|
useBashMeta,
|