@gaunt-sloth/core 2.0.0-alpha.22 → 2.0.0-alpha.24

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.
Files changed (76) hide show
  1. package/README.md +43 -18
  2. package/dist/config/defaults.d.ts +5 -7
  3. package/dist/config/defaults.js +3 -8
  4. package/dist/config/defaults.js.map +1 -1
  5. package/dist/config/loader.d.ts +40 -0
  6. package/dist/config/loader.js +179 -6
  7. package/dist/config/loader.js.map +1 -1
  8. package/dist/config/profiles.d.ts +68 -0
  9. package/dist/config/profiles.js +93 -0
  10. package/dist/config/profiles.js.map +1 -0
  11. package/dist/config/schema.d.ts +69 -2
  12. package/dist/config/schema.js +87 -5
  13. package/dist/config/schema.js.map +1 -1
  14. package/dist/config/types.d.ts +103 -5
  15. package/dist/config/types.js.map +1 -1
  16. package/dist/config.d.ts +1 -0
  17. package/dist/config.js +1 -0
  18. package/dist/config.js.map +1 -1
  19. package/dist/core/GthAbstractAgent.d.ts +26 -1
  20. package/dist/core/GthAbstractAgent.js +122 -1
  21. package/dist/core/GthAbstractAgent.js.map +1 -1
  22. package/dist/core/GthAgentRunner.js +40 -0
  23. package/dist/core/GthAgentRunner.js.map +1 -1
  24. package/dist/core/GthLangChainAgent.d.ts +99 -0
  25. package/dist/core/GthLangChainAgent.js +333 -2
  26. package/dist/core/GthLangChainAgent.js.map +1 -1
  27. package/dist/core/debugCapture.d.ts +16 -0
  28. package/dist/core/debugCapture.js.map +1 -1
  29. package/dist/core/plainToolIndication.js +48 -28
  30. package/dist/core/plainToolIndication.js.map +1 -1
  31. package/dist/core/refusal.d.ts +53 -0
  32. package/dist/core/refusal.js +129 -0
  33. package/dist/core/refusal.js.map +1 -0
  34. package/dist/core/runStats.d.ts +14 -3
  35. package/dist/core/runStats.js +48 -3
  36. package/dist/core/runStats.js.map +1 -1
  37. package/dist/core/toolCallRepair/grammar.d.ts +41 -0
  38. package/dist/core/toolCallRepair/grammar.js +116 -0
  39. package/dist/core/toolCallRepair/grammar.js.map +1 -0
  40. package/dist/core/toolCallRepair/index.d.ts +2 -0
  41. package/dist/core/toolCallRepair/index.js +7 -0
  42. package/dist/core/toolCallRepair/index.js.map +1 -0
  43. package/dist/core/toolCallRepair/payload.d.ts +36 -0
  44. package/dist/core/toolCallRepair/payload.js +341 -0
  45. package/dist/core/toolCallRepair/payload.js.map +1 -0
  46. package/dist/core/toolCallRepair/promote.d.ts +45 -0
  47. package/dist/core/toolCallRepair/promote.js +90 -0
  48. package/dist/core/toolCallRepair/promote.js.map +1 -0
  49. package/dist/core/toolDisplay.d.ts +14 -1
  50. package/dist/core/toolDisplay.js +69 -12
  51. package/dist/core/toolDisplay.js.map +1 -1
  52. package/dist/core/toolOutputChannel.d.ts +33 -6
  53. package/dist/core/toolOutputChannel.js +61 -8
  54. package/dist/core/toolOutputChannel.js.map +1 -1
  55. package/dist/core/types.d.ts +22 -0
  56. package/dist/providers/geminiSchemaSanitizer.d.ts +6 -3
  57. package/dist/providers/geminiSchemaSanitizer.js +152 -6
  58. package/dist/providers/geminiSchemaSanitizer.js.map +1 -1
  59. package/dist/providers/modelDiscovery.d.ts +18 -4
  60. package/dist/providers/modelDiscovery.js +67 -17
  61. package/dist/providers/modelDiscovery.js.map +1 -1
  62. package/dist/providers/openai.js +34 -0
  63. package/dist/providers/openai.js.map +1 -1
  64. package/dist/utils/crashHandler.d.ts +87 -0
  65. package/dist/utils/crashHandler.js +128 -0
  66. package/dist/utils/crashHandler.js.map +1 -0
  67. package/dist/utils/debugDump.d.ts +58 -0
  68. package/dist/utils/debugDump.js +149 -15
  69. package/dist/utils/debugDump.js.map +1 -1
  70. package/dist/utils/llmUtils.d.ts +22 -8
  71. package/dist/utils/llmUtils.js +48 -8
  72. package/dist/utils/llmUtils.js.map +1 -1
  73. package/dist/utils/redactSecrets.js +68 -20
  74. package/dist/utils/redactSecrets.js.map +1 -1
  75. package/package.json +1 -1
  76. package/schema/gsloth-config.schema.json +224 -5
