@qilin-agent/tools 3.1.5 → 3.1.6
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.i18n.yaml +2 -2
- package/README.md +1 -1
- package/README.zh.md +1 -1
- package/lib/index.js +373 -275
- package/lib/types/index.d.ts +2 -1
- package/lib/types/index.js +5 -3
- package/lib/types/presentation.d.ts +3 -3
- package/lib/types/ptc-calls.d.ts +24 -0
- package/lib/types/ptc-calls.js +62 -0
- package/lib/types/ptc.d.ts +5 -3
- package/lib/types/ptc.js +363 -342
- package/lib/types/py-types.d.ts +1 -1
- package/lib/types/py-types.js +3 -3
- package/lib/types/ts-types.d.ts +2 -1
- package/lib/types/ts-types.js +26 -9
- package/locale/en.json +5 -0
- package/locale/zh.json +6 -0
- package/package.json +30 -28
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Service } from "@qilin-agent/kylin";
|
|
1
|
+
import { Service, symbols } from "@qilin-agent/kylin";
|
|
2
2
|
import z from "@qilin-agent/schemastery";
|
|
3
3
|
import { AnonymousEntries, NamedEntries, ScopedLayers, scopeOf, scopeTarget } from "@qilin-agent/scope";
|
|
4
4
|
import { HarnessError, createUserMessage } from "@qilin-agent/llm";
|
|
@@ -884,6 +884,63 @@ function defineTool(options) {
|
|
|
884
884
|
return tool;
|
|
885
885
|
}
|
|
886
886
|
//#endregion
|
|
887
|
+
//#region lib/types/ptc-calls.js
|
|
888
|
+
/** Keeps calls under a changed policy from overlapping their canceled predecessors. */
|
|
889
|
+
var PtcCalls = class {
|
|
890
|
+
calls = /* @__PURE__ */ new Map();
|
|
891
|
+
disposed = false;
|
|
892
|
+
/**
|
|
893
|
+
* Observe policy changes for the owning tool registry.
|
|
894
|
+
* @param ctx - Registry lifecycle and Session events.
|
|
895
|
+
*/
|
|
896
|
+
constructor(ctx) {
|
|
897
|
+
ctx.on("session/event", (session, event) => {
|
|
898
|
+
if (event.type !== "sandbox/mode") return;
|
|
899
|
+
for (const call of this.calls.get(session.id) ?? []) if (call.mode !== event.data.mode) call.controller.abort(/* @__PURE__ */ new Error("sandbox mode changed"));
|
|
900
|
+
}, { global: true });
|
|
901
|
+
ctx.effect(() => async () => {
|
|
902
|
+
this.disposed = true;
|
|
903
|
+
const calls = [...this.calls.values()].flatMap((calls) => [...calls]);
|
|
904
|
+
for (const call of calls) call.controller.abort(/* @__PURE__ */ new Error("PTC tool registry disposed"));
|
|
905
|
+
await Promise.all(calls.map((call) => call.done));
|
|
906
|
+
}, "PTC calls");
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
909
|
+
* Await canceled predecessors before resolving authority and admitting a call.
|
|
910
|
+
* @param sessionId - Calling Session.
|
|
911
|
+
* @param signal - Caller cancellation.
|
|
912
|
+
* @param mode - Current standing mode, read after preceding cleanup.
|
|
913
|
+
* @param run - Complete call, including nested-tool cleanup.
|
|
914
|
+
* @returns The call result.
|
|
915
|
+
*/
|
|
916
|
+
async run(sessionId, signal, mode, run) {
|
|
917
|
+
for (;;) {
|
|
918
|
+
const pending = [...this.calls.get(sessionId) ?? []].filter((call) => call.controller.signal.aborted);
|
|
919
|
+
if (pending.length === 0) break;
|
|
920
|
+
await Promise.all(pending.map((call) => call.done));
|
|
921
|
+
}
|
|
922
|
+
signal.throwIfAborted();
|
|
923
|
+
if (this.disposed) throw new Error("PTC tool registry disposed");
|
|
924
|
+
const controller = new AbortController();
|
|
925
|
+
const done = Promise.withResolvers();
|
|
926
|
+
const call = {
|
|
927
|
+
mode: mode(),
|
|
928
|
+
controller,
|
|
929
|
+
done: done.promise
|
|
930
|
+
};
|
|
931
|
+
let calls = this.calls.get(sessionId);
|
|
932
|
+
if (calls === void 0) this.calls.set(sessionId, calls = /* @__PURE__ */ new Set());
|
|
933
|
+
calls.add(call);
|
|
934
|
+
try {
|
|
935
|
+
return await run(AbortSignal.any([signal, controller.signal]));
|
|
936
|
+
} finally {
|
|
937
|
+
calls.delete(call);
|
|
938
|
+
if (calls.size === 0) this.calls.delete(sessionId);
|
|
939
|
+
done.resolve(void 0);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
};
|
|
943
|
+
//#endregion
|
|
887
944
|
//#region lib/types/ptc.js
|
|
888
945
|
/**
|
|
889
946
|
* PTC mode `run_code` transport. Programs call the registry's agent-visible
|
|
@@ -894,23 +951,27 @@ function defineTool(options) {
|
|
|
894
951
|
*/
|
|
895
952
|
/** The model-facing name of the PTC mode tool. */
|
|
896
953
|
const RUN_CODE_NAME = "run_code";
|
|
897
|
-
/**
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
954
|
+
/** Keep tool-call rules here; the runtime owns evaluation syntax and state retention. */
|
|
955
|
+
function runCodeFlavor(language, execution = {
|
|
956
|
+
programDescription: "the program to execute",
|
|
957
|
+
outputInstruction: "Curate the program output.",
|
|
958
|
+
codeDescription: `The ${language} program to execute, following this tool's execution instructions.`
|
|
959
|
+
}) {
|
|
960
|
+
return {
|
|
961
|
+
description: `Execute a ${language} program against the available tools. Takes two required arguments: \`description\`, a short summary of what the program does, and \`code\`, ${execution.programDescription}. Call tools as \`await tools.name(args)\` per the declarations in the system prompt. ${execution.outputInstruction} Image-bearing subtool results are attached after the run.`,
|
|
962
|
+
codeDescription: execution.codeDescription
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
const TYPESCRIPT_FLAVOR = runCodeFlavor("TypeScript");
|
|
966
|
+
const TYPESCRIPT_FUNCTION_FLAVOR = runCodeFlavor("TypeScript", {
|
|
967
|
+
programDescription: "the BODY of an async function (erasable syntax only; top-level `await` and `return` work)",
|
|
968
|
+
outputInstruction: "Only what you print or return is program output — curate it.",
|
|
905
969
|
codeDescription: "The program: the body of an async TypeScript function."
|
|
906
|
-
};
|
|
970
|
+
});
|
|
907
971
|
/** Per-language `run_code` schema flavors (see {@link RunCodeFlavor}); one entry per {@link PtcSdkLanguage}. */
|
|
908
972
|
const RUN_CODE_FLAVORS = {
|
|
909
973
|
typescript: TYPESCRIPT_FLAVOR,
|
|
910
|
-
python:
|
|
911
|
-
description: "Execute a Python program against the available tools. Takes two required arguments: `description`, a short summary of what the program does, and `code`, the BODY of an async function (top-level `await` and `return` work). Call tools as `await tools.name(args)` per the declarations in the system prompt. Use `print(...)` and/or `return <value>` for program output — curate it. Image-bearing subtool results are attached after the run.",
|
|
912
|
-
codeDescription: "The program: the body of an async Python function."
|
|
913
|
-
}
|
|
974
|
+
python: runCodeFlavor("Python")
|
|
914
975
|
};
|
|
915
976
|
/**
|
|
916
977
|
* The `description` parameter's model-facing description: language-independent
|
|
@@ -972,7 +1033,7 @@ function resolveFlavor(peekRuntime) {
|
|
|
972
1033
|
const known = Object.keys(RUN_CODE_FLAVORS).map((name) => JSON.stringify(name)).join(", ");
|
|
973
1034
|
throw new Error(`qilin-tools: no run_code schema flavor registered for runtime language ${JSON.stringify(runtime.language)} (known: ${known})`);
|
|
974
1035
|
}
|
|
975
|
-
return flavor;
|
|
1036
|
+
return runtime.language === "typescript" && runtime.programFormat === "function-body" ? TYPESCRIPT_FUNCTION_FLAVOR : flavor;
|
|
976
1037
|
}
|
|
977
1038
|
/**
|
|
978
1039
|
* Thrown by `run_code` when the program run itself failed — a program
|
|
@@ -1104,18 +1165,21 @@ function renderValue(value) {
|
|
|
1104
1165
|
return typeof value === "string" ? value : renderJsonValue(value);
|
|
1105
1166
|
}
|
|
1106
1167
|
/**
|
|
1107
|
-
* Build the `run_code` {@link ToolDefinition}: required `
|
|
1108
|
-
* `
|
|
1168
|
+
* Build the `run_code` {@link ToolDefinition}: required `code` and
|
|
1169
|
+
* `description` parameters, executed through the dispatch bridge described
|
|
1109
1170
|
* above. The
|
|
1110
1171
|
* registry reserves it as presentation infrastructure under non-native modes,
|
|
1111
1172
|
* outside the filterable global/scoped capability layers.
|
|
1112
1173
|
* @param registry - the owning registry (sub-calls go through its `execute`,
|
|
1113
1174
|
* bindings cover its registered tools).
|
|
1114
1175
|
* @param options - the registry-private capabilities described above.
|
|
1176
|
+
* @param ctx - Owning registry context, independent of the calling Agent.
|
|
1115
1177
|
* @returns the registry-ready definition.
|
|
1116
1178
|
*/
|
|
1117
|
-
function createRunCodeTool(registry, options) {
|
|
1179
|
+
function createRunCodeTool(registry, options, ctx) {
|
|
1118
1180
|
const { requireRuntime, peekRuntime, maxParallel, shapeDispatchLog } = options;
|
|
1181
|
+
const stateOwners = /* @__PURE__ */ new WeakMap();
|
|
1182
|
+
const calls = new PtcCalls(ctx);
|
|
1119
1183
|
const definition = defineTool({
|
|
1120
1184
|
name: RUN_CODE_NAME,
|
|
1121
1185
|
description: TYPESCRIPT_FLAVOR.description,
|
|
@@ -1182,265 +1246,287 @@ function createRunCodeTool(registry, options) {
|
|
|
1182
1246
|
async execute(args, exec) {
|
|
1183
1247
|
if (args.description.trim().length === 0) throw new Error("invalid description: expected a non-empty string");
|
|
1184
1248
|
const runtime = requireRuntime();
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
if (
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1249
|
+
const run = async (callSignal) => {
|
|
1250
|
+
validateEscalationArgs(args.sandbox_permissions, args.justification);
|
|
1251
|
+
if (args.timeoutMs !== void 0 && runtime.timeout === void 0) throw new Error("timeoutMs is not available for this PTC runtime");
|
|
1252
|
+
if (args.timeoutMs !== void 0 && (!Number.isFinite(args.timeoutMs) || args.timeoutMs <= 0)) throw new Error("invalid timeoutMs: expected a positive finite number");
|
|
1253
|
+
const standingPolicy = runtime.sandboxMode === void 0 ? void 0 : options.resolveSandboxPolicy(exec);
|
|
1254
|
+
let policy = standingPolicy;
|
|
1255
|
+
if (args.sandbox_permissions !== void 0 && args.justification !== void 0) {
|
|
1256
|
+
if (standingPolicy === void 0) throw new Error("sandbox_permissions is not available for this PTC runtime");
|
|
1257
|
+
const approvedMode = await approveEscalation({
|
|
1258
|
+
requestedMode: args.sandbox_permissions,
|
|
1259
|
+
justification: args.justification,
|
|
1260
|
+
effectiveMode: standingPolicy.mode,
|
|
1261
|
+
subject: "program"
|
|
1262
|
+
}, {
|
|
1263
|
+
approver: options.peekApprover(),
|
|
1264
|
+
agent: exec.agent,
|
|
1265
|
+
callId: exec.callId,
|
|
1266
|
+
toolName: RUN_CODE_NAME,
|
|
1267
|
+
signal: callSignal
|
|
1268
|
+
});
|
|
1269
|
+
policy = {
|
|
1270
|
+
...standingPolicy,
|
|
1271
|
+
mode: approvedMode
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
const cwd = await options.resolveWorkingDirectory({
|
|
1275
|
+
...exec,
|
|
1276
|
+
signal: callSignal
|
|
1203
1277
|
});
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1278
|
+
callSignal.throwIfAborted();
|
|
1279
|
+
const runController = new AbortController();
|
|
1280
|
+
const onOuterAbort = () => {
|
|
1281
|
+
runController.abort(callSignal.reason);
|
|
1207
1282
|
};
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
wake = resolve;
|
|
1246
|
-
});
|
|
1247
|
-
const commitHead = commitQueue[0];
|
|
1248
|
-
if (commitHead !== void 0 && commitHead.settled) {
|
|
1249
|
-
commitQueue.shift();
|
|
1250
|
-
await commitHead.commit();
|
|
1251
|
-
if (commitHead.mode === "exclusive") exclusiveActive = false;
|
|
1252
|
-
continue;
|
|
1253
|
-
}
|
|
1254
|
-
const head = pendingQueue[0];
|
|
1255
|
-
if (head !== void 0) {
|
|
1256
|
-
if (runController.signal.aborted) {
|
|
1257
|
-
pendingQueue.shift();
|
|
1258
|
-
head.abandon();
|
|
1283
|
+
callSignal.addEventListener("abort", onOuterAbort, { once: true });
|
|
1284
|
+
let dispatches = 0;
|
|
1285
|
+
const pendingQueue = [];
|
|
1286
|
+
const inFlight = /* @__PURE__ */ new Set();
|
|
1287
|
+
/** Tracked settle-event side work (log-content listener + append), drained at run settlement. */
|
|
1288
|
+
const logWork = /* @__PURE__ */ new Set();
|
|
1289
|
+
const commitQueue = [];
|
|
1290
|
+
let exclusiveActive = false;
|
|
1291
|
+
let driving = false;
|
|
1292
|
+
let driverRun = Promise.resolve();
|
|
1293
|
+
let wake;
|
|
1294
|
+
const wakeup = () => {
|
|
1295
|
+
const release = wake;
|
|
1296
|
+
wake = void 0;
|
|
1297
|
+
release?.();
|
|
1298
|
+
};
|
|
1299
|
+
/**
|
|
1300
|
+
* The single ordered lane. Each pass commits the head-of-line settled
|
|
1301
|
+
* dispatch (ordered post-execute), then starts the next queued entry if
|
|
1302
|
+
* its slot is free (ordered pre-execute), and otherwise sleeps until a
|
|
1303
|
+
* body settles or a new submission arrives. One run reaching the
|
|
1304
|
+
* empty-queues/empty-pool state is quiescence.
|
|
1305
|
+
*/
|
|
1306
|
+
const drive = () => {
|
|
1307
|
+
if (driving) return driverRun;
|
|
1308
|
+
driving = true;
|
|
1309
|
+
driverRun = (async () => {
|
|
1310
|
+
try {
|
|
1311
|
+
for (;;) {
|
|
1312
|
+
const signal = new Promise((resolve) => {
|
|
1313
|
+
wake = resolve;
|
|
1314
|
+
});
|
|
1315
|
+
const commitHead = commitQueue[0];
|
|
1316
|
+
if (commitHead !== void 0 && commitHead.settled) {
|
|
1317
|
+
commitQueue.shift();
|
|
1318
|
+
await commitHead.commit();
|
|
1319
|
+
if (commitHead.mode === "exclusive") exclusiveActive = false;
|
|
1259
1320
|
continue;
|
|
1260
1321
|
}
|
|
1261
|
-
const
|
|
1262
|
-
if (
|
|
1263
|
-
if (
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
const
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1322
|
+
const head = pendingQueue[0];
|
|
1323
|
+
if (head !== void 0) {
|
|
1324
|
+
if (runController.signal.aborted) {
|
|
1325
|
+
pendingQueue.shift();
|
|
1326
|
+
head.abandon();
|
|
1327
|
+
continue;
|
|
1328
|
+
}
|
|
1329
|
+
const mode = head.classify();
|
|
1330
|
+
if (!exclusiveActive && (mode === "exclusive" ? inFlight.size === 0 : inFlight.size < maxParallel)) {
|
|
1331
|
+
if (mode === "exclusive") exclusiveActive = true;
|
|
1332
|
+
head.mode = mode;
|
|
1333
|
+
pendingQueue.shift();
|
|
1334
|
+
commitQueue.push(head);
|
|
1335
|
+
await head.start();
|
|
1336
|
+
const flight = head.flight.finally(() => {
|
|
1337
|
+
inFlight.delete(flight);
|
|
1338
|
+
wakeup();
|
|
1339
|
+
});
|
|
1340
|
+
inFlight.add(flight);
|
|
1341
|
+
continue;
|
|
1342
|
+
}
|
|
1274
1343
|
}
|
|
1344
|
+
if (pendingQueue.length === 0 && commitQueue.length === 0 && inFlight.size === 0) return;
|
|
1345
|
+
await signal;
|
|
1275
1346
|
}
|
|
1276
|
-
|
|
1277
|
-
|
|
1347
|
+
} finally {
|
|
1348
|
+
driving = false;
|
|
1349
|
+
wake = void 0;
|
|
1278
1350
|
}
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1281
|
-
wake = void 0;
|
|
1282
|
-
}
|
|
1283
|
-
})();
|
|
1284
|
-
return driverRun;
|
|
1285
|
-
};
|
|
1286
|
-
/** Every dispatch settled AND committed; nothing can start (the run is aborted at call time). */
|
|
1287
|
-
const drainDispatches = async () => {
|
|
1288
|
-
await drive();
|
|
1289
|
-
while (logWork.size > 0) await Promise.allSettled([...logWork]);
|
|
1290
|
-
};
|
|
1291
|
-
const runOver = () => runController.signal.aborted;
|
|
1292
|
-
const binding = (schema) => async (rawArgs) => {
|
|
1293
|
-
const { name } = schema;
|
|
1294
|
-
if (runOver()) throw new Error(`run_code run is over (${String(runController.signal.reason)}); ${name} not dispatched`);
|
|
1295
|
-
const normalized = jsonNormalizeArgs(rawArgs);
|
|
1296
|
-
const n = ++dispatches;
|
|
1297
|
-
const subCallId = brandString(`${String(exec.callId)}:ptc:${n}`);
|
|
1298
|
-
const input = {
|
|
1299
|
-
callId: subCallId,
|
|
1300
|
-
rootCallId: exec.rootCallId,
|
|
1301
|
-
name,
|
|
1302
|
-
schema,
|
|
1303
|
-
arguments: normalized.dispatched,
|
|
1304
|
-
...exec.agent ? { agent: exec.agent } : {},
|
|
1305
|
-
parent: exec.token,
|
|
1306
|
-
signal: runController.signal
|
|
1351
|
+
})();
|
|
1352
|
+
return driverRun;
|
|
1307
1353
|
};
|
|
1308
|
-
|
|
1309
|
-
const
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
});
|
|
1330
|
-
agent.session.append("tool/ptc-dispatch", {
|
|
1331
|
-
rootCallId: exec.rootCallId,
|
|
1332
|
-
parentCallId: exec.callId,
|
|
1333
|
-
subCallId,
|
|
1334
|
-
name,
|
|
1335
|
-
arguments: normalized.logged,
|
|
1336
|
-
isError: result.isError,
|
|
1337
|
-
...result.error?.info === void 0 ? {} : { error: result.error.info },
|
|
1338
|
-
content: logged,
|
|
1339
|
-
...result.meta === void 0 ? {} : { meta: result.meta }
|
|
1340
|
-
});
|
|
1341
|
-
})().finally(() => {
|
|
1342
|
-
logWork.delete(task);
|
|
1343
|
-
});
|
|
1344
|
-
logWork.add(task);
|
|
1354
|
+
/** Every dispatch settled AND committed; nothing can start (the run is aborted at call time). */
|
|
1355
|
+
const drainDispatches = async () => {
|
|
1356
|
+
await drive();
|
|
1357
|
+
while (logWork.size > 0) await Promise.allSettled([...logWork]);
|
|
1358
|
+
};
|
|
1359
|
+
const runOver = () => runController.signal.aborted;
|
|
1360
|
+
const binding = (schema) => async (rawArgs) => {
|
|
1361
|
+
const { name } = schema;
|
|
1362
|
+
if (runOver()) throw new Error(`run_code run is over (${String(runController.signal.reason)}); ${name} not dispatched`);
|
|
1363
|
+
const normalized = jsonNormalizeArgs(rawArgs);
|
|
1364
|
+
const n = ++dispatches;
|
|
1365
|
+
const subCallId = brandString(`${String(exec.callId)}:ptc:${n}`);
|
|
1366
|
+
const input = {
|
|
1367
|
+
callId: subCallId,
|
|
1368
|
+
rootCallId: exec.rootCallId,
|
|
1369
|
+
name,
|
|
1370
|
+
schema,
|
|
1371
|
+
arguments: normalized.dispatched,
|
|
1372
|
+
...exec.agent ? { agent: exec.agent } : {},
|
|
1373
|
+
parent: exec.token,
|
|
1374
|
+
signal: runController.signal
|
|
1345
1375
|
};
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1376
|
+
const scheduler = registry[TOOL_RUNTIME_SCHEDULER];
|
|
1377
|
+
const outcome = await new Promise((resolve, reject) => {
|
|
1378
|
+
let parked;
|
|
1379
|
+
const settle = (result) => {
|
|
1380
|
+
resolve(result.isError ? {
|
|
1381
|
+
isError: true,
|
|
1382
|
+
message: result.error.message
|
|
1383
|
+
} : {
|
|
1384
|
+
isError: false,
|
|
1385
|
+
value: result.value
|
|
1386
|
+
});
|
|
1387
|
+
const agent = exec.agent;
|
|
1388
|
+
if (agent === void 0) return;
|
|
1389
|
+
const task = (async () => {
|
|
1390
|
+
const logged = await shapeDispatchLog({
|
|
1391
|
+
exec,
|
|
1392
|
+
agent,
|
|
1393
|
+
subCallId,
|
|
1394
|
+
name,
|
|
1395
|
+
isError: result.isError,
|
|
1396
|
+
content: result.content
|
|
1397
|
+
});
|
|
1398
|
+
agent.session.append("tool/ptc-dispatch", {
|
|
1399
|
+
rootCallId: exec.rootCallId,
|
|
1400
|
+
parentCallId: exec.callId,
|
|
1401
|
+
subCallId,
|
|
1402
|
+
name,
|
|
1403
|
+
arguments: normalized.logged,
|
|
1404
|
+
isError: result.isError,
|
|
1405
|
+
...result.error?.info === void 0 ? {} : { error: result.error.info },
|
|
1406
|
+
content: logged,
|
|
1407
|
+
...result.meta === void 0 ? {} : { meta: result.meta }
|
|
1408
|
+
});
|
|
1409
|
+
})().finally(() => {
|
|
1410
|
+
logWork.delete(task);
|
|
1360
1411
|
});
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1412
|
+
logWork.add(task);
|
|
1413
|
+
};
|
|
1414
|
+
pendingQueue.push({
|
|
1415
|
+
flight: Promise.resolve(),
|
|
1416
|
+
settled: false,
|
|
1417
|
+
classify: () => registry.executionMode(input).kind,
|
|
1418
|
+
abandon: () => {
|
|
1419
|
+
reject(/* @__PURE__ */ new Error(`run_code run is over (${String(runController.signal.reason)}); ${name} tool call abandoned`));
|
|
1420
|
+
},
|
|
1421
|
+
async start() {
|
|
1422
|
+
exec.agent?.session.append("tool/ptc-dispatch-start", {
|
|
1423
|
+
rootCallId: exec.rootCallId,
|
|
1424
|
+
parentCallId: exec.callId,
|
|
1425
|
+
subCallId,
|
|
1426
|
+
name,
|
|
1427
|
+
arguments: normalized.logged
|
|
1370
1428
|
});
|
|
1371
|
-
|
|
1429
|
+
const prepared = await scheduler.prepare(input);
|
|
1430
|
+
if (prepared.kind === "dispatch") {
|
|
1431
|
+
this.flight = scheduler.dispatch(prepared.exec).then((dispatchOutcome) => {
|
|
1432
|
+
parked = {
|
|
1433
|
+
kind: dispatchOutcome.kind,
|
|
1434
|
+
exec: prepared.exec,
|
|
1435
|
+
result: dispatchOutcome.result
|
|
1436
|
+
};
|
|
1437
|
+
this.settled = true;
|
|
1438
|
+
});
|
|
1439
|
+
return;
|
|
1440
|
+
}
|
|
1441
|
+
parked = {
|
|
1442
|
+
kind: prepared.kind,
|
|
1443
|
+
exec: prepared.exec,
|
|
1444
|
+
result: prepared.result
|
|
1445
|
+
};
|
|
1446
|
+
this.settled = true;
|
|
1447
|
+
},
|
|
1448
|
+
async commit() {
|
|
1449
|
+
/* v8 ignore next -- commit() runs only after `settled` flipped, which set parked. */
|
|
1450
|
+
if (parked === void 0) return;
|
|
1451
|
+
const result = parked.kind === "post-result" ? await scheduler.finalize(parked.exec, parked.result) : scheduler.finish(parked.exec, parked.result);
|
|
1452
|
+
if (!result.isError && result.content.some((block) => block.type === "image")) exec.deferContext(createUserMessage({
|
|
1453
|
+
content: result.content,
|
|
1454
|
+
source: { kind: "ptc-mode" }
|
|
1455
|
+
}));
|
|
1456
|
+
for (const context of result.additionalContexts ?? []) exec.deferContext(context);
|
|
1457
|
+
if (result.concludesTurn) exec.concludeTurn();
|
|
1458
|
+
settle(result);
|
|
1459
|
+
while (logWork.size > maxParallel) await Promise.race(logWork);
|
|
1372
1460
|
}
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
result: prepared.result
|
|
1377
|
-
};
|
|
1378
|
-
this.settled = true;
|
|
1379
|
-
},
|
|
1380
|
-
async commit() {
|
|
1381
|
-
/* v8 ignore next -- commit() runs only after `settled` flipped, which set parked. */
|
|
1382
|
-
if (parked === void 0) return;
|
|
1383
|
-
const result = parked.kind === "post-result" ? await scheduler.finalize(parked.exec, parked.result) : scheduler.finish(parked.exec, parked.result);
|
|
1384
|
-
if (!result.isError && result.content.some((block) => block.type === "image")) exec.deferContext(createUserMessage({
|
|
1385
|
-
content: result.content,
|
|
1386
|
-
source: { kind: "ptc-mode" }
|
|
1387
|
-
}));
|
|
1388
|
-
for (const context of result.additionalContexts ?? []) exec.deferContext(context);
|
|
1389
|
-
if (result.concludesTurn) exec.concludeTurn();
|
|
1390
|
-
settle(result);
|
|
1391
|
-
while (logWork.size > maxParallel) await Promise.race(logWork);
|
|
1392
|
-
}
|
|
1461
|
+
});
|
|
1462
|
+
wakeup();
|
|
1463
|
+
drive();
|
|
1393
1464
|
});
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
value: binding(deepFreeze(schema))
|
|
1407
|
-
});
|
|
1408
|
-
}
|
|
1409
|
-
try {
|
|
1410
|
-
let result;
|
|
1465
|
+
if (runOver()) throw new Error(`run_code run is over (${String(runController.signal.reason)}); ${name} result discarded`);
|
|
1466
|
+
if (outcome.isError) throw new Error(outcome.message);
|
|
1467
|
+
return outcome.value;
|
|
1468
|
+
};
|
|
1469
|
+
const functions = Object.create(null);
|
|
1470
|
+
for (const schema of registry.schemas(exec.agent)) {
|
|
1471
|
+
if (schema.name === "run_code") continue;
|
|
1472
|
+
Object.defineProperty(functions, schema.name, {
|
|
1473
|
+
enumerable: true,
|
|
1474
|
+
value: binding(deepFreeze(schema))
|
|
1475
|
+
});
|
|
1476
|
+
}
|
|
1411
1477
|
try {
|
|
1412
|
-
result
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1478
|
+
let result;
|
|
1479
|
+
try {
|
|
1480
|
+
if (exec.agent !== void 0) {
|
|
1481
|
+
const agent = exec.agent;
|
|
1482
|
+
let runtimes = stateOwners.get(agent);
|
|
1483
|
+
if (runtimes === void 0) stateOwners.set(agent, runtimes = /* @__PURE__ */ new WeakSet());
|
|
1484
|
+
let provider = runtime;
|
|
1485
|
+
for (;;) {
|
|
1486
|
+
const original = provider[symbols.original];
|
|
1487
|
+
if (original === void 0) break;
|
|
1488
|
+
provider = original;
|
|
1420
1489
|
}
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1490
|
+
if (!runtimes.has(provider)) {
|
|
1491
|
+
agent.ctx.effect(() => () => runtime.release(agent), "PTC retained environment");
|
|
1492
|
+
runtimes.add(provider);
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
result = await runtime.run(runtime.resolve({
|
|
1496
|
+
program: args.code,
|
|
1497
|
+
...exec.agent === void 0 || policy?.mode !== standingPolicy?.mode ? {} : { stateOwner: exec.agent },
|
|
1498
|
+
bindings: [{
|
|
1499
|
+
global: "tools",
|
|
1500
|
+
functions,
|
|
1501
|
+
errorClass: {
|
|
1502
|
+
name: "ToolCallError",
|
|
1503
|
+
memberNameProperty: "toolName"
|
|
1504
|
+
}
|
|
1505
|
+
}],
|
|
1506
|
+
signal: runController.signal,
|
|
1507
|
+
...cwd !== void 0 ? { cwd } : {},
|
|
1508
|
+
...policy !== void 0 ? { sandboxPolicy: policy } : {},
|
|
1509
|
+
...args.timeoutMs !== void 0 ? { timeoutMs: args.timeoutMs } : {}
|
|
1510
|
+
}));
|
|
1511
|
+
} finally {
|
|
1512
|
+
runController.abort("run_code settled");
|
|
1513
|
+
await drainDispatches();
|
|
1514
|
+
}
|
|
1515
|
+
if (result.error) {
|
|
1516
|
+
const logsText = result.logs.length > 0 ? `\nCaptured output:\n${result.logs.join("\n")}` : "";
|
|
1517
|
+
const sandboxText = result.sandbox === void 0 ? "" : `\nFile sandbox: ${result.sandbox.mode}${result.sandbox.enforcement === void 0 ? "" : `; enforcement: ${result.sandbox.enforcement}`}${result.sandbox.denied ? "; operation denied" : ""}.`;
|
|
1518
|
+
throw new CodeRunFailedError(`code run failed (${result.error.kind}): ${result.error.message}${logsText}${sandboxText}${result.sandbox?.denied ? escalationGuidance(runtime) : ""}`);
|
|
1519
|
+
}
|
|
1520
|
+
return {
|
|
1521
|
+
logs: result.logs,
|
|
1522
|
+
...result.sandbox === void 0 ? {} : { sandbox: result.sandbox },
|
|
1523
|
+
...result.value !== void 0 ? { result: result.value } : {}
|
|
1524
|
+
};
|
|
1427
1525
|
} finally {
|
|
1428
|
-
|
|
1429
|
-
await drainDispatches();
|
|
1430
|
-
}
|
|
1431
|
-
if (result.error) {
|
|
1432
|
-
const logsText = result.logs.length > 0 ? `\nCaptured output:\n${result.logs.join("\n")}` : "";
|
|
1433
|
-
const sandboxText = result.sandbox === void 0 ? "" : `\nFile sandbox: ${result.sandbox.mode}${result.sandbox.enforcement === void 0 ? "" : `; enforcement: ${result.sandbox.enforcement}`}${result.sandbox.denied ? "; operation denied" : ""}.`;
|
|
1434
|
-
throw new CodeRunFailedError(`code run failed (${result.error.kind}): ${result.error.message}${logsText}${sandboxText}${result.sandbox?.denied ? escalationGuidance(runtime) : ""}`);
|
|
1526
|
+
callSignal.removeEventListener("abort", onOuterAbort);
|
|
1435
1527
|
}
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
...result.sandbox === void 0 ? {} : { sandbox: result.sandbox },
|
|
1439
|
-
...result.value !== void 0 ? { result: result.value } : {}
|
|
1440
|
-
};
|
|
1441
|
-
} finally {
|
|
1442
|
-
exec.signal.removeEventListener("abort", onOuterAbort);
|
|
1443
|
-
}
|
|
1528
|
+
};
|
|
1529
|
+
return runtime.sandboxMode === void 0 || exec.agent === void 0 ? run(exec.signal) : calls.run(exec.agent.session.id, exec.signal, () => options.resolveSandboxPolicy(exec).mode, run);
|
|
1444
1530
|
},
|
|
1445
1531
|
presentCall: (args) => ({
|
|
1446
1532
|
card: "generic",
|
|
@@ -1685,24 +1771,29 @@ function jsonSchemaToTs(schema, indent = 0) {
|
|
|
1685
1771
|
return "unknown";
|
|
1686
1772
|
}
|
|
1687
1773
|
}
|
|
1688
|
-
/**
|
|
1689
|
-
|
|
1774
|
+
/** Render the argument and call instructions for the selected execution syntax. */
|
|
1775
|
+
function renderSdkInstructions(programFormat) {
|
|
1776
|
+
return `## Writing code for run_code
|
|
1690
1777
|
|
|
1691
|
-
\`run_code\` takes two required arguments: \`description\`, a short summary of what the program does, and \`code
|
|
1692
|
-
|
|
1778
|
+
\`run_code\` takes two required arguments: \`description\`, a short summary of what the program does, and \`code\`, ${programFormat === "function-body" ? "the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped)" : "the program to execute"}. The declarations below are SDK bindings for this program. A declaration does not make its name a directly callable tool; only names supplied as separate tool schemas may be called directly.`;
|
|
1779
|
+
}
|
|
1780
|
+
/** Render tool execution and output instructions for the selected execution syntax. */
|
|
1781
|
+
function renderSdkProgramInstructions(programFormat) {
|
|
1782
|
+
return `Inside the program:
|
|
1693
1783
|
|
|
1694
1784
|
- Call tools as \`await tools.name(args)\` — quoted access for exotic names: \`tools["my-tool"](args)\`. Every call resolves to the tool's typed canonical JSON value. Tool arguments must be lossless JSON.
|
|
1695
1785
|
- A FAILED tool call rejects with \`ToolCallError\`, whose \`toolName\` identifies the failed tool and whose \`message\` is human-readable — \`try/catch\` it to handle and continue.
|
|
1696
1786
|
- Independent read-only calls MAY overlap under \`Promise.all\` (safe calls run concurrently; mutating calls run alone, in submission order). Sequence dependent work with \`await\`.
|
|
1697
|
-
- Emit results with
|
|
1787
|
+
- ${programFormat === "function-body" ? "Emit results with `return` and/or `console.log(...)`. Only what you print or return is program output." : "Only values emitted as program output enter the conversation."} A successful tool result containing an image is attached after the run so you can inspect it on the next step; every other intermediate result stays out of the conversation, so extract just what you need.
|
|
1698
1788
|
|
|
1699
1789
|
Program-only SDK bindings:`;
|
|
1790
|
+
}
|
|
1700
1791
|
/** Whether one string schema accepts the literal used by the bash example. */
|
|
1701
1792
|
function acceptsExampleString(schema, value) {
|
|
1702
1793
|
return schema?.type === "string" && (schema.const === void 0 || schema.const === value) && (schema.enum === void 0 || schema.enum.includes(value));
|
|
1703
1794
|
}
|
|
1704
1795
|
/** Render the bash example only when its literal arguments satisfy the current parameter schema. */
|
|
1705
|
-
function renderBashExample(schemas) {
|
|
1796
|
+
function renderBashExample(schemas, programFormat) {
|
|
1706
1797
|
const bash = schemas.find((schema) => schema.name === "bash");
|
|
1707
1798
|
if (bash === void 0) return "";
|
|
1708
1799
|
const parameters = bash.parameters;
|
|
@@ -1712,7 +1803,8 @@ function renderBashExample(schemas) {
|
|
|
1712
1803
|
if (!acceptsExampleString(parameters.properties?.command, "pwd")) return "";
|
|
1713
1804
|
const needsDescription = required.includes("description");
|
|
1714
1805
|
if (needsDescription && !acceptsExampleString(parameters.properties?.description, "Show current directory")) return "";
|
|
1715
|
-
|
|
1806
|
+
const description = needsDescription ? "description: 'Show current directory', " : "";
|
|
1807
|
+
return ` When no separate \`bash\` schema is supplied, invoke a declared \`bash\` binding inside \`run_code\`:\n\n\`run_code({ description: "Show current directory", code: "${programFormat === "function-body" ? `return await tools.bash({ ${description}command: 'pwd' })` : `console.log(await tools.bash({ ${description}command: 'pwd' }))`}" })\``;
|
|
1716
1808
|
}
|
|
1717
1809
|
/**
|
|
1718
1810
|
* Render the full `tools:sdk` prompt section: the fixed usage instructions
|
|
@@ -1724,9 +1816,10 @@ function renderBashExample(schemas) {
|
|
|
1724
1816
|
* by name, so the input never carries a duplicate.
|
|
1725
1817
|
* @param schemas - the tool schemas to declare (the caller excludes
|
|
1726
1818
|
* `run_code` itself).
|
|
1819
|
+
* @param programFormat - Runtime syntax; function bodies support returning a result, REPLs print it.
|
|
1727
1820
|
* @returns the complete section text.
|
|
1728
1821
|
*/
|
|
1729
|
-
function renderToolsSdk(schemas) {
|
|
1822
|
+
function renderToolsSdk(schemas, programFormat = "function-body") {
|
|
1730
1823
|
const sorted = [...schemas].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
1731
1824
|
const argsMembers = [];
|
|
1732
1825
|
const outputMembers = [];
|
|
@@ -1751,7 +1844,10 @@ function renderToolsSdk(schemas) {
|
|
|
1751
1844
|
"}"
|
|
1752
1845
|
].join("\n")
|
|
1753
1846
|
].join("\n\n");
|
|
1754
|
-
|
|
1847
|
+
const jsonValue = "type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue }";
|
|
1848
|
+
const instructions = renderSdkInstructions(programFormat);
|
|
1849
|
+
const programInstructions = renderSdkProgramInstructions(programFormat);
|
|
1850
|
+
return `${instructions}${renderBashExample(sorted, programFormat)}\n\n${programInstructions}\n\n\`\`\`ts\n${jsonValue}\n\n${declaration}\n\`\`\``;
|
|
1755
1851
|
}
|
|
1756
1852
|
//#endregion
|
|
1757
1853
|
//#region lib/types/py-types.js
|
|
@@ -1764,7 +1860,7 @@ function renderToolsSdk(schemas) {
|
|
|
1764
1860
|
*
|
|
1765
1861
|
* Under `mode: 'ptc'` the native tool schemas are omitted from the request, so this generated
|
|
1766
1862
|
* SDK is the model's ONLY source for each tool's argument names, required fields, types,
|
|
1767
|
-
* descriptions, and canonical output
|
|
1863
|
+
* descriptions, and canonical output types. Object-shaped arguments and outputs render as one
|
|
1768
1864
|
* named `TypedDict` per tool (and per nested object), not an opaque `dict[str, Any]`, so the
|
|
1769
1865
|
* shape survives into the program under the mode that has nothing else to carry it.
|
|
1770
1866
|
* @module @qilin-agent/tools/src/py-types
|
|
@@ -2402,12 +2498,12 @@ function jsonSchemaToPy(schema) {
|
|
|
2402
2498
|
/** The fixed model-facing usage contract rendered above the declarations. */
|
|
2403
2499
|
const SDK_INSTRUCTIONS = `## Writing code for run_code
|
|
2404
2500
|
|
|
2405
|
-
\`run_code\` takes two required arguments: \`description\`, a short summary of what the program does, and \`code
|
|
2501
|
+
\`run_code\` takes two required arguments: \`description\`, a short summary of what the program does, and \`code\`, the program to execute. At run time exactly two of the names declared below are bound: \`tools\` and \`ToolCallError\`. Everything else is a STATIC STUB describing argument and return types — in particular the \`TypedDict\` classes do NOT exist at run time, so build arguments as plain \`dict\`/\`list\` JSON values: \`await tools.name({"field": 1})\`, never \`FooArgs(field=1)\`, which raises \`NameError\`. Inside the program:
|
|
2406
2502
|
|
|
2407
2503
|
- Call tools as \`await tools.name(args)\` — subscript access for exotic, reserved, or underscore-leading names: \`await tools["my-tool"](args)\`. Every call resolves to the tool's typed canonical JSON value (each method's return type below). Tool arguments must be lossless JSON.
|
|
2408
2504
|
- A FAILED tool call raises \`ToolCallError\`, whose \`toolName\` identifies the failed tool and whose message is human-readable — wrap in \`try/except\` to handle and continue.
|
|
2409
2505
|
- Independent read-only calls MAY overlap under \`asyncio.gather\` (safe calls run concurrently; mutating calls run alone, in submission order). Sequence dependent work with \`await\`.
|
|
2410
|
-
-
|
|
2506
|
+
- Only values emitted as program output enter the conversation. A successful tool result containing an image is attached after the run so you can inspect it on the next step; every other intermediate result stays out of the conversation, so extract just what you need.
|
|
2411
2507
|
|
|
2412
2508
|
The available tools:`;
|
|
2413
2509
|
/**
|
|
@@ -2694,11 +2790,13 @@ var ToolRuntime = class extends Service {
|
|
|
2694
2790
|
* Reserved presentation transport, kept outside the filterable registration
|
|
2695
2791
|
* layers. Built on first need rather than at construction: which agents run
|
|
2696
2792
|
* a PTC mode is no longer known when the service is constructed, and the
|
|
2697
|
-
* transport
|
|
2793
|
+
* transport owns its PTC call tracking and state-owner registrations.
|
|
2698
2794
|
*/
|
|
2699
2795
|
ptcTransport;
|
|
2796
|
+
ptcContext;
|
|
2700
2797
|
constructor(ctx, config = {}) {
|
|
2701
2798
|
super(ctx, "tools");
|
|
2799
|
+
this.ptcContext = ctx;
|
|
2702
2800
|
this.defaultMode = config.mode ?? "native";
|
|
2703
2801
|
this.maxParallelSubCalls = resolveMaxParallelSubCalls(config.maxParallelSubCalls);
|
|
2704
2802
|
ctx.systemPrompt.tools((context) => this.wireSchemas(context.scope));
|
|
@@ -2751,7 +2849,7 @@ var ToolRuntime = class extends Service {
|
|
|
2751
2849
|
const render = SDK_RENDERERS[runtime.language];
|
|
2752
2850
|
/* v8 ignore next -- requirePtcRuntime rejects an unknown language before this runs. */
|
|
2753
2851
|
if (render === void 0) throw new Error(`qilin-tools: no SDK renderer for ${runtime.language}`);
|
|
2754
|
-
return render(this.sdkSchemas(context.scope));
|
|
2852
|
+
return render(this.sdkSchemas(context.scope), runtime.programFormat);
|
|
2755
2853
|
}
|
|
2756
2854
|
};
|
|
2757
2855
|
}
|
|
@@ -2796,7 +2894,7 @@ var ToolRuntime = class extends Service {
|
|
|
2796
2894
|
peekRuntime: () => this.ctx.get("ptcRuntime"),
|
|
2797
2895
|
maxParallel: this.maxParallelSubCalls,
|
|
2798
2896
|
shapeDispatchLog: (dispatch) => this.shapeDispatchLog(dispatch)
|
|
2799
|
-
});
|
|
2897
|
+
}, this.ptcContext);
|
|
2800
2898
|
return this.ptcTransport;
|
|
2801
2899
|
}
|
|
2802
2900
|
/**
|