@botbotgo/agent-harness 0.0.65 → 0.0.67
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 +4 -0
- package/README.zh.md +4 -0
- package/dist/api.d.ts +3 -1
- package/dist/api.js +6 -0
- package/dist/contracts/types.d.ts +3 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/persistence/file-store.d.ts +4 -1
- package/dist/persistence/file-store.js +33 -18
- package/dist/runtime/agent-runtime-adapter.d.ts +3 -0
- package/dist/runtime/agent-runtime-adapter.js +561 -15
- package/dist/runtime/harness.d.ts +8 -1
- package/dist/runtime/harness.js +62 -8
- package/dist/runtime/support/harness-support.js +10 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -207,6 +207,8 @@ const result = await run(runtime, {
|
|
|
207
207
|
|
|
208
208
|
`run(runtime, { ... })` creates or continues a persisted thread and returns `threadId`, `runId`, `state`, and compact text `output`. Richer upstream result shapes stay available through `outputContent`, `contentBlocks`, and `structuredResponse`.
|
|
209
209
|
|
|
210
|
+
Use `listRuns(runtime)` and `getRun(runtime, runId)` when a product needs a run-centric operations surface such as a review queue or execution dashboard.
|
|
211
|
+
|
|
210
212
|
Use `invocation` as the runtime-facing request envelope:
|
|
211
213
|
|
|
212
214
|
- `invocation.context` for request-scoped execution context
|
|
@@ -575,6 +577,8 @@ Primary exports:
|
|
|
575
577
|
- `run`
|
|
576
578
|
- `resolveApproval`
|
|
577
579
|
- `subscribe`
|
|
580
|
+
- `listRuns`
|
|
581
|
+
- `getRun`
|
|
578
582
|
- `listThreads`
|
|
579
583
|
- `getThread`
|
|
580
584
|
- `deleteThread`
|
package/README.zh.md
CHANGED
|
@@ -206,6 +206,8 @@ const result = await run(runtime, {
|
|
|
206
206
|
|
|
207
207
|
`run(runtime, { ... })` 会创建或延续持久化线程,并返回 `threadId`、`runId`、`state` 以及紧凑文本 `output`。更丰富的上游结果形态仍可通过 `outputContent`、`contentBlocks`、`structuredResponse` 等获得。
|
|
208
208
|
|
|
209
|
+
如果产品需要 run 视角的操作界面,例如 review queue 或执行看板,可使用 `listRuns(runtime)` 与 `getRun(runtime, runId)`。
|
|
210
|
+
|
|
209
211
|
将 `invocation` 作为面向运行时的请求信封:
|
|
210
212
|
|
|
211
213
|
- `invocation.context`:请求级执行上下文
|
|
@@ -567,6 +569,8 @@ spec:
|
|
|
567
569
|
- `run`
|
|
568
570
|
- `resolveApproval`
|
|
569
571
|
- `subscribe`
|
|
572
|
+
- `listRuns`
|
|
573
|
+
- `getRun`
|
|
570
574
|
- `listThreads`
|
|
571
575
|
- `getThread`
|
|
572
576
|
- `deleteThread`
|
package/dist/api.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ApprovalRecord, RunOptions, ResumeOptions, RuntimeAdapterOptions, ThreadSummary, ThreadRecord, WorkspaceLoadOptions } from "./contracts/types.js";
|
|
1
|
+
import type { ApprovalRecord, RunRecord, RunOptions, RunSummary, ResumeOptions, RuntimeAdapterOptions, ThreadSummary, ThreadRecord, WorkspaceLoadOptions } from "./contracts/types.js";
|
|
2
2
|
import { AgentHarnessRuntime } from "./runtime/harness.js";
|
|
3
3
|
import type { InventoryAgentRecord, InventorySkillRecord } from "./runtime/inventory.js";
|
|
4
4
|
import type { RequirementAssessmentOptions } from "./runtime/skill-requirements.js";
|
|
@@ -13,7 +13,9 @@ export declare function createAgentHarness(workspaceRoot: string, options?: Crea
|
|
|
13
13
|
export declare function run(runtime: AgentHarnessRuntime, options: RunOptions): Promise<import("./contracts/types.js").RunResult>;
|
|
14
14
|
export declare function subscribe(runtime: AgentHarnessRuntime, listener: Parameters<AgentHarnessRuntime["subscribe"]>[0]): () => void;
|
|
15
15
|
export declare function listThreads(runtime: AgentHarnessRuntime, filter?: Parameters<AgentHarnessRuntime["listThreads"]>[0]): Promise<ThreadSummary[]>;
|
|
16
|
+
export declare function listRuns(runtime: AgentHarnessRuntime, filter?: Parameters<AgentHarnessRuntime["listRuns"]>[0]): Promise<RunSummary[]>;
|
|
16
17
|
export declare function getThread(runtime: AgentHarnessRuntime, threadId: string): Promise<ThreadRecord | null>;
|
|
18
|
+
export declare function getRun(runtime: AgentHarnessRuntime, runId: string): Promise<RunRecord | null>;
|
|
17
19
|
export declare function deleteThread(runtime: AgentHarnessRuntime, threadId: string): Promise<boolean>;
|
|
18
20
|
export declare function listApprovals(runtime: AgentHarnessRuntime, filter?: Parameters<AgentHarnessRuntime["listApprovals"]>[0]): Promise<ApprovalRecord[]>;
|
|
19
21
|
export declare function getApproval(runtime: AgentHarnessRuntime, approvalId: string): Promise<ApprovalRecord | null>;
|
package/dist/api.js
CHANGED
|
@@ -16,9 +16,15 @@ export function subscribe(runtime, listener) {
|
|
|
16
16
|
export async function listThreads(runtime, filter) {
|
|
17
17
|
return runtime.listThreads(filter);
|
|
18
18
|
}
|
|
19
|
+
export async function listRuns(runtime, filter) {
|
|
20
|
+
return runtime.listRuns(filter);
|
|
21
|
+
}
|
|
19
22
|
export async function getThread(runtime, threadId) {
|
|
20
23
|
return runtime.getThread(threadId);
|
|
21
24
|
}
|
|
25
|
+
export async function getRun(runtime, runId) {
|
|
26
|
+
return runtime.getRun(runId);
|
|
27
|
+
}
|
|
22
28
|
export async function deleteThread(runtime, threadId) {
|
|
23
29
|
return runtime.deleteThread(threadId);
|
|
24
30
|
}
|
|
@@ -361,6 +361,7 @@ export type TranscriptMessage = {
|
|
|
361
361
|
};
|
|
362
362
|
export type ThreadRunRecord = {
|
|
363
363
|
runId: string;
|
|
364
|
+
threadId: string;
|
|
364
365
|
agentId: string;
|
|
365
366
|
executionMode: string;
|
|
366
367
|
adapterKind?: string;
|
|
@@ -370,6 +371,8 @@ export type ThreadRunRecord = {
|
|
|
370
371
|
checkpointRef: string | null;
|
|
371
372
|
resumable: boolean;
|
|
372
373
|
};
|
|
374
|
+
export type RunSummary = ThreadRunRecord;
|
|
375
|
+
export type RunRecord = RunSummary;
|
|
373
376
|
export type ThreadRecord = {
|
|
374
377
|
threadId: string;
|
|
375
378
|
entryAgentId: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { AgentHarnessRuntime, createAgentHarness, createToolMcpServer, deleteThread, describeInventory, getApproval, getThread, listAgentSkills, listApprovals, listThreads, resolveApproval, run, serveToolsOverStdio, subscribe, stop, } from "./api.js";
|
|
1
|
+
export { AgentHarnessRuntime, createAgentHarness, createToolMcpServer, deleteThread, describeInventory, getApproval, getRun, getThread, listAgentSkills, listApprovals, listRuns, listThreads, resolveApproval, run, serveToolsOverStdio, subscribe, stop, } from "./api.js";
|
|
2
2
|
export type { ToolMcpServerOptions } from "./mcp.js";
|
|
3
3
|
export { tool } from "./tools.js";
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { AgentHarnessRuntime, createAgentHarness, createToolMcpServer, deleteThread, describeInventory, getApproval, getThread, listAgentSkills, listApprovals, listThreads, resolveApproval, run, serveToolsOverStdio, subscribe, stop, } from "./api.js";
|
|
1
|
+
export { AgentHarnessRuntime, createAgentHarness, createToolMcpServer, deleteThread, describeInventory, getApproval, getRun, getThread, listAgentSkills, listApprovals, listRuns, listThreads, resolveApproval, run, serveToolsOverStdio, subscribe, stop, } from "./api.js";
|
|
2
2
|
export { tool } from "./tools.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const AGENT_HARNESS_VERSION = "0.0.
|
|
1
|
+
export declare const AGENT_HARNESS_VERSION = "0.0.66";
|
package/dist/package-version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const AGENT_HARNESS_VERSION = "0.0.
|
|
1
|
+
export const AGENT_HARNESS_VERSION = "0.0.66";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ArtifactListing, ArtifactRecord, DelegationRecord, HarnessEvent, InternalApprovalRecord, InvocationEnvelope, MessageContent, RunState, ThreadSummary, ThreadRunRecord, TranscriptMessage } from "../contracts/types.js";
|
|
1
|
+
import type { ArtifactListing, ArtifactRecord, DelegationRecord, HarnessEvent, InternalApprovalRecord, InvocationEnvelope, MessageContent, RunSummary, RunState, ThreadSummary, ThreadRunRecord, TranscriptMessage } from "../contracts/types.js";
|
|
2
2
|
type ThreadMeta = {
|
|
3
3
|
threadId: string;
|
|
4
4
|
workspaceId: string;
|
|
@@ -73,6 +73,9 @@ export declare class FilePersistence {
|
|
|
73
73
|
appendEvent(event: HarnessEvent): Promise<void>;
|
|
74
74
|
listSessions(): Promise<ThreadSummary[]>;
|
|
75
75
|
listRunIndexes(): Promise<RunIndexRecord[]>;
|
|
76
|
+
private readRunSummary;
|
|
77
|
+
listRuns(): Promise<RunSummary[]>;
|
|
78
|
+
getRun(runId: string): Promise<RunSummary | null>;
|
|
76
79
|
getSession(threadId: string): Promise<ThreadSummary | null>;
|
|
77
80
|
getThreadMeta(threadId: string): Promise<ThreadMeta | null>;
|
|
78
81
|
listThreadRuns(threadId: string): Promise<ThreadRunRecord[]>;
|
|
@@ -190,6 +190,38 @@ export class FilePersistence {
|
|
|
190
190
|
const entries = (await readdir(runIndexDir)).sort();
|
|
191
191
|
return Promise.all(entries.map((entry) => readJson(path.join(runIndexDir, entry))));
|
|
192
192
|
}
|
|
193
|
+
async readRunSummary(threadId, runId) {
|
|
194
|
+
const runDir = this.runDir(threadId, runId);
|
|
195
|
+
const [meta, lifecycle] = await Promise.all([
|
|
196
|
+
readJson(path.join(runDir, "meta.json")),
|
|
197
|
+
readJson(path.join(runDir, "lifecycle.json")),
|
|
198
|
+
]);
|
|
199
|
+
return {
|
|
200
|
+
runId: meta.runId,
|
|
201
|
+
threadId: meta.threadId,
|
|
202
|
+
agentId: meta.agentId,
|
|
203
|
+
executionMode: meta.executionMode,
|
|
204
|
+
adapterKind: meta.adapterKind ?? meta.executionMode,
|
|
205
|
+
createdAt: meta.createdAt,
|
|
206
|
+
updatedAt: meta.updatedAt,
|
|
207
|
+
state: lifecycle.state,
|
|
208
|
+
checkpointRef: lifecycle.checkpointRef,
|
|
209
|
+
resumable: lifecycle.resumable,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
async listRuns() {
|
|
213
|
+
const indexes = await this.listRunIndexes();
|
|
214
|
+
const runs = await Promise.all(indexes.map((record) => this.readRunSummary(record.threadId, record.runId)));
|
|
215
|
+
return runs.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
|
|
216
|
+
}
|
|
217
|
+
async getRun(runId) {
|
|
218
|
+
const indexPath = this.runIndexPath(runId);
|
|
219
|
+
if (!(await fileExists(indexPath))) {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
const index = await readJson(indexPath);
|
|
223
|
+
return this.readRunSummary(index.threadId, index.runId);
|
|
224
|
+
}
|
|
193
225
|
async getSession(threadId) {
|
|
194
226
|
const filePath = path.join(this.runRoot, "indexes", "threads", `${threadId}.json`);
|
|
195
227
|
if (!(await fileExists(filePath))) {
|
|
@@ -221,24 +253,7 @@ export class FilePersistence {
|
|
|
221
253
|
return [];
|
|
222
254
|
}
|
|
223
255
|
const runIds = (await readdir(runsDir)).sort();
|
|
224
|
-
const runs = await Promise.all(runIds.map(async (runId) =>
|
|
225
|
-
const runDir = this.runDir(threadId, runId);
|
|
226
|
-
const [meta, lifecycle] = await Promise.all([
|
|
227
|
-
readJson(path.join(runDir, "meta.json")),
|
|
228
|
-
readJson(path.join(runDir, "lifecycle.json")),
|
|
229
|
-
]);
|
|
230
|
-
return {
|
|
231
|
-
runId: meta.runId,
|
|
232
|
-
agentId: meta.agentId,
|
|
233
|
-
executionMode: meta.executionMode,
|
|
234
|
-
adapterKind: meta.adapterKind ?? meta.executionMode,
|
|
235
|
-
createdAt: meta.createdAt,
|
|
236
|
-
updatedAt: meta.updatedAt,
|
|
237
|
-
state: lifecycle.state,
|
|
238
|
-
checkpointRef: lifecycle.checkpointRef,
|
|
239
|
-
resumable: lifecycle.resumable,
|
|
240
|
-
};
|
|
241
|
-
}));
|
|
256
|
+
const runs = await Promise.all(runIds.map(async (runId) => this.readRunSummary(threadId, runId)));
|
|
242
257
|
return runs.sort((left, right) => right.createdAt.localeCompare(left.createdAt));
|
|
243
258
|
}
|
|
244
259
|
async listRunEvents(threadId, runId) {
|
|
@@ -42,6 +42,9 @@ export declare class AgentRuntimeAdapter {
|
|
|
42
42
|
private compileInterruptOn;
|
|
43
43
|
private resolveInterruptOn;
|
|
44
44
|
private resolveFilesystemBackend;
|
|
45
|
+
private resolveBuiltinMiddlewareBackend;
|
|
46
|
+
private invokeBuiltinTaskTool;
|
|
47
|
+
private resolveBuiltinMiddlewareTools;
|
|
45
48
|
private resolveLangChainAutomaticMiddleware;
|
|
46
49
|
private resolveMiddleware;
|
|
47
50
|
private resolveCheckpointer;
|
|
@@ -3,7 +3,8 @@ import { existsSync, statSync } from "node:fs";
|
|
|
3
3
|
import { cp, mkdir, rm } from "node:fs/promises";
|
|
4
4
|
import { Command, MemorySaver } from "@langchain/langgraph";
|
|
5
5
|
import { tool as createLangChainTool } from "@langchain/core/tools";
|
|
6
|
-
import {
|
|
6
|
+
import { HumanMessage, ToolMessage } from "@langchain/core/messages";
|
|
7
|
+
import { DEFAULT_SUBAGENT_PROMPT, createDeepAgent, createMemoryMiddleware, createPatchToolCallsMiddleware, createSkillsMiddleware, createSummarizationMiddleware, createSubAgentMiddleware, FilesystemBackend, StateBackend, isSandboxBackend, } from "deepagents";
|
|
7
8
|
import { ChatAnthropic } from "@langchain/anthropic";
|
|
8
9
|
import { tools as anthropicProviderTools } from "@langchain/anthropic";
|
|
9
10
|
import { ChatGoogle } from "@langchain/google";
|
|
@@ -12,7 +13,7 @@ import { ChatOpenAI } from "@langchain/openai";
|
|
|
12
13
|
import { tools as openAIProviderTools } from "@langchain/openai";
|
|
13
14
|
import { createAgent, humanInTheLoopMiddleware, initChatModel } from "langchain";
|
|
14
15
|
import { z } from "zod";
|
|
15
|
-
import { extractEmptyAssistantMessageFailure, extractContentBlocks, extractOutputContent, extractReasoningText, extractToolFallbackContext, extractVisibleOutput, isLikelyToolArgsObject, isToolCallParseFailure, STRICT_TOOL_JSON_INSTRUCTION, sanitizeVisibleText, tryParseJson, wrapResolvedModel, } from "./parsing/output-parsing.js";
|
|
16
|
+
import { extractEmptyAssistantMessageFailure, extractContentBlocks, extractOutputContent, extractReasoningText, extractToolFallbackContext, extractVisibleOutput, salvageToolArgs, isLikelyToolArgsObject, isToolCallParseFailure, STRICT_TOOL_JSON_INSTRUCTION, sanitizeVisibleText, tryParseJson, wrapResolvedModel, } from "./parsing/output-parsing.js";
|
|
16
17
|
import { computeIncrementalOutput, extractAgentStep, extractInterruptPayload, extractReasoningStreamOutput, extractStateStreamOutput, extractVisibleStreamOutput, extractTerminalStreamOutput, extractToolResult, normalizeTerminalOutputKey, readStreamDelta, } from "./parsing/stream-event-parsing.js";
|
|
17
18
|
import { wrapToolForExecution } from "./tool-hitl.js";
|
|
18
19
|
import { resolveDeclaredMiddleware } from "./declared-middleware.js";
|
|
@@ -24,6 +25,17 @@ function countConfiguredTools(binding) {
|
|
|
24
25
|
}
|
|
25
26
|
const AGENT_INTERRUPT_SENTINEL_PREFIX = "__agent_harness_interrupt__:";
|
|
26
27
|
const MODEL_SAFE_TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
28
|
+
const UPSTREAM_BUILTIN_MIDDLEWARE_TOOL_NAMES = Object.freeze([
|
|
29
|
+
"write_todos",
|
|
30
|
+
"ls",
|
|
31
|
+
"read_file",
|
|
32
|
+
"write_file",
|
|
33
|
+
"edit_file",
|
|
34
|
+
"glob",
|
|
35
|
+
"grep",
|
|
36
|
+
"execute",
|
|
37
|
+
"task",
|
|
38
|
+
]);
|
|
27
39
|
class RuntimeOperationTimeoutError extends Error {
|
|
28
40
|
operation;
|
|
29
41
|
timeoutMs;
|
|
@@ -152,7 +164,8 @@ function normalizeOpenAICompatibleInit(init) {
|
|
|
152
164
|
return normalized;
|
|
153
165
|
}
|
|
154
166
|
function sanitizeToolNameForModel(name) {
|
|
155
|
-
const
|
|
167
|
+
const withoutNamespace = name.includes(".") ? name.split(".").at(-1) ?? name : name;
|
|
168
|
+
const sanitized = withoutNamespace
|
|
156
169
|
.replaceAll(".", "__")
|
|
157
170
|
.replace(/[^a-zA-Z0-9_-]+/g, "_")
|
|
158
171
|
.replace(/^_+|_+$/g, "");
|
|
@@ -252,6 +265,193 @@ function normalizeResolvedToolSchema(resolvedTool) {
|
|
|
252
265
|
}
|
|
253
266
|
return z.object({}).passthrough();
|
|
254
267
|
}
|
|
268
|
+
function isRecord(value) {
|
|
269
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
270
|
+
}
|
|
271
|
+
function isObject(value) {
|
|
272
|
+
return isRecord(value);
|
|
273
|
+
}
|
|
274
|
+
function stringifyToolOutput(output) {
|
|
275
|
+
if (typeof output === "string") {
|
|
276
|
+
return output;
|
|
277
|
+
}
|
|
278
|
+
try {
|
|
279
|
+
return JSON.stringify(output);
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
return `${String(output)}`;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function normalizeToolArgsForSchema(args, schema) {
|
|
286
|
+
const schemaDef = isObject(schema) ? schema._def : undefined;
|
|
287
|
+
const shape = schemaDef
|
|
288
|
+
? isRecord(schemaDef.shape)
|
|
289
|
+
? schemaDef.shape
|
|
290
|
+
: typeof schemaDef.shape === "function"
|
|
291
|
+
? schemaDef.shape()
|
|
292
|
+
: undefined
|
|
293
|
+
: undefined;
|
|
294
|
+
if (!shape || !isRecord(shape)) {
|
|
295
|
+
return args;
|
|
296
|
+
}
|
|
297
|
+
const keys = Object.keys(shape);
|
|
298
|
+
if (keys.length !== 1) {
|
|
299
|
+
return args;
|
|
300
|
+
}
|
|
301
|
+
const [expectedKey] = keys;
|
|
302
|
+
if (expectedKey in args) {
|
|
303
|
+
return args;
|
|
304
|
+
}
|
|
305
|
+
const aliasesByExpected = {
|
|
306
|
+
city: ["location", "locality", "place"],
|
|
307
|
+
location: ["city", "city_name"],
|
|
308
|
+
};
|
|
309
|
+
const aliases = aliasesByExpected[expectedKey] ?? [];
|
|
310
|
+
const aliasKey = aliases.find((candidate) => candidate in args);
|
|
311
|
+
if (!aliasKey || !(aliasKey in args)) {
|
|
312
|
+
return args;
|
|
313
|
+
}
|
|
314
|
+
return {
|
|
315
|
+
...args,
|
|
316
|
+
[expectedKey]: args[aliasKey],
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
function extractToolCallsFromResult(result) {
|
|
320
|
+
const messages = isRecord(result) && Array.isArray(result.messages) ? result.messages : [];
|
|
321
|
+
const lastMessage = messages.at(-1);
|
|
322
|
+
if (!isObject(lastMessage)) {
|
|
323
|
+
return [];
|
|
324
|
+
}
|
|
325
|
+
const messageKwargs = isObject(lastMessage.kwargs) ? lastMessage.kwargs : undefined;
|
|
326
|
+
const rawToolCalls = Array.isArray(lastMessage.tool_calls)
|
|
327
|
+
? (lastMessage.tool_calls ?? [])
|
|
328
|
+
: Array.isArray(messageKwargs?.tool_calls)
|
|
329
|
+
? messageKwargs.tool_calls
|
|
330
|
+
: [];
|
|
331
|
+
return rawToolCalls
|
|
332
|
+
.map((toolCall) => {
|
|
333
|
+
if (!isObject(toolCall)) {
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
const functionPayload = isObject(toolCall.function) ? toolCall.function : undefined;
|
|
337
|
+
const name = typeof toolCall.name === "string"
|
|
338
|
+
? toolCall.name
|
|
339
|
+
: typeof functionPayload?.name === "string"
|
|
340
|
+
? functionPayload.name
|
|
341
|
+
: null;
|
|
342
|
+
if (!name) {
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
const rawArgs = salvageToolArgs(toolCall.args ?? functionPayload?.arguments) ?? {};
|
|
346
|
+
if (!isObject(rawArgs)) {
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
const id = typeof toolCall.id === "string" ? toolCall.id : undefined;
|
|
350
|
+
return { id, name, args: rawArgs };
|
|
351
|
+
})
|
|
352
|
+
.filter((item) => item !== null);
|
|
353
|
+
}
|
|
354
|
+
function truncateLines(lines, maxChars = 12_000) {
|
|
355
|
+
const joined = lines.join("\n");
|
|
356
|
+
if (joined.length <= maxChars) {
|
|
357
|
+
return joined;
|
|
358
|
+
}
|
|
359
|
+
return `${joined.slice(0, maxChars - 18)}\n...[truncated]`;
|
|
360
|
+
}
|
|
361
|
+
function summarizeBuiltinWriteTodosArgs(args) {
|
|
362
|
+
const todos = Array.isArray(args.todos) ? args.todos : [];
|
|
363
|
+
const items = todos.flatMap((todo) => {
|
|
364
|
+
if (!isObject(todo)) {
|
|
365
|
+
return [];
|
|
366
|
+
}
|
|
367
|
+
const content = typeof todo.content === "string" && todo.content.trim().length > 0
|
|
368
|
+
? todo.content.trim()
|
|
369
|
+
: typeof todo.description === "string" && todo.description.trim().length > 0
|
|
370
|
+
? todo.description.trim()
|
|
371
|
+
: "";
|
|
372
|
+
const status = typeof todo.status === "string" && todo.status.trim().length > 0 ? todo.status.trim() : "pending";
|
|
373
|
+
return content ? [{ content, status }] : [];
|
|
374
|
+
});
|
|
375
|
+
return {
|
|
376
|
+
total: items.length,
|
|
377
|
+
pending: items.filter((item) => item.status !== "completed").length,
|
|
378
|
+
completed: items.filter((item) => item.status === "completed").length,
|
|
379
|
+
items,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
function createModelFacingToolNameCandidates(toolName) {
|
|
383
|
+
const trimmed = toolName.trim();
|
|
384
|
+
if (!trimmed) {
|
|
385
|
+
return [];
|
|
386
|
+
}
|
|
387
|
+
const lastSegment = trimmed.split(".").at(-1) ?? "";
|
|
388
|
+
const candidates = new Set([
|
|
389
|
+
trimmed,
|
|
390
|
+
sanitizeToolNameForModel(trimmed),
|
|
391
|
+
lastSegment,
|
|
392
|
+
lastSegment.trim() ? sanitizeToolNameForModel(lastSegment) : "",
|
|
393
|
+
trimmed.replace(/[^a-zA-Z0-9_]+/g, ""),
|
|
394
|
+
]);
|
|
395
|
+
candidates.delete("");
|
|
396
|
+
return [...candidates];
|
|
397
|
+
}
|
|
398
|
+
function createModelFacingToolNameLookupCandidates(toolName) {
|
|
399
|
+
const trimmed = toolName.trim();
|
|
400
|
+
if (!trimmed) {
|
|
401
|
+
return [];
|
|
402
|
+
}
|
|
403
|
+
const candidates = new Set();
|
|
404
|
+
const add = (value) => {
|
|
405
|
+
for (const candidate of createModelFacingToolNameCandidates(value)) {
|
|
406
|
+
candidates.add(candidate);
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
const addSuffixStripped = (value) => {
|
|
410
|
+
const stripped = value.includes(".") ? value.split(".").at(-1) ?? value : value;
|
|
411
|
+
if (stripped !== value) {
|
|
412
|
+
add(stripped);
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
add(trimmed);
|
|
416
|
+
if (trimmed.startsWith("functions.")) {
|
|
417
|
+
const stripped = trimmed.substring("functions.".length);
|
|
418
|
+
add(stripped);
|
|
419
|
+
addSuffixStripped(stripped);
|
|
420
|
+
}
|
|
421
|
+
else if (trimmed.startsWith("function.")) {
|
|
422
|
+
const stripped = trimmed.substring("function.".length);
|
|
423
|
+
add(stripped);
|
|
424
|
+
addSuffixStripped(stripped);
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
addSuffixStripped(trimmed);
|
|
428
|
+
}
|
|
429
|
+
return [...candidates];
|
|
430
|
+
}
|
|
431
|
+
function resolveModelFacingToolName(toolName, mapping, tools) {
|
|
432
|
+
const candidateNames = createModelFacingToolNameLookupCandidates(toolName);
|
|
433
|
+
const candidateSet = new Set(candidateNames);
|
|
434
|
+
for (const candidate of candidateNames) {
|
|
435
|
+
const mappedToolName = mapping.modelFacingToOriginal.get(candidate);
|
|
436
|
+
if (mappedToolName) {
|
|
437
|
+
return mappedToolName;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
for (const candidate of candidateNames) {
|
|
441
|
+
const directMatch = tools.find((tool) => tool.name === candidate);
|
|
442
|
+
if (directMatch) {
|
|
443
|
+
return directMatch.name;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
const candidateMatches = tools.filter((tool) => {
|
|
447
|
+
const modelFacingCandidates = createModelFacingToolNameCandidates(tool.name);
|
|
448
|
+
return modelFacingCandidates.some((candidate) => candidateSet.has(candidate));
|
|
449
|
+
});
|
|
450
|
+
if (candidateMatches.length === 1) {
|
|
451
|
+
return candidateMatches[0].name;
|
|
452
|
+
}
|
|
453
|
+
return toolName;
|
|
454
|
+
}
|
|
255
455
|
function asStructuredExecutableTool(resolvedTool, modelFacingName, description) {
|
|
256
456
|
if (!hasCallableToolHandler(resolvedTool)) {
|
|
257
457
|
return resolvedTool;
|
|
@@ -589,6 +789,228 @@ export class AgentRuntimeAdapter {
|
|
|
589
789
|
: 10,
|
|
590
790
|
});
|
|
591
791
|
}
|
|
792
|
+
resolveBuiltinMiddlewareBackend(binding, options = {}) {
|
|
793
|
+
const runtimeState = {
|
|
794
|
+
...(options.state ?? {}),
|
|
795
|
+
...(isRecord(options.files) ? { files: options.files } : {}),
|
|
796
|
+
};
|
|
797
|
+
const runtimeLike = {
|
|
798
|
+
state: runtimeState,
|
|
799
|
+
store: this.options.storeResolver?.(binding),
|
|
800
|
+
};
|
|
801
|
+
const configuredBackend = isDeepAgentBinding(binding)
|
|
802
|
+
? this.options.backendResolver?.(binding)
|
|
803
|
+
: this.resolveFilesystemBackend(binding);
|
|
804
|
+
if (typeof configuredBackend === "function") {
|
|
805
|
+
return configuredBackend(runtimeLike);
|
|
806
|
+
}
|
|
807
|
+
if (configuredBackend) {
|
|
808
|
+
return configuredBackend;
|
|
809
|
+
}
|
|
810
|
+
return new StateBackend(runtimeLike);
|
|
811
|
+
}
|
|
812
|
+
async invokeBuiltinTaskTool(binding, input, options = {}) {
|
|
813
|
+
if (!isDeepAgentBinding(binding)) {
|
|
814
|
+
throw new Error("The built-in task tool is only available for deepagent bindings.");
|
|
815
|
+
}
|
|
816
|
+
const params = getBindingDeepAgentParams(binding);
|
|
817
|
+
if (!params) {
|
|
818
|
+
throw new Error(`Agent ${binding.agent.id} has no deepagent params`);
|
|
819
|
+
}
|
|
820
|
+
const typedInput = isObject(input) ? input : {};
|
|
821
|
+
const description = typeof typedInput.description === "string" ? typedInput.description : "";
|
|
822
|
+
const subagentType = typeof typedInput.subagent_type === "string" ? typedInput.subagent_type : "";
|
|
823
|
+
const builtinBackend = this.resolveBuiltinMiddlewareBackend(binding, options);
|
|
824
|
+
const resolvedSubagents = await this.resolveSubagents(params.subagents, binding);
|
|
825
|
+
const selectedSubagent = resolvedSubagents.find((subagent) => subagent.name === subagentType);
|
|
826
|
+
if (!selectedSubagent) {
|
|
827
|
+
const allowed = [
|
|
828
|
+
...resolvedSubagents.map((subagent) => subagent.name),
|
|
829
|
+
...(params.generalPurposeAgent ? ["general-purpose"] : []),
|
|
830
|
+
];
|
|
831
|
+
throw new Error(`Error: invoked agent of type ${subagentType}, the only allowed types are ${allowed.map((name) => `\`${name}\``).join(", ")}`);
|
|
832
|
+
}
|
|
833
|
+
const summarizationModel = selectedSubagent.model
|
|
834
|
+
? await this.resolveModel(selectedSubagent.model)
|
|
835
|
+
: await this.resolveModel(params.model);
|
|
836
|
+
const middleware = [
|
|
837
|
+
...(selectedSubagent.skills?.length
|
|
838
|
+
? [createSkillsMiddleware({ backend: builtinBackend, sources: selectedSubagent.skills })]
|
|
839
|
+
: []),
|
|
840
|
+
...(selectedSubagent.memory?.length
|
|
841
|
+
? [createMemoryMiddleware({ backend: builtinBackend, sources: selectedSubagent.memory })]
|
|
842
|
+
: []),
|
|
843
|
+
...(selectedSubagent.middleware ??
|
|
844
|
+
[
|
|
845
|
+
createPatchToolCallsMiddleware(),
|
|
846
|
+
createSummarizationMiddleware({
|
|
847
|
+
model: summarizationModel,
|
|
848
|
+
backend: builtinBackend,
|
|
849
|
+
}),
|
|
850
|
+
]),
|
|
851
|
+
...(selectedSubagent.interruptOn
|
|
852
|
+
? [humanInTheLoopMiddleware({
|
|
853
|
+
interruptOn: this.compileInterruptOn(selectedSubagent.tools ?? [], selectedSubagent.interruptOn),
|
|
854
|
+
})]
|
|
855
|
+
: []),
|
|
856
|
+
];
|
|
857
|
+
const runnable = createAgent({
|
|
858
|
+
model: (selectedSubagent.model ?? (await this.resolveModel(params.model))),
|
|
859
|
+
tools: (selectedSubagent.tools ?? this.resolveTools(params.tools, binding)),
|
|
860
|
+
systemPrompt: selectedSubagent.systemPrompt ?? DEFAULT_SUBAGENT_PROMPT,
|
|
861
|
+
middleware: middleware,
|
|
862
|
+
responseFormat: selectedSubagent.responseFormat,
|
|
863
|
+
contextSchema: selectedSubagent.contextSchema,
|
|
864
|
+
name: selectedSubagent.name,
|
|
865
|
+
description: selectedSubagent.description,
|
|
866
|
+
});
|
|
867
|
+
const result = await runnable.invoke({ messages: [new HumanMessage({ content: description })] }, { configurable: { thread_id: `${binding.agent.id}:builtin-task` }, ...(options.context ? { context: options.context } : {}) });
|
|
868
|
+
const visibleOutput = extractVisibleOutput(result);
|
|
869
|
+
const fallbackOutput = extractToolFallbackContext(result);
|
|
870
|
+
return visibleOutput || fallbackOutput || JSON.stringify(result);
|
|
871
|
+
}
|
|
872
|
+
async resolveBuiltinMiddlewareTools(binding, options = {}) {
|
|
873
|
+
const tools = new Map();
|
|
874
|
+
const backend = this.resolveBuiltinMiddlewareBackend(binding, options);
|
|
875
|
+
tools.set("write_todos", {
|
|
876
|
+
name: "write_todos",
|
|
877
|
+
schema: z.object({
|
|
878
|
+
todos: z.array(z.object({}).passthrough()).optional(),
|
|
879
|
+
}).passthrough(),
|
|
880
|
+
invoke: async (input) => {
|
|
881
|
+
const args = isObject(input) ? input : {};
|
|
882
|
+
const summary = summarizeBuiltinWriteTodosArgs(args);
|
|
883
|
+
return {
|
|
884
|
+
ok: true,
|
|
885
|
+
tool: "write_todos",
|
|
886
|
+
message: `Tracked ${summary.total} todo item(s).`,
|
|
887
|
+
summary,
|
|
888
|
+
};
|
|
889
|
+
},
|
|
890
|
+
});
|
|
891
|
+
tools.set("ls", {
|
|
892
|
+
name: "ls",
|
|
893
|
+
schema: z.object({ path: z.string().optional().default("/") }).passthrough(),
|
|
894
|
+
invoke: async (input) => {
|
|
895
|
+
const targetPath = isObject(input) && typeof input.path === "string" ? input.path : "/";
|
|
896
|
+
const infos = (await Promise.resolve(backend.lsInfo?.(targetPath))) ?? [];
|
|
897
|
+
if (infos.length === 0) {
|
|
898
|
+
return `No files found in ${targetPath}`;
|
|
899
|
+
}
|
|
900
|
+
return truncateLines(infos.map((info) => info.is_dir ? `${info.path} (directory)` : `${info.path}${info.size ? ` (${info.size} bytes)` : ""}`));
|
|
901
|
+
},
|
|
902
|
+
});
|
|
903
|
+
tools.set("read_file", {
|
|
904
|
+
name: "read_file",
|
|
905
|
+
schema: z.object({
|
|
906
|
+
file_path: z.string(),
|
|
907
|
+
offset: z.number().optional(),
|
|
908
|
+
limit: z.number().optional(),
|
|
909
|
+
}).passthrough(),
|
|
910
|
+
invoke: async (input) => {
|
|
911
|
+
const typed = isObject(input) ? input : {};
|
|
912
|
+
const filePath = typeof typed.file_path === "string" ? typed.file_path : "";
|
|
913
|
+
const offset = typeof typed.offset === "number" ? typed.offset : 0;
|
|
914
|
+
const limit = typeof typed.limit === "number" ? typed.limit : 500;
|
|
915
|
+
return Promise.resolve(backend.read?.(filePath, offset, limit)) ?? "";
|
|
916
|
+
},
|
|
917
|
+
});
|
|
918
|
+
tools.set("write_file", {
|
|
919
|
+
name: "write_file",
|
|
920
|
+
schema: z.object({ file_path: z.string(), content: z.string().optional() }).passthrough(),
|
|
921
|
+
invoke: async (input) => {
|
|
922
|
+
const typed = isObject(input) ? input : {};
|
|
923
|
+
const result = await Promise.resolve(backend.write?.(typeof typed.file_path === "string" ? typed.file_path : "", typeof typed.content === "string" ? typed.content : ""));
|
|
924
|
+
return result?.error ?? `Successfully wrote to '${result?.path ?? (typed.file_path ?? "")}'`;
|
|
925
|
+
},
|
|
926
|
+
});
|
|
927
|
+
tools.set("edit_file", {
|
|
928
|
+
name: "edit_file",
|
|
929
|
+
schema: z.object({
|
|
930
|
+
file_path: z.string(),
|
|
931
|
+
old_string: z.string(),
|
|
932
|
+
new_string: z.string(),
|
|
933
|
+
replace_all: z.boolean().optional(),
|
|
934
|
+
}).passthrough(),
|
|
935
|
+
invoke: async (input) => {
|
|
936
|
+
const typed = isObject(input) ? input : {};
|
|
937
|
+
const result = await Promise.resolve(backend.edit?.(typeof typed.file_path === "string" ? typed.file_path : "", typeof typed.old_string === "string" ? typed.old_string : "", typeof typed.new_string === "string" ? typed.new_string : "", typed.replace_all === true));
|
|
938
|
+
return result?.error ?? `Successfully replaced ${result?.occurrences ?? 0} occurrence(s) in '${result?.path ?? (typed.file_path ?? "")}'`;
|
|
939
|
+
},
|
|
940
|
+
});
|
|
941
|
+
tools.set("glob", {
|
|
942
|
+
name: "glob",
|
|
943
|
+
schema: z.object({ pattern: z.string(), path: z.string().optional().default("/") }).passthrough(),
|
|
944
|
+
invoke: async (input) => {
|
|
945
|
+
const typed = isObject(input) ? input : {};
|
|
946
|
+
const pattern = typeof typed.pattern === "string" ? typed.pattern : "";
|
|
947
|
+
const targetPath = typeof typed.path === "string" ? typed.path : "/";
|
|
948
|
+
const infos = (await Promise.resolve(backend.globInfo?.(pattern, targetPath))) ?? [];
|
|
949
|
+
if (infos.length === 0) {
|
|
950
|
+
return `No files found matching pattern '${pattern}'`;
|
|
951
|
+
}
|
|
952
|
+
return truncateLines(infos.map((info) => info.path));
|
|
953
|
+
},
|
|
954
|
+
});
|
|
955
|
+
tools.set("grep", {
|
|
956
|
+
name: "grep",
|
|
957
|
+
schema: z.object({
|
|
958
|
+
pattern: z.string(),
|
|
959
|
+
path: z.string().optional().default("/"),
|
|
960
|
+
glob: z.string().nullable().optional(),
|
|
961
|
+
}).passthrough(),
|
|
962
|
+
invoke: async (input) => {
|
|
963
|
+
const typed = isObject(input) ? input : {};
|
|
964
|
+
const result = await Promise.resolve(backend.grepRaw?.(typeof typed.pattern === "string" ? typed.pattern : "", typeof typed.path === "string" ? typed.path : "/", typeof typed.glob === "string" ? typed.glob : null));
|
|
965
|
+
if (typeof result === "string") {
|
|
966
|
+
return result;
|
|
967
|
+
}
|
|
968
|
+
if (!result || result.length === 0) {
|
|
969
|
+
return `No matches found for pattern '${typeof typed.pattern === "string" ? typed.pattern : ""}'`;
|
|
970
|
+
}
|
|
971
|
+
const lines = [];
|
|
972
|
+
let currentFile = "";
|
|
973
|
+
for (const match of result) {
|
|
974
|
+
if (match.path !== currentFile) {
|
|
975
|
+
currentFile = match.path;
|
|
976
|
+
lines.push(`\n${currentFile}:`);
|
|
977
|
+
}
|
|
978
|
+
lines.push(` ${match.line}: ${match.text}`);
|
|
979
|
+
}
|
|
980
|
+
return truncateLines(lines);
|
|
981
|
+
},
|
|
982
|
+
});
|
|
983
|
+
tools.set("execute", {
|
|
984
|
+
name: "execute",
|
|
985
|
+
schema: z.object({ command: z.string() }).passthrough(),
|
|
986
|
+
invoke: async (input) => {
|
|
987
|
+
if (!isSandboxBackend(backend) || typeof backend.execute !== "function") {
|
|
988
|
+
return "Error: Execution not available. This agent's backend does not support command execution (SandboxBackendProtocol).";
|
|
989
|
+
}
|
|
990
|
+
const typed = isObject(input) ? input : {};
|
|
991
|
+
const result = await Promise.resolve(backend.execute(typeof typed.command === "string" ? typed.command : ""));
|
|
992
|
+
const parts = [result.output];
|
|
993
|
+
if (result.exitCode !== null) {
|
|
994
|
+
parts.push(`\n[Command ${result.exitCode === 0 ? "succeeded" : "failed"} with exit code ${result.exitCode}]`);
|
|
995
|
+
}
|
|
996
|
+
if (result.truncated) {
|
|
997
|
+
parts.push("\n[Output was truncated due to size limits]");
|
|
998
|
+
}
|
|
999
|
+
return parts.join("");
|
|
1000
|
+
},
|
|
1001
|
+
});
|
|
1002
|
+
if (isDeepAgentBinding(binding)) {
|
|
1003
|
+
tools.set("task", {
|
|
1004
|
+
name: "task",
|
|
1005
|
+
schema: z.object({
|
|
1006
|
+
description: z.string(),
|
|
1007
|
+
subagent_type: z.string(),
|
|
1008
|
+
}).passthrough(),
|
|
1009
|
+
invoke: async (input) => this.invokeBuiltinTaskTool(binding, input, options),
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
return tools;
|
|
1013
|
+
}
|
|
592
1014
|
async resolveLangChainAutomaticMiddleware(binding) {
|
|
593
1015
|
const params = getBindingLangChainParams(binding);
|
|
594
1016
|
if (!params) {
|
|
@@ -781,17 +1203,117 @@ export class AgentRuntimeAdapter {
|
|
|
781
1203
|
? this.buildInvocationRequest(binding, history, input, options)
|
|
782
1204
|
: new Command({ resume: resumePayload });
|
|
783
1205
|
let result;
|
|
784
|
-
|
|
785
|
-
const runnable = await this.create(
|
|
786
|
-
|
|
1206
|
+
const callRuntime = async (activeBinding, activeRequest) => {
|
|
1207
|
+
const runnable = await this.create(activeBinding);
|
|
1208
|
+
return (await this.withTimeout(() => runnable.invoke(activeRequest, { configurable: { thread_id: threadId }, ...(options.context ? { context: options.context } : {}) }), this.resolveBindingTimeout(activeBinding), "agent invoke", "invoke"));
|
|
1209
|
+
};
|
|
1210
|
+
const callRuntimeWithToolParseRecovery = async (activeRequest) => {
|
|
1211
|
+
try {
|
|
1212
|
+
return await callRuntime(binding, activeRequest);
|
|
1213
|
+
}
|
|
1214
|
+
catch (error) {
|
|
1215
|
+
if (resumePayload !== undefined || !isToolCallParseFailure(error)) {
|
|
1216
|
+
throw error;
|
|
1217
|
+
}
|
|
1218
|
+
return callRuntime(this.applyStrictToolJsonInstruction(binding), activeRequest);
|
|
1219
|
+
}
|
|
1220
|
+
};
|
|
1221
|
+
const executedToolResults = [];
|
|
1222
|
+
if (resumePayload !== undefined) {
|
|
1223
|
+
result = await callRuntimeWithToolParseRecovery(request);
|
|
787
1224
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
1225
|
+
else {
|
|
1226
|
+
const primaryTools = getBindingPrimaryTools(binding);
|
|
1227
|
+
const defersToUpstreamHitlExecution = primaryTools.some((tool) => tool.hitl?.enabled === true);
|
|
1228
|
+
if (defersToUpstreamHitlExecution) {
|
|
1229
|
+
result = await callRuntimeWithToolParseRecovery(request);
|
|
1230
|
+
}
|
|
1231
|
+
else {
|
|
1232
|
+
const resolvedTools = this.resolveTools(primaryTools, binding);
|
|
1233
|
+
const toolNameMapping = this.buildToolNameMapping(primaryTools);
|
|
1234
|
+
const executableTools = new Map();
|
|
1235
|
+
const builtinExecutableTools = await this.resolveBuiltinMiddlewareTools(binding, options);
|
|
1236
|
+
for (let index = 0; index < primaryTools.length; index += 1) {
|
|
1237
|
+
const compiledTool = primaryTools[index];
|
|
1238
|
+
const resolvedTool = resolvedTools[index];
|
|
1239
|
+
if (!compiledTool || !resolvedTool || !hasCallableToolHandler(resolvedTool)) {
|
|
1240
|
+
continue;
|
|
1241
|
+
}
|
|
1242
|
+
const handler = async (toolInput) => {
|
|
1243
|
+
const callable = typeof resolvedTool.invoke === "function"
|
|
1244
|
+
? resolvedTool.invoke
|
|
1245
|
+
: typeof resolvedTool.call === "function"
|
|
1246
|
+
? resolvedTool.call
|
|
1247
|
+
: resolvedTool.func;
|
|
1248
|
+
if (!callable) {
|
|
1249
|
+
throw new Error(`Tool ${compiledTool.name} has no callable handler.`);
|
|
1250
|
+
}
|
|
1251
|
+
return Promise.resolve(callable.call(resolvedTool, toolInput, options.context ? { context: options.context } : undefined));
|
|
1252
|
+
};
|
|
1253
|
+
const modelFacingName = toolNameMapping.originalToModelFacing.get(compiledTool.name) ?? compiledTool.name;
|
|
1254
|
+
const normalizedSchema = normalizeResolvedToolSchema(resolvedTool);
|
|
1255
|
+
executableTools.set(modelFacingName, {
|
|
1256
|
+
name: compiledTool.name,
|
|
1257
|
+
schema: normalizedSchema,
|
|
1258
|
+
invoke: handler,
|
|
1259
|
+
});
|
|
1260
|
+
executableTools.set(compiledTool.name, {
|
|
1261
|
+
name: compiledTool.name,
|
|
1262
|
+
schema: normalizedSchema,
|
|
1263
|
+
invoke: handler,
|
|
1264
|
+
});
|
|
1265
|
+
}
|
|
1266
|
+
let activeRequest = request;
|
|
1267
|
+
let currentMessages = Array.isArray(activeRequest.messages) ? [...activeRequest.messages] : [];
|
|
1268
|
+
const maxToolIterations = 8;
|
|
1269
|
+
for (let iteration = 0; iteration < maxToolIterations; iteration += 1) {
|
|
1270
|
+
result = await callRuntimeWithToolParseRecovery(activeRequest);
|
|
1271
|
+
const toolCalls = extractToolCallsFromResult(result);
|
|
1272
|
+
if (toolCalls.length === 0) {
|
|
1273
|
+
break;
|
|
1274
|
+
}
|
|
1275
|
+
if (iteration + 1 === maxToolIterations) {
|
|
1276
|
+
throw new Error(`Tool-calling loop exceeded the maximum of ${maxToolIterations} iterations`);
|
|
1277
|
+
}
|
|
1278
|
+
const resultMessages = result.messages;
|
|
1279
|
+
const nextMessages = Array.isArray(resultMessages)
|
|
1280
|
+
? [...resultMessages]
|
|
1281
|
+
: [...currentMessages];
|
|
1282
|
+
for (let toolIndex = 0; toolIndex < toolCalls.length; toolIndex += 1) {
|
|
1283
|
+
const toolCall = toolCalls[toolIndex];
|
|
1284
|
+
const resolvedToolName = resolveModelFacingToolName(toolCall.name, toolNameMapping, primaryTools);
|
|
1285
|
+
const executable = executableTools.get(toolCall.name) ?? executableTools.get(resolvedToolName);
|
|
1286
|
+
const builtinExecutable = builtinExecutableTools.get(toolCall.name) ??
|
|
1287
|
+
builtinExecutableTools.get(resolvedToolName) ??
|
|
1288
|
+
createModelFacingToolNameLookupCandidates(toolCall.name)
|
|
1289
|
+
.map((candidate) => builtinExecutableTools.get(candidate))
|
|
1290
|
+
.find((candidate) => candidate !== undefined);
|
|
1291
|
+
const activeExecutable = executable ?? builtinExecutable;
|
|
1292
|
+
if (!activeExecutable) {
|
|
1293
|
+
throw new Error(`Tool ${toolCall.name} is not configured for this agent.`);
|
|
1294
|
+
}
|
|
1295
|
+
const normalizedArgs = normalizeToolArgsForSchema(toolCall.args, activeExecutable.schema);
|
|
1296
|
+
const toolResult = await activeExecutable.invoke(normalizedArgs);
|
|
1297
|
+
executedToolResults.push({
|
|
1298
|
+
toolName: activeExecutable.name,
|
|
1299
|
+
output: toolResult,
|
|
1300
|
+
});
|
|
1301
|
+
nextMessages.push(new ToolMessage({
|
|
1302
|
+
name: activeExecutable.name,
|
|
1303
|
+
tool_call_id: toolCall.id ?? `tool-${iteration + 1}-${toolIndex + 1}`,
|
|
1304
|
+
content: stringifyToolOutput(toolResult),
|
|
1305
|
+
}));
|
|
1306
|
+
}
|
|
1307
|
+
currentMessages = nextMessages;
|
|
1308
|
+
activeRequest = {
|
|
1309
|
+
...activeRequest,
|
|
1310
|
+
messages: currentMessages,
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
791
1313
|
}
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
1314
|
+
}
|
|
1315
|
+
if (!result) {
|
|
1316
|
+
throw new Error("Agent invocation returned no result");
|
|
795
1317
|
}
|
|
796
1318
|
const interruptContent = Array.isArray(result.__interrupt__) && result.__interrupt__.length > 0 ? JSON.stringify(result.__interrupt__) : undefined;
|
|
797
1319
|
const extractedOutput = extractVisibleOutput(result);
|
|
@@ -819,6 +1341,7 @@ export class AgentRuntimeAdapter {
|
|
|
819
1341
|
...(contentBlocks.length > 0 ? { contentBlocks } : {}),
|
|
820
1342
|
...(structuredResponse !== undefined ? { structuredResponse } : {}),
|
|
821
1343
|
metadata: {
|
|
1344
|
+
...(executedToolResults.length > 0 ? { executedToolResults } : {}),
|
|
822
1345
|
...(structuredResponse !== undefined ? { structuredResponse } : {}),
|
|
823
1346
|
...(outputContent !== undefined ? { outputContent } : {}),
|
|
824
1347
|
...(contentBlocks.length > 0 ? { contentBlocks } : {}),
|
|
@@ -833,6 +1356,12 @@ export class AgentRuntimeAdapter {
|
|
|
833
1356
|
const invokeTimeoutMs = this.resolveBindingTimeout(binding);
|
|
834
1357
|
const streamIdleTimeoutMs = this.resolveStreamIdleTimeout(binding);
|
|
835
1358
|
const streamDeadlineAt = invokeTimeoutMs ? Date.now() + invokeTimeoutMs : undefined;
|
|
1359
|
+
const primaryTools = getBindingPrimaryTools(binding);
|
|
1360
|
+
const toolNameMapping = this.buildToolNameMapping(primaryTools);
|
|
1361
|
+
const primaryModel = getBindingPrimaryModel(binding);
|
|
1362
|
+
const forceInvokeFallback = isLangChainBinding(binding) &&
|
|
1363
|
+
primaryTools.length > 0 &&
|
|
1364
|
+
primaryModel?.provider === "openai-compatible";
|
|
836
1365
|
if (isLangChainBinding(binding)) {
|
|
837
1366
|
const langchainParams = getBindingLangChainParams(binding);
|
|
838
1367
|
const resolvedModel = (await this.resolveModel(langchainParams.model));
|
|
@@ -867,7 +1396,7 @@ export class AgentRuntimeAdapter {
|
|
|
867
1396
|
}
|
|
868
1397
|
const runnable = await this.create(binding);
|
|
869
1398
|
const request = this.buildInvocationRequest(binding, history, input, options);
|
|
870
|
-
if (typeof runnable.streamEvents === "function") {
|
|
1399
|
+
if (!forceInvokeFallback && typeof runnable.streamEvents === "function") {
|
|
871
1400
|
const events = await this.withTimeout(() => runnable.streamEvents(request, { configurable: { thread_id: threadId }, version: "v2", ...(options.context ? { context: options.context } : {}) }), computeRemainingTimeoutMs(streamDeadlineAt, invokeTimeoutMs), "agent streamEvents start", "stream");
|
|
872
1401
|
const allowVisibleStreamDeltas = isLangChainBinding(binding);
|
|
873
1402
|
let emittedOutput = "";
|
|
@@ -912,7 +1441,13 @@ export class AgentRuntimeAdapter {
|
|
|
912
1441
|
const toolResult = extractToolResult(event);
|
|
913
1442
|
if (toolResult) {
|
|
914
1443
|
emittedToolError = emittedToolError || toolResult.isError === true;
|
|
915
|
-
|
|
1444
|
+
const resolvedToolName = resolveModelFacingToolName(toolResult.toolName, toolNameMapping, primaryTools);
|
|
1445
|
+
yield {
|
|
1446
|
+
kind: "tool-result",
|
|
1447
|
+
toolName: resolvedToolName,
|
|
1448
|
+
output: toolResult.output,
|
|
1449
|
+
isError: toolResult.isError,
|
|
1450
|
+
};
|
|
916
1451
|
}
|
|
917
1452
|
const output = extractTerminalStreamOutput(event);
|
|
918
1453
|
if (output) {
|
|
@@ -934,7 +1469,7 @@ export class AgentRuntimeAdapter {
|
|
|
934
1469
|
return;
|
|
935
1470
|
}
|
|
936
1471
|
}
|
|
937
|
-
if (isLangChainBinding(binding) && typeof runnable.stream === "function") {
|
|
1472
|
+
if (!forceInvokeFallback && isLangChainBinding(binding) && typeof runnable.stream === "function") {
|
|
938
1473
|
const stream = await this.withTimeout(() => runnable.stream(request, { configurable: { thread_id: threadId } }), computeRemainingTimeoutMs(streamDeadlineAt, invokeTimeoutMs), "agent stream start", "stream");
|
|
939
1474
|
let emitted = false;
|
|
940
1475
|
for await (const chunk of this.iterateWithTimeout(stream, streamIdleTimeoutMs, "agent stream", streamDeadlineAt, invokeTimeoutMs)) {
|
|
@@ -954,6 +1489,17 @@ export class AgentRuntimeAdapter {
|
|
|
954
1489
|
}
|
|
955
1490
|
}
|
|
956
1491
|
const result = await this.invoke(binding, input, threadId, threadId);
|
|
1492
|
+
const executedToolResults = Array.isArray(result.metadata?.executedToolResults)
|
|
1493
|
+
? result.metadata.executedToolResults
|
|
1494
|
+
: [];
|
|
1495
|
+
for (const toolResult of executedToolResults) {
|
|
1496
|
+
yield {
|
|
1497
|
+
kind: "tool-result",
|
|
1498
|
+
toolName: toolResult.toolName,
|
|
1499
|
+
output: toolResult.output,
|
|
1500
|
+
isError: toolResult.isError,
|
|
1501
|
+
};
|
|
1502
|
+
}
|
|
957
1503
|
if (result.output) {
|
|
958
1504
|
yield { kind: "content", content: sanitizeVisibleText(result.output) };
|
|
959
1505
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ApprovalRecord, HarnessEvent, HarnessStreamItem, MessageContent, RunStartOptions, RestartConversationOptions, RuntimeAdapterOptions, ResumeOptions, RunOptions, RunResult, ThreadSummary, ThreadRecord, WorkspaceBundle } from "../contracts/types.js";
|
|
1
|
+
import type { ApprovalRecord, HarnessEvent, HarnessStreamItem, MessageContent, RunRecord, RunStartOptions, RestartConversationOptions, RuntimeAdapterOptions, ResumeOptions, RunOptions, RunResult, RunSummary, ThreadSummary, ThreadRecord, WorkspaceBundle } from "../contracts/types.js";
|
|
2
2
|
import { type ToolMcpServerOptions } from "../mcp.js";
|
|
3
3
|
import { type InventoryAgentRecord, type InventorySkillRecord } from "./inventory.js";
|
|
4
4
|
import type { RequirementAssessmentOptions } from "./skill-requirements.js";
|
|
@@ -49,6 +49,12 @@ export declare class AgentHarnessRuntime {
|
|
|
49
49
|
listThreads(filter?: {
|
|
50
50
|
agentId?: string;
|
|
51
51
|
}): Promise<ThreadSummary[]>;
|
|
52
|
+
listRuns(filter?: {
|
|
53
|
+
agentId?: string;
|
|
54
|
+
threadId?: string;
|
|
55
|
+
state?: RunSummary["state"];
|
|
56
|
+
}): Promise<RunSummary[]>;
|
|
57
|
+
getRun(runId: string): Promise<RunRecord | null>;
|
|
52
58
|
private getSession;
|
|
53
59
|
getThread(threadId: string): Promise<ThreadRecord | null>;
|
|
54
60
|
listApprovals(filter?: {
|
|
@@ -94,6 +100,7 @@ export declare class AgentHarnessRuntime {
|
|
|
94
100
|
run(options: RunOptions): Promise<RunResult>;
|
|
95
101
|
streamEvents(options: RunStartOptions): AsyncGenerator<HarnessStreamItem>;
|
|
96
102
|
resume(options: ResumeOptions): Promise<RunResult>;
|
|
103
|
+
private buildResumePayload;
|
|
97
104
|
restartConversation(options: RestartConversationOptions): Promise<RunResult & {
|
|
98
105
|
restart: Record<string, string>;
|
|
99
106
|
}>;
|
package/dist/runtime/harness.js
CHANGED
|
@@ -15,7 +15,7 @@ import { FileBackedStore } from "./store.js";
|
|
|
15
15
|
import { CheckpointMaintenanceLoop, discoverCheckpointMaintenanceTargets, readCheckpointMaintenanceConfig, } from "./checkpoint-maintenance.js";
|
|
16
16
|
import { extractMessageText, normalizeMessageContent } from "../utils/message-content.js";
|
|
17
17
|
import { createToolMcpServerFromTools, serveToolsOverStdioFromHarness } from "../mcp.js";
|
|
18
|
-
import { getBindingAdapterKind, getBindingPrimaryTools, getBindingStoreConfig } from "./support/compiled-binding.js";
|
|
18
|
+
import { getBindingAdapterKind, getBindingPrimaryTools, getBindingStoreConfig, isDeepAgentBinding } from "./support/compiled-binding.js";
|
|
19
19
|
import { describeWorkspaceInventory, listAgentSkills as listWorkspaceAgentSkills, } from "./inventory.js";
|
|
20
20
|
export class AgentHarnessRuntime {
|
|
21
21
|
workspace;
|
|
@@ -234,6 +234,24 @@ export class AgentHarnessRuntime {
|
|
|
234
234
|
}
|
|
235
235
|
return threadSummaries.filter((thread) => thread.agentId === filter.agentId);
|
|
236
236
|
}
|
|
237
|
+
async listRuns(filter) {
|
|
238
|
+
const runs = await this.persistence.listRuns();
|
|
239
|
+
return runs.filter((run) => {
|
|
240
|
+
if (filter?.agentId && run.agentId !== filter.agentId) {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
if (filter?.threadId && run.threadId !== filter.threadId) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
if (filter?.state && run.state !== filter.state) {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
return true;
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
async getRun(runId) {
|
|
253
|
+
return this.persistence.getRun(runId);
|
|
254
|
+
}
|
|
237
255
|
async getSession(threadId) {
|
|
238
256
|
return this.persistence.getSession(threadId);
|
|
239
257
|
}
|
|
@@ -383,6 +401,9 @@ export class AgentHarnessRuntime {
|
|
|
383
401
|
const event = createHarnessEvent(threadId, runId, sequence, eventType, payload, source);
|
|
384
402
|
await this.persistence.appendEvent(event);
|
|
385
403
|
this.eventBus.publish(event);
|
|
404
|
+
if (this.threadMemorySync.shouldHandle(event)) {
|
|
405
|
+
await this.threadMemorySync.handleEvent(event);
|
|
406
|
+
}
|
|
386
407
|
return event;
|
|
387
408
|
}
|
|
388
409
|
async ensureThreadStarted(selectedAgentId, binding, input, existingThreadId) {
|
|
@@ -1048,6 +1069,7 @@ export class AgentHarnessRuntime {
|
|
|
1048
1069
|
if (!binding) {
|
|
1049
1070
|
throw new Error(`Unknown agent ${thread.agentId}`);
|
|
1050
1071
|
}
|
|
1072
|
+
const resumePayload = this.buildResumePayload(binding, approval, options);
|
|
1051
1073
|
await this.persistence.setRunState(threadId, runId, "resuming", `checkpoints/${threadId}/${runId}/cp-1`);
|
|
1052
1074
|
const releaseRunSlot = await this.acquireRunSlot(threadId, runId, "resuming");
|
|
1053
1075
|
try {
|
|
@@ -1055,9 +1077,7 @@ export class AgentHarnessRuntime {
|
|
|
1055
1077
|
kind: "approval-decision",
|
|
1056
1078
|
savedAt: new Date().toISOString(),
|
|
1057
1079
|
checkpointRef: `checkpoints/${threadId}/${runId}/cp-1`,
|
|
1058
|
-
resumePayload
|
|
1059
|
-
? { decision: "edit", editedInput: options.editedInput }
|
|
1060
|
-
: (options.decision ?? "approve"),
|
|
1080
|
+
resumePayload,
|
|
1061
1081
|
attempts: 0,
|
|
1062
1082
|
});
|
|
1063
1083
|
await this.emit(threadId, runId, 5, "run.resumed", {
|
|
@@ -1077,11 +1097,8 @@ export class AgentHarnessRuntime {
|
|
|
1077
1097
|
const history = await this.persistence.listThreadMessages(threadId);
|
|
1078
1098
|
const priorHistory = history.filter((message) => message.runId !== runId);
|
|
1079
1099
|
const runInput = await this.loadRunInput(threadId, runId);
|
|
1080
|
-
const resumeDecision = options.decision === "edit" && options.editedInput
|
|
1081
|
-
? { decision: "edit", editedInput: options.editedInput }
|
|
1082
|
-
: (options.decision ?? "approve");
|
|
1083
1100
|
try {
|
|
1084
|
-
const actual = await this.runtimeAdapter.invoke(binding, "", threadId, runId,
|
|
1101
|
+
const actual = await this.runtimeAdapter.invoke(binding, "", threadId, runId, resumePayload, priorHistory);
|
|
1085
1102
|
await this.persistence.clearRecoveryIntent(threadId, runId);
|
|
1086
1103
|
const finalized = await this.finalizeContinuedRun(threadId, runId, runInput, actual, {
|
|
1087
1104
|
previousState: "resuming",
|
|
@@ -1102,6 +1119,43 @@ export class AgentHarnessRuntime {
|
|
|
1102
1119
|
releaseRunSlot();
|
|
1103
1120
|
}
|
|
1104
1121
|
}
|
|
1122
|
+
buildResumePayload(binding, approval, options) {
|
|
1123
|
+
if (!isDeepAgentBinding(binding)) {
|
|
1124
|
+
return options.decision === "edit" && options.editedInput
|
|
1125
|
+
? { decision: "edit", editedInput: options.editedInput }
|
|
1126
|
+
: (options.decision ?? "approve");
|
|
1127
|
+
}
|
|
1128
|
+
const decisionType = options.decision ?? "approve";
|
|
1129
|
+
if (decisionType === "edit" && options.editedInput) {
|
|
1130
|
+
return {
|
|
1131
|
+
decisions: [
|
|
1132
|
+
{
|
|
1133
|
+
type: "edit",
|
|
1134
|
+
editedAction: {
|
|
1135
|
+
name: approval.toolName,
|
|
1136
|
+
args: options.editedInput,
|
|
1137
|
+
},
|
|
1138
|
+
},
|
|
1139
|
+
],
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
if (decisionType === "reject") {
|
|
1143
|
+
return {
|
|
1144
|
+
decisions: [
|
|
1145
|
+
{
|
|
1146
|
+
type: "reject",
|
|
1147
|
+
},
|
|
1148
|
+
],
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
return {
|
|
1152
|
+
decisions: [
|
|
1153
|
+
{
|
|
1154
|
+
type: "approve",
|
|
1155
|
+
},
|
|
1156
|
+
],
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1105
1159
|
async restartConversation(options) {
|
|
1106
1160
|
const thread = await this.getSession(options.threadId);
|
|
1107
1161
|
if (!thread) {
|
|
@@ -130,23 +130,26 @@ export function createPendingApproval(threadId, runId, checkpointRef, input, int
|
|
|
130
130
|
}
|
|
131
131
|
export function inferRoutingBindings(workspace) {
|
|
132
132
|
const hostBindings = Array.from(workspace.bindings.values()).filter((binding) => binding.harnessRuntime.hostFacing);
|
|
133
|
-
const
|
|
134
|
-
const
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
const
|
|
133
|
+
const deepAgentHosts = hostBindings.filter((binding) => binding.agent.executionMode === "deepagent" || Boolean(binding.deepAgentParams));
|
|
134
|
+
const routingHosts = deepAgentHosts.length > 0 ? deepAgentHosts : hostBindings;
|
|
135
|
+
const researchBinding = routingHosts.find((binding) => binding.agent.id === "research-lite" || binding.agent.id === "research");
|
|
136
|
+
const directBinding = routingHosts.find((binding) => binding.agent.id === "direct");
|
|
137
|
+
const delegationHosts = routingHosts.filter((binding) => isDelegationCapableBinding(binding));
|
|
138
|
+
const lightweightHosts = routingHosts.filter((binding) => !isDelegationCapableBinding(binding));
|
|
139
|
+
const defaultOrchestratingHost = routingHosts.find((binding) => binding.agent.id === "orchestra") ??
|
|
138
140
|
delegationHosts.find((binding) => (binding.deepAgentParams?.subagents.length ?? 0) > 0) ??
|
|
139
141
|
delegationHosts[0];
|
|
140
142
|
const delegationPreferredSecondary = delegationHosts.find((binding) => (binding.deepAgentParams?.subagents.length ?? 0) > 0) ??
|
|
141
143
|
delegationHosts[0];
|
|
142
144
|
const genericLightweightHost = lightweightHosts.find((binding) => binding.agent.id !== researchBinding?.agent.id);
|
|
143
|
-
const primaryBinding = defaultOrchestratingHost ?? directBinding ?? genericLightweightHost ?? hostBindings[0];
|
|
145
|
+
const primaryBinding = defaultOrchestratingHost ?? directBinding ?? genericLightweightHost ?? routingHosts[0] ?? hostBindings[0];
|
|
144
146
|
const secondaryBinding = genericLightweightHost && genericLightweightHost.agent.id !== primaryBinding?.agent.id
|
|
145
147
|
? genericLightweightHost
|
|
146
148
|
: directBinding && directBinding.agent.id !== primaryBinding?.agent.id
|
|
147
149
|
? directBinding
|
|
148
150
|
: delegationPreferredSecondary && delegationPreferredSecondary.agent.id !== primaryBinding?.agent.id
|
|
149
151
|
? delegationPreferredSecondary
|
|
150
|
-
:
|
|
152
|
+
: routingHosts.find((binding) => binding.agent.id !== primaryBinding?.agent.id) ??
|
|
153
|
+
(deepAgentHosts.length > 0 ? undefined : hostBindings.find((binding) => binding.agent.id !== primaryBinding?.agent.id));
|
|
151
154
|
return { primaryBinding, secondaryBinding, researchBinding, hostBindings };
|
|
152
155
|
}
|