@@ -0,0 +1,341 @@
1
+ // EXT-35 — plain-text tool-call repair payload parser.
2
+ //
3
+ // Ported (TypeScript, gaunt-sloth house style) from the openclaw
4
+ // `@openclaw/tool-call-repair` reference (`packages/tool-call-repair/src/payload.ts`).
5
+ // Carries the three text-emitted tool-call dialects small/local models produce:
6
+ // • bracket: `[tool:name]{...json...}` / `[name]\n{...json...}[/name]`
7
+ // • XML-ish: `<function=name><parameter=key>value</parameter></function>`
8
+ // • Harmony: `<|channel|>commentary to=name code<|message|>{...json...}<|call|>`
9
+ // — plus the real gpt-oss variant (EXT-43) with a NAMESPACED name and a `<|constrain|>` marker:
10
+ // `<|channel|>commentary to=functions.get_weather<|constrain|>json<|message|>{...}<|call|>`
11
+ //
12
+ // {@link parseStandalonePlainTextToolCallBlocks} is STANDALONE-only by construction: it walks the
13
+ // WHOLE input and returns null the moment it hits text that is not a tool-call block, so prose that
14
+ // merely mentions a tool or contains brackets never parses as a call. Allow-list + payload-size
15
+ // gating are threaded through {@link PlainTextToolCallParseOptions}. The reference's streaming
16
+ // normalizer and user-visible strip helpers are intentionally omitted (not needed for promotion).
17
+ import { consumeLineBreak, END_TOOL_REQUEST, finalDotSegment, findJsonObjectEnd, HARMONY_CALL_MARKER, HARMONY_CHANNEL_MARKER, HARMONY_CONSTRAIN_MARKER, HARMONY_MESSAGE_MARKER, isHarmonyToolNameChar, isPlainTextToolNameChar, skipHorizontalWhitespace, skipWhitespace, } from './grammar.js';
18
+ /**
19
+ * The single source of truth for the plain-text tool-call payload cap (256 KB). A serialized call
20
+ * larger than this is never treated as a tool call. Re-exported from `./promote.js` (and the module
21
+ * index) as `MAX_TEXT_EMITTED_TOOL_CALL_PAYLOAD_BYTES` — the public name — so there is ONE constant,
22
+ * not two duplicated literals. (EXT-43 unified the former `DEFAULT_MAX_PLAIN_TEXT_TOOL_PAYLOAD_BYTES`
23
+ * here with `promote.ts`'s copy.)
24
+ */
25
+ export const MAX_TEXT_EMITTED_TOOL_CALL_PAYLOAD_BYTES = 256_000;
26
+ const utf8Encoder = new TextEncoder();
27
+ function utf8ByteLengthWithinLimit(text, start, end, maxBytes) {
28
+ if (end - start > maxBytes) {
29
+ return null;
30
+ }
31
+ const byteLength = utf8Encoder.encode(text.slice(start, end)).byteLength;
32
+ return byteLength <= maxBytes ? byteLength : null;
33
+ }
34
+ function parseBracketOpening(text, start) {
35
+ if (text[start] !== '[') {
36
+ return null;
37
+ }
38
+ let cursor = start + 1;
39
+ if (text.startsWith('tool:', cursor)) {
40
+ cursor += 'tool:'.length;
41
+ const nameStart = cursor;
42
+ while (isPlainTextToolNameChar(text[cursor])) {
43
+ cursor += 1;
44
+ }
45
+ if (cursor === nameStart || text[cursor] !== ']') {
46
+ return null;
47
+ }
48
+ return {
49
+ allowsOptionalXmlishClose: true,
50
+ end: cursor + 1,
51
+ name: text.slice(nameStart, cursor),
52
+ requiresClosing: false,
53
+ };
54
+ }
55
+ const nameStart = cursor;
56
+ while (isPlainTextToolNameChar(text[cursor])) {
57
+ cursor += 1;
58
+ }
59
+ if (cursor === nameStart || text[cursor] !== ']') {
60
+ return null;
61
+ }
62
+ const name = text.slice(nameStart, cursor);
63
+ cursor += 1;
64
+ cursor = skipHorizontalWhitespace(text, cursor);
65
+ const afterLineBreak = consumeLineBreak(text, cursor);
66
+ if (afterLineBreak === null) {
67
+ return null;
68
+ }
69
+ return { end: afterLineBreak, name, requiresClosing: true };
70
+ }
71
+ function parseHarmonyOpening(text, start) {
72
+ let cursor = start;
73
+ if (text.startsWith(HARMONY_CHANNEL_MARKER, cursor)) {
74
+ cursor += HARMONY_CHANNEL_MARKER.length;
75
+ }
76
+ const channelStart = cursor;
77
+ while (/[A-Za-z_]/.test(text[cursor] ?? '')) {
78
+ cursor += 1;
79
+ }
80
+ const channel = text.slice(channelStart, cursor);
81
+ if (channel !== 'commentary' && channel !== 'analysis' && channel !== 'final') {
82
+ return null;
83
+ }
84
+ cursor = skipHorizontalWhitespace(text, cursor);
85
+ if (!text.startsWith('to=', cursor)) {
86
+ return null;
87
+ }
88
+ cursor += 3;
89
+ // Tool name. EXT-43: accept the real gpt-oss NAMESPACED form (`to=functions.get_weather`) by
90
+ // scanning dot-inclusive chars, then take the FINAL dot-segment as the allow-listed tool name.
91
+ // The reference dotless form (`to=get_weather`) is the single-segment degenerate case.
92
+ const nameStart = cursor;
93
+ while (isHarmonyToolNameChar(text[cursor])) {
94
+ cursor += 1;
95
+ }
96
+ if (cursor === nameStart) {
97
+ return null;
98
+ }
99
+ const name = finalDotSegment(text.slice(nameStart, cursor));
100
+ if (!name) {
101
+ // Trailing-dot / empty final segment (e.g. `to=functions.`) is not a valid tool name.
102
+ return null;
103
+ }
104
+ cursor = skipHorizontalWhitespace(text, cursor);
105
+ // Payload-format marker between the header and the JSON. The reference grammar requires the
106
+ // literal `code` token; real gpt-oss instead emits `<|constrain|>json` (EXT-43). Accept EITHER —
107
+ // but a marker is still REQUIRED, so the widening does not relax prose-safety (a bare
108
+ // `commentary to=x {…}` never promotes).
109
+ if (text.startsWith('code', cursor)) {
110
+ cursor += 4;
111
+ }
112
+ else if (text.startsWith(HARMONY_CONSTRAIN_MARKER, cursor)) {
113
+ cursor += HARMONY_CONSTRAIN_MARKER.length;
114
+ // Optional constraint-format token that follows the marker (e.g. `json`).
115
+ cursor = skipHorizontalWhitespace(text, cursor);
116
+ while (isPlainTextToolNameChar(text[cursor])) {
117
+ cursor += 1;
118
+ }
119
+ }
120
+ else {
121
+ return null;
122
+ }
123
+ cursor = skipWhitespace(text, cursor);
124
+ if (text.startsWith(HARMONY_MESSAGE_MARKER, cursor)) {
125
+ cursor = skipWhitespace(text, cursor + HARMONY_MESSAGE_MARKER.length);
126
+ }
127
+ return { end: cursor, name, requiresClosing: false };
128
+ }
129
+ function parseXmlishFunctionOpening(text, start) {
130
+ const match = /^<function=([A-Za-z0-9_.:-]{1,120})>\s*/i.exec(text.slice(start));
131
+ if (!match?.[1]) {
132
+ return null;
133
+ }
134
+ return { end: start + match[0].length, name: match[1], requiresClosing: false };
135
+ }
136
+ function parseOpening(text, start) {
137
+ return parseBracketOpening(text, start) ?? parseHarmonyOpening(text, start);
138
+ }
139
+ function consumeJsonObject(text, start, maxPayloadBytes) {
140
+ const cursor = skipWhitespace(text, start);
141
+ if (text[cursor] !== '{') {
142
+ return null;
143
+ }
144
+ const end = findJsonObjectEnd(text, cursor, maxPayloadBytes);
145
+ if (end === null || utf8ByteLengthWithinLimit(text, cursor, end, maxPayloadBytes) === null) {
146
+ return null;
147
+ }
148
+ const rawJson = text.slice(cursor, end);
149
+ try {
150
+ const parsed = JSON.parse(rawJson);
151
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
152
+ return null;
153
+ }
154
+ return { end, value: parsed };
155
+ }
156
+ catch {
157
+ return null;
158
+ }
159
+ }
160
+ function parseClosing(text, start, name) {
161
+ const cursor = skipWhitespace(text, start);
162
+ if (text.startsWith(END_TOOL_REQUEST, cursor)) {
163
+ return cursor + END_TOOL_REQUEST.length;
164
+ }
165
+ const namedClosing = `[/${name}]`;
166
+ if (text.startsWith(namedClosing, cursor)) {
167
+ return cursor + namedClosing.length;
168
+ }
169
+ return null;
170
+ }
171
+ function parseOptionalHarmonyClosing(text, start) {
172
+ const cursor = skipWhitespace(text, start);
173
+ if (text.startsWith(HARMONY_CALL_MARKER, cursor)) {
174
+ return cursor + HARMONY_CALL_MARKER.length;
175
+ }
176
+ return start;
177
+ }
178
+ function parsePlainTextToolCallBlockAt(text, start, options) {
179
+ const opening = parseOpening(text, start);
180
+ if (!opening) {
181
+ return null;
182
+ }
183
+ const allowedToolNames = options?.allowedToolNames
184
+ ? new Set(options.allowedToolNames)
185
+ : undefined;
186
+ if (allowedToolNames && !allowedToolNames.has(opening.name)) {
187
+ return null;
188
+ }
189
+ const payload = consumeJsonObject(text, opening.end, options?.maxPayloadBytes ?? MAX_TEXT_EMITTED_TOOL_CALL_PAYLOAD_BYTES);
190
+ if (!payload) {
191
+ return null;
192
+ }
193
+ const closingEnd = opening.requiresClosing
194
+ ? parseClosing(text, payload.end, opening.name)
195
+ : parseOptionalHarmonyClosing(text, payload.end);
196
+ if (closingEnd === null) {
197
+ return null;
198
+ }
199
+ return {
200
+ arguments: payload.value,
201
+ end: closingEnd,
202
+ name: opening.name,
203
+ raw: text.slice(start, closingEnd),
204
+ start,
205
+ };
206
+ }
207
+ function findXmlishParameterBlock(text, start) {
208
+ const cursor = skipWhitespace(text, start);
209
+ const openMatch = /^<parameter=([A-Za-z0-9_.:-]{1,120})>/i.exec(text.slice(cursor));
210
+ if (!openMatch?.[1]) {
211
+ return null;
212
+ }
213
+ const payloadStart = cursor + openMatch[0].length;
214
+ const closeMatch = /<\/parameter>/i.exec(text.slice(payloadStart));
215
+ if (!closeMatch) {
216
+ return null;
217
+ }
218
+ const closeStart = payloadStart + closeMatch.index;
219
+ const closeEnd = closeStart + closeMatch[0].length;
220
+ return {
221
+ closeStart,
222
+ end: closeEnd,
223
+ name: openMatch[1],
224
+ payloadStart,
225
+ start: cursor,
226
+ };
227
+ }
228
+ function extractXmlishParameterValue(text, start, end) {
229
+ let payloadStart = start;
230
+ let payloadEnd = end;
231
+ const afterOpeningLineBreak = consumeLineBreak(text, payloadStart);
232
+ if (afterOpeningLineBreak !== null) {
233
+ payloadStart = afterOpeningLineBreak;
234
+ if (payloadEnd > payloadStart && text[payloadEnd - 1] === '\n') {
235
+ payloadEnd -= 1;
236
+ if (payloadEnd > payloadStart && text[payloadEnd - 1] === '\r') {
237
+ payloadEnd -= 1;
238
+ }
239
+ }
240
+ else if (payloadEnd > payloadStart && text[payloadEnd - 1] === '\r') {
241
+ payloadEnd -= 1;
242
+ }
243
+ }
244
+ return text.slice(payloadStart, payloadEnd);
245
+ }
246
+ function consumeXmlishParameterBlock(text, start, maxPayloadBytes) {
247
+ const bounds = findXmlishParameterBlock(text, start);
248
+ if (!bounds) {
249
+ return null;
250
+ }
251
+ const byteLength = utf8ByteLengthWithinLimit(text, start, bounds.end, maxPayloadBytes);
252
+ if (byteLength === null) {
253
+ return null;
254
+ }
255
+ return {
256
+ byteLength,
257
+ end: bounds.end,
258
+ name: bounds.name,
259
+ value: extractXmlishParameterValue(text, bounds.payloadStart, bounds.closeStart),
260
+ };
261
+ }
262
+ function consumeXmlishFunctionClose(text, start) {
263
+ const cursor = skipWhitespace(text, start);
264
+ return text.slice(cursor).toLowerCase().startsWith('</function>')
265
+ ? cursor + '</function>'.length
266
+ : null;
267
+ }
268
+ function consumeOptionalXmlishFunctionClose(text, start) {
269
+ return consumeXmlishFunctionClose(text, start) ?? start;
270
+ }
271
+ function parseXmlishOpening(text, start) {
272
+ return parseBracketOpening(text, start) ?? parseXmlishFunctionOpening(text, start);
273
+ }
274
+ function parseXmlishPlainTextToolCallBlockAt(text, start, options) {
275
+ const opening = parseXmlishOpening(text, start);
276
+ if (!opening) {
277
+ return null;
278
+ }
279
+ const allowedToolNames = options?.allowedToolNames
280
+ ? new Set(options.allowedToolNames)
281
+ : undefined;
282
+ if (allowedToolNames && !allowedToolNames.has(opening.name)) {
283
+ return null;
284
+ }
285
+ const maxPayloadBytes = options?.maxPayloadBytes ?? MAX_TEXT_EMITTED_TOOL_CALL_PAYLOAD_BYTES;
286
+ const args = {};
287
+ let cursor = opening.end;
288
+ let parameterCount = 0;
289
+ let payloadBytes = 0;
290
+ while (true) {
291
+ const parameter = consumeXmlishParameterBlock(text, cursor, maxPayloadBytes);
292
+ if (!parameter) {
293
+ break;
294
+ }
295
+ payloadBytes += parameter.byteLength;
296
+ if (payloadBytes > maxPayloadBytes) {
297
+ return null;
298
+ }
299
+ args[parameter.name] = parameter.value;
300
+ parameterCount += 1;
301
+ cursor = parameter.end;
302
+ }
303
+ if (parameterCount === 0) {
304
+ return null;
305
+ }
306
+ const end = opening.allowsOptionalXmlishClose
307
+ ? consumeOptionalXmlishFunctionClose(text, cursor)
308
+ : consumeXmlishFunctionClose(text, cursor);
309
+ if (end === null) {
310
+ return null;
311
+ }
312
+ return {
313
+ arguments: args,
314
+ end,
315
+ name: opening.name,
316
+ raw: text.slice(start, end),
317
+ start,
318
+ };
319
+ }
320
+ /**
321
+ * Parse the WHOLE input into standalone plain-text tool-call blocks, or return null if any part of
322
+ * it is not such a block. This all-or-nothing walk is the standalone-only gate: prose that merely
323
+ * mentions a tool name, contains brackets, or embeds a call inside a sentence never parses (the
324
+ * first non-block character aborts the parse). Allow-list + payload-cap gating are applied per
325
+ * block via {@link PlainTextToolCallParseOptions}.
326
+ */
327
+ export function parseStandalonePlainTextToolCallBlocks(text, options) {
328
+ const blocks = [];
329
+ let cursor = skipWhitespace(text, 0);
330
+ while (cursor < text.length) {
331
+ const block = parsePlainTextToolCallBlockAt(text, cursor, options) ??
332
+ parseXmlishPlainTextToolCallBlockAt(text, cursor, options);
333
+ if (!block) {
334
+ return null;
335
+ }
336
+ blocks.push(block);
337
+ cursor = skipWhitespace(text, block.end);
338
+ }
339
+ return blocks.length > 0 ? blocks : null;
340
+ }
341
+ //# sourceMappingURL=payload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payload.js","sourceRoot":"","sources":["../../../src/core/toolCallRepair/payload.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ import { AIMessage } from '@langchain/core/messages';
2
+ /**
3
+ * A sane default cap on a single repaired call's serialized payload. A text blob larger than this
4
+ * is never treated as a tool call. Overridable per call so a test can assert the cap with a small
5
+ * value; production leaves it at the default. The single source of truth lives in `./payload.js`
6
+ * and is re-exported here under its public name (EXT-43 unified the former duplicated literal).
7
+ */
8
+ export { MAX_TEXT_EMITTED_TOOL_CALL_PAYLOAD_BYTES } from './payload.js';
9
+ /** LangChain native tool-call shape (structurally a `ToolCall` from `@langchain/core/messages`). */
10
+ type NativeToolCall = {
11
+ id: string;
12
+ name: string;
13
+ args: Record<string, unknown>;
14
+ type: 'tool_call';
15
+ };
16
+ /** Gates for {@link textToNativeToolCalls} / {@link promoteTextEmittedToolCallMessage}. */
17
+ export interface TextToolCallRepairOptions {
18
+ /**
19
+ * The names of the tools bound to this run (the allow-list). Only a text-emitted call whose name
20
+ * is one of these promotes; an unknown name is left as text. An EMPTY set promotes nothing — the
21
+ * prose-safe default (no toolset ⇒ nothing is a call).
22
+ */
23
+ allowedToolNames: Iterable<string>;
24
+ /** Payload-size cap; defaults to {@link MAX_TEXT_EMITTED_TOOL_CALL_PAYLOAD_BYTES}. */
25
+ maxPayloadBytes?: number;
26
+ }
27
+ /**
28
+ * Parse assistant `text` as one-or-more STANDALONE text-emitted tool calls and return them as
29
+ * native LangChain tool_calls, or `undefined` when the text is not a standalone call, names a tool
30
+ * outside the allow-list, or exceeds the payload cap. Fresh `id`s are minted per call (the model
31
+ * emitted none). Empty allow-list ⇒ `undefined` (never promote).
32
+ */
33
+ export declare function textToNativeToolCalls(text: string | undefined, options: TextToolCallRepairOptions): NativeToolCall[] | undefined;
34
+ /**
35
+ * Repair one assistant {@link AIMessage}: if it has NO native tool_calls but its content is a
36
+ * standalone text-emitted call (allow-listed, within the payload cap), return a NEW AIMessage that
37
+ * carries the promoted native tool_calls; otherwise return `undefined` (the caller keeps the
38
+ * original, so the native happy path is untouched).
39
+ *
40
+ * The returned message PRESERVES the original `id`. That is load-bearing: LangGraph's message-state
41
+ * reducer merges by id, so a same-id message REPLACES the original in graph state (rather than
42
+ * appending a duplicate/dangling assistant message). Content is cleared to '' since the raw
43
+ * text-call is now represented natively as tool_calls.
44
+ */
45
+ export declare function promoteTextEmittedToolCallMessage(message: AIMessage, options: TextToolCallRepairOptions): AIMessage | undefined;
@@ -0,0 +1,90 @@
1
+ // EXT-35 — promote a text-emitted tool call to a native LangChain tool_call.
2
+ //
3
+ // The LangChain-message adaptation of the openclaw `@openclaw/tool-call-repair` reference
4
+ // (`packages/tool-call-repair/src/promote.ts`'s `promoteStandalonePlainTextToolCallMessage`).
5
+ // Small/local models (Gemma, lmstudio, gpt-oss) often serialise a tool call as assistant TEXT
6
+ // rather than a native `tool_call`; the ReAct graph then sees no tool_calls and ends the turn
7
+ // ("no tool calls = done"), stalling the loop. This module rescues a STANDALONE text-emitted call
8
+ // by rewriting the assistant message into one carrying a native `tool_call`, so the graph routes to
9
+ // the tools node and the loop continues.
10
+ //
11
+ // Three hard gates keep prose from ever being misread as a call:
12
+ // 1. name allow-list — only a call whose tool name is in the bound toolset promotes (an empty
13
+ // allow-list promotes nothing, the prose-safe default);
14
+ // 2. payload-size cap — an oversized JSON blob is not treated as a call;
15
+ // 3. standalone-only — the message content must be ESSENTIALLY JUST the call (enforced by
16
+ // {@link parseStandalonePlainTextToolCallBlocks}'s whole-input walk).
17
+ //
18
+ // Unlike the reference we do NOT set a `stopReason`/`toolUse` flag: langchain's ReAct router keys
19
+ // purely off the presence of `tool_calls` on the last AIMessage, so promoting the tool_calls array
20
+ // is sufficient to continue the loop.
21
+ import { AIMessage } from '@langchain/core/messages';
22
+ import { randomUUID } from 'node:crypto';
23
+ import { MAX_TEXT_EMITTED_TOOL_CALL_PAYLOAD_BYTES, parseStandalonePlainTextToolCallBlocks, } from './payload.js';
24
+ /**
25
+ * A sane default cap on a single repaired call's serialized payload. A text blob larger than this
26
+ * is never treated as a tool call. Overridable per call so a test can assert the cap with a small
27
+ * value; production leaves it at the default. The single source of truth lives in `./payload.js`
28
+ * and is re-exported here under its public name (EXT-43 unified the former duplicated literal).
29
+ */
30
+ export { MAX_TEXT_EMITTED_TOOL_CALL_PAYLOAD_BYTES } from './payload.js';
31
+ /**
32
+ * Parse assistant `text` as one-or-more STANDALONE text-emitted tool calls and return them as
33
+ * native LangChain tool_calls, or `undefined` when the text is not a standalone call, names a tool
34
+ * outside the allow-list, or exceeds the payload cap. Fresh `id`s are minted per call (the model
35
+ * emitted none). Empty allow-list ⇒ `undefined` (never promote).
36
+ */
37
+ export function textToNativeToolCalls(text, options) {
38
+ const allowedToolNames = new Set(options.allowedToolNames);
39
+ // Prose-safety default: with no bound tools nothing can be a call.
40
+ if (allowedToolNames.size === 0) {
41
+ return undefined;
42
+ }
43
+ const trimmed = (text ?? '').trim();
44
+ if (!trimmed) {
45
+ return undefined;
46
+ }
47
+ const parseOptions = {
48
+ allowedToolNames,
49
+ maxPayloadBytes: options.maxPayloadBytes ?? MAX_TEXT_EMITTED_TOOL_CALL_PAYLOAD_BYTES,
50
+ };
51
+ const blocks = parseStandalonePlainTextToolCallBlocks(trimmed, parseOptions);
52
+ if (!blocks) {
53
+ return undefined;
54
+ }
55
+ return blocks.map((block) => ({
56
+ id: randomUUID(),
57
+ name: block.name,
58
+ args: block.arguments,
59
+ type: 'tool_call',
60
+ }));
61
+ }
62
+ /**
63
+ * Repair one assistant {@link AIMessage}: if it has NO native tool_calls but its content is a
64
+ * standalone text-emitted call (allow-listed, within the payload cap), return a NEW AIMessage that
65
+ * carries the promoted native tool_calls; otherwise return `undefined` (the caller keeps the
66
+ * original, so the native happy path is untouched).
67
+ *
68
+ * The returned message PRESERVES the original `id`. That is load-bearing: LangGraph's message-state
69
+ * reducer merges by id, so a same-id message REPLACES the original in graph state (rather than
70
+ * appending a duplicate/dangling assistant message). Content is cleared to '' since the raw
71
+ * text-call is now represented natively as tool_calls.
72
+ */
73
+ export function promoteTextEmittedToolCallMessage(message, options) {
74
+ // Happy path untouched: repair engages ONLY when there are no native tool_calls.
75
+ if (message.tool_calls && message.tool_calls.length > 0) {
76
+ return undefined;
77
+ }
78
+ const toolCalls = textToNativeToolCalls(message.text, options);
79
+ if (!toolCalls) {
80
+ return undefined;
81
+ }
82
+ return new AIMessage({
83
+ id: message.id,
84
+ content: '',
85
+ tool_calls: toolCalls,
86
+ additional_kwargs: message.additional_kwargs,
87
+ response_metadata: message.response_metadata,
88
+ });
89
+ }
90
+ //# sourceMappingURL=promote.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"promote.js","sourceRoot":"","sources":["../../../src/core/toolCallRepair/promote.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,EAAE;AACF,0FAA0F;AAC1F,8FAA8F;AAC9F,8FAA8F;AAC9F,8FAA8F;AAC9F,kGAAkG;AAClG,oGAAoG;AACpG,yCAAyC;AACzC,EAAE;AACF,iEAAiE;AACjE,gGAAgG;AAChG,6DAA6D;AAC7D,2EAA2E;AAC3E,4FAA4F;AAC5F,2EAA2E;AAC3E,EAAE;AACF,kGAAkG;AAClG,mGAAmG;AACnG,sCAAsC;AAEtC,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EACL,wCAAwC,EACxC,sCAAsC,GAEvC,MAAM,cAAc,CAAC;AAEtB;;;;;GAKG;AACH,OAAO,EAAE,wCAAwC,EAAE,MAAM,cAAc,CAAC;AAsBxE;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CACnC,IAAwB,EACxB,OAAkC;IAElC,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC3D,mEAAmE;IACnE,IAAI,gBAAgB,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QAChC,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,OAAO,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACpC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,YAAY,GAAkC;QAClD,gBAAgB;QAChB,eAAe,EAAE,OAAO,CAAC,eAAe,IAAI,wCAAwC;KACrF,CAAC;IACF,MAAM,MAAM,GAAG,sCAAsC,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IAC7E,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC5B,EAAE,EAAE,UAAU,EAAE;QAChB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,IAAI,EAAE,KAAK,CAAC,SAAS;QACrB,IAAI,EAAE,WAAoB;KAC3B,CAAC,CAAC,CAAC;AACN,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,iCAAiC,CAC/C,OAAkB,EAClB,OAAkC;IAElC,iFAAiF;IACjF,IAAI,OAAO,CAAC,UAAU,IAAI,OAAO,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxD,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,SAAS,GAAG,qBAAqB,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC/D,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,IAAI,SAAS,CAAC;QACnB,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,OAAO,EAAE,EAAE;QACX,UAAU,EAAE,SAAS;QACrB,iBAAiB,EAAE,OAAO,CAAC,iBAAiB;QAC5C,iBAAiB,EAAE,OAAO,CAAC,iBAAiB;KAC7C,CAAC,CAAC;AACL,CAAC"}
@@ -46,7 +46,13 @@ export interface ToolCallDisplayInput {
46
46
  * defensive posture: a half-streamed or malformed buffer never throws — it returns `null`.
47
47
  */
48
48
  export declare function parseToolArgsSafe(argsText: string | undefined): Record<string, unknown> | null;
49
- /** Test seam: drop the cached env-derived secret literals so specs can vary the env. */
49
+ /**
50
+ * Register the live config for inline-secret collection (TUI-C32 residual a). Resets the secret
51
+ * cache so the next {@link getDefaultSecrets} recomputes with the config's inline literals folded
52
+ * in. Idempotent; a later call with a fresh config supersedes the previous one.
53
+ */
54
+ export declare function setToolDisplayConfig(config: unknown): void;
55
+ /** Test seam: drop the cached secret literals + registered config so specs can vary both. */
50
56
  export declare function resetToolDisplaySecretsCacheForTests(): void;
51
57
  /**
52
58
  * Parse the shared shell-result shape (`Executing '…'…\n\n<COMMAND_OUTPUT>…</COMMAND_OUTPUT>\n\n
@@ -57,6 +63,13 @@ export declare function parseCommandOutputResult(result: string | undefined): {
57
63
  body: string;
58
64
  tail: string;
59
65
  } | null;
66
+ /**
67
+ * TUI-C32 residual c — is THIS tool call genuinely a shell-shaped result (name allows it AND the
68
+ * result actually carries the `<COMMAND_OUTPUT>` shape)? Used by the plain surface to decide
69
+ * `liveOutputAlreadyShown` (the child streamed live via the tool-output channel's default sink),
70
+ * so a non-shell tool whose result merely quotes the marker no longer has its body suppressed.
71
+ */
72
+ export declare function isShellShapedResult(name: string, result: string | undefined): boolean;
60
73
  /** The registry glyph for a tool name (generic `⚙` when unknown). */
61
74
  export declare function getToolGlyph(name: string): string;
62
75
  /**
@@ -64,16 +64,35 @@ export function parseToolArgsSafe(argsText) {
64
64
  }
65
65
  }
66
66
  /**
67
- * Lazily-computed default secret literals for redaction (GS2-47 technique 1), harvested from
68
- * the process env the same way `/debug-dump` does. Cached because `collectSecretValues` walks
69
- * the whole env; the set cannot change mid-process in any way this render path must react to.
70
- * `redactText` additionally always applies the provider-key patterns (technique 2).
67
+ * The live gth config, registered once per run so {@link getDefaultSecrets} can harvest INLINE
68
+ * config secrets (a pasted `apiKey`/`token` value) via the GS2-47 config walk not only the
69
+ * env-derived literals. `GthAgentRunner.init` sets this next to the crash-context hand-off, so
70
+ * both render surfaces (the plain observer and the Ink TUI) see it. `undefined` (no config yet)
71
+ * degrades to env-only collection + the provider patterns, exactly as before.
72
+ */
73
+ let displayConfig = undefined;
74
+ /**
75
+ * Register the live config for inline-secret collection (TUI-C32 residual a). Resets the secret
76
+ * cache so the next {@link getDefaultSecrets} recomputes with the config's inline literals folded
77
+ * in. Idempotent; a later call with a fresh config supersedes the previous one.
78
+ */
79
+ export function setToolDisplayConfig(config) {
80
+ displayConfig = config;
81
+ cachedSecrets = null;
82
+ }
83
+ /**
84
+ * Lazily-computed default secret literals for redaction (GS2-47 technique 1), harvested from the
85
+ * process env AND the registered config (inline `apiKey`/`token` values) the same way
86
+ * `/debug-dump` does. Cached because `collectSecretValues` walks the whole env + config; the set
87
+ * cannot change mid-process in any way this render path must react to (a new config resets it via
88
+ * {@link setToolDisplayConfig}). `redactText` additionally always applies the provider-key
89
+ * patterns (technique 2).
71
90
  */
72
91
  let cachedSecrets = null;
73
92
  function getDefaultSecrets() {
74
93
  if (cachedSecrets === null) {
75
94
  try {
76
- cachedSecrets = collectSecretValues(undefined, env ?? {});
95
+ cachedSecrets = collectSecretValues(displayConfig, env ?? {});
77
96
  }
78
97
  catch {
79
98
  cachedSecrets = []; // patterns still apply via redactText
@@ -81,9 +100,10 @@ function getDefaultSecrets() {
81
100
  }
82
101
  return cachedSecrets;
83
102
  }
84
- /** Test seam: drop the cached env-derived secret literals so specs can vary the env. */
103
+ /** Test seam: drop the cached secret literals + registered config so specs can vary both. */
85
104
  export function resetToolDisplaySecretsCacheForTests() {
86
105
  cachedSecrets = null;
106
+ displayConfig = undefined;
87
107
  }
88
108
  /** Collapse whitespace runs (incl. newlines) so a value stays a one-line token. */
89
109
  function inline(value) {
@@ -154,6 +174,26 @@ export function parseCommandOutputResult(result) {
154
174
  return null;
155
175
  return { body: match[1] ?? '', tail: (match[2] ?? '').trim() };
156
176
  }
177
+ /**
178
+ * TUI-C32 residual c — may this tool NAME be treated as shell-shaped at all? A registered entry
179
+ * must be explicitly {@link ToolDisplayEntry.shellShaped}; an unregistered/custom name falls back
180
+ * to shape-detection (custom toolkit tools own user-defined names yet share the result shape). A
181
+ * registered non-shell tool (`read_file`, `list_directory`, …) is therefore NEVER shell-parsed
182
+ * just because its result happens to contain the `<COMMAND_OUTPUT>` marker.
183
+ */
184
+ function nameAllowsShellShape(name) {
185
+ const entry = TOOL_DISPLAY_REGISTRY[name];
186
+ return entry ? entry.shellShaped === true : true;
187
+ }
188
+ /**
189
+ * TUI-C32 residual c — is THIS tool call genuinely a shell-shaped result (name allows it AND the
190
+ * result actually carries the `<COMMAND_OUTPUT>` shape)? Used by the plain surface to decide
191
+ * `liveOutputAlreadyShown` (the child streamed live via the tool-output channel's default sink),
192
+ * so a non-shell tool whose result merely quotes the marker no longer has its body suppressed.
193
+ */
194
+ export function isShellShapedResult(name, result) {
195
+ return nameAllowsShellShape(name) && parseCommandOutputResult(result) !== null;
196
+ }
157
197
  /**
158
198
  * Body formatter for shell-shaped calls: the child's output (preferring the LIVE streamed
159
199
  * output — verbatim what the child printed — over the result's `<COMMAND_OUTPUT>` copy of it,
@@ -170,7 +210,15 @@ function formatShellBody(input) {
170
210
  return null;
171
211
  const lines = [];
172
212
  if (!input.liveOutputAlreadyShown) {
173
- const body = input.output && input.output.length > 0 ? input.output : (parsed?.body ?? '');
213
+ // TUI-C32 residual d the streamed live output is normally the verbatim, most-complete copy,
214
+ // so it is preferred (TUI-C17 dedupe). But if the live channel dropped straggler/tail chunks
215
+ // it can be a strict PREFIX of the result's `<COMMAND_OUTPUT>` copy (which the model always
216
+ // receives in full) — in that case fall back to the fuller copy so the expanded view can still
217
+ // recover what the stream missed. The common case (live === result, an independent/capped
218
+ // result, or an empty live output) is unchanged: only a genuine dropped-tail prefix overrides.
219
+ const live = input.output ?? '';
220
+ const resultBody = parsed?.body ?? '';
221
+ const body = resultBody.length > live.length && resultBody.startsWith(live) ? resultBody : live;
174
222
  if (body.trim().length > 0)
175
223
  lines.push(...styled(toLines(body), 'dim'));
176
224
  }
@@ -249,11 +297,17 @@ const TOOL_DISPLAY_REGISTRY = {
249
297
  glyph: SHELL_GLYPH,
250
298
  summariseArgs: ['command'],
251
299
  formatBody: formatShellBody,
300
+ shellShaped: true,
301
+ },
302
+ run_tests: { glyph: SHELL_GLYPH, formatBody: formatShellBody, shellShaped: true },
303
+ run_single_test: {
304
+ glyph: SHELL_GLYPH,
305
+ summariseArgs: ['testPath'],
306
+ formatBody: formatShellBody,
307
+ shellShaped: true,
252
308
  },
253
- run_tests: { glyph: SHELL_GLYPH, formatBody: formatShellBody },
254
- run_single_test: { glyph: SHELL_GLYPH, summariseArgs: ['testPath'], formatBody: formatShellBody },
255
- run_lint: { glyph: SHELL_GLYPH, formatBody: formatShellBody },
256
- run_build: { glyph: SHELL_GLYPH, formatBody: formatShellBody },
309
+ run_lint: { glyph: SHELL_GLYPH, formatBody: formatShellBody, shellShaped: true },
310
+ run_build: { glyph: SHELL_GLYPH, formatBody: formatShellBody, shellShaped: true },
257
311
  task: { glyph: '🤖', summariseArgs: ['subagent_type', 'description'] },
258
312
  };
259
313
  /** The registry glyph for a tool name (generic `⚙` when unknown). */
@@ -311,7 +365,10 @@ export function buildToolBodyLines(input, secrets = getDefaultSecrets()) {
311
365
  const args = parseToolArgsSafe(input.argsText);
312
366
  const entry = TOOL_DISPLAY_REGISTRY[input.name];
313
367
  let lines = entry?.formatBody?.(input, args) ?? null;
314
- if (lines === null)
368
+ // TUI-C32 residual c — only fall back to the shape-based shell formatter for names that may be
369
+ // shell-shaped (flagged registry entries + unregistered custom tools), so a registered non-shell
370
+ // tool whose result merely contains `<COMMAND_OUTPUT>` is rendered generically, not shell-parsed.
371
+ if (lines === null && nameAllowsShellShape(input.name))
315
372
  lines = formatShellBody(input);
316
373
  if (lines === null) {
317
374
  lines = [];