@tangle-network/agent-app 0.44.23 → 0.44.25
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.
|
@@ -32,6 +32,75 @@ var fail = (error) => ({
|
|
|
32
32
|
error: error instanceof Error ? error : new Error(String(error))
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
+
// src/sandbox/model.ts
|
|
36
|
+
function resolveModelSelection(config, override) {
|
|
37
|
+
const c = config ?? {};
|
|
38
|
+
const explicitBaseUrl = trimOrNull(c.routerBaseUrl);
|
|
39
|
+
const explicitApiKey = trimOrNull(override?.modelApiKey) ?? trimOrNull(c.apiKey);
|
|
40
|
+
const providerName = trimOrNull(c.providerName);
|
|
41
|
+
const openaiApiKey = trimOrNull(c.openaiApiKey);
|
|
42
|
+
const provider = providerName ?? (explicitApiKey ? "openai-compat" : openaiApiKey ? "openai" : void 0);
|
|
43
|
+
const overrideModel = trimOrNull(override?.model);
|
|
44
|
+
const configModel = trimOrNull(c.modelName);
|
|
45
|
+
const defaultModel = trimOrNull(c.defaultModel);
|
|
46
|
+
const modelName = overrideModel ?? configModel ?? (provider === "openai" || provider === "openai-compat" ? defaultModel : null);
|
|
47
|
+
if (!modelName) return { succeeded: true, value: void 0 };
|
|
48
|
+
const source = overrideModel ? "override" : configModel ? "config" : "default";
|
|
49
|
+
if (!provider) return { succeeded: false, error: "no_provider", model: modelName, source };
|
|
50
|
+
const apiKey = explicitApiKey ?? (provider === "openai" ? openaiApiKey : void 0);
|
|
51
|
+
if (!apiKey && !c.allowKeylessModel) {
|
|
52
|
+
return { succeeded: false, error: "no_api_key", model: modelName, provider, source };
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
succeeded: true,
|
|
56
|
+
value: {
|
|
57
|
+
model: modelName,
|
|
58
|
+
provider,
|
|
59
|
+
...apiKey ? { apiKey } : {},
|
|
60
|
+
...explicitBaseUrl ? { baseUrl: explicitBaseUrl } : {}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function resolveModel(config, override) {
|
|
65
|
+
const selection = resolveModelSelection(config, override);
|
|
66
|
+
return selection.succeeded ? selection.value : void 0;
|
|
67
|
+
}
|
|
68
|
+
var SandboxModelResolutionError = class extends Error {
|
|
69
|
+
code;
|
|
70
|
+
model;
|
|
71
|
+
provider;
|
|
72
|
+
source;
|
|
73
|
+
constructor(failure, context) {
|
|
74
|
+
const missing = failure.error === "no_provider" ? "no provider could be resolved for it" : `provider "${failure.provider}" resolved but no API key is configured`;
|
|
75
|
+
const fix = failure.error === "no_provider" ? "set providerName explicitly (provider cannot be inferred without one)" : 'set apiKey (or openaiApiKey when provider is "openai"), or pass allowKeylessModel:true to mint a keyless box';
|
|
76
|
+
super(
|
|
77
|
+
`${context}: model "${failure.model}" (from ${failure.source}) is not transportable \u2014 ${missing}. Fix: ${fix}. The requested model was NOT sent to the box.`
|
|
78
|
+
);
|
|
79
|
+
this.name = "SandboxModelResolutionError";
|
|
80
|
+
this.code = failure.error;
|
|
81
|
+
this.model = failure.model;
|
|
82
|
+
if (failure.error === "no_api_key") this.provider = failure.provider;
|
|
83
|
+
this.source = failure.source;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
function requireTransportableModel(selection, context) {
|
|
87
|
+
if (selection.succeeded) return selection.value;
|
|
88
|
+
if (selection.source === "override") {
|
|
89
|
+
throw new SandboxModelResolutionError(selection, context);
|
|
90
|
+
}
|
|
91
|
+
const reason = selection.error === "no_api_key" ? `provider "${selection.provider}" has no api key` : "no provider resolved";
|
|
92
|
+
if (selection.source === "config") {
|
|
93
|
+
console.error(
|
|
94
|
+
`[sandbox] ${context}: dropping configured provider.modelName "${selection.model}" (${reason}); the box will use its own default model \u2014 set allowKeylessModel:true to bake a keyless model, or configure an api key`
|
|
95
|
+
);
|
|
96
|
+
} else {
|
|
97
|
+
console.error(
|
|
98
|
+
`[sandbox] ${context}: dropping provider.defaultModel "${selection.model}" (${reason}); using the box default`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
return void 0;
|
|
102
|
+
}
|
|
103
|
+
|
|
35
104
|
// src/sandbox/binary-read.ts
|
|
36
105
|
function shellQuote(value) {
|
|
37
106
|
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
@@ -722,6 +791,21 @@ var SandboxRuntimeAuthRefreshError = class extends Error {
|
|
|
722
791
|
this.name = "SandboxRuntimeAuthRefreshError";
|
|
723
792
|
}
|
|
724
793
|
};
|
|
794
|
+
var SandboxRecoveryFailedError = class extends Error {
|
|
795
|
+
boxKey;
|
|
796
|
+
stage;
|
|
797
|
+
phase;
|
|
798
|
+
constructor(stage, boxKey, phase, detail, cause) {
|
|
799
|
+
super(
|
|
800
|
+
`${stage} sandbox ${boxKey} failed liveness recovery at ${phase}: ${detail}. The workspace is preserved; pass forceNew to replace the box.`,
|
|
801
|
+
{ cause }
|
|
802
|
+
);
|
|
803
|
+
this.name = "SandboxRecoveryFailedError";
|
|
804
|
+
this.boxKey = boxKey;
|
|
805
|
+
this.stage = stage;
|
|
806
|
+
this.phase = phase;
|
|
807
|
+
}
|
|
808
|
+
};
|
|
725
809
|
function fileApiTarget(mount) {
|
|
726
810
|
if (mount.resource.kind !== "inline") return null;
|
|
727
811
|
let rel;
|
|
@@ -1081,6 +1165,39 @@ async function resumeStoppedBox(box, timeoutMs, onProgress) {
|
|
|
1081
1165
|
return fail(err);
|
|
1082
1166
|
}
|
|
1083
1167
|
}
|
|
1168
|
+
async function recoverUnresponsiveBox(client, box, harness, probe, stage, name, resumeTimeout, onProgress) {
|
|
1169
|
+
try {
|
|
1170
|
+
await box.stop();
|
|
1171
|
+
} catch (err) {
|
|
1172
|
+
throw new SandboxRecoveryFailedError(
|
|
1173
|
+
stage,
|
|
1174
|
+
name,
|
|
1175
|
+
"stop",
|
|
1176
|
+
"the platform could not stop the box for a state-preserving restart",
|
|
1177
|
+
err
|
|
1178
|
+
);
|
|
1179
|
+
}
|
|
1180
|
+
const resumed = await resumeStoppedBox(box, resumeTimeout, onProgress);
|
|
1181
|
+
if (!resumed.succeeded) {
|
|
1182
|
+
throw new SandboxRecoveryFailedError(
|
|
1183
|
+
stage,
|
|
1184
|
+
name,
|
|
1185
|
+
"resume",
|
|
1186
|
+
"the box did not reach running after a state-preserving restart",
|
|
1187
|
+
resumed.error
|
|
1188
|
+
);
|
|
1189
|
+
}
|
|
1190
|
+
const recovered = await refreshRuntimeConnection(client, resumed.value);
|
|
1191
|
+
if (!await isReusableBox(recovered, harness, probe)) {
|
|
1192
|
+
throw new SandboxRecoveryFailedError(
|
|
1193
|
+
stage,
|
|
1194
|
+
name,
|
|
1195
|
+
"probe",
|
|
1196
|
+
"the box is still unresponsive after a state-preserving restart"
|
|
1197
|
+
);
|
|
1198
|
+
}
|
|
1199
|
+
return recovered;
|
|
1200
|
+
}
|
|
1084
1201
|
async function resolveWorkspaceSandboxClient(shell, workspaceId, userId) {
|
|
1085
1202
|
const scope = { workspaceId, ...userId ? { userId } : {} };
|
|
1086
1203
|
const creds = await shell.credentials(scope);
|
|
@@ -1100,6 +1217,29 @@ async function peekWorkspaceSandbox(shell, options) {
|
|
|
1100
1217
|
if (match.status !== "running") return { status: "not-running", state: match.status, box: match };
|
|
1101
1218
|
return { status: "running", box: match };
|
|
1102
1219
|
}
|
|
1220
|
+
async function finalizeExistingBox(shell, client, box, stage, name, workspaceId, userId, harness, scope) {
|
|
1221
|
+
const written = await materializeDeferredFilesForExistingBox(
|
|
1222
|
+
shell,
|
|
1223
|
+
client,
|
|
1224
|
+
box,
|
|
1225
|
+
stage,
|
|
1226
|
+
name,
|
|
1227
|
+
workspaceId,
|
|
1228
|
+
userId,
|
|
1229
|
+
harness
|
|
1230
|
+
);
|
|
1231
|
+
if (!written.succeeded) {
|
|
1232
|
+
throw deferredProfileWriteFailed(stage, name, written.error);
|
|
1233
|
+
}
|
|
1234
|
+
const finalBox = written.value;
|
|
1235
|
+
if (shell.bootstrap) {
|
|
1236
|
+
const boot = await shell.bootstrap(finalBox, scope);
|
|
1237
|
+
if (!boot.succeeded) {
|
|
1238
|
+
throw new Error(`bootstrap failed on ${stage} box ${name}`, { cause: boot.error });
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
return finalBox;
|
|
1242
|
+
}
|
|
1103
1243
|
async function ensureWorkspaceSandbox(shell, options) {
|
|
1104
1244
|
const { workspaceId, userId, harness, forceNew, onProgress, billingOwnerId } = options;
|
|
1105
1245
|
const resolved = await resolveWorkspaceSandboxClient(shell, workspaceId, userId);
|
|
@@ -1119,40 +1259,24 @@ async function ensureWorkspaceSandbox(shell, options) {
|
|
|
1119
1259
|
} else if (found.metadata?.harness === harness) {
|
|
1120
1260
|
const ready = await refreshRuntimeConnection(client, found);
|
|
1121
1261
|
if (await isReusableBox(ready, harness, shell.livenessProbe)) {
|
|
1122
|
-
|
|
1123
|
-
shell,
|
|
1124
|
-
client,
|
|
1125
|
-
ready,
|
|
1126
|
-
"reused",
|
|
1127
|
-
name,
|
|
1128
|
-
workspaceId,
|
|
1129
|
-
userId,
|
|
1130
|
-
harness
|
|
1131
|
-
);
|
|
1132
|
-
if (!written.succeeded) {
|
|
1133
|
-
throw deferredProfileWriteFailed("reused", name, written.error);
|
|
1134
|
-
}
|
|
1135
|
-
const reusedBox = written.value;
|
|
1136
|
-
if (shell.bootstrap) {
|
|
1137
|
-
const boot = await shell.bootstrap(reusedBox, scope);
|
|
1138
|
-
if (!boot.succeeded) {
|
|
1139
|
-
throw new Error(`bootstrap failed on reused box ${name}`, { cause: boot.error });
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
1142
|
-
return reusedBox;
|
|
1143
|
-
}
|
|
1144
|
-
const dropped = await deleteBox(ready);
|
|
1145
|
-
if (!dropped.succeeded) {
|
|
1146
|
-
throw new Error(
|
|
1147
|
-
`sandbox ${name} (was ${String(found.metadata?.harness ?? "unknown")}, want ${harness}, or unresponsive) could not be deleted`,
|
|
1148
|
-
{ cause: dropped.error }
|
|
1149
|
-
);
|
|
1262
|
+
return finalizeExistingBox(shell, client, ready, "reused", name, workspaceId, userId, harness, scope);
|
|
1150
1263
|
}
|
|
1264
|
+
const recovered = await recoverUnresponsiveBox(
|
|
1265
|
+
client,
|
|
1266
|
+
ready,
|
|
1267
|
+
harness,
|
|
1268
|
+
shell.livenessProbe,
|
|
1269
|
+
"reused",
|
|
1270
|
+
name,
|
|
1271
|
+
resumeTimeout,
|
|
1272
|
+
onProgress
|
|
1273
|
+
);
|
|
1274
|
+
return finalizeExistingBox(shell, client, recovered, "reused", name, workspaceId, userId, harness, scope);
|
|
1151
1275
|
} else {
|
|
1152
1276
|
const dropped = await deleteBox(found);
|
|
1153
1277
|
if (!dropped.succeeded) {
|
|
1154
1278
|
throw new Error(
|
|
1155
|
-
`sandbox ${name} (was ${String(found.metadata?.harness ?? "unknown")}, want ${harness}
|
|
1279
|
+
`sandbox ${name} (was ${String(found.metadata?.harness ?? "unknown")}, want ${harness}) could not be deleted`,
|
|
1156
1280
|
{ cause: dropped.error }
|
|
1157
1281
|
);
|
|
1158
1282
|
}
|
|
@@ -1185,35 +1309,19 @@ async function ensureWorkspaceSandbox(shell, options) {
|
|
|
1185
1309
|
} else {
|
|
1186
1310
|
const box2 = await refreshRuntimeConnection(client, resumed.value);
|
|
1187
1311
|
if (await isReusableBox(box2, harness, shell.livenessProbe)) {
|
|
1188
|
-
|
|
1189
|
-
shell,
|
|
1190
|
-
client,
|
|
1191
|
-
box2,
|
|
1192
|
-
"resumed",
|
|
1193
|
-
name,
|
|
1194
|
-
workspaceId,
|
|
1195
|
-
userId,
|
|
1196
|
-
harness
|
|
1197
|
-
);
|
|
1198
|
-
if (!written.succeeded) {
|
|
1199
|
-
throw deferredProfileWriteFailed("resumed", name, written.error);
|
|
1200
|
-
}
|
|
1201
|
-
const resumedBox = written.value;
|
|
1202
|
-
if (shell.bootstrap) {
|
|
1203
|
-
const boot = await shell.bootstrap(resumedBox, scope);
|
|
1204
|
-
if (!boot.succeeded) {
|
|
1205
|
-
throw new Error(`bootstrap failed on resumed box ${name}`, { cause: boot.error });
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
return resumedBox;
|
|
1209
|
-
}
|
|
1210
|
-
const dropped = await deleteBox(box2);
|
|
1211
|
-
if (!dropped.succeeded) {
|
|
1212
|
-
throw new Error(
|
|
1213
|
-
`resumed sandbox ${name} (was ${String(box2.metadata?.harness ?? "unknown")}, want ${harness}, or unresponsive) could not be deleted`,
|
|
1214
|
-
{ cause: dropped.error }
|
|
1215
|
-
);
|
|
1312
|
+
return finalizeExistingBox(shell, client, box2, "resumed", name, workspaceId, userId, harness, scope);
|
|
1216
1313
|
}
|
|
1314
|
+
const recovered = await recoverUnresponsiveBox(
|
|
1315
|
+
client,
|
|
1316
|
+
box2,
|
|
1317
|
+
harness,
|
|
1318
|
+
shell.livenessProbe,
|
|
1319
|
+
"resumed",
|
|
1320
|
+
name,
|
|
1321
|
+
resumeTimeout,
|
|
1322
|
+
onProgress
|
|
1323
|
+
);
|
|
1324
|
+
return finalizeExistingBox(shell, client, recovered, "resumed", name, workspaceId, userId, harness, scope);
|
|
1217
1325
|
}
|
|
1218
1326
|
}
|
|
1219
1327
|
}
|
|
@@ -1232,7 +1340,7 @@ async function ensureWorkspaceSandbox(shell, options) {
|
|
|
1232
1340
|
const { leanProfile, deferredFiles } = shell.deferProfileFiles ? splitDeferredProfileFiles(fullProfile) : { leanProfile: fullProfile, deferredFiles: [] };
|
|
1233
1341
|
const profile = leanProfile;
|
|
1234
1342
|
const role = userId && shell.permissionRole ? shell.permissionRole("developer") : void 0;
|
|
1235
|
-
let model = shell.backendModelAtCreate ?
|
|
1343
|
+
let model = shell.backendModelAtCreate ? requireTransportableModel(resolveModelSelection(shell.provider), `backendModelAtCreate for ${name}`) : void 0;
|
|
1236
1344
|
if (model && shell.childKeyMint && model.provider === "openai-compat") {
|
|
1237
1345
|
const minted = await shell.childKeyMint(scope);
|
|
1238
1346
|
if (minted.succeeded) model = { ...model, apiKey: minted.value };
|
|
@@ -1298,22 +1406,6 @@ async function ensureWorkspaceSandbox(shell, options) {
|
|
|
1298
1406
|
}
|
|
1299
1407
|
return box;
|
|
1300
1408
|
}
|
|
1301
|
-
function resolveModel(config, override) {
|
|
1302
|
-
const c = config ?? {};
|
|
1303
|
-
const explicitBaseUrl = c.routerBaseUrl;
|
|
1304
|
-
const explicitApiKey = override?.modelApiKey ?? c.apiKey;
|
|
1305
|
-
const provider = c.providerName ?? (explicitApiKey ? "openai-compat" : c.openaiApiKey ? "openai" : void 0);
|
|
1306
|
-
const modelName = override?.model ?? c.modelName ?? (provider === "openai" || provider === "openai-compat" ? c.defaultModel : void 0);
|
|
1307
|
-
const apiKey = explicitApiKey ?? (provider === "openai" ? c.openaiApiKey : void 0);
|
|
1308
|
-
if (!provider || !modelName) return void 0;
|
|
1309
|
-
if (!apiKey && !c.allowKeylessModel) return void 0;
|
|
1310
|
-
return {
|
|
1311
|
-
model: modelName,
|
|
1312
|
-
provider,
|
|
1313
|
-
...apiKey ? { apiKey } : {},
|
|
1314
|
-
...explicitBaseUrl ? { baseUrl: explicitBaseUrl } : {}
|
|
1315
|
-
};
|
|
1316
|
-
}
|
|
1317
1409
|
function historyTranscript(history) {
|
|
1318
1410
|
return history.map((entry) => `${entry.role === "assistant" ? "Assistant" : "User"}: ${entry.content}`).join("\n\n");
|
|
1319
1411
|
}
|
|
@@ -1359,10 +1451,13 @@ function attachReasoningEffort(profile, harness, effort) {
|
|
|
1359
1451
|
}
|
|
1360
1452
|
async function* streamSandboxPrompt(shell, box, message, options) {
|
|
1361
1453
|
const harness = options?.harness ?? "opencode";
|
|
1362
|
-
const model =
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1454
|
+
const model = requireTransportableModel(
|
|
1455
|
+
resolveModelSelection(shell.provider, {
|
|
1456
|
+
model: options?.model,
|
|
1457
|
+
modelApiKey: options?.modelApiKey
|
|
1458
|
+
}),
|
|
1459
|
+
"streamSandboxPrompt"
|
|
1460
|
+
);
|
|
1366
1461
|
if (model?.model) assertHarnessModelCompatible(harness, model.model);
|
|
1367
1462
|
const prompt = typeof message === "string" ? flattenHistory(message, options?.history) : mergeHistoryIntoParts(message, options?.history);
|
|
1368
1463
|
const appToolMcp = options?.appToolMcp ?? {};
|
|
@@ -1535,10 +1630,13 @@ async function mintSandboxScopedToken(box, options) {
|
|
|
1535
1630
|
}
|
|
1536
1631
|
async function driveSandboxTurn(shell, box, message, options) {
|
|
1537
1632
|
const harness = options.harness ?? "opencode";
|
|
1538
|
-
const model =
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1633
|
+
const model = requireTransportableModel(
|
|
1634
|
+
resolveModelSelection(shell.provider, {
|
|
1635
|
+
model: options.model,
|
|
1636
|
+
modelApiKey: options.modelApiKey
|
|
1637
|
+
}),
|
|
1638
|
+
"driveSandboxTurn"
|
|
1639
|
+
);
|
|
1542
1640
|
if (model?.model) assertHarnessModelCompatible(harness, model.model);
|
|
1543
1641
|
const prompt = typeof message === "string" ? flattenHistory(message, options.history) : mergeHistoryIntoParts(message, options.history);
|
|
1544
1642
|
const appToolMcp = options.appToolMcp ?? {};
|
|
@@ -1614,6 +1712,10 @@ function firstQuestionText(value) {
|
|
|
1614
1712
|
}
|
|
1615
1713
|
|
|
1616
1714
|
export {
|
|
1715
|
+
resolveModelSelection,
|
|
1716
|
+
resolveModel,
|
|
1717
|
+
SandboxModelResolutionError,
|
|
1718
|
+
requireTransportableModel,
|
|
1617
1719
|
shellQuote,
|
|
1618
1720
|
statSandboxFileSize,
|
|
1619
1721
|
readSandboxBinaryBytes,
|
|
@@ -1645,6 +1747,7 @@ export {
|
|
|
1645
1747
|
runSandboxToolPathSetup,
|
|
1646
1748
|
splitDeferredProfileFiles,
|
|
1647
1749
|
SandboxRuntimeAuthRefreshError,
|
|
1750
|
+
SandboxRecoveryFailedError,
|
|
1648
1751
|
writeProfileFilesToBox,
|
|
1649
1752
|
deferredCorpusHash,
|
|
1650
1753
|
PROVISION_PAYLOAD_MAX_BYTES,
|
|
@@ -1654,7 +1757,6 @@ export {
|
|
|
1654
1757
|
assertEnvWithinLimits,
|
|
1655
1758
|
peekWorkspaceSandbox,
|
|
1656
1759
|
ensureWorkspaceSandbox,
|
|
1657
|
-
resolveModel,
|
|
1658
1760
|
flattenHistory,
|
|
1659
1761
|
mergeHistoryIntoParts,
|
|
1660
1762
|
mergeExtraMcp,
|
|
@@ -1675,4 +1777,4 @@ export {
|
|
|
1675
1777
|
isTerminalPromptEvent,
|
|
1676
1778
|
detectInteractiveQuestion
|
|
1677
1779
|
};
|
|
1678
|
-
//# sourceMappingURL=chunk-
|
|
1780
|
+
//# sourceMappingURL=chunk-Q74BS43G.js.map
|