@runtypelabs/sdk 1.0.2 → 1.2.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/dist/endpoints.d.ts +261 -5
- package/dist/endpoints.d.ts.map +1 -1
- package/dist/endpoints.js +511 -0
- package/dist/endpoints.js.map +1 -1
- package/dist/flow-builder.js +1 -1
- package/dist/flow-builder.js.map +1 -1
- package/dist/flows-namespace.js +1 -1
- package/dist/flows-namespace.js.map +1 -1
- package/dist/generated-tool-gate.d.ts +75 -0
- package/dist/generated-tool-gate.d.ts.map +1 -0
- package/dist/generated-tool-gate.js +310 -0
- package/dist/generated-tool-gate.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/endpoints.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.AgentsEndpoint = exports.ClientTokensEndpoint = exports.EvalEndpoint = exports.ToolsEndpoint = exports.ContextTemplatesEndpoint = exports.FlowStepsEndpoint = exports.AnalyticsEndpoint = exports.UsersEndpoint = exports.ChatEndpoint = exports.DispatchEndpoint = exports.ModelConfigsEndpoint = exports.ApiKeysEndpoint = exports.RecordsEndpoint = exports.PromptsEndpoint = exports.FlowsEndpoint = void 0;
|
|
7
|
+
const generated_tool_gate_1 = require("./generated-tool-gate");
|
|
7
8
|
/**
|
|
8
9
|
* Flows endpoint handlers
|
|
9
10
|
*/
|
|
@@ -419,6 +420,34 @@ class DispatchEndpoint {
|
|
|
419
420
|
}
|
|
420
421
|
return this.client.post('/dispatch/resume', data);
|
|
421
422
|
}
|
|
423
|
+
/**
|
|
424
|
+
* Evaluate a model-proposed runtime tool against a configurable allowlist policy.
|
|
425
|
+
* Useful for local `propose_runtime_tool` handlers before redispatch.
|
|
426
|
+
*/
|
|
427
|
+
gateGeneratedRuntimeToolProposal(proposal, options) {
|
|
428
|
+
return (0, generated_tool_gate_1.evaluateGeneratedRuntimeToolProposal)(proposal, options);
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Build standardized local-tool output for a generated tool proposal.
|
|
432
|
+
* Returns `{ approved, reason, violations, tool? }`.
|
|
433
|
+
*/
|
|
434
|
+
buildGeneratedRuntimeToolGateOutput(proposal, options) {
|
|
435
|
+
return (0, generated_tool_gate_1.buildGeneratedRuntimeToolGateOutput)(proposal, options);
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Attach approved runtime tools to a prompt step in a redispatch request.
|
|
439
|
+
* Returns a new request object and does not mutate the original.
|
|
440
|
+
*/
|
|
441
|
+
attachApprovedRuntimeTools(request, runtimeTools, options) {
|
|
442
|
+
return (0, generated_tool_gate_1.attachRuntimeToolsToDispatchRequest)(request, runtimeTools, options);
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Validate a generated runtime tool proposal and attach it to the redispatch
|
|
446
|
+
* request if approved, in one call.
|
|
447
|
+
*/
|
|
448
|
+
applyGeneratedRuntimeToolProposal(request, proposal, options) {
|
|
449
|
+
return (0, generated_tool_gate_1.applyGeneratedRuntimeToolProposalToDispatchRequest)(request, proposal, options);
|
|
450
|
+
}
|
|
422
451
|
}
|
|
423
452
|
exports.DispatchEndpoint = DispatchEndpoint;
|
|
424
453
|
/**
|
|
@@ -885,6 +914,9 @@ function dispatchAgentEvent(event, callbacks) {
|
|
|
885
914
|
case 'agent_error':
|
|
886
915
|
callbacks.onError?.(typedData);
|
|
887
916
|
break;
|
|
917
|
+
case 'agent_paused':
|
|
918
|
+
callbacks.onAgentPaused?.(typedData);
|
|
919
|
+
break;
|
|
888
920
|
case 'agent_ping':
|
|
889
921
|
callbacks.onPing?.(typedData);
|
|
890
922
|
break;
|
|
@@ -927,6 +959,57 @@ async function processAgentStream(body, callbacks) {
|
|
|
927
959
|
reader.releaseLock();
|
|
928
960
|
}
|
|
929
961
|
}
|
|
962
|
+
const GENERATED_RUNTIME_TOOL_PROPOSAL_SCHEMA = {
|
|
963
|
+
type: 'object',
|
|
964
|
+
properties: {
|
|
965
|
+
name: {
|
|
966
|
+
type: 'string',
|
|
967
|
+
description: 'Tool name. Use letters/numbers/underscore only.',
|
|
968
|
+
},
|
|
969
|
+
description: {
|
|
970
|
+
type: 'string',
|
|
971
|
+
description: 'Clear description of what the generated tool does.',
|
|
972
|
+
},
|
|
973
|
+
toolType: {
|
|
974
|
+
type: 'string',
|
|
975
|
+
enum: ['custom'],
|
|
976
|
+
description: 'Must be "custom" for generated code execution tools.',
|
|
977
|
+
},
|
|
978
|
+
parametersSchema: {
|
|
979
|
+
type: 'object',
|
|
980
|
+
description: 'JSON schema for tool call arguments.',
|
|
981
|
+
},
|
|
982
|
+
config: {
|
|
983
|
+
type: 'object',
|
|
984
|
+
description: 'Runtime tool config including code, sandboxProvider, language, and timeout.',
|
|
985
|
+
},
|
|
986
|
+
reason: {
|
|
987
|
+
type: 'string',
|
|
988
|
+
description: 'Why this tool is needed.',
|
|
989
|
+
},
|
|
990
|
+
},
|
|
991
|
+
required: ['name', 'description', 'toolType', 'parametersSchema', 'config'],
|
|
992
|
+
};
|
|
993
|
+
function appendRuntimeToolsToAgentRequest(request, runtimeTools) {
|
|
994
|
+
const existing = request.tools?.runtimeTools || [];
|
|
995
|
+
const existingNames = new Set(existing.map((tool) => tool.name));
|
|
996
|
+
const converted = runtimeTools
|
|
997
|
+
.filter((tool) => !existingNames.has(tool.name))
|
|
998
|
+
.map((tool) => ({
|
|
999
|
+
name: tool.name,
|
|
1000
|
+
description: tool.description,
|
|
1001
|
+
toolType: tool.toolType,
|
|
1002
|
+
parametersSchema: tool.parametersSchema,
|
|
1003
|
+
...(tool.config ? { config: tool.config } : {}),
|
|
1004
|
+
}));
|
|
1005
|
+
return {
|
|
1006
|
+
...request,
|
|
1007
|
+
tools: {
|
|
1008
|
+
...request.tools,
|
|
1009
|
+
runtimeTools: [...existing, ...converted],
|
|
1010
|
+
},
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
930
1013
|
/**
|
|
931
1014
|
* Agents endpoint handlers
|
|
932
1015
|
*/
|
|
@@ -964,6 +1047,54 @@ class AgentsEndpoint {
|
|
|
964
1047
|
async delete(id) {
|
|
965
1048
|
return this.client.delete(`/agents/${id}`);
|
|
966
1049
|
}
|
|
1050
|
+
/**
|
|
1051
|
+
* Evaluate a model-proposed runtime tool against a configurable allowlist policy.
|
|
1052
|
+
* Useful for local `propose_runtime_tool` handlers before follow-up execution.
|
|
1053
|
+
*/
|
|
1054
|
+
gateGeneratedRuntimeToolProposal(proposal, options) {
|
|
1055
|
+
return (0, generated_tool_gate_1.evaluateGeneratedRuntimeToolProposal)(proposal, options);
|
|
1056
|
+
}
|
|
1057
|
+
/**
|
|
1058
|
+
* Build standardized local-tool output for a generated tool proposal.
|
|
1059
|
+
* Returns `{ approved, reason, violations, tool? }`.
|
|
1060
|
+
*/
|
|
1061
|
+
buildGeneratedRuntimeToolGateOutput(proposal, options) {
|
|
1062
|
+
return (0, generated_tool_gate_1.buildGeneratedRuntimeToolGateOutput)(proposal, options);
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Create a local tool definition that validates model-proposed runtime tools.
|
|
1066
|
+
* Plug this into `executeWithLocalTools()` under a name like `propose_runtime_tool`.
|
|
1067
|
+
*/
|
|
1068
|
+
createGeneratedRuntimeToolGateLocalTool(options) {
|
|
1069
|
+
const { description, ...gateOptions } = options || {};
|
|
1070
|
+
return {
|
|
1071
|
+
description: description ||
|
|
1072
|
+
'Validate a generated runtime custom tool and return { approved, reason, violations, tool? }',
|
|
1073
|
+
parametersSchema: GENERATED_RUNTIME_TOOL_PROPOSAL_SCHEMA,
|
|
1074
|
+
execute: async (args) => (0, generated_tool_gate_1.buildGeneratedRuntimeToolGateOutput)(args, gateOptions),
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* Attach approved runtime tools to an agent execute request.
|
|
1079
|
+
* Returns a new request object and does not mutate the original.
|
|
1080
|
+
*/
|
|
1081
|
+
attachApprovedRuntimeTools(request, runtimeTools) {
|
|
1082
|
+
return appendRuntimeToolsToAgentRequest(request, runtimeTools);
|
|
1083
|
+
}
|
|
1084
|
+
/**
|
|
1085
|
+
* Validate a generated runtime tool proposal and append it to an agent execute
|
|
1086
|
+
* request if approved, in one call.
|
|
1087
|
+
*/
|
|
1088
|
+
applyGeneratedRuntimeToolProposal(request, proposal, options) {
|
|
1089
|
+
const decision = (0, generated_tool_gate_1.evaluateGeneratedRuntimeToolProposal)(proposal, options);
|
|
1090
|
+
if (!decision.approved || !decision.tool) {
|
|
1091
|
+
return { decision, request };
|
|
1092
|
+
}
|
|
1093
|
+
return {
|
|
1094
|
+
decision,
|
|
1095
|
+
request: appendRuntimeToolsToAgentRequest(request, [decision.tool]),
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
967
1098
|
/**
|
|
968
1099
|
* Execute an agent (non-streaming)
|
|
969
1100
|
*/
|
|
@@ -1048,6 +1179,386 @@ class AgentsEndpoint {
|
|
|
1048
1179
|
});
|
|
1049
1180
|
return completeEvent;
|
|
1050
1181
|
}
|
|
1182
|
+
/**
|
|
1183
|
+
* Execute an agent with local tool support (pause/resume loop)
|
|
1184
|
+
*
|
|
1185
|
+
* When the agent hits a tool with `toolType: 'local'`, the server emits
|
|
1186
|
+
* `agent_paused`. This method automatically executes the local tool and
|
|
1187
|
+
* resumes execution, repeating until the agent completes.
|
|
1188
|
+
*
|
|
1189
|
+
* @example
|
|
1190
|
+
* ```typescript
|
|
1191
|
+
* const result = await client.agents.executeWithLocalTools('agt_123', {
|
|
1192
|
+
* messages: [{ role: 'user', content: 'Create a file called hello.txt' }],
|
|
1193
|
+
* }, {
|
|
1194
|
+
* write_file: async ({ path, content }) => {
|
|
1195
|
+
* fs.writeFileSync(path, content)
|
|
1196
|
+
* return 'ok'
|
|
1197
|
+
* },
|
|
1198
|
+
* })
|
|
1199
|
+
* ```
|
|
1200
|
+
*/
|
|
1201
|
+
async executeWithLocalTools(id, data, localTools, callbacks) {
|
|
1202
|
+
// Build runtime tool definitions from local tool schemas and inject into request
|
|
1203
|
+
const runtimeTools = Object.entries(localTools).map(([name, def]) => ({
|
|
1204
|
+
name,
|
|
1205
|
+
description: def.description,
|
|
1206
|
+
toolType: 'local',
|
|
1207
|
+
parametersSchema: def.parametersSchema,
|
|
1208
|
+
}));
|
|
1209
|
+
const requestData = {
|
|
1210
|
+
...data,
|
|
1211
|
+
tools: {
|
|
1212
|
+
...data.tools,
|
|
1213
|
+
runtimeTools: [
|
|
1214
|
+
...(data.tools?.runtimeTools || []),
|
|
1215
|
+
...runtimeTools,
|
|
1216
|
+
],
|
|
1217
|
+
},
|
|
1218
|
+
};
|
|
1219
|
+
const response = await this.executeStream(id, requestData);
|
|
1220
|
+
if (!response.ok) {
|
|
1221
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
1222
|
+
throw new Error(error.error || `HTTP ${response.status}`);
|
|
1223
|
+
}
|
|
1224
|
+
let currentBody = response.body;
|
|
1225
|
+
while (true) {
|
|
1226
|
+
let pausedEvent = null;
|
|
1227
|
+
let completeEvent = null;
|
|
1228
|
+
await processAgentStream(currentBody, {
|
|
1229
|
+
...callbacks,
|
|
1230
|
+
onAgentPaused: (event) => {
|
|
1231
|
+
pausedEvent = event;
|
|
1232
|
+
callbacks?.onAgentPaused?.(event);
|
|
1233
|
+
},
|
|
1234
|
+
onAgentComplete: (event) => {
|
|
1235
|
+
completeEvent = event;
|
|
1236
|
+
callbacks?.onAgentComplete?.(event);
|
|
1237
|
+
},
|
|
1238
|
+
});
|
|
1239
|
+
if (completeEvent)
|
|
1240
|
+
return completeEvent;
|
|
1241
|
+
if (pausedEvent) {
|
|
1242
|
+
const { toolName, parameters, executionId } = pausedEvent;
|
|
1243
|
+
const toolDef = localTools[toolName];
|
|
1244
|
+
if (!toolDef) {
|
|
1245
|
+
throw new Error(`Local tool "${toolName}" required but not provided`);
|
|
1246
|
+
}
|
|
1247
|
+
// Recursively unwrap stringified parameters — the server pipeline may
|
|
1248
|
+
// double-serialize: object → JSON string → JSON string
|
|
1249
|
+
let parsedParams = {};
|
|
1250
|
+
let current = parameters;
|
|
1251
|
+
for (let i = 0; i < 3; i++) {
|
|
1252
|
+
if (typeof current === 'string') {
|
|
1253
|
+
try {
|
|
1254
|
+
current = JSON.parse(current);
|
|
1255
|
+
}
|
|
1256
|
+
catch {
|
|
1257
|
+
console.warn(`[local-tools] Failed to parse parameters (attempt ${i + 1}):`, typeof current, String(current).slice(0, 200));
|
|
1258
|
+
break;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
else {
|
|
1262
|
+
break;
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
if (current && typeof current === 'object' && !Array.isArray(current)) {
|
|
1266
|
+
parsedParams = current;
|
|
1267
|
+
}
|
|
1268
|
+
else {
|
|
1269
|
+
console.warn('[local-tools] Parameters could not be resolved to an object:', typeof current, String(current).slice(0, 200));
|
|
1270
|
+
}
|
|
1271
|
+
let toolResult;
|
|
1272
|
+
try {
|
|
1273
|
+
toolResult = await toolDef.execute(parsedParams);
|
|
1274
|
+
}
|
|
1275
|
+
catch (err) {
|
|
1276
|
+
// Return the error as a tool result so the agent can recover
|
|
1277
|
+
toolResult = `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
1278
|
+
}
|
|
1279
|
+
// Resume via agent resume endpoint
|
|
1280
|
+
const resumeResponse = await this.client.requestStream(`/agents/${id}/resume`, {
|
|
1281
|
+
method: 'POST',
|
|
1282
|
+
body: JSON.stringify({
|
|
1283
|
+
executionId,
|
|
1284
|
+
toolOutputs: { [toolName]: toolResult },
|
|
1285
|
+
streamResponse: true,
|
|
1286
|
+
debugMode: data.debugMode,
|
|
1287
|
+
}),
|
|
1288
|
+
});
|
|
1289
|
+
if (!resumeResponse.ok) {
|
|
1290
|
+
const error = await resumeResponse.json().catch(() => ({ error: 'Unknown error' }));
|
|
1291
|
+
throw new Error(error.error || `HTTP ${resumeResponse.status}`);
|
|
1292
|
+
}
|
|
1293
|
+
currentBody = resumeResponse.body;
|
|
1294
|
+
continue;
|
|
1295
|
+
}
|
|
1296
|
+
// Stream ended without complete or paused
|
|
1297
|
+
return null;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
// ─── Long-Task Agent Execution ───────────────────────────────────────
|
|
1301
|
+
/**
|
|
1302
|
+
* Run a long-task agent across multiple sessions with automatic state management.
|
|
1303
|
+
*
|
|
1304
|
+
* Each session is a single agent execution. The SDK drives the loop client-side,
|
|
1305
|
+
* calling the agent's execute endpoint repeatedly and accumulating context.
|
|
1306
|
+
* Progress is optionally synced to a Runtype record for dashboard visibility.
|
|
1307
|
+
*
|
|
1308
|
+
* @example
|
|
1309
|
+
* ```typescript
|
|
1310
|
+
* const result = await client.agents.runTask('agt_123', {
|
|
1311
|
+
* message: 'Build a REST API with CRUD endpoints',
|
|
1312
|
+
* maxSessions: 20,
|
|
1313
|
+
* maxCost: 5.00,
|
|
1314
|
+
* trackProgress: true,
|
|
1315
|
+
* onSession: (state) => {
|
|
1316
|
+
* console.log(`Session ${state.sessionCount}: ${state.lastStopReason} ($${state.totalCost.toFixed(4)})`)
|
|
1317
|
+
* },
|
|
1318
|
+
* })
|
|
1319
|
+
*
|
|
1320
|
+
* console.log(`Finished: ${result.status} after ${result.sessionCount} sessions`)
|
|
1321
|
+
* ```
|
|
1322
|
+
*/
|
|
1323
|
+
async runTask(id, options) {
|
|
1324
|
+
const maxSessions = options.maxSessions ?? 50;
|
|
1325
|
+
const maxCost = options.maxCost;
|
|
1326
|
+
const useStream = options.stream ?? true;
|
|
1327
|
+
// Resolve agent metadata
|
|
1328
|
+
const agent = await this.get(id);
|
|
1329
|
+
const taskName = typeof options.trackProgress === 'string'
|
|
1330
|
+
? options.trackProgress
|
|
1331
|
+
: options.trackProgress
|
|
1332
|
+
? `${agent.name} task`
|
|
1333
|
+
: '';
|
|
1334
|
+
// Initialize state
|
|
1335
|
+
const state = {
|
|
1336
|
+
agentId: id,
|
|
1337
|
+
agentName: agent.name,
|
|
1338
|
+
taskName: taskName || `${agent.name} task`,
|
|
1339
|
+
status: 'running',
|
|
1340
|
+
sessionCount: 0,
|
|
1341
|
+
totalCost: 0,
|
|
1342
|
+
lastOutput: '',
|
|
1343
|
+
lastStopReason: 'complete',
|
|
1344
|
+
sessions: [],
|
|
1345
|
+
startedAt: new Date().toISOString(),
|
|
1346
|
+
updatedAt: new Date().toISOString(),
|
|
1347
|
+
};
|
|
1348
|
+
// Track the record ID if we're syncing
|
|
1349
|
+
let recordId;
|
|
1350
|
+
// Extract local tool names for prompt injection
|
|
1351
|
+
const localToolNames = options.localTools ? Object.keys(options.localTools) : undefined;
|
|
1352
|
+
// Session loop
|
|
1353
|
+
for (let session = 0; session < maxSessions; session++) {
|
|
1354
|
+
// Build messages for this session
|
|
1355
|
+
const messages = this.buildSessionMessages(options.message, state, session, maxSessions, localToolNames);
|
|
1356
|
+
// Execute one session
|
|
1357
|
+
let sessionResult;
|
|
1358
|
+
const sessionData = { messages, debugMode: options.debugMode, model: options.model };
|
|
1359
|
+
if (useStream && options.localTools) {
|
|
1360
|
+
// Local tools require the pause/resume streaming loop
|
|
1361
|
+
const completeEvent = await this.executeWithLocalTools(id, sessionData, options.localTools, options.streamCallbacks);
|
|
1362
|
+
if (!completeEvent) {
|
|
1363
|
+
throw new Error('Agent stream ended without a complete event');
|
|
1364
|
+
}
|
|
1365
|
+
sessionResult = {
|
|
1366
|
+
success: completeEvent.success,
|
|
1367
|
+
result: completeEvent.finalOutput || '',
|
|
1368
|
+
iterations: completeEvent.iterations,
|
|
1369
|
+
totalCost: completeEvent.totalCost || 0,
|
|
1370
|
+
stopReason: completeEvent.stopReason,
|
|
1371
|
+
error: completeEvent.error,
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
else if (useStream && options.streamCallbacks) {
|
|
1375
|
+
const completeEvent = await this.executeWithCallbacks(id, sessionData, options.streamCallbacks);
|
|
1376
|
+
if (!completeEvent) {
|
|
1377
|
+
throw new Error('Agent stream ended without a complete event');
|
|
1378
|
+
}
|
|
1379
|
+
sessionResult = {
|
|
1380
|
+
success: completeEvent.success,
|
|
1381
|
+
result: completeEvent.finalOutput || '',
|
|
1382
|
+
iterations: completeEvent.iterations,
|
|
1383
|
+
totalCost: completeEvent.totalCost || 0,
|
|
1384
|
+
stopReason: completeEvent.stopReason,
|
|
1385
|
+
error: completeEvent.error,
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
else {
|
|
1389
|
+
sessionResult = await this.execute(id, sessionData);
|
|
1390
|
+
}
|
|
1391
|
+
// Update state
|
|
1392
|
+
const sessionCost = sessionResult.totalCost;
|
|
1393
|
+
state.sessionCount = session + 1;
|
|
1394
|
+
state.totalCost += sessionCost;
|
|
1395
|
+
state.lastOutput = sessionResult.result;
|
|
1396
|
+
state.lastStopReason = sessionResult.stopReason;
|
|
1397
|
+
state.updatedAt = new Date().toISOString();
|
|
1398
|
+
state.sessions.push({
|
|
1399
|
+
index: session + 1,
|
|
1400
|
+
cost: sessionCost,
|
|
1401
|
+
iterations: sessionResult.iterations,
|
|
1402
|
+
stopReason: sessionResult.stopReason,
|
|
1403
|
+
outputPreview: sessionResult.result.slice(0, 300),
|
|
1404
|
+
completedAt: new Date().toISOString(),
|
|
1405
|
+
});
|
|
1406
|
+
// Keep session log trimmed to last 50 entries
|
|
1407
|
+
if (state.sessions.length > 50) {
|
|
1408
|
+
state.sessions = state.sessions.slice(-50);
|
|
1409
|
+
}
|
|
1410
|
+
// Check terminal conditions
|
|
1411
|
+
if (sessionResult.stopReason === 'complete') {
|
|
1412
|
+
state.status = 'complete';
|
|
1413
|
+
}
|
|
1414
|
+
else if (sessionResult.stopReason === 'error') {
|
|
1415
|
+
state.status = 'complete';
|
|
1416
|
+
}
|
|
1417
|
+
else if (sessionResult.stopReason === 'max_cost') {
|
|
1418
|
+
state.status = 'budget_exceeded';
|
|
1419
|
+
}
|
|
1420
|
+
else if (this.detectTaskCompletion(sessionResult.result)) {
|
|
1421
|
+
// Client-side stop-phrase detection for non-loop agents returning 'end_turn'
|
|
1422
|
+
state.status = 'complete';
|
|
1423
|
+
}
|
|
1424
|
+
else if (maxCost && state.totalCost >= maxCost) {
|
|
1425
|
+
state.status = 'budget_exceeded';
|
|
1426
|
+
}
|
|
1427
|
+
else if (session + 1 >= maxSessions) {
|
|
1428
|
+
state.status = 'max_sessions';
|
|
1429
|
+
}
|
|
1430
|
+
// Sync to record if enabled
|
|
1431
|
+
if (options.trackProgress) {
|
|
1432
|
+
recordId = await this.syncProgressRecord(state, recordId);
|
|
1433
|
+
}
|
|
1434
|
+
// Notify caller
|
|
1435
|
+
if (options.onSession) {
|
|
1436
|
+
const shouldStop = await options.onSession(state);
|
|
1437
|
+
if (shouldStop === false) {
|
|
1438
|
+
state.status = 'paused';
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
// Stop if terminal
|
|
1442
|
+
if (state.status !== 'running') {
|
|
1443
|
+
break;
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
return {
|
|
1447
|
+
status: state.status,
|
|
1448
|
+
sessionCount: state.sessionCount,
|
|
1449
|
+
totalCost: state.totalCost,
|
|
1450
|
+
lastOutput: state.lastOutput,
|
|
1451
|
+
sessions: state.sessions,
|
|
1452
|
+
recordId,
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
/**
|
|
1456
|
+
* Client-side fallback for detecting task completion in agent output.
|
|
1457
|
+
* Mirrors the API's detectAutoComplete() for non-loop agents that return 'end_turn'.
|
|
1458
|
+
*/
|
|
1459
|
+
detectTaskCompletion(output) {
|
|
1460
|
+
const upper = output.toUpperCase();
|
|
1461
|
+
return AgentsEndpoint.STOP_PHRASES.some((phrase) => upper.includes(phrase.toUpperCase()));
|
|
1462
|
+
}
|
|
1463
|
+
/**
|
|
1464
|
+
* Build messages for a session, injecting progress context for continuation sessions.
|
|
1465
|
+
*/
|
|
1466
|
+
buildSessionMessages(originalMessage, state, sessionIndex, maxSessions, localToolNames) {
|
|
1467
|
+
// Build local tools guidance block when tools are available
|
|
1468
|
+
const toolsBlock = localToolNames?.length
|
|
1469
|
+
? [
|
|
1470
|
+
'',
|
|
1471
|
+
'--- Local Tools ---',
|
|
1472
|
+
`You have access to local filesystem tools (${localToolNames.join(', ')}) that execute directly on the user's machine.`,
|
|
1473
|
+
'Use these tools to create working, runnable files — not just code in your response.',
|
|
1474
|
+
'Prefer creating self-contained HTML files that the user can open in a web browser.',
|
|
1475
|
+
'For example, write a single .html file with inline CSS and JavaScript that demonstrates the result.',
|
|
1476
|
+
'Always use write_file to save your output so the user can run it immediately.',
|
|
1477
|
+
].join('\n')
|
|
1478
|
+
: '';
|
|
1479
|
+
// First session: user message + completion signal instruction
|
|
1480
|
+
if (sessionIndex === 0) {
|
|
1481
|
+
const content = [
|
|
1482
|
+
originalMessage,
|
|
1483
|
+
toolsBlock,
|
|
1484
|
+
'',
|
|
1485
|
+
`This is a multi-session task (session 1/${maxSessions}). When you have fully completed the task, end your response with TASK_COMPLETE on its own line.`,
|
|
1486
|
+
].join('\n');
|
|
1487
|
+
return [{ role: 'user', content }];
|
|
1488
|
+
}
|
|
1489
|
+
// Continuation sessions: inject progress context
|
|
1490
|
+
const recentSessions = state.sessions.slice(-5);
|
|
1491
|
+
const progressSummary = recentSessions
|
|
1492
|
+
.map((s) => ` Session ${s.index}: ${s.stopReason} ($${s.cost.toFixed(4)}) — ${s.outputPreview.slice(0, 100)}`)
|
|
1493
|
+
.join('\n');
|
|
1494
|
+
const content = [
|
|
1495
|
+
originalMessage,
|
|
1496
|
+
toolsBlock,
|
|
1497
|
+
'',
|
|
1498
|
+
`--- Progress (session ${sessionIndex + 1}/${maxSessions}, $${state.totalCost.toFixed(4)} spent) ---`,
|
|
1499
|
+
`Previous sessions:`,
|
|
1500
|
+
progressSummary,
|
|
1501
|
+
'',
|
|
1502
|
+
`Last output (do NOT repeat this — build on it):`,
|
|
1503
|
+
state.lastOutput.slice(0, 1000),
|
|
1504
|
+
'',
|
|
1505
|
+
'Continue where you left off. Do not redo previous work. If the task is already complete, respond with TASK_COMPLETE.',
|
|
1506
|
+
].join('\n');
|
|
1507
|
+
return [{ role: 'user', content }];
|
|
1508
|
+
}
|
|
1509
|
+
/**
|
|
1510
|
+
* Upsert a record to sync long-task progress to the dashboard.
|
|
1511
|
+
* Creates the record on first call, updates it on subsequent calls.
|
|
1512
|
+
*/
|
|
1513
|
+
async syncProgressRecord(state, existingRecordId) {
|
|
1514
|
+
const metadata = {
|
|
1515
|
+
agentId: state.agentId,
|
|
1516
|
+
agentName: state.agentName,
|
|
1517
|
+
status: state.status,
|
|
1518
|
+
sessionCount: state.sessionCount,
|
|
1519
|
+
totalCost: state.totalCost,
|
|
1520
|
+
lastStopReason: state.lastStopReason,
|
|
1521
|
+
lastOutputPreview: state.lastOutput.slice(0, 500),
|
|
1522
|
+
sessions: state.sessions.slice(-10), // Keep last 10 in the record
|
|
1523
|
+
startedAt: state.startedAt,
|
|
1524
|
+
updatedAt: state.updatedAt,
|
|
1525
|
+
};
|
|
1526
|
+
try {
|
|
1527
|
+
if (existingRecordId) {
|
|
1528
|
+
// Update existing record
|
|
1529
|
+
const record = await this.client.put(`/records/${existingRecordId}`, { metadata });
|
|
1530
|
+
return record.id;
|
|
1531
|
+
}
|
|
1532
|
+
else {
|
|
1533
|
+
// Try to find existing record by type + name first
|
|
1534
|
+
const existing = await this.client.get('/records', { type: 'agent-task', name: state.taskName, limit: 1 });
|
|
1535
|
+
if (existing.data.length > 0) {
|
|
1536
|
+
const record = await this.client.put(`/records/${existing.data[0].id}`, { metadata });
|
|
1537
|
+
return record.id;
|
|
1538
|
+
}
|
|
1539
|
+
// Create new record
|
|
1540
|
+
const record = await this.client.post('/records', {
|
|
1541
|
+
type: 'agent-task',
|
|
1542
|
+
name: state.taskName,
|
|
1543
|
+
metadata,
|
|
1544
|
+
});
|
|
1545
|
+
return record.id;
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
catch {
|
|
1549
|
+
// Record sync is best-effort — don't fail the task
|
|
1550
|
+
return existingRecordId || '';
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1051
1553
|
}
|
|
1052
1554
|
exports.AgentsEndpoint = AgentsEndpoint;
|
|
1555
|
+
/** Stop phrases that indicate the agent considers its task complete. */
|
|
1556
|
+
AgentsEndpoint.STOP_PHRASES = [
|
|
1557
|
+
'DONE:',
|
|
1558
|
+
'TASK_COMPLETE',
|
|
1559
|
+
'FINISHED',
|
|
1560
|
+
'[COMPLETE]',
|
|
1561
|
+
'STATUS: RESOLVED',
|
|
1562
|
+
'STATUS: COMPLETE',
|
|
1563
|
+
];
|
|
1053
1564
|
//# sourceMappingURL=endpoints.js.map
|