@lazyingart/agintiflow 0.20.144 → 0.20.145
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/docs/skills-and-tools.md +7 -0
- package/package.json +1 -1
- package/scripts/smoke-coding-tools.js +27 -0
- package/skills/structured-json/SKILL.md +22 -0
- package/src/agent-runner.js +48 -0
- package/src/guardrails.js +60 -0
- package/src/json-specialist.js +422 -0
- package/src/model-client.js +87 -0
- package/src/step-budget-controller.js +2 -0
package/docs/skills-and-tools.md
CHANGED
|
@@ -71,3 +71,10 @@ For substantial writing tasks, prefer:
|
|
|
71
71
|
- The main agent for all non-writing work around that draft: file names, workspace edits, citations, Markdown/LaTeX/Final Draft formatting, PDF compilation, canvas publishing, and verification.
|
|
72
72
|
|
|
73
73
|
The writer receives only writing context: brief, canon, style guide, prior draft, target, audience, constraints, length, and downstream format intent. It should not receive shell/file/browser policy or agent-runtime details.
|
|
74
|
+
|
|
75
|
+
For schema-bound structured data, prefer:
|
|
76
|
+
|
|
77
|
+
- `json_specialist` for one isolated extraction, annotation, conversion, or validation request.
|
|
78
|
+
- `json_specialist_batch` for independent chunks that can be requested in parallel without shared writes.
|
|
79
|
+
|
|
80
|
+
The JSON specialist receives only the task, focused instructions, minimal context, input, and JSON Schema. It tries provider-native structured output (`json_schema` or JSON object mode) when available, then falls back to prompt-and-validate parsing.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.145",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AgInTiFlow is a project-aware agent workspace for hybrid wet-dry R&D, hardware-aware intelligence, software automation, and industrial workflows.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -25,6 +25,7 @@ import { createPlan } from "../src/model-client.js";
|
|
|
25
25
|
import { selectModelRoute } from "../src/model-routing.js";
|
|
26
26
|
import { listParallelScouts, runParallelScouts, shouldRunParallelScouts } from "../src/parallel-scouts.js";
|
|
27
27
|
import { buildFailedCommandAdvice, buildPermissionAdvice } from "../src/permission-advice.js";
|
|
28
|
+
import { runJsonSpecialist } from "../src/json-specialist.js";
|
|
28
29
|
import { SessionStore } from "../src/session-store.js";
|
|
29
30
|
import { getTaskProfile } from "../src/task-profiles.js";
|
|
30
31
|
import { searchWeb } from "../src/web-search.js";
|
|
@@ -613,6 +614,30 @@ try {
|
|
|
613
614
|
writingRun.events.some((event) => event.type === "tool.completed" && event.data?.toolName === "writing_specialist"),
|
|
614
615
|
"mock agent did not route writing work through writing_specialist"
|
|
615
616
|
);
|
|
617
|
+
const jsonResult = await runJsonSpecialist(
|
|
618
|
+
{
|
|
619
|
+
task: "Return a strict JSON status object.",
|
|
620
|
+
schema: {
|
|
621
|
+
type: "object",
|
|
622
|
+
properties: {
|
|
623
|
+
summary: { type: "string" },
|
|
624
|
+
complete: { type: "boolean" },
|
|
625
|
+
},
|
|
626
|
+
required: ["summary", "complete"],
|
|
627
|
+
additionalProperties: false,
|
|
628
|
+
},
|
|
629
|
+
inputText: "structured JSON smoke",
|
|
630
|
+
provider: "mock",
|
|
631
|
+
},
|
|
632
|
+
{ provider: "mock", model: "mock-agent" },
|
|
633
|
+
new SessionStore(runtimeDir, "json-specialist-smoke", { projectRoot: workspace, commandCwd: workspace })
|
|
634
|
+
);
|
|
635
|
+
assert(jsonResult.ok && jsonResult.result?.complete === true, "json_specialist mock result did not satisfy schema");
|
|
636
|
+
const jsonRun = await runMock("Extract a valid JSON object with schema from this text.", "coding-json-specialist");
|
|
637
|
+
assert(
|
|
638
|
+
jsonRun.events.some((event) => event.type === "tool.completed" && event.data?.toolName === "json_specialist"),
|
|
639
|
+
"mock agent did not route structured JSON work through json_specialist"
|
|
640
|
+
);
|
|
616
641
|
|
|
617
642
|
await fs.mkdir(path.join(workspace, "src"), { recursive: true });
|
|
618
643
|
await fs.mkdir(path.join(workspace, "test"), { recursive: true });
|
|
@@ -1104,6 +1129,8 @@ try {
|
|
|
1104
1129
|
"deepseek_pro_writing_route",
|
|
1105
1130
|
"writing_specialist_mock",
|
|
1106
1131
|
"writing_specialist_mock_routing",
|
|
1132
|
+
"json_specialist_mock",
|
|
1133
|
+
"json_specialist_mock_routing",
|
|
1107
1134
|
"runtime_time_context",
|
|
1108
1135
|
"large_profile_pro_route",
|
|
1109
1136
|
"auto_system_pro_route",
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: structured-json
|
|
3
|
+
description: Use isolated schema-bound JSON generation for extraction, annotation, conversion, classification, or chunked data production without mixing in agent runtime context.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Structured JSON
|
|
7
|
+
|
|
8
|
+
Use this skill when the user needs reliable JSON that follows an explicit schema, especially for repetitive chunk processing.
|
|
9
|
+
|
|
10
|
+
## Workflow
|
|
11
|
+
|
|
12
|
+
1. Define the smallest useful JSON Schema for the next data artifact.
|
|
13
|
+
2. Pass only the task, focused instructions, minimal domain context, input, and schema to `json_specialist`.
|
|
14
|
+
3. Use `json_specialist_batch` only when items are independent and can be processed in parallel without shared writes.
|
|
15
|
+
4. Keep formatting, file writes, validation scripts, compilation, and project-specific orchestration in the main agent.
|
|
16
|
+
5. After the tool returns, validate any project-specific invariants with local scripts before treating the data as complete.
|
|
17
|
+
|
|
18
|
+
## Boundaries
|
|
19
|
+
|
|
20
|
+
- Do not pass shell, browser, file policy, package-install, or agent-planning context into the JSON specialist.
|
|
21
|
+
- Do not make schemas book-, app-, or project-specific inside AgInTiFlow core. Project schemas belong in the target repository.
|
|
22
|
+
- Prefer provider-native structured output when available, but keep fallback parsing enabled unless the user explicitly wants hard failure on unsupported response formats.
|
package/src/agent-runner.js
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
import { refreshCodebaseMap } from "./codebase-map.js";
|
|
27
27
|
import { readImage, researchWrapper, webResearch } from "./perception-tools.js";
|
|
28
28
|
import { searchWeb } from "./web-search.js";
|
|
29
|
+
import { runJsonSpecialist, runJsonSpecialistBatch } from "./json-specialist.js";
|
|
29
30
|
import { runWritingSpecialist } from "./writing-specialist.js";
|
|
30
31
|
import { runParallelScouts, shouldRunParallelScouts } from "./parallel-scouts.js";
|
|
31
32
|
import { readProjectInstructions } from "./project.js";
|
|
@@ -1214,6 +1215,26 @@ function sanitizeToolArgs(toolName, args) {
|
|
|
1214
1215
|
typeof args.constraints === "string" ? `[${Buffer.byteLength(args.constraints, "utf8")} bytes sha256=${hashForLog(args.constraints)}]` : safeArgs.constraints,
|
|
1215
1216
|
};
|
|
1216
1217
|
}
|
|
1218
|
+
if (toolName === "json_specialist") {
|
|
1219
|
+
return {
|
|
1220
|
+
...safeArgs,
|
|
1221
|
+
task: typeof args.task === "string" ? `[${Buffer.byteLength(args.task, "utf8")} bytes sha256=${hashForLog(args.task)}]` : safeArgs.task,
|
|
1222
|
+
instructions:
|
|
1223
|
+
typeof args.instructions === "string" ? `[${Buffer.byteLength(args.instructions, "utf8")} bytes sha256=${hashForLog(args.instructions)}]` : safeArgs.instructions,
|
|
1224
|
+
context: typeof args.context === "string" ? `[${Buffer.byteLength(args.context, "utf8")} bytes sha256=${hashForLog(args.context)}]` : safeArgs.context,
|
|
1225
|
+
inputText:
|
|
1226
|
+
typeof args.inputText === "string" ? `[${Buffer.byteLength(args.inputText, "utf8")} bytes sha256=${hashForLog(args.inputText)}]` : safeArgs.inputText,
|
|
1227
|
+
schemaJson:
|
|
1228
|
+
typeof args.schemaJson === "string" ? `[${Buffer.byteLength(args.schemaJson, "utf8")} bytes sha256=${hashForLog(args.schemaJson)}]` : safeArgs.schemaJson,
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
if (toolName === "json_specialist_batch") {
|
|
1232
|
+
return {
|
|
1233
|
+
...safeArgs,
|
|
1234
|
+
defaults: args.defaults ? "[json specialist defaults redacted]" : safeArgs.defaults,
|
|
1235
|
+
items: Array.isArray(args.items) ? `[${args.items.length} json specialist items]` : safeArgs.items,
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1217
1238
|
if (toolName === "apply_patch") {
|
|
1218
1239
|
return {
|
|
1219
1240
|
...safeArgs,
|
|
@@ -1309,6 +1330,19 @@ export function sanitizeToolResult(result) {
|
|
|
1309
1330
|
safeResult.draftTruncated = true;
|
|
1310
1331
|
delete safeResult.draft;
|
|
1311
1332
|
}
|
|
1333
|
+
if (safeResult.toolName === "json_specialist" && safeResult.result !== undefined) {
|
|
1334
|
+
const encoded = JSON.stringify(safeResult.result);
|
|
1335
|
+
safeResult.resultBytes = Buffer.byteLength(encoded, "utf8");
|
|
1336
|
+
if (safeResult.resultBytes > TOOL_RESULT_INLINE_CONTENT_BYTES) {
|
|
1337
|
+
safeResult.resultPreview = encoded.slice(0, TOOL_RESULT_CONTENT_PREVIEW_CHARS);
|
|
1338
|
+
safeResult.resultTruncated = true;
|
|
1339
|
+
delete safeResult.result;
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
if (safeResult.toolName === "json_specialist_batch" && Array.isArray(safeResult.results)) {
|
|
1343
|
+
safeResult.resultCount = safeResult.results.length;
|
|
1344
|
+
safeResult.results = safeResult.results.map((item) => sanitizeToolResult(item));
|
|
1345
|
+
}
|
|
1312
1346
|
return safeResult;
|
|
1313
1347
|
}
|
|
1314
1348
|
|
|
@@ -1584,6 +1618,20 @@ async function executeTool(browserState, toolCall, snapshot, config, store, obse
|
|
|
1584
1618
|
observers.event(result.ok ? "tool.completed" : "tool.failed", eventResult);
|
|
1585
1619
|
return result;
|
|
1586
1620
|
}
|
|
1621
|
+
case "json_specialist": {
|
|
1622
|
+
const result = await runJsonSpecialist(args, config, store);
|
|
1623
|
+
const eventResult = sanitizeToolResult(result);
|
|
1624
|
+
await store.appendEvent(result.ok ? "tool.completed" : "tool.failed", eventResult);
|
|
1625
|
+
observers.event(result.ok ? "tool.completed" : "tool.failed", eventResult);
|
|
1626
|
+
return result;
|
|
1627
|
+
}
|
|
1628
|
+
case "json_specialist_batch": {
|
|
1629
|
+
const result = await runJsonSpecialistBatch(args.items || [], { ...args, items: undefined }, config, store);
|
|
1630
|
+
const eventResult = sanitizeToolResult(result);
|
|
1631
|
+
await store.appendEvent(result.ok ? "tool.completed" : "tool.failed", eventResult);
|
|
1632
|
+
observers.event(result.ok ? "tool.completed" : "tool.failed", eventResult);
|
|
1633
|
+
return result;
|
|
1634
|
+
}
|
|
1587
1635
|
case "writing_specialist": {
|
|
1588
1636
|
const result = await runWritingSpecialist(args, config, store);
|
|
1589
1637
|
const eventResult = sanitizeToolResult(result);
|
package/src/guardrails.js
CHANGED
|
@@ -304,6 +304,66 @@ export function checkToolUse({ toolName, args, snapshot, config }) {
|
|
|
304
304
|
return { allowed: true };
|
|
305
305
|
}
|
|
306
306
|
|
|
307
|
+
if (toolName === "json_specialist") {
|
|
308
|
+
const task = String(args.task || args.prompt || "").trim();
|
|
309
|
+
if (!task) return { allowed: false, reason: "JSON specialist requires task.", category: "json-specialist" };
|
|
310
|
+
if (!args.schema && !String(args.schemaJson || "").trim()) {
|
|
311
|
+
return { allowed: false, reason: "JSON specialist requires schema or schemaJson.", category: "json-specialist" };
|
|
312
|
+
}
|
|
313
|
+
const provider = String(args.provider || process.env.AGINTI_JSON_PROVIDER || "").trim();
|
|
314
|
+
if (provider && !["deepseek", "openai", "qwen", "venice", "mock"].includes(provider)) {
|
|
315
|
+
return { allowed: false, reason: `Unknown JSON specialist provider: ${provider}`, category: "json-specialist" };
|
|
316
|
+
}
|
|
317
|
+
const payloadBytes = Buffer.byteLength(
|
|
318
|
+
[
|
|
319
|
+
task,
|
|
320
|
+
args.instructions,
|
|
321
|
+
args.requirements,
|
|
322
|
+
args.context,
|
|
323
|
+
args.inputText,
|
|
324
|
+
args.source,
|
|
325
|
+
args.content,
|
|
326
|
+
args.schemaJson,
|
|
327
|
+
args.inputJson ? JSON.stringify(args.inputJson) : "",
|
|
328
|
+
args.schema ? JSON.stringify(args.schema) : "",
|
|
329
|
+
]
|
|
330
|
+
.filter(Boolean)
|
|
331
|
+
.join("\n"),
|
|
332
|
+
"utf8"
|
|
333
|
+
);
|
|
334
|
+
if (payloadBytes > 220_000) {
|
|
335
|
+
return {
|
|
336
|
+
allowed: false,
|
|
337
|
+
reason: "JSON specialist payload is too large. Split the source into smaller independent items or save inputs to files and pass a focused excerpt.",
|
|
338
|
+
category: "json-specialist",
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
return { allowed: true };
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (toolName === "json_specialist_batch") {
|
|
345
|
+
const items = Array.isArray(args.items) ? args.items : [];
|
|
346
|
+
if (items.length === 0) return { allowed: false, reason: "JSON specialist batch requires items.", category: "json-specialist" };
|
|
347
|
+
if (items.length > 32) return { allowed: false, reason: "JSON specialist batch is limited to 32 items per tool call.", category: "json-specialist" };
|
|
348
|
+
const concurrency = Number(args.concurrency || 4);
|
|
349
|
+
if (Number.isFinite(concurrency) && concurrency > 16) {
|
|
350
|
+
return { allowed: false, reason: "JSON specialist batch concurrency is limited to 16.", category: "json-specialist" };
|
|
351
|
+
}
|
|
352
|
+
const provider = String(args.provider || args.defaults?.provider || process.env.AGINTI_JSON_PROVIDER || "").trim();
|
|
353
|
+
if (provider && !["deepseek", "openai", "qwen", "venice", "mock"].includes(provider)) {
|
|
354
|
+
return { allowed: false, reason: `Unknown JSON specialist provider: ${provider}`, category: "json-specialist" };
|
|
355
|
+
}
|
|
356
|
+
const payloadBytes = Buffer.byteLength(JSON.stringify({ defaults: args.defaults || {}, items }), "utf8");
|
|
357
|
+
if (payloadBytes > 360_000) {
|
|
358
|
+
return {
|
|
359
|
+
allowed: false,
|
|
360
|
+
reason: "JSON specialist batch payload is too large. Use fewer items or smaller chunk text per call.",
|
|
361
|
+
category: "json-specialist",
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
return { allowed: true };
|
|
365
|
+
}
|
|
366
|
+
|
|
307
367
|
if (toolName === "generate_image") {
|
|
308
368
|
if (!config.allowAuxiliaryTools) {
|
|
309
369
|
return { allowed: false, reason: "Auxiliary tools are disabled for this run.", category: "auxiliary-tools" };
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { createChatCompletion, createClient } from "./model-client.js";
|
|
3
|
+
import { getProviderDefaults } from "./model-routing.js";
|
|
4
|
+
import { redactSensitiveText, redactValue } from "./redaction.js";
|
|
5
|
+
|
|
6
|
+
const MAX_INLINE_PREVIEW = 1600;
|
|
7
|
+
const JSON_PROVIDERS = new Set(["openai", "deepseek", "qwen", "venice", "mock"]);
|
|
8
|
+
|
|
9
|
+
function compact(value = "", limit = MAX_INLINE_PREVIEW) {
|
|
10
|
+
const text = redactSensitiveText(String(value || "").trim());
|
|
11
|
+
if (text.length <= limit) return text;
|
|
12
|
+
return `${text.slice(0, Math.max(0, limit - 24))} ... [truncated]`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function parseJsonValue(content = "") {
|
|
16
|
+
const text = String(content || "").trim();
|
|
17
|
+
if (!text) return { ok: false, error: "empty JSON response" };
|
|
18
|
+
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
19
|
+
const candidates = [fenced?.[1], text].filter(Boolean);
|
|
20
|
+
for (const candidate of candidates) {
|
|
21
|
+
try {
|
|
22
|
+
return { ok: true, value: JSON.parse(candidate.trim()) };
|
|
23
|
+
} catch {
|
|
24
|
+
// Try a balanced excerpt below.
|
|
25
|
+
}
|
|
26
|
+
const starts = ["{", "["]
|
|
27
|
+
.map((char) => ({ char, index: candidate.indexOf(char) }))
|
|
28
|
+
.filter((item) => item.index >= 0)
|
|
29
|
+
.sort((a, b) => a.index - b.index);
|
|
30
|
+
for (const start of starts) {
|
|
31
|
+
const close = start.char === "{" ? "}" : "]";
|
|
32
|
+
const end = candidate.lastIndexOf(close);
|
|
33
|
+
if (end <= start.index) continue;
|
|
34
|
+
try {
|
|
35
|
+
return { ok: true, value: JSON.parse(candidate.slice(start.index, end + 1)) };
|
|
36
|
+
} catch {
|
|
37
|
+
// Keep looking.
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return { ok: false, error: "response was not parseable JSON" };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function normalizeSchema(raw) {
|
|
45
|
+
let schema = raw;
|
|
46
|
+
let name = "";
|
|
47
|
+
let strict = true;
|
|
48
|
+
if (typeof schema === "string" && schema.trim()) {
|
|
49
|
+
schema = JSON.parse(schema);
|
|
50
|
+
}
|
|
51
|
+
if (schema?.schema && typeof schema.schema === "object" && !schema.type) {
|
|
52
|
+
name = String(schema.name || "").trim();
|
|
53
|
+
strict = schema.strict !== false;
|
|
54
|
+
schema = schema.schema;
|
|
55
|
+
}
|
|
56
|
+
if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
|
|
57
|
+
throw new Error("JSON specialist requires a JSON Schema object.");
|
|
58
|
+
}
|
|
59
|
+
return { schema, name, strict };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function normalizeInputValue(args = {}) {
|
|
63
|
+
if (args.inputJson !== undefined) return redactValue(args.inputJson);
|
|
64
|
+
if (args.input !== undefined) return redactValue(args.input);
|
|
65
|
+
const text = args.inputText ?? args.source ?? args.content ?? "";
|
|
66
|
+
return redactSensitiveText(String(text || ""));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function normalizeResponseFormat(value = "") {
|
|
70
|
+
const normalized = String(value || "auto").trim().toLowerCase();
|
|
71
|
+
if (["auto", "json_schema", "json_object", "prompt"].includes(normalized)) return normalized;
|
|
72
|
+
return "auto";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function normalizeJsonRequest(args = {}) {
|
|
76
|
+
const normalizedSchema = normalizeSchema(args.schemaJson || args.schema);
|
|
77
|
+
const task = redactSensitiveText(String(args.task || args.prompt || "").trim());
|
|
78
|
+
const instructions = redactSensitiveText(String(args.instructions || args.requirements || "").trim());
|
|
79
|
+
const context = redactSensitiveText(String(args.context || "").trim());
|
|
80
|
+
const temperature = Number.isFinite(Number(args.temperature)) ? Math.min(Math.max(Number(args.temperature), 0), 1.2) : 0;
|
|
81
|
+
const maxTokens = Number.isFinite(Number(args.maxTokens)) ? Math.max(256, Math.floor(Number(args.maxTokens))) : 4096;
|
|
82
|
+
const provider = String(args.provider || process.env.AGINTI_JSON_PROVIDER || "").trim();
|
|
83
|
+
const model = String(args.model || process.env.AGINTI_JSON_MODEL || "").trim();
|
|
84
|
+
const schemaName = String(args.schemaName || normalizedSchema.name || "aginti_structured_output")
|
|
85
|
+
.trim()
|
|
86
|
+
.replace(/[^A-Za-z0-9_-]/g, "_")
|
|
87
|
+
.slice(0, 64) || "aginti_structured_output";
|
|
88
|
+
return {
|
|
89
|
+
task,
|
|
90
|
+
instructions,
|
|
91
|
+
context,
|
|
92
|
+
input: normalizeInputValue(args),
|
|
93
|
+
schema: normalizedSchema.schema,
|
|
94
|
+
schemaName,
|
|
95
|
+
strict: args.strict === undefined ? normalizedSchema.strict : args.strict !== false,
|
|
96
|
+
responseFormat: normalizeResponseFormat(args.responseFormat),
|
|
97
|
+
fallbackOnInvalid: args.fallbackOnInvalid !== false,
|
|
98
|
+
temperature,
|
|
99
|
+
maxTokens,
|
|
100
|
+
provider,
|
|
101
|
+
model,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function jsonSystemPrompt() {
|
|
106
|
+
return [
|
|
107
|
+
"You are the isolated AgInTiFlow JSON Specialist.",
|
|
108
|
+
"You transform only the supplied task, input, and schema into structured JSON.",
|
|
109
|
+
"You do not know or discuss AgInTiFlow internals, shell tools, browser tools, file policies, planning, package installs, or execution constraints.",
|
|
110
|
+
"Never include commentary, markdown fences, prose explanations, or partial objects.",
|
|
111
|
+
"Return one JSON value that satisfies the provided schema.",
|
|
112
|
+
].join(" ");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function jsonUserPrompt(request, attempt) {
|
|
116
|
+
return JSON.stringify(
|
|
117
|
+
{
|
|
118
|
+
boundary:
|
|
119
|
+
"This is the complete context visible to the JSON specialist. Ignore absent agent/runtime details. Produce only schema-valid JSON.",
|
|
120
|
+
task: request.task,
|
|
121
|
+
instructions: request.instructions,
|
|
122
|
+
context: request.context,
|
|
123
|
+
input: request.input,
|
|
124
|
+
json_schema: request.schema,
|
|
125
|
+
output_contract: {
|
|
126
|
+
mode: attempt,
|
|
127
|
+
strict: request.strict,
|
|
128
|
+
requirement: "Return exactly one JSON value matching json_schema. Do not wrap it in markdown.",
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
null,
|
|
132
|
+
2
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function allowedTypes(schema) {
|
|
137
|
+
if (!schema || schema.type === undefined) return [];
|
|
138
|
+
return Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function typeMatches(value, type) {
|
|
142
|
+
if (type === "array") return Array.isArray(value);
|
|
143
|
+
if (type === "object") return value && typeof value === "object" && !Array.isArray(value);
|
|
144
|
+
if (type === "integer") return Number.isInteger(value);
|
|
145
|
+
if (type === "number") return typeof value === "number" && Number.isFinite(value);
|
|
146
|
+
if (type === "string") return typeof value === "string";
|
|
147
|
+
if (type === "boolean") return typeof value === "boolean";
|
|
148
|
+
if (type === "null") return value === null;
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function validateSchema(value, schema, path = "$") {
|
|
153
|
+
if (!schema || typeof schema !== "object") return [];
|
|
154
|
+
if (Array.isArray(schema.anyOf) && schema.anyOf.length > 0) {
|
|
155
|
+
const variants = schema.anyOf.map((variant) => validateSchema(value, variant, path));
|
|
156
|
+
return variants.some((errs) => errs.length === 0) ? [] : [`${path}: did not match anyOf`];
|
|
157
|
+
}
|
|
158
|
+
if (Array.isArray(schema.oneOf) && schema.oneOf.length > 0) {
|
|
159
|
+
const matches = schema.oneOf.filter((variant) => validateSchema(value, variant, path).length === 0).length;
|
|
160
|
+
return matches === 1 ? [] : [`${path}: did not match exactly one oneOf variant`];
|
|
161
|
+
}
|
|
162
|
+
const errors = [];
|
|
163
|
+
if (schema.const !== undefined && JSON.stringify(value) !== JSON.stringify(schema.const)) {
|
|
164
|
+
errors.push(`${path}: expected const ${JSON.stringify(schema.const)}`);
|
|
165
|
+
}
|
|
166
|
+
if (Array.isArray(schema.enum) && !schema.enum.some((item) => JSON.stringify(item) === JSON.stringify(value))) {
|
|
167
|
+
errors.push(`${path}: value is not in enum`);
|
|
168
|
+
}
|
|
169
|
+
const types = allowedTypes(schema);
|
|
170
|
+
if (types.length > 0 && !types.some((type) => typeMatches(value, type))) {
|
|
171
|
+
errors.push(`${path}: expected type ${types.join("|")}`);
|
|
172
|
+
return errors;
|
|
173
|
+
}
|
|
174
|
+
if (Array.isArray(value)) {
|
|
175
|
+
if (Number.isFinite(schema.minItems) && value.length < schema.minItems) errors.push(`${path}: fewer than minItems`);
|
|
176
|
+
if (Number.isFinite(schema.maxItems) && value.length > schema.maxItems) errors.push(`${path}: more than maxItems`);
|
|
177
|
+
if (schema.items) {
|
|
178
|
+
value.forEach((item, index) => errors.push(...validateSchema(item, schema.items, `${path}[${index}]`)));
|
|
179
|
+
}
|
|
180
|
+
} else if (value && typeof value === "object") {
|
|
181
|
+
const properties = schema.properties && typeof schema.properties === "object" ? schema.properties : {};
|
|
182
|
+
for (const key of schema.required || []) {
|
|
183
|
+
if (!(key in value)) errors.push(`${path}.${key}: required property missing`);
|
|
184
|
+
}
|
|
185
|
+
for (const [key, child] of Object.entries(properties)) {
|
|
186
|
+
if (key in value) errors.push(...validateSchema(value[key], child, `${path}.${key}`));
|
|
187
|
+
}
|
|
188
|
+
if (schema.additionalProperties === false) {
|
|
189
|
+
for (const key of Object.keys(value)) {
|
|
190
|
+
if (!(key in properties)) errors.push(`${path}.${key}: additional property not allowed`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return errors;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function mockValueForSchema(schema) {
|
|
198
|
+
if (!schema || typeof schema !== "object") return {};
|
|
199
|
+
if (schema.const !== undefined) return schema.const;
|
|
200
|
+
if (Array.isArray(schema.enum) && schema.enum.length > 0) return schema.enum[0];
|
|
201
|
+
const type = allowedTypes(schema)[0] || (schema.properties ? "object" : "string");
|
|
202
|
+
if (type === "string") return "mock";
|
|
203
|
+
if (type === "integer") return 1;
|
|
204
|
+
if (type === "number") return 1;
|
|
205
|
+
if (type === "boolean") return true;
|
|
206
|
+
if (type === "null") return null;
|
|
207
|
+
if (type === "array") {
|
|
208
|
+
const count = Number.isFinite(schema.minItems) ? Math.max(1, schema.minItems) : 1;
|
|
209
|
+
return Array.from({ length: count }, () => mockValueForSchema(schema.items || { type: "string" }));
|
|
210
|
+
}
|
|
211
|
+
const result = {};
|
|
212
|
+
const properties = schema.properties && typeof schema.properties === "object" ? schema.properties : {};
|
|
213
|
+
const keys = new Set([...(schema.required || []), ...Object.keys(properties).slice(0, 6)]);
|
|
214
|
+
for (const key of keys) result[key] = mockValueForSchema(properties[key] || { type: "string" });
|
|
215
|
+
return result;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function attemptsForRequest(request, provider) {
|
|
219
|
+
if (request.responseFormat === "prompt") return ["prompt"];
|
|
220
|
+
if (request.responseFormat === "json_schema") return ["json_schema", "prompt"];
|
|
221
|
+
if (request.responseFormat === "json_object") return ["json_object", "prompt"];
|
|
222
|
+
if (["openai", "deepseek", "qwen"].includes(provider)) return ["json_schema", "json_object", "prompt"];
|
|
223
|
+
return ["json_object", "prompt"];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function responseFormatForAttempt(request, attempt) {
|
|
227
|
+
if (attempt === "json_schema") {
|
|
228
|
+
return {
|
|
229
|
+
type: "json_schema",
|
|
230
|
+
json_schema: {
|
|
231
|
+
name: request.schemaName,
|
|
232
|
+
strict: request.strict,
|
|
233
|
+
schema: request.schema,
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
if (attempt === "json_object") return { type: "json_object" };
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function errorLooksLikeUnsupportedResponseFormat(error) {
|
|
242
|
+
const message = [error?.message, error?.error?.message, error?.response?.data?.error?.message]
|
|
243
|
+
.filter(Boolean)
|
|
244
|
+
.join(" ");
|
|
245
|
+
return /response_format|json_schema|json_object|unsupported|invalid request|400/i.test(message);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function parsedRawPreview(rawContent = "") {
|
|
249
|
+
return rawContent ? compact(rawContent, 1200) : "";
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export async function runJsonSpecialist(args = {}, config = {}, store = null) {
|
|
253
|
+
let request;
|
|
254
|
+
try {
|
|
255
|
+
request = normalizeJsonRequest(args);
|
|
256
|
+
} catch (error) {
|
|
257
|
+
return {
|
|
258
|
+
ok: false,
|
|
259
|
+
toolName: "json_specialist",
|
|
260
|
+
reason: redactSensitiveText(error instanceof Error ? error.message : String(error)),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
if (!request.task) {
|
|
264
|
+
return {
|
|
265
|
+
ok: false,
|
|
266
|
+
toolName: "json_specialist",
|
|
267
|
+
reason: "task is required.",
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const startedAt = new Date().toISOString();
|
|
272
|
+
const requestFingerprint = crypto.createHash("sha256").update(JSON.stringify(redactValue(request))).digest("hex");
|
|
273
|
+
let model = request.model || config.model || "";
|
|
274
|
+
let provider = request.provider || config.provider || "";
|
|
275
|
+
let rawContent = "";
|
|
276
|
+
const attemptNotes = [];
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
let result;
|
|
280
|
+
let validationErrors = [];
|
|
281
|
+
let usedResponseFormat = "mock";
|
|
282
|
+
if (config.provider === "mock" || provider === "mock") {
|
|
283
|
+
provider = "mock";
|
|
284
|
+
model = request.model || config.model || "mock-agent";
|
|
285
|
+
result = mockValueForSchema(request.schema);
|
|
286
|
+
validationErrors = validateSchema(result, request.schema);
|
|
287
|
+
} else {
|
|
288
|
+
if (provider && !JSON_PROVIDERS.has(provider)) {
|
|
289
|
+
throw new Error(`Unknown JSON specialist provider: ${provider}`);
|
|
290
|
+
}
|
|
291
|
+
const providerDefaults = request.provider ? getProviderDefaults(request.provider) : {};
|
|
292
|
+
const jsonConfig = {
|
|
293
|
+
...config,
|
|
294
|
+
...providerDefaults,
|
|
295
|
+
provider: provider || config.provider,
|
|
296
|
+
model: model || providerDefaults.model || config.model,
|
|
297
|
+
};
|
|
298
|
+
model = jsonConfig.model;
|
|
299
|
+
provider = jsonConfig.provider;
|
|
300
|
+
const client = createClient(jsonConfig);
|
|
301
|
+
const attempts = attemptsForRequest(request, provider);
|
|
302
|
+
for (const attempt of attempts) {
|
|
303
|
+
const responseFormat = responseFormatForAttempt(request, attempt);
|
|
304
|
+
const payload = {
|
|
305
|
+
model: jsonConfig.model,
|
|
306
|
+
temperature: request.temperature,
|
|
307
|
+
max_tokens: request.maxTokens,
|
|
308
|
+
messages: [
|
|
309
|
+
{ role: "system", content: jsonSystemPrompt() },
|
|
310
|
+
{ role: "user", content: jsonUserPrompt(request, attempt) },
|
|
311
|
+
],
|
|
312
|
+
...(responseFormat ? { response_format: responseFormat } : {}),
|
|
313
|
+
};
|
|
314
|
+
try {
|
|
315
|
+
const response = await createChatCompletion(client, payload, jsonConfig, `json specialist ${attempt} request`);
|
|
316
|
+
rawContent = response.choices[0]?.message?.content || "";
|
|
317
|
+
const parsed = parseJsonValue(rawContent);
|
|
318
|
+
if (!parsed.ok) {
|
|
319
|
+
attemptNotes.push(`${attempt}: ${parsed.error}`);
|
|
320
|
+
if (request.fallbackOnInvalid && attempt !== attempts.at(-1)) continue;
|
|
321
|
+
throw new Error(parsed.error);
|
|
322
|
+
}
|
|
323
|
+
const errors = validateSchema(parsed.value, request.schema);
|
|
324
|
+
if (errors.length > 0) {
|
|
325
|
+
attemptNotes.push(`${attempt}: ${errors.slice(0, 4).join("; ")}`);
|
|
326
|
+
if (request.fallbackOnInvalid && attempt !== attempts.at(-1)) continue;
|
|
327
|
+
}
|
|
328
|
+
result = parsed.value;
|
|
329
|
+
validationErrors = errors;
|
|
330
|
+
usedResponseFormat = attempt;
|
|
331
|
+
break;
|
|
332
|
+
} catch (error) {
|
|
333
|
+
const message = redactSensitiveText(error instanceof Error ? error.message : String(error));
|
|
334
|
+
attemptNotes.push(`${attempt}: ${message}`);
|
|
335
|
+
if (attempt !== attempts.at(-1) && (request.fallbackOnInvalid || errorLooksLikeUnsupportedResponseFormat(error))) continue;
|
|
336
|
+
throw error;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const artifact = {
|
|
342
|
+
version: 1,
|
|
343
|
+
generatedAt: new Date().toISOString(),
|
|
344
|
+
startedAt,
|
|
345
|
+
provider,
|
|
346
|
+
model,
|
|
347
|
+
responseFormat: usedResponseFormat,
|
|
348
|
+
requestFingerprint,
|
|
349
|
+
request: redactValue(request),
|
|
350
|
+
result: redactValue(result),
|
|
351
|
+
validationErrors,
|
|
352
|
+
attemptNotes,
|
|
353
|
+
rawPreview: parsedRawPreview(rawContent),
|
|
354
|
+
};
|
|
355
|
+
const artifactPath = store
|
|
356
|
+
? await store.saveJsonArtifact(`json-specialist-${Date.now()}.json`, artifact).catch(() => "")
|
|
357
|
+
: "";
|
|
358
|
+
return {
|
|
359
|
+
ok: validationErrors.length === 0,
|
|
360
|
+
toolName: "json_specialist",
|
|
361
|
+
provider,
|
|
362
|
+
model,
|
|
363
|
+
responseFormat: usedResponseFormat,
|
|
364
|
+
args: {
|
|
365
|
+
task: request.task,
|
|
366
|
+
schemaName: request.schemaName,
|
|
367
|
+
responseFormat: request.responseFormat,
|
|
368
|
+
provider: request.provider,
|
|
369
|
+
requestFingerprint,
|
|
370
|
+
},
|
|
371
|
+
artifactPath,
|
|
372
|
+
result,
|
|
373
|
+
validationErrors,
|
|
374
|
+
attemptNotes,
|
|
375
|
+
rawPreview: parsedRawPreview(rawContent),
|
|
376
|
+
};
|
|
377
|
+
} catch (error) {
|
|
378
|
+
return {
|
|
379
|
+
ok: false,
|
|
380
|
+
toolName: "json_specialist",
|
|
381
|
+
provider,
|
|
382
|
+
model,
|
|
383
|
+
args: {
|
|
384
|
+
task: request.task,
|
|
385
|
+
schemaName: request.schemaName,
|
|
386
|
+
responseFormat: request.responseFormat,
|
|
387
|
+
provider: request.provider,
|
|
388
|
+
requestFingerprint,
|
|
389
|
+
},
|
|
390
|
+
error: redactSensitiveText(error instanceof Error ? error.message : String(error)),
|
|
391
|
+
attemptNotes,
|
|
392
|
+
rawPreview: parsedRawPreview(rawContent),
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export async function runJsonSpecialistBatch(tasks = [], options = {}, config = {}, store = null) {
|
|
398
|
+
const items = Array.isArray(tasks) ? tasks : [];
|
|
399
|
+
const concurrency = Math.min(Math.max(Number(options.concurrency) || 4, 1), 32);
|
|
400
|
+
const results = new Array(items.length);
|
|
401
|
+
let nextIndex = 0;
|
|
402
|
+
|
|
403
|
+
async function worker() {
|
|
404
|
+
while (nextIndex < items.length) {
|
|
405
|
+
const index = nextIndex;
|
|
406
|
+
nextIndex += 1;
|
|
407
|
+
const task = items[index] || {};
|
|
408
|
+
results[index] = await runJsonSpecialist({ ...options.defaults, ...task }, config, store);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => worker()));
|
|
413
|
+
return {
|
|
414
|
+
ok: results.every((item) => item?.ok),
|
|
415
|
+
toolName: "json_specialist_batch",
|
|
416
|
+
count: results.length,
|
|
417
|
+
succeeded: results.filter((item) => item?.ok).length,
|
|
418
|
+
failed: results.filter((item) => !item?.ok).length,
|
|
419
|
+
concurrency,
|
|
420
|
+
results,
|
|
421
|
+
};
|
|
422
|
+
}
|
package/src/model-client.js
CHANGED
|
@@ -567,6 +567,27 @@ function mockWritingSpecialistToolForGoal(goal = "", taskProfile = "") {
|
|
|
567
567
|
});
|
|
568
568
|
}
|
|
569
569
|
|
|
570
|
+
function mockJsonSpecialistToolForGoal(goal = "") {
|
|
571
|
+
const text = String(goal || "");
|
|
572
|
+
if (!/\b(json|schema|structured output|extract structured|valid object|valid array)\b/i.test(text)) return null;
|
|
573
|
+
return mockToolCall("json_specialist", {
|
|
574
|
+
task: text.replace(/\s+/g, " ").slice(0, 800) || "Return structured JSON.",
|
|
575
|
+
schemaName: "mock_structured_output",
|
|
576
|
+
schema: {
|
|
577
|
+
type: "object",
|
|
578
|
+
properties: {
|
|
579
|
+
summary: { type: "string" },
|
|
580
|
+
complete: { type: "boolean" },
|
|
581
|
+
},
|
|
582
|
+
required: ["summary", "complete"],
|
|
583
|
+
additionalProperties: false,
|
|
584
|
+
},
|
|
585
|
+
inputText: text.slice(0, 1200),
|
|
586
|
+
responseFormat: "prompt",
|
|
587
|
+
provider: "mock",
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
|
|
570
591
|
function mockPreviewToolForGoal(goal = "") {
|
|
571
592
|
const text = String(goal).toLowerCase();
|
|
572
593
|
if (!/(open|preview|view|browser|website|web\s*site)/.test(text)) return null;
|
|
@@ -676,6 +697,7 @@ export async function createPlan(client, config, state) {
|
|
|
676
697
|
? "web_search is available for lightweight snippets. web_research is available for auditable sourced research with persisted artifacts; use mode=snippets by default and mode=openai only when hosted OpenAI web research is needed and configured. Prefer these tools over opening a search engine in the browser."
|
|
677
698
|
: "web_search is disabled for this run.",
|
|
678
699
|
"For substantial writing work such as novels, chapters, books, scripts, essays, LaTeX manuscripts, or research-paper prose, plan to call writing_specialist with only the writing brief/canon/style/draft context. The main agent should handle files, citations, checks, and Markdown/LaTeX/Final Draft formatting after the isolated writing draft returns.",
|
|
700
|
+
"For repetitive schema-bound extraction, annotation, conversion, or validation tasks, use json_specialist with only the task, input, schema, and focused instructions. It calls the model directly for strict JSON, tries provider-native structured output when supported, and keeps agent/runtime/tool context out of the specialist prompt.",
|
|
679
701
|
config.allowFileTools
|
|
680
702
|
? "read_image is available for workspace-local or allowed remote screenshots/images using OpenAI vision when OPENAI_API_KEY is configured. It returns typed visual observations and persists a perception artifact; if credentials are missing, report the blocker instead of guessing from the filename."
|
|
681
703
|
: "",
|
|
@@ -729,6 +751,66 @@ export async function requestNextStep(client, config, messages) {
|
|
|
729
751
|
},
|
|
730
752
|
},
|
|
731
753
|
},
|
|
754
|
+
{
|
|
755
|
+
type: "function",
|
|
756
|
+
function: {
|
|
757
|
+
name: "json_specialist",
|
|
758
|
+
description:
|
|
759
|
+
"Call an isolated schema-only LLM context for strict JSON extraction, annotation, classification, conversion, or validation. Pass only the task, input, schema, and focused instructions; do not pass shell/browser/file policy, agent runtime, or broad planning context. The tool tries provider-native structured JSON where supported and falls back to prompt-and-validate parsing.",
|
|
760
|
+
parameters: {
|
|
761
|
+
type: "object",
|
|
762
|
+
properties: {
|
|
763
|
+
task: { type: "string", description: "Focused JSON task. Required." },
|
|
764
|
+
instructions: { type: "string", description: "Additional schema-specific rules or quality constraints." },
|
|
765
|
+
context: { type: "string", description: "Minimal domain context needed for the JSON transformation." },
|
|
766
|
+
inputText: { type: "string", description: "Source text or serialized input to transform." },
|
|
767
|
+
inputJson: { type: "object", description: "Source object to transform.", additionalProperties: true },
|
|
768
|
+
schema: { type: "object", description: "JSON Schema object for the expected output.", additionalProperties: true },
|
|
769
|
+
schemaJson: { type: "string", description: "JSON-stringified schema alternative when schema is easier to pass as text." },
|
|
770
|
+
schemaName: { type: "string", description: "Short schema name for provider-native structured output." },
|
|
771
|
+
responseFormat: {
|
|
772
|
+
type: "string",
|
|
773
|
+
enum: ["auto", "json_schema", "json_object", "prompt"],
|
|
774
|
+
description: "Native structured-output preference. auto tries native JSON schema/object then prompt fallback.",
|
|
775
|
+
},
|
|
776
|
+
fallbackOnInvalid: { type: "boolean", description: "Try a weaker fallback if native structured output is unsupported or invalid. Defaults true." },
|
|
777
|
+
strict: { type: "boolean", description: "Use strict provider schema mode where supported. Defaults true." },
|
|
778
|
+
temperature: { type: "number", description: "Defaults to 0 for deterministic structured data." },
|
|
779
|
+
maxTokens: { type: "integer", description: "Maximum completion tokens for the JSON specialist call." },
|
|
780
|
+
provider: {
|
|
781
|
+
type: "string",
|
|
782
|
+
enum: ["deepseek", "openai", "qwen", "venice", "mock"],
|
|
783
|
+
description: "Optional provider override. Defaults to AGINTI_JSON_PROVIDER or current provider.",
|
|
784
|
+
},
|
|
785
|
+
model: { type: "string", description: "Optional model override. Defaults to AGINTI_JSON_MODEL or current model." },
|
|
786
|
+
},
|
|
787
|
+
required: ["task"],
|
|
788
|
+
additionalProperties: false,
|
|
789
|
+
},
|
|
790
|
+
},
|
|
791
|
+
},
|
|
792
|
+
{
|
|
793
|
+
type: "function",
|
|
794
|
+
function: {
|
|
795
|
+
name: "json_specialist_batch",
|
|
796
|
+
description:
|
|
797
|
+
"Run many isolated json_specialist requests concurrently for chunked structured-data work. Each item is one independent schema-bound task. Use only for independent items with no shared writes or ordering dependency.",
|
|
798
|
+
parameters: {
|
|
799
|
+
type: "object",
|
|
800
|
+
properties: {
|
|
801
|
+
concurrency: { type: "integer", description: "Parallel request count, clamped by runtime guardrails." },
|
|
802
|
+
defaults: { type: "object", description: "Default json_specialist arguments applied to every item.", additionalProperties: true },
|
|
803
|
+
items: {
|
|
804
|
+
type: "array",
|
|
805
|
+
description: "Independent json_specialist argument objects.",
|
|
806
|
+
items: { type: "object", additionalProperties: true },
|
|
807
|
+
},
|
|
808
|
+
},
|
|
809
|
+
required: ["items"],
|
|
810
|
+
additionalProperties: false,
|
|
811
|
+
},
|
|
812
|
+
},
|
|
813
|
+
},
|
|
732
814
|
{
|
|
733
815
|
type: "function",
|
|
734
816
|
function: {
|
|
@@ -1393,6 +1475,11 @@ export async function requestNextStep(client, config, messages) {
|
|
|
1393
1475
|
}
|
|
1394
1476
|
}
|
|
1395
1477
|
|
|
1478
|
+
const jsonTool = mockJsonSpecialistToolForGoal(config.goal);
|
|
1479
|
+
if (jsonTool) {
|
|
1480
|
+
return mockChatResponse("Mock mode will exercise the isolated JSON specialist.", [jsonTool]);
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1396
1483
|
const writingTool = mockWritingSpecialistToolForGoal(config.goal, config.taskProfile);
|
|
1397
1484
|
if (writingTool) {
|
|
1398
1485
|
return mockChatResponse("Mock mode will exercise the isolated writing specialist before file or format work.", [writingTool]);
|