@botbotgo/agent-harness 0.0.347 → 0.0.348
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/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/resource/backend/workspace-scoped-backend.d.ts +9 -2
- package/dist/resource/backend/workspace-scoped-backend.js +42 -22
- package/dist/runtime/adapter/flow/stream-runtime.js +2 -1
- package/dist/runtime/adapter/model/model-providers.js +108 -12
- package/dist/workspace/framework-contract-validation.js +6 -2
- package/package.json +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const AGENT_HARNESS_VERSION = "0.0.
|
|
1
|
+
export declare const AGENT_HARNESS_VERSION = "0.0.348";
|
|
2
2
|
export declare const AGENT_HARNESS_RELEASE_DATE = "2026-04-24";
|
package/dist/package-version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const AGENT_HARNESS_VERSION = "0.0.
|
|
1
|
+
export const AGENT_HARNESS_VERSION = "0.0.348";
|
|
2
2
|
export const AGENT_HARNESS_RELEASE_DATE = "2026-04-24";
|
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import { CompositeBackend } from "deepagents";
|
|
2
2
|
import type { RuntimeAdapterOptions, WorkspaceBundle } from "../../contracts/types.js";
|
|
3
|
-
export declare function normalizeWorkspaceScopedPath(rootDir: string, inputPath: string
|
|
3
|
+
export declare function normalizeWorkspaceScopedPath(rootDir: string, inputPath: string, options?: {
|
|
4
|
+
allowVirtualAbsolutePath?: boolean;
|
|
5
|
+
}): string;
|
|
4
6
|
export declare class WorkspaceScopedBackend {
|
|
5
7
|
private readonly backend;
|
|
8
|
+
private readonly options;
|
|
6
9
|
readonly id?: string;
|
|
7
10
|
readonly cwd: string;
|
|
8
11
|
readonly rootDir: string;
|
|
9
12
|
readonly root: string;
|
|
10
13
|
readonly virtualMode?: boolean;
|
|
11
14
|
readonly execute?: (command: string) => Promise<unknown>;
|
|
12
|
-
constructor(backend: Record<string, unknown>, rootDir: string
|
|
15
|
+
constructor(backend: Record<string, unknown>, rootDir: string, options?: {
|
|
16
|
+
allowVirtualAbsolutePath?: boolean;
|
|
17
|
+
});
|
|
13
18
|
ls(filePath: string): unknown;
|
|
14
19
|
read(filePath: string, offset?: number, limit?: number): unknown;
|
|
15
20
|
readRaw(filePath: string): unknown;
|
|
@@ -25,7 +30,9 @@ declare class CompatibleCompositeBackend {
|
|
|
25
30
|
readonly id?: string;
|
|
26
31
|
readonly execute?: (command: string) => ReturnType<CompositeBackend["execute"]>;
|
|
27
32
|
private readonly composite;
|
|
33
|
+
private readonly routePrefixes;
|
|
28
34
|
constructor(defaultBackend: unknown, routes: Record<string, unknown>);
|
|
35
|
+
private normalizeCompositePath;
|
|
29
36
|
ls(filePath: string): ReturnType<CompositeBackend["ls"]>;
|
|
30
37
|
read(filePath: string, offset?: number, limit?: number): ReturnType<CompositeBackend["read"]>;
|
|
31
38
|
readRaw(filePath: string): ReturnType<CompositeBackend["readRaw"]>;
|
|
@@ -30,7 +30,7 @@ function normalizeVirtualExecuteCommand(command, rootDir) {
|
|
|
30
30
|
return `${prefix}${quote}${translatedPath}${quote}`;
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
|
-
export function normalizeWorkspaceScopedPath(rootDir, inputPath) {
|
|
33
|
+
export function normalizeWorkspaceScopedPath(rootDir, inputPath, options = {}) {
|
|
34
34
|
if (typeof inputPath !== "string" || inputPath.length === 0 || !path.isAbsolute(inputPath)) {
|
|
35
35
|
return inputPath;
|
|
36
36
|
}
|
|
@@ -53,21 +53,26 @@ export function normalizeWorkspaceScopedPath(rootDir, inputPath) {
|
|
|
53
53
|
if (normalizedInputPath === normalizedRootDir || normalizedInputPath.startsWith(`${normalizedRootDir}${path.sep}`)) {
|
|
54
54
|
return path.relative(normalizedRootDir, normalizedInputPath) || ".";
|
|
55
55
|
}
|
|
56
|
+
if (options.allowVirtualAbsolutePath === true) {
|
|
57
|
+
return inputPath.replace(/\/+/g, "/");
|
|
58
|
+
}
|
|
56
59
|
throw new Error(`Path '${inputPath}' is outside the workspace root '${normalizedRootDir}'. Use a workspace-relative path instead.`);
|
|
57
60
|
}
|
|
58
|
-
function normalizeWorkspaceScopedNullablePath(rootDir, inputPath) {
|
|
59
|
-
return typeof inputPath === "string" ? normalizeWorkspaceScopedPath(rootDir, inputPath) : inputPath;
|
|
61
|
+
function normalizeWorkspaceScopedNullablePath(rootDir, inputPath, options = {}) {
|
|
62
|
+
return typeof inputPath === "string" ? normalizeWorkspaceScopedPath(rootDir, inputPath, options) : inputPath;
|
|
60
63
|
}
|
|
61
64
|
export class WorkspaceScopedBackend {
|
|
62
65
|
backend;
|
|
66
|
+
options;
|
|
63
67
|
id;
|
|
64
68
|
cwd;
|
|
65
69
|
rootDir;
|
|
66
70
|
root;
|
|
67
71
|
virtualMode;
|
|
68
72
|
execute;
|
|
69
|
-
constructor(backend, rootDir) {
|
|
73
|
+
constructor(backend, rootDir, options = {}) {
|
|
70
74
|
this.backend = backend;
|
|
75
|
+
this.options = options;
|
|
71
76
|
this.rootDir = path.resolve(rootDir);
|
|
72
77
|
this.root = this.rootDir;
|
|
73
78
|
this.cwd = this.rootDir;
|
|
@@ -78,34 +83,36 @@ export class WorkspaceScopedBackend {
|
|
|
78
83
|
: undefined;
|
|
79
84
|
}
|
|
80
85
|
ls(filePath) {
|
|
81
|
-
return this.backend.ls(normalizeWorkspaceScopedPath(this.rootDir, filePath));
|
|
86
|
+
return this.backend.ls(normalizeWorkspaceScopedPath(this.rootDir, filePath, this.options));
|
|
82
87
|
}
|
|
83
88
|
read(filePath, offset, limit) {
|
|
84
|
-
return this.backend.read(normalizeWorkspaceScopedPath(this.rootDir, filePath), offset, limit);
|
|
89
|
+
return this.backend.read(normalizeWorkspaceScopedPath(this.rootDir, filePath, this.options), offset, limit);
|
|
85
90
|
}
|
|
86
91
|
readRaw(filePath) {
|
|
87
|
-
return this.backend.readRaw(normalizeWorkspaceScopedPath(this.rootDir, filePath));
|
|
92
|
+
return this.backend.readRaw(normalizeWorkspaceScopedPath(this.rootDir, filePath, this.options));
|
|
88
93
|
}
|
|
89
94
|
grep(pattern, filePath, glob) {
|
|
90
|
-
return this.backend.grep(pattern, normalizeWorkspaceScopedNullablePath(this.rootDir, filePath), glob);
|
|
95
|
+
return this.backend.grep(pattern, normalizeWorkspaceScopedNullablePath(this.rootDir, filePath, this.options), glob);
|
|
91
96
|
}
|
|
92
97
|
grepRaw(pattern, filePath, glob) {
|
|
93
|
-
return this.backend.grepRaw(pattern, normalizeWorkspaceScopedNullablePath(this.rootDir, filePath), glob);
|
|
98
|
+
return this.backend.grepRaw(pattern, normalizeWorkspaceScopedNullablePath(this.rootDir, filePath, this.options), glob);
|
|
94
99
|
}
|
|
95
100
|
glob(pattern, filePath) {
|
|
96
|
-
return this.backend.glob(pattern, typeof filePath === "string" ? normalizeWorkspaceScopedPath(this.rootDir, filePath) : filePath);
|
|
101
|
+
return this.backend.glob(pattern, typeof filePath === "string" ? normalizeWorkspaceScopedPath(this.rootDir, filePath, this.options) : filePath);
|
|
97
102
|
}
|
|
98
103
|
write(filePath, content) {
|
|
99
|
-
return this.backend.write(normalizeWorkspaceScopedPath(this.rootDir, filePath), content);
|
|
104
|
+
return this.backend.write(normalizeWorkspaceScopedPath(this.rootDir, filePath, this.options), content);
|
|
100
105
|
}
|
|
101
106
|
edit(filePath, oldString, newString, replaceAll) {
|
|
102
|
-
return this.backend.edit(normalizeWorkspaceScopedPath(this.rootDir, filePath), oldString, newString, replaceAll);
|
|
107
|
+
return this.backend.edit(normalizeWorkspaceScopedPath(this.rootDir, filePath, this.options), oldString, newString, replaceAll);
|
|
103
108
|
}
|
|
104
109
|
uploadFiles(files) {
|
|
105
110
|
return this.backend.uploadFiles(files);
|
|
106
111
|
}
|
|
107
112
|
downloadFiles(paths) {
|
|
108
|
-
const normalizedPaths = Array.isArray(paths)
|
|
113
|
+
const normalizedPaths = Array.isArray(paths)
|
|
114
|
+
? paths.map((currentPath) => normalizeWorkspaceScopedPath(this.rootDir, currentPath, this.options))
|
|
115
|
+
: paths;
|
|
109
116
|
return this.backend.downloadFiles(normalizedPaths);
|
|
110
117
|
}
|
|
111
118
|
}
|
|
@@ -113,8 +120,10 @@ class CompatibleCompositeBackend {
|
|
|
113
120
|
id;
|
|
114
121
|
execute;
|
|
115
122
|
composite;
|
|
123
|
+
routePrefixes;
|
|
116
124
|
constructor(defaultBackend, routes) {
|
|
117
125
|
this.composite = new CompositeBackend(defaultBackend, routes);
|
|
126
|
+
this.routePrefixes = Object.keys(routes).filter((route) => route.startsWith("/"));
|
|
118
127
|
const sandboxLike = defaultBackend;
|
|
119
128
|
if (typeof sandboxLike.id === "string" && typeof sandboxLike.execute === "function") {
|
|
120
129
|
this.id = sandboxLike.id;
|
|
@@ -125,32 +134,43 @@ class CompatibleCompositeBackend {
|
|
|
125
134
|
: (command) => this.composite.execute(command);
|
|
126
135
|
}
|
|
127
136
|
}
|
|
137
|
+
normalizeCompositePath(filePath) {
|
|
138
|
+
if (!path.isAbsolute(filePath)) {
|
|
139
|
+
return filePath;
|
|
140
|
+
}
|
|
141
|
+
const normalized = filePath.replace(/\/+/g, "/");
|
|
142
|
+
if (this.routePrefixes.some((route) => normalized === route || normalized.startsWith(route))) {
|
|
143
|
+
return normalized;
|
|
144
|
+
}
|
|
145
|
+
return path.join(...normalized.split("/").filter(Boolean));
|
|
146
|
+
}
|
|
128
147
|
ls(filePath) {
|
|
129
|
-
return this.composite.ls(filePath);
|
|
148
|
+
return this.composite.ls(this.normalizeCompositePath(filePath));
|
|
130
149
|
}
|
|
131
150
|
read(filePath, offset, limit) {
|
|
132
|
-
return this.composite.read(filePath, offset, limit);
|
|
151
|
+
return this.composite.read(this.normalizeCompositePath(filePath), offset, limit);
|
|
133
152
|
}
|
|
134
153
|
readRaw(filePath) {
|
|
135
|
-
return this.composite.readRaw(filePath);
|
|
154
|
+
return this.composite.readRaw(this.normalizeCompositePath(filePath));
|
|
136
155
|
}
|
|
137
156
|
grep(pattern, filePath, glob) {
|
|
138
|
-
return this.composite.grep(pattern, filePath
|
|
157
|
+
return this.composite.grep(pattern, filePath ? this.normalizeCompositePath(filePath) : undefined, glob ?? undefined);
|
|
139
158
|
}
|
|
140
159
|
glob(pattern, filePath) {
|
|
141
|
-
return this.composite.glob(pattern, filePath);
|
|
160
|
+
return this.composite.glob(pattern, filePath ? this.normalizeCompositePath(filePath) : filePath);
|
|
142
161
|
}
|
|
143
162
|
write(filePath, content) {
|
|
144
|
-
return this.composite.write(filePath, content);
|
|
163
|
+
return this.composite.write(this.normalizeCompositePath(filePath), content);
|
|
145
164
|
}
|
|
146
165
|
edit(filePath, oldString, newString, replaceAll) {
|
|
147
|
-
return this.composite.edit(filePath, oldString, newString, replaceAll);
|
|
166
|
+
return this.composite.edit(this.normalizeCompositePath(filePath), oldString, newString, replaceAll);
|
|
148
167
|
}
|
|
149
168
|
uploadFiles(files) {
|
|
150
169
|
return this.composite.uploadFiles(files);
|
|
151
170
|
}
|
|
152
171
|
downloadFiles(paths) {
|
|
153
|
-
|
|
172
|
+
const normalizedPaths = Array.isArray(paths) ? paths.map((currentPath) => this.normalizeCompositePath(currentPath)) : paths;
|
|
173
|
+
return this.composite.downloadFiles(normalizedPaths);
|
|
154
174
|
}
|
|
155
175
|
}
|
|
156
176
|
function omitKind(config) {
|
|
@@ -289,7 +309,7 @@ export function createInlineBackendResolver(workspace) {
|
|
|
289
309
|
const routeKind = typeof routeConfig?.kind === "string" ? routeConfig.kind : "StoreBackend";
|
|
290
310
|
return [route, createInlineBackendInstance(workspace.workspaceRoot, routeKind, routeConfig, runtimeLike)];
|
|
291
311
|
}));
|
|
292
|
-
return new WorkspaceScopedBackend(new CompatibleCompositeBackend(createInlineBackendInstance(workspace.workspaceRoot, defaultBackendKind, stateConfig, runtimeLike), mappedRoutes), workspace.workspaceRoot);
|
|
312
|
+
return new WorkspaceScopedBackend(new CompatibleCompositeBackend(createInlineBackendInstance(workspace.workspaceRoot, defaultBackendKind, stateConfig, runtimeLike), mappedRoutes), workspace.workspaceRoot, { allowVirtualAbsolutePath: true });
|
|
293
313
|
}
|
|
294
314
|
default:
|
|
295
315
|
return unsupportedInlineBackend(kind);
|
|
@@ -705,7 +705,8 @@ export async function* streamRuntimeExecution(options) {
|
|
|
705
705
|
hasToolResultEvidence: invokeExecutionEvidence.hasSuccessfulNonTodoToolResultEvidence,
|
|
706
706
|
})
|
|
707
707
|
: null;
|
|
708
|
-
const effectiveInvokeFallbackRecoveryInstruction = invokeFallbackMissingPlanRecoveryInstruction
|
|
708
|
+
const effectiveInvokeFallbackRecoveryInstruction = invokeFallbackMissingPlanRecoveryInstruction
|
|
709
|
+
?? invokeFallbackRecoveryInstruction;
|
|
709
710
|
if (effectiveInvokeFallbackRecoveryInstruction) {
|
|
710
711
|
const recovered = await options.invoke(options.applyToolRecoveryInstruction(options.binding, effectiveInvokeFallbackRecoveryInstruction), options.input, options.sessionId, options.runtimeOptions.requestId ?? options.sessionId, undefined, options.history, options.runtimeOptions);
|
|
711
712
|
const recoveredToolResults = Array.isArray(recovered.metadata?.executedToolResults)
|
|
@@ -13,8 +13,16 @@ const NODE_LLAMA_CPP_TOOL_CALL_INSTRUCTION = [
|
|
|
13
13
|
"If you need a tool, respond with only one JSON object.",
|
|
14
14
|
'Use this exact shape: {"name":"tool_name","arguments":{"key":"value"}}',
|
|
15
15
|
"Do not add markdown, prose, or code fences unless the output is wrapped inside <tool_call>...</tool_call>.",
|
|
16
|
+
"When the latest user message explicitly requests a tool call or provides a tool-call JSON object, call that tool instead of answering locally.",
|
|
17
|
+
"If the conversation already contains TOOL_RESULT for the requested work, answer from that result instead of repeating the same tool call.",
|
|
16
18
|
"If no tool is needed, answer normally.",
|
|
17
19
|
].join("\n");
|
|
20
|
+
const PROMPTED_JSON_FINAL_TOOL_CALL_REMINDER = [
|
|
21
|
+
"Final tool-call rule:",
|
|
22
|
+
"If the correct next step is a tool call, return exactly one JSON object and no prose.",
|
|
23
|
+
"If a TOOL_RESULT is already present for the requested work, do not repeat that tool call; answer normally.",
|
|
24
|
+
'Shape: {"name":"tool_name","arguments":{}}',
|
|
25
|
+
].join("\n");
|
|
18
26
|
function readModelText(value) {
|
|
19
27
|
if (typeof value === "string") {
|
|
20
28
|
return value.trim();
|
|
@@ -175,6 +183,23 @@ function readToolMessageMetadata(value) {
|
|
|
175
183
|
: undefined;
|
|
176
184
|
return { name, toolCallId };
|
|
177
185
|
}
|
|
186
|
+
function hasPriorToolResultForToolName(input, toolName) {
|
|
187
|
+
if (!toolName) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
if (Array.isArray(input)) {
|
|
191
|
+
return input.some((message) => {
|
|
192
|
+
if (mapMessageRole(message) !== "TOOL") {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
return readToolMessageMetadata(message).name === toolName;
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
if (typeof input === "object" && input !== null && Array.isArray(input.messages)) {
|
|
199
|
+
return hasPriorToolResultForToolName(input.messages, toolName);
|
|
200
|
+
}
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
178
203
|
function normalizeReadFileToolContent(name, content) {
|
|
179
204
|
if (name !== "read_file") {
|
|
180
205
|
return content;
|
|
@@ -274,22 +299,75 @@ function extractToolCallPayload(text) {
|
|
|
274
299
|
if (direct) {
|
|
275
300
|
return direct;
|
|
276
301
|
}
|
|
277
|
-
const fenced = trimmed
|
|
302
|
+
const fenced = extractFencePayload(trimmed);
|
|
278
303
|
if (fenced) {
|
|
279
304
|
const parsed = tryParseJson(fenced);
|
|
280
305
|
if (parsed) {
|
|
281
306
|
return parsed;
|
|
282
307
|
}
|
|
283
308
|
}
|
|
284
|
-
const xml = trimmed
|
|
309
|
+
const xml = extractTaggedContent(trimmed, "tool_call");
|
|
285
310
|
if (xml) {
|
|
286
311
|
const parsed = tryParseJson(xml);
|
|
287
312
|
if (parsed) {
|
|
288
313
|
return parsed;
|
|
289
314
|
}
|
|
290
315
|
}
|
|
316
|
+
const toolCallsXml = extractTaggedContent(trimmed, "tool_calls");
|
|
317
|
+
if (toolCallsXml) {
|
|
318
|
+
const parsed = tryParseJson(toolCallsXml);
|
|
319
|
+
if (parsed) {
|
|
320
|
+
return parsed;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
const codeBlock = extractToolCodePayload(trimmed);
|
|
324
|
+
if (codeBlock) {
|
|
325
|
+
return codeBlock;
|
|
326
|
+
}
|
|
291
327
|
return null;
|
|
292
328
|
}
|
|
329
|
+
function extractFencePayload(text) {
|
|
330
|
+
const start = text.indexOf("```");
|
|
331
|
+
if (start < 0) {
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
const contentStart = text.indexOf("\n", start + 3);
|
|
335
|
+
const bodyStart = contentStart >= 0 ? contentStart + 1 : start + 3;
|
|
336
|
+
const end = text.indexOf("```", bodyStart);
|
|
337
|
+
if (end < 0) {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
return text.slice(bodyStart, end).trim();
|
|
341
|
+
}
|
|
342
|
+
function extractTaggedContent(text, tagName) {
|
|
343
|
+
const lowerText = text.toLowerCase();
|
|
344
|
+
const openTag = `<${tagName}>`;
|
|
345
|
+
const closeTag = `</${tagName}>`;
|
|
346
|
+
const start = lowerText.indexOf(openTag);
|
|
347
|
+
if (start < 0) {
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
const bodyStart = start + openTag.length;
|
|
351
|
+
const end = lowerText.indexOf(closeTag, bodyStart);
|
|
352
|
+
if (end < 0) {
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
return text.slice(bodyStart, end).trim();
|
|
356
|
+
}
|
|
357
|
+
function extractToolCodePayload(text) {
|
|
358
|
+
const name = extractTaggedContent(text, "tool_code");
|
|
359
|
+
if (!name) {
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
const rawArgs = extractTaggedContent(text, "tool_args");
|
|
363
|
+
const parsedArgs = rawArgs ? tryParseJson(rawArgs.trim()) : null;
|
|
364
|
+
const args = typeof parsedArgs === "object" && parsedArgs !== null && !Array.isArray(parsedArgs)
|
|
365
|
+
? parsedArgs
|
|
366
|
+
: Array.isArray(parsedArgs)
|
|
367
|
+
? { args: parsedArgs }
|
|
368
|
+
: {};
|
|
369
|
+
return { name, arguments: args };
|
|
370
|
+
}
|
|
293
371
|
function normalizeParsedToolCall(payload) {
|
|
294
372
|
if (typeof payload !== "object" || payload === null) {
|
|
295
373
|
return null;
|
|
@@ -305,7 +383,9 @@ function normalizeParsedToolCall(payload) {
|
|
|
305
383
|
return null;
|
|
306
384
|
}
|
|
307
385
|
const argsCandidate = typed.arguments ?? typed.args ?? typed.parameters ?? typed.input ?? functionPayload?.arguments ?? {};
|
|
308
|
-
const args =
|
|
386
|
+
const args = Array.isArray(argsCandidate)
|
|
387
|
+
? { args: argsCandidate }
|
|
388
|
+
: salvageToolArgs(argsCandidate) ?? {};
|
|
309
389
|
return { name, args };
|
|
310
390
|
}
|
|
311
391
|
function formatBoundToolInstruction(tool) {
|
|
@@ -325,16 +405,16 @@ function formatBoundToolInstruction(tool) {
|
|
|
325
405
|
`Arguments JSON schema: ${JSON.stringify(schema)}`,
|
|
326
406
|
].filter(Boolean).join("\n");
|
|
327
407
|
}
|
|
328
|
-
function
|
|
408
|
+
function withPromptedJsonToolPrompt(input, tools) {
|
|
329
409
|
const toolInstructions = tools.map((tool) => formatBoundToolInstruction(tool)).filter((value) => Boolean(value));
|
|
330
410
|
if (toolInstructions.length === 0) {
|
|
331
411
|
return stringifyNodeLlamaCppInput(input);
|
|
332
412
|
}
|
|
333
413
|
const systemContent = `${NODE_LLAMA_CPP_TOOL_CALL_INSTRUCTION}\n\n${toolInstructions.join("\n\n")}`;
|
|
334
414
|
const prompt = stringifyNodeLlamaCppInput(input);
|
|
335
|
-
return [systemContent, prompt].filter(Boolean).join("\n\n");
|
|
415
|
+
return [systemContent, prompt, PROMPTED_JSON_FINAL_TOOL_CALL_REMINDER].filter(Boolean).join("\n\n");
|
|
336
416
|
}
|
|
337
|
-
function
|
|
417
|
+
function createPromptedJsonToolBindableModel(model, boundTools = []) {
|
|
338
418
|
return new Proxy(model, {
|
|
339
419
|
has(target, prop) {
|
|
340
420
|
if (prop === "bindTools" || prop === "invoke" || prop === "stream" || prop === "withConfig") {
|
|
@@ -344,11 +424,11 @@ function createNodeLlamaCppToolBindableModel(model, boundTools = []) {
|
|
|
344
424
|
},
|
|
345
425
|
get(target, prop, receiver) {
|
|
346
426
|
if (prop === "bindTools") {
|
|
347
|
-
return (tools) =>
|
|
427
|
+
return (tools) => createPromptedJsonToolBindableModel(target, tools);
|
|
348
428
|
}
|
|
349
429
|
if (prop === "invoke") {
|
|
350
430
|
return async (input, config) => {
|
|
351
|
-
const rawResult = await target.invoke(boundTools.length > 0 ?
|
|
431
|
+
const rawResult = await target.invoke(boundTools.length > 0 ? withPromptedJsonToolPrompt(input, boundTools) : input, config);
|
|
352
432
|
if (boundTools.length === 0) {
|
|
353
433
|
return rawResult;
|
|
354
434
|
}
|
|
@@ -357,6 +437,9 @@ function createNodeLlamaCppToolBindableModel(model, boundTools = []) {
|
|
|
357
437
|
if (!parsedToolCall) {
|
|
358
438
|
return rawResult;
|
|
359
439
|
}
|
|
440
|
+
if (hasPriorToolResultForToolName(input, parsedToolCall.name)) {
|
|
441
|
+
return rawResult;
|
|
442
|
+
}
|
|
360
443
|
return new AIMessage({
|
|
361
444
|
content: "",
|
|
362
445
|
tool_calls: [{
|
|
@@ -377,7 +460,7 @@ function createNodeLlamaCppToolBindableModel(model, boundTools = []) {
|
|
|
377
460
|
};
|
|
378
461
|
}
|
|
379
462
|
if (prop === "withConfig" && typeof target.withConfig === "function") {
|
|
380
|
-
return (config) =>
|
|
463
|
+
return (config) => createPromptedJsonToolBindableModel(target.withConfig(config), boundTools);
|
|
381
464
|
}
|
|
382
465
|
const member = Reflect.get(target, prop, receiver);
|
|
383
466
|
return typeof member === "function" ? member.bind(target) : member;
|
|
@@ -409,7 +492,7 @@ async function createNodeLlamaCppModel(model) {
|
|
|
409
492
|
}
|
|
410
493
|
try {
|
|
411
494
|
const { ChatLlamaCpp } = await import("@langchain/community/chat_models/llama_cpp");
|
|
412
|
-
return
|
|
495
|
+
return createPromptedJsonToolBindableModel(await ChatLlamaCpp.initialize({
|
|
413
496
|
...model.init,
|
|
414
497
|
modelPath,
|
|
415
498
|
}));
|
|
@@ -423,10 +506,23 @@ export async function createResolvedModel(model, modelResolver) {
|
|
|
423
506
|
return modelResolver(model.id);
|
|
424
507
|
}
|
|
425
508
|
if (model.provider === "ollama") {
|
|
426
|
-
|
|
509
|
+
const { toolCallingMode, ...init } = model.init ?? {};
|
|
510
|
+
const resolved = new ChatOllama({ model: model.model, ...init });
|
|
511
|
+
if (toolCallingMode === "prompted-json") {
|
|
512
|
+
return createPromptedJsonToolBindableModel(resolved);
|
|
513
|
+
}
|
|
514
|
+
return createProviderToolMessageCompatModel(resolved);
|
|
427
515
|
}
|
|
428
516
|
if (model.provider === "openai-compatible") {
|
|
429
|
-
|
|
517
|
+
const { toolCallingMode, ...init } = model.init ?? {};
|
|
518
|
+
const resolved = new ChatOpenAI({
|
|
519
|
+
model: model.model,
|
|
520
|
+
...normalizeOpenAICompatibleInit(init),
|
|
521
|
+
});
|
|
522
|
+
if (toolCallingMode === "prompted-json") {
|
|
523
|
+
return createPromptedJsonToolBindableModel(resolved);
|
|
524
|
+
}
|
|
525
|
+
return createProviderToolMessageCompatModel(resolved);
|
|
430
526
|
}
|
|
431
527
|
if (model.provider === "openai") {
|
|
432
528
|
return createProviderToolMessageCompatModel(new ChatOpenAI({ model: model.model, ...model.init }));
|
|
@@ -140,8 +140,12 @@ function validateAgentContract(agent, referencedSubagentIds, tools, issues) {
|
|
|
140
140
|
if (hasTools) {
|
|
141
141
|
addIssue(issues, "agent.orchestrator.mixed_tool_surface", `Delegating agent ${agent.id} defines both subagents and direct tools. Keep routing agents focused on delegation, and move execution tools to specialist agents.`);
|
|
142
142
|
}
|
|
143
|
-
|
|
144
|
-
|
|
143
|
+
const modelExposedBuiltins = builtinTools?.modelExposed;
|
|
144
|
+
const exposesOnlyTask = Array.isArray(modelExposedBuiltins)
|
|
145
|
+
&& modelExposedBuiltins.length === 1
|
|
146
|
+
&& modelExposedBuiltins[0] === "task";
|
|
147
|
+
if (modelExposedBuiltins !== false && !exposesOnlyTask) {
|
|
148
|
+
addIssue(issues, "agent.orchestrator.model_exposed_builtins", `Delegating agent ${agent.id} should expose only the task builtin or set config.builtinTools.modelExposed: false so raw built-in tools do not compete with specialist routing.`);
|
|
145
149
|
}
|
|
146
150
|
if (!systemPrompt?.trim()) {
|
|
147
151
|
addIssue(issues, "agent.orchestrator.missing_prompt", `Delegating agent ${agent.id} should define a systemPrompt that explains decomposition, delegation, synthesis, and stop conditions.`);
|