zames_pro 2.6.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-loop.js +118 -82
- package/dist/browser.js +40 -41
- package/dist/config-menu.js +25 -25
- package/dist/config.js +5 -5
- package/dist/gitTools.js +4 -4
- package/dist/i18n.js +11 -7
- package/dist/index.js +153 -150
- package/dist/input.js +82 -77
- package/dist/markdown.js +1 -1
- package/dist/net-capture.js +20 -20
- package/dist/self-review.js +16 -16
- package/dist/sessions.js +11 -11
- package/dist/spinner.js +20 -16
- package/dist/system-prompt.js +23 -0
- package/dist/theme.js +26 -26
- package/dist/tools.js +9 -9
- package/dist/transcript.js +2 -2
- package/dist/types.js +2 -2
- package/dist/web.js +16 -16
- package/dist/xml-toolcall.js +14 -14
- package/package.json +1 -1
package/dist/spinner.js
CHANGED
|
@@ -2,8 +2,8 @@ import ora from 'ora';
|
|
|
2
2
|
import { theme } from './theme.js';
|
|
3
3
|
import { renderMarkdown } from './markdown.js';
|
|
4
4
|
import { translate } from './i18n.js';
|
|
5
|
-
//
|
|
6
|
-
//
|
|
5
|
+
// Phrases for the "agent is working" spinner. Chosen in random order.
|
|
6
|
+
// The text comes from i18n via the key 'spinner.phrases' (strings separated by "|").
|
|
7
7
|
export function randomThinkingPhrase(locale = 'ru') {
|
|
8
8
|
const raw = translate(locale)('spinner.phrases');
|
|
9
9
|
const phrases = raw.split('|').filter((s) => s.trim().length);
|
|
@@ -11,7 +11,7 @@ export function randomThinkingPhrase(locale = 'ru') {
|
|
|
11
11
|
return '';
|
|
12
12
|
return phrases[Math.floor(Math.random() * phrases.length)];
|
|
13
13
|
}
|
|
14
|
-
//
|
|
14
|
+
// Strip the trailing ellipsis from the phrase — dots are animated separately.
|
|
15
15
|
export function stripEllipsis(phrase) {
|
|
16
16
|
return phrase.replace(/[.…]+\s*$/, '');
|
|
17
17
|
}
|
|
@@ -20,8 +20,8 @@ export function createSpinner(locale = 'ru') {
|
|
|
20
20
|
let dotTimer = null;
|
|
21
21
|
let dotPhase = 0;
|
|
22
22
|
let pending = null;
|
|
23
|
-
//
|
|
24
|
-
//
|
|
23
|
+
// Dot animation: start from an empty string (0 dots), then grow.
|
|
24
|
+
// We align the width to the maximum (3) so the hint doesn't shift.
|
|
25
25
|
const DOTS = ['', '.', '..', '...'];
|
|
26
26
|
const DOTS_PAD = ' ';
|
|
27
27
|
const start = (text) => {
|
|
@@ -38,14 +38,14 @@ export function createSpinner(locale = 'ru') {
|
|
|
38
38
|
if (spinner)
|
|
39
39
|
spinner.stop();
|
|
40
40
|
};
|
|
41
|
-
//
|
|
42
|
-
//
|
|
41
|
+
// Hint in the status line: while the agent works the terminal is live,
|
|
42
|
+
// so you can type the next message. Without it this is not obvious.
|
|
43
43
|
const HINT = theme.dim(' · ' + translate(locale)('spinner.hint'));
|
|
44
|
-
//
|
|
44
|
+
// Start the animated status: brown text + "running" dots.
|
|
45
45
|
const startThinking = () => {
|
|
46
46
|
const base = theme.brown(stripEllipsis(randomThinkingPhrase(locale)));
|
|
47
|
-
//
|
|
48
|
-
//
|
|
47
|
+
// The dots are the same color as the base and of fixed width — otherwise
|
|
48
|
+
// the hint on the right "jumps" when the animation phase changes.
|
|
49
49
|
const dots = (n) => theme.brown(DOTS[n] + DOTS_PAD.slice(DOTS[n].length));
|
|
50
50
|
start(base + dots(0) + HINT);
|
|
51
51
|
dotPhase = 0;
|
|
@@ -60,8 +60,8 @@ export function createSpinner(locale = 'ru') {
|
|
|
60
60
|
if (dotTimer.unref)
|
|
61
61
|
dotTimer.unref();
|
|
62
62
|
};
|
|
63
|
-
//
|
|
64
|
-
//
|
|
63
|
+
// Show the typed but not yet sent text instead of the spinner.
|
|
64
|
+
// Lets you type a message right while the agent is working.
|
|
65
65
|
const showPending = () => {
|
|
66
66
|
if (dotTimer) {
|
|
67
67
|
clearInterval(dotTimer);
|
|
@@ -80,8 +80,8 @@ export function createSpinner(locale = 'ru') {
|
|
|
80
80
|
}
|
|
81
81
|
startThinking();
|
|
82
82
|
},
|
|
83
|
-
//
|
|
84
|
-
//
|
|
83
|
+
// The text the user types while the agent is working.
|
|
84
|
+
// Empty string / null — restore the regular spinner.
|
|
85
85
|
setPending: (text) => {
|
|
86
86
|
pending = text && String(text).length ? String(text) : null;
|
|
87
87
|
if (pending)
|
|
@@ -104,12 +104,16 @@ export function createSpinner(locale = 'ru') {
|
|
|
104
104
|
stop();
|
|
105
105
|
const NL = String.fromCharCode(10);
|
|
106
106
|
const rendered = renderMarkdown(msg);
|
|
107
|
-
//
|
|
108
|
-
//
|
|
107
|
+
// Model answer marker: helps visually separate it from the user's input
|
|
108
|
+
// (which is highlighted by the prompt with a golden arrow).
|
|
109
109
|
console.log(NL + theme.assistant('● Ответ') + NL);
|
|
110
110
|
console.log(rendered);
|
|
111
111
|
console.log(theme.dim('─'.repeat(60)) + NL);
|
|
112
112
|
},
|
|
113
|
+
warning: (msg) => {
|
|
114
|
+
stop();
|
|
115
|
+
console.log(String.fromCharCode(10) + theme.warn('⚠ ' + msg));
|
|
116
|
+
},
|
|
113
117
|
stop,
|
|
114
118
|
succeed: () => { },
|
|
115
119
|
fail: () => { },
|
package/dist/system-prompt.js
CHANGED
|
@@ -65,6 +65,29 @@ To give a final answer to the user, respond with:
|
|
|
65
65
|
|
|
66
66
|
{"tool": "respond", "args": {"message": "your final answer here"}}
|
|
67
67
|
|
|
68
|
+
## TOOL CALL FORMAT - EXACT REQUIREMENTS (read carefully)
|
|
69
|
+
|
|
70
|
+
The parser is strict about the SHAPE of your call. These mistakes have caused
|
|
71
|
+
the agent to silently stall (the call was not recognized, so the task ended):
|
|
72
|
+
|
|
73
|
+
1. ALWAYS wrap the call in curly braces { ... }. Never emit tool-colon-quote
|
|
74
|
+
without the leading brace. Never drop the very first character.
|
|
75
|
+
2. ALWAYS put DOUBLE quotes around keys and string values, e.g.
|
|
76
|
+
{"tool": "Read", "args": {"path": "a.ts"}}.
|
|
77
|
+
NEVER use single quotes; that is not valid JSON.
|
|
78
|
+
3. NEVER wrap the call in markdown fences or in XML/DSML tags
|
|
79
|
+
(invoke, parameter, tool_calls, DSML variants). Plain JSON only.
|
|
80
|
+
4. NEVER put prose before or after the JSON in the same response. Not even
|
|
81
|
+
a short lead-in. The whole response is the JSON.
|
|
82
|
+
5. Do NOT truncate long calls. If a Bash command, file content, or Edit is
|
|
83
|
+
large, SPLIT it: run several smaller Bash commands, or write the file in
|
|
84
|
+
parts (Write then Edit). A cut-off JSON never parses and stalls the agent.
|
|
85
|
+
6. One object per call; to call several independent tools at once, use a JSON
|
|
86
|
+
ARRAY of objects, still no prose around it.
|
|
87
|
+
|
|
88
|
+
If you catch yourself about to emit anything other than a bare JSON object or
|
|
89
|
+
array, stop and reformat it first. A malformed call is worse than a slow one.
|
|
90
|
+
|
|
68
91
|
## Git
|
|
69
92
|
|
|
70
93
|
- You CAN commit and push using GitAdd / GitCommit / GitPush. Prefer these over raw \`git\` through Bash.
|
package/dist/theme.js
CHANGED
|
@@ -1,32 +1,32 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// (
|
|
5
|
-
//
|
|
2
|
+
// A calm palette for a dark terminal.
|
|
3
|
+
// Muted, slightly "faded" tones without high saturation
|
|
4
|
+
// (in the spirit of Tokyo Night / Nord). The goal is comfortable reading of
|
|
5
|
+
// long answers, not maximum contrast.
|
|
6
6
|
//
|
|
7
|
-
//
|
|
8
|
-
// - user —
|
|
9
|
-
// - prompt —
|
|
10
|
-
// - dir —
|
|
11
|
-
// - assistant —
|
|
12
|
-
// - tool —
|
|
13
|
-
// - toolResult —
|
|
14
|
-
// - system —
|
|
15
|
-
// - warn —
|
|
16
|
-
// - error —
|
|
7
|
+
// Roles:
|
|
8
|
+
// - user — soft lavender-gray (service messages)
|
|
9
|
+
// - prompt — warm gold (input prompt, noticeable)
|
|
10
|
+
// - dir — light blue (working directory name)
|
|
11
|
+
// - assistant — warm sand (model answers)
|
|
12
|
+
// - tool — muted amber
|
|
13
|
+
// - toolResult — gray-blue
|
|
14
|
+
// - system — neutral gray
|
|
15
|
+
// - warn — calm ochre
|
|
16
|
+
// - error — muted terracotta
|
|
17
17
|
export const theme = {
|
|
18
|
-
user: chalk.hex('#b4b8d0'), //
|
|
19
|
-
prompt: chalk.hex('#c8b06a').bold, //
|
|
20
|
-
dir: chalk.hex('#7fc4f0'), //
|
|
21
|
-
assistant: chalk.hex('#cfc9b0'), //
|
|
22
|
-
tool: chalk.hex('#c6a97e'), //
|
|
23
|
-
toolResult: chalk.hex('#8a9bb5'), //
|
|
24
|
-
system: chalk.hex('#808896'), //
|
|
25
|
-
dim: chalk.hex('#5b616e'), //
|
|
26
|
-
warn: chalk.hex('#c9a86a'), //
|
|
27
|
-
error: chalk.hex('#c98a80'), //
|
|
28
|
-
success: chalk.hex('#a9c08c'), //
|
|
29
|
-
brown: chalk.hex('#a1723f'), //
|
|
18
|
+
user: chalk.hex('#b4b8d0'), // soft lavender-gray
|
|
19
|
+
prompt: chalk.hex('#c8b06a').bold, // input prompt — warm gold, noticeable
|
|
20
|
+
dir: chalk.hex('#7fc4f0'), // working directory name — noticeably blue
|
|
21
|
+
assistant: chalk.hex('#cfc9b0'), // warm sand
|
|
22
|
+
tool: chalk.hex('#c6a97e'), // muted amber
|
|
23
|
+
toolResult: chalk.hex('#8a9bb5'), // gray-blue
|
|
24
|
+
system: chalk.hex('#808896'), // neutral gray
|
|
25
|
+
dim: chalk.hex('#5b616e'), // dark gray
|
|
26
|
+
warn: chalk.hex('#c9a86a'), // calm ochre
|
|
27
|
+
error: chalk.hex('#c98a80'), // muted terracotta
|
|
28
|
+
success: chalk.hex('#a9c08c'), // soft sage
|
|
29
|
+
brown: chalk.hex('#a1723f'), // brown (status/spinner)
|
|
30
30
|
bold: chalk.bold,
|
|
31
31
|
};
|
|
32
32
|
export default theme;
|
package/dist/tools.js
CHANGED
|
@@ -7,16 +7,16 @@ export function createTools(workdir, { undo } = {}) {
|
|
|
7
7
|
const root = path.resolve(workdir);
|
|
8
8
|
const safe = (p) => {
|
|
9
9
|
const resolved = path.resolve(root, p);
|
|
10
|
-
// startsWith(root)
|
|
11
|
-
// (C:\work\proj vs C:\work\proj-old).
|
|
10
|
+
// startsWith(root) would let through sibling paths with a common prefix
|
|
11
|
+
// (C:\work\proj vs C:\work\proj-old). We compute via relative().
|
|
12
12
|
const rel = path.relative(root, resolved);
|
|
13
13
|
if (rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
14
14
|
throw new Error(`Доступ за пределы рабочей директории: ${p}`);
|
|
15
15
|
}
|
|
16
16
|
return resolved;
|
|
17
17
|
};
|
|
18
|
-
// Sandbox (
|
|
19
|
-
//
|
|
18
|
+
// Sandbox (option A): don't let the command go above root.
|
|
19
|
+
// This is a protective barrier, not full OS isolation.
|
|
20
20
|
const assertCommandInsideRoot = (command) => {
|
|
21
21
|
const cmd = String(command || '');
|
|
22
22
|
const cdRe = /(?:^|[;&|]|\s)(?:cd|pushd)\s+([^;&|]+)/gi;
|
|
@@ -74,10 +74,10 @@ export function createTools(workdir, { undo } = {}) {
|
|
|
74
74
|
resolve(parts.join('\n'));
|
|
75
75
|
});
|
|
76
76
|
});
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
77
|
+
// Extracts text from content/content_base64. base64 is needed because the
|
|
78
|
+
// channel that delivers the model's answer may corrupt characters ($,
|
|
79
|
+
// backslashes, newlines). base64 consists only of [A-Za-z0-9+/=] and is not
|
|
80
|
+
// subject to corruption.
|
|
81
81
|
const decodeContent = (content, contentBase64) => {
|
|
82
82
|
if (typeof contentBase64 === 'string' && contentBase64.length) {
|
|
83
83
|
return Buffer.from(contentBase64, 'base64').toString('utf-8');
|
|
@@ -172,7 +172,7 @@ export function createTools(workdir, { undo } = {}) {
|
|
|
172
172
|
const { glob } = await import('fs/promises');
|
|
173
173
|
const results = [];
|
|
174
174
|
for await (const f of glob(String(pattern), { cwd: workdir })) {
|
|
175
|
-
// Sandbox:
|
|
175
|
+
// Sandbox: ignore anything that goes outside root.
|
|
176
176
|
const abs = path.resolve(workdir, f);
|
|
177
177
|
const rel = path.relative(root, abs);
|
|
178
178
|
if (rel.startsWith('..') || path.isAbsolute(rel))
|
package/dist/transcript.js
CHANGED
|
@@ -17,8 +17,8 @@ export class Transcript {
|
|
|
17
17
|
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
18
18
|
this.file = path.join(dir, `${sessionName}-${stamp}.jsonl`);
|
|
19
19
|
this.stream = fs.createWriteStream(this.file, { flags: 'a' });
|
|
20
|
-
//
|
|
21
|
-
//
|
|
20
|
+
// Write errors (disk full, file deleted, etc.) arrive as an 'error'
|
|
21
|
+
// event; without a listener this is an uncaught exception.
|
|
22
22
|
this.stream.on('error', (e) => {
|
|
23
23
|
console.error(`transcript: ошибка записи: ${e.message}`);
|
|
24
24
|
this.enabled = false;
|
package/dist/types.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
1
|
+
// Shared contract types for the whole agent.
|
|
2
|
+
// Types only here — no runtime code, no side effects.
|
|
3
3
|
export {};
|
package/dist/web.js
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
import { chromium } from 'playwright';
|
|
2
2
|
const DEFAULT_TIMEOUT = 20_000;
|
|
3
3
|
const MAX_TEXT = 12_000;
|
|
4
|
-
//
|
|
4
|
+
// Removes scripts, styles, and turns HTML into readable text.
|
|
5
5
|
function htmlToText(html) {
|
|
6
|
-
//
|
|
6
|
+
// Remove blocks we don't need
|
|
7
7
|
let s = html
|
|
8
8
|
.replace(/<script[\s\S]*?<\/script>/gi, '')
|
|
9
9
|
.replace(/<style[\s\S]*?<\/style>/gi, '')
|
|
10
10
|
.replace(/<noscript[\s\S]*?<\/noscript>/gi, '')
|
|
11
11
|
.replace(/<svg[\s\S]*?<\/svg>/gi, '')
|
|
12
12
|
.replace(/<!--[\s\S]*?-->/g, '');
|
|
13
|
-
//
|
|
13
|
+
// Links: <a href="URL">text</a> → text (URL)
|
|
14
14
|
s = s.replace(/<a\b[^>]*href=["']([^"']+)["'][^>]*>([\s\S]*?)<\/a>/gi, (_, href, text) => {
|
|
15
15
|
const t = text.replace(/<[^>]+>/g, '').trim();
|
|
16
16
|
return t ? `${t} (${href})` : href;
|
|
17
17
|
});
|
|
18
|
-
//
|
|
18
|
+
// Headings, paragraphs, BR → newlines
|
|
19
19
|
s = s
|
|
20
20
|
.replace(/<\/(h[1-6]|p|div|li|tr|section|article)>/gi, '\n')
|
|
21
21
|
.replace(/<br\s*\/?>/gi, '\n')
|
|
22
22
|
.replace(/<li[^>]*>/gi, '- ')
|
|
23
23
|
.replace(/<[^>]+>/g, '');
|
|
24
|
-
// HTML entities —
|
|
24
|
+
// HTML entities — the basic ones
|
|
25
25
|
s = s
|
|
26
26
|
.replace(/ /g, ' ')
|
|
27
27
|
.replace(/&/g, '&')
|
|
@@ -34,7 +34,7 @@ function htmlToText(html) {
|
|
|
34
34
|
.replace(/…/g, '…')
|
|
35
35
|
.replace(/&#(\d+);/g, (_, n) => String.fromCodePoint(Number(n)))
|
|
36
36
|
.replace(/&#x([0-9a-f]+);/gi, (_, n) => String.fromCodePoint(parseInt(n, 16)));
|
|
37
|
-
//
|
|
37
|
+
// Collapse blank lines
|
|
38
38
|
s = s
|
|
39
39
|
.split('\n')
|
|
40
40
|
.map((l) => l.replace(/[ \t]+/g, ' ').trim())
|
|
@@ -42,7 +42,7 @@ function htmlToText(html) {
|
|
|
42
42
|
.join('\n');
|
|
43
43
|
return s;
|
|
44
44
|
}
|
|
45
|
-
// fetch
|
|
45
|
+
// fetch with redirects and a timeout
|
|
46
46
|
async function httpFetch(url, { timeout = DEFAULT_TIMEOUT, headers = {} } = {}) {
|
|
47
47
|
const ctrl = new AbortController();
|
|
48
48
|
const t = setTimeout(() => ctrl.abort(), timeout);
|
|
@@ -64,8 +64,8 @@ async function httpFetch(url, { timeout = DEFAULT_TIMEOUT, headers = {} } = {})
|
|
|
64
64
|
clearTimeout(t);
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
-
//
|
|
68
|
-
//
|
|
67
|
+
// A separate headless browser for JS pages.
|
|
68
|
+
// Lazy initialization so we don't waste resources.
|
|
69
69
|
let _headless = null;
|
|
70
70
|
async function getHeadless() {
|
|
71
71
|
if (_headless)
|
|
@@ -96,7 +96,7 @@ async function renderWithHeadless(url, { timeout = DEFAULT_TIMEOUT } = {}) {
|
|
|
96
96
|
waitUntil: 'domcontentloaded',
|
|
97
97
|
timeout,
|
|
98
98
|
});
|
|
99
|
-
//
|
|
99
|
+
// Give JS a little time to finish rendering
|
|
100
100
|
await page.waitForTimeout(1500);
|
|
101
101
|
const html = await page.content();
|
|
102
102
|
const status = resp ? resp.status() : 0;
|
|
@@ -107,7 +107,7 @@ async function renderWithHeadless(url, { timeout = DEFAULT_TIMEOUT } = {}) {
|
|
|
107
107
|
await ctx.close();
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
|
-
// ----------
|
|
110
|
+
// ---------- tools ----------
|
|
111
111
|
export function createWebTools() {
|
|
112
112
|
return [
|
|
113
113
|
{
|
|
@@ -132,14 +132,14 @@ export function createWebTools() {
|
|
|
132
132
|
return formatResult(r.status, r.url, text, limit);
|
|
133
133
|
}
|
|
134
134
|
const r = await httpFetch(String(url));
|
|
135
|
-
//
|
|
135
|
+
// If it's JSON/plain text — return as is
|
|
136
136
|
if (/application\/json|text\/plain|text\/markdown/i.test(r.contentType)) {
|
|
137
137
|
const trimmed = r.body.slice(0, limit);
|
|
138
138
|
return `HTTP ${r.status} ${r.url}\nContent-Type: ${r.contentType}\n\n${trimmed}`;
|
|
139
139
|
}
|
|
140
|
-
// HTML —
|
|
140
|
+
// HTML — clean it up
|
|
141
141
|
const text = htmlToText(r.body);
|
|
142
|
-
//
|
|
142
|
+
// If there's almost no text — probably a JS page, give a hint
|
|
143
143
|
if (text.length < 200) {
|
|
144
144
|
return (`HTTP ${r.status} ${r.url}\n` +
|
|
145
145
|
`Content-Type: ${r.contentType}\n\n` +
|
|
@@ -177,13 +177,13 @@ export function createWebTools() {
|
|
|
177
177
|
if (r.status !== 200) {
|
|
178
178
|
return `DuckDuckGo вернул HTTP ${r.status}`;
|
|
179
179
|
}
|
|
180
|
-
//
|
|
180
|
+
// We parse with simple regexes. The html.duckduckgo.com format is stable.
|
|
181
181
|
const results = [];
|
|
182
182
|
const re = /<a[^>]*class="[^"]*result__a[^"]*"[^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>[\s\S]*?(?:<a[^>]*class="[^"]*result__snippet[^"]*"[^>]*>([\s\S]*?)<\/a>)?/gi;
|
|
183
183
|
let m;
|
|
184
184
|
while ((m = re.exec(r.body)) && results.length < limit) {
|
|
185
185
|
let href = m[1];
|
|
186
|
-
// DuckDuckGo
|
|
186
|
+
// DuckDuckGo wraps links in a redirect like /l/?uddg=...
|
|
187
187
|
const uddgMatch = href.match(/[?&]uddg=([^&]+)/);
|
|
188
188
|
if (uddgMatch)
|
|
189
189
|
href = decodeURIComponent(uddgMatch[1]);
|
package/dist/xml-toolcall.js
CHANGED
|
@@ -13,13 +13,13 @@ function readAttr(attrs, name) {
|
|
|
13
13
|
const m = attrs.match(re);
|
|
14
14
|
return m ? m[2] : null;
|
|
15
15
|
}
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
// (<parameter …> … </|DSML| parameter>).
|
|
19
|
-
// <…parameter…> … </…parameter…>,
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
16
|
+
// Extracts parameters from an <invoke> body. The opening and closing tags may
|
|
17
|
+
// carry an arbitrary prefix (DSML, etc.), and some models confuse tag pairs
|
|
18
|
+
// (<parameter …> … </|DSML| parameter>). The regex below matches any pair
|
|
19
|
+
// <…parameter…> … </…parameter…>, so such "mixed" tags are parsed too.
|
|
20
|
+
// Values are deduplicated: if the model repeated a parameter several times,
|
|
21
|
+
// we take the FIRST value, not the last (repeats usually duplicate a correct
|
|
22
|
+
// parameter).
|
|
23
23
|
function parseParameters(body) {
|
|
24
24
|
const args = {};
|
|
25
25
|
const paramRe = /<[^>]*?parameter([^>]*)>([\s\S]*?)<\/[^>]*?parameter[^>]*>/gi;
|
|
@@ -57,8 +57,8 @@ export function parseXmlToolCalls(text) {
|
|
|
57
57
|
const closeMatch = closeRe.exec(rest);
|
|
58
58
|
const body = closeMatch ? rest.slice(0, closeMatch.index) : rest;
|
|
59
59
|
const args = parseParameters(body);
|
|
60
|
-
//
|
|
61
|
-
// parameter
|
|
60
|
+
// Some models put the whole JSON arguments object into a single
|
|
61
|
+
// parameter named args. We unwrap it so there's no
|
|
62
62
|
// {args: {args: {...}}}.
|
|
63
63
|
let finalArgs = args;
|
|
64
64
|
const keys = Object.keys(args);
|
|
@@ -73,10 +73,10 @@ export function parseXmlToolCalls(text) {
|
|
|
73
73
|
}
|
|
74
74
|
if (calls.length)
|
|
75
75
|
return calls.length === 1 ? calls[0] : calls;
|
|
76
|
-
//
|
|
77
|
-
// (<|DSML| calls> … <|DSML| parameter name=…>…).
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
// (looksLikeToolCall
|
|
76
|
+
// The model sometimes omits the <invoke> tag and leaves only the DSML
|
|
77
|
+
// wrapper (<|DSML| calls> … <|DSML| parameter name=…>…). There's no tool
|
|
78
|
+
// name in such an answer, so the call can't be restored — but it also must
|
|
79
|
+
// not be silently taken as the final answer: agent-loop will ask to retry
|
|
80
|
+
// (looksLikeToolCall triggers on the word parameter/DSML).
|
|
81
81
|
return null;
|
|
82
82
|
}
|