@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.
- package/README.md +43 -18
- package/dist/config/defaults.d.ts +5 -7
- package/dist/config/defaults.js +3 -8
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/loader.d.ts +40 -0
- package/dist/config/loader.js +179 -6
- package/dist/config/loader.js.map +1 -1
- package/dist/config/profiles.d.ts +68 -0
- package/dist/config/profiles.js +93 -0
- package/dist/config/profiles.js.map +1 -0
- package/dist/config/schema.d.ts +69 -2
- package/dist/config/schema.js +87 -5
- package/dist/config/schema.js.map +1 -1
- package/dist/config/types.d.ts +103 -5
- package/dist/config/types.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/core/GthAbstractAgent.d.ts +26 -1
- package/dist/core/GthAbstractAgent.js +122 -1
- package/dist/core/GthAbstractAgent.js.map +1 -1
- package/dist/core/GthAgentRunner.js +40 -0
- package/dist/core/GthAgentRunner.js.map +1 -1
- package/dist/core/GthLangChainAgent.d.ts +99 -0
- package/dist/core/GthLangChainAgent.js +333 -2
- package/dist/core/GthLangChainAgent.js.map +1 -1
- package/dist/core/debugCapture.d.ts +16 -0
- package/dist/core/debugCapture.js.map +1 -1
- package/dist/core/plainToolIndication.js +48 -28
- package/dist/core/plainToolIndication.js.map +1 -1
- package/dist/core/refusal.d.ts +53 -0
- package/dist/core/refusal.js +129 -0
- package/dist/core/refusal.js.map +1 -0
- package/dist/core/runStats.d.ts +14 -3
- package/dist/core/runStats.js +48 -3
- package/dist/core/runStats.js.map +1 -1
- package/dist/core/toolCallRepair/grammar.d.ts +41 -0
- package/dist/core/toolCallRepair/grammar.js +116 -0
- package/dist/core/toolCallRepair/grammar.js.map +1 -0
- package/dist/core/toolCallRepair/index.d.ts +2 -0
- package/dist/core/toolCallRepair/index.js +7 -0
- package/dist/core/toolCallRepair/index.js.map +1 -0
- package/dist/core/toolCallRepair/payload.d.ts +36 -0
- package/dist/core/toolCallRepair/payload.js +341 -0
- package/dist/core/toolCallRepair/payload.js.map +1 -0
- package/dist/core/toolCallRepair/promote.d.ts +45 -0
- package/dist/core/toolCallRepair/promote.js +90 -0
- package/dist/core/toolCallRepair/promote.js.map +1 -0
- package/dist/core/toolDisplay.d.ts +14 -1
- package/dist/core/toolDisplay.js +69 -12
- package/dist/core/toolDisplay.js.map +1 -1
- package/dist/core/toolOutputChannel.d.ts +33 -6
- package/dist/core/toolOutputChannel.js +61 -8
- package/dist/core/toolOutputChannel.js.map +1 -1
- package/dist/core/types.d.ts +22 -0
- package/dist/providers/geminiSchemaSanitizer.d.ts +6 -3
- package/dist/providers/geminiSchemaSanitizer.js +152 -6
- package/dist/providers/geminiSchemaSanitizer.js.map +1 -1
- package/dist/providers/modelDiscovery.d.ts +18 -4
- package/dist/providers/modelDiscovery.js +67 -17
- package/dist/providers/modelDiscovery.js.map +1 -1
- package/dist/providers/openai.js +34 -0
- package/dist/providers/openai.js.map +1 -1
- package/dist/utils/crashHandler.d.ts +87 -0
- package/dist/utils/crashHandler.js +128 -0
- package/dist/utils/crashHandler.js.map +1 -0
- package/dist/utils/debugDump.d.ts +58 -0
- package/dist/utils/debugDump.js +149 -15
- package/dist/utils/debugDump.js.map +1 -1
- package/dist/utils/llmUtils.d.ts +22 -8
- package/dist/utils/llmUtils.js +48 -8
- package/dist/utils/llmUtils.js.map +1 -1
- package/dist/utils/redactSecrets.js +68 -20
- package/dist/utils/redactSecrets.js.map +1 -1
- package/package.json +1 -1
- package/schema/gsloth-config.schema.json +224 -5
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EXT-37 — content-policy refusal detection for the agent run loop.
|
|
3
|
+
*
|
|
4
|
+
* A *successful* model response (HTTP 200) can carry a stop/finish reason that means the model — or
|
|
5
|
+
* the provider's safety system — declined to answer. The content is usually empty, so without this
|
|
6
|
+
* detection the response falls through the empty-response retry in {@link GthAgentRunner} and is
|
|
7
|
+
* mis-surfaced as "no content, try again" — burning a second, paid call to reproduce a
|
|
8
|
+
* DETERMINISTIC refusal. This module normalizes the per-provider shapes into one signal so the run
|
|
9
|
+
* loop can surface the refusal clearly and terminate (never retry the same prompt).
|
|
10
|
+
*
|
|
11
|
+
* Prior art: hermes-agent `conversation_loop.py` treats `finish_reason == "content_filter"` as a
|
|
12
|
+
* terminal, non-retryable "content policy blocked" outcome and surfaces the model's explanation.
|
|
13
|
+
*
|
|
14
|
+
* Detection lives here (and is called from {@link GthAbstractAgent}, the invoke/stream loop over
|
|
15
|
+
* messages/chunks) because that is the only layer where a message's `response_metadata` /
|
|
16
|
+
* `additional_kwargs` — where finish/stop reasons live — are visible; `GthAgentRunner` only ever
|
|
17
|
+
* sees the rendered string.
|
|
18
|
+
*/
|
|
19
|
+
/** One detected refusal, normalized across providers. */
|
|
20
|
+
export interface RefusalInfo {
|
|
21
|
+
/** Best-effort provider family the signal came from (for logging / the surfaced message). */
|
|
22
|
+
provider: 'openai' | 'anthropic' | 'bedrock' | 'unknown';
|
|
23
|
+
/** The raw stop/finish reason token that flagged the refusal (e.g. `content_filter`). */
|
|
24
|
+
reason: string;
|
|
25
|
+
/** Any model-provided explanation text (empty string when the refusal carried none). */
|
|
26
|
+
explanation: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Inspect a finished model message (an `AIMessage` / `AIMessageChunk`, or any object exposing
|
|
30
|
+
* `response_metadata` / `additional_kwargs`) and return a {@link RefusalInfo} when its stop/finish
|
|
31
|
+
* reason indicates a content-policy refusal, else `null`. Defensive: any non-message / unexpected
|
|
32
|
+
* shape yields `null`, so a normal turn is never mistaken for a refusal.
|
|
33
|
+
*
|
|
34
|
+
* Covered shapes:
|
|
35
|
+
* - OpenAI-family `finish_reason: 'content_filter'` (also under `additional_kwargs`).
|
|
36
|
+
* - Anthropic `stop_reason: 'refusal'`.
|
|
37
|
+
* - Bedrock Converse guardrail intervention: `stopReason`/`stop_reason`/`finish_reason` ===
|
|
38
|
+
* `'guardrail_intervened'`, or `additional_kwargs['amazon-bedrock-guardrailAction'] ===
|
|
39
|
+
* `'INTERVENED'`.
|
|
40
|
+
* - Bedrock Converse content filter: `stopReason`/`stop_reason`/`finish_reason` ===
|
|
41
|
+
* `'content_filtered'` (EXT-41 — a distinct `StopReason` enum value from `guardrail_intervened`
|
|
42
|
+
* that was previously mapped to `null`, i.e. a silent empty turn / false negative).
|
|
43
|
+
*/
|
|
44
|
+
export declare function detectRefusal(message: unknown): RefusalInfo | null;
|
|
45
|
+
/**
|
|
46
|
+
* Build the clear, user-facing message shown when the model declines. Framed as the model /
|
|
47
|
+
* provider's own policy decision (not a Gaunt Sloth fault) and stated as terminal — a refusal is
|
|
48
|
+
* deterministic for the same input, so retrying as-is will not help. Any model-provided explanation
|
|
49
|
+
* is included verbatim. This string is BOTH surfaced to the console and RETURNED as the turn's
|
|
50
|
+
* answer, so the non-interactive caller writes it to the output file and exits `ok` (it is a
|
|
51
|
+
* successful, if declined, response — not a failure to be re-wrapped as "Failed to get answer").
|
|
52
|
+
*/
|
|
53
|
+
export declare function buildRefusalMessage(info: RefusalInfo): string;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EXT-37 — content-policy refusal detection for the agent run loop.
|
|
3
|
+
*
|
|
4
|
+
* A *successful* model response (HTTP 200) can carry a stop/finish reason that means the model — or
|
|
5
|
+
* the provider's safety system — declined to answer. The content is usually empty, so without this
|
|
6
|
+
* detection the response falls through the empty-response retry in {@link GthAgentRunner} and is
|
|
7
|
+
* mis-surfaced as "no content, try again" — burning a second, paid call to reproduce a
|
|
8
|
+
* DETERMINISTIC refusal. This module normalizes the per-provider shapes into one signal so the run
|
|
9
|
+
* loop can surface the refusal clearly and terminate (never retry the same prompt).
|
|
10
|
+
*
|
|
11
|
+
* Prior art: hermes-agent `conversation_loop.py` treats `finish_reason == "content_filter"` as a
|
|
12
|
+
* terminal, non-retryable "content policy blocked" outcome and surfaces the model's explanation.
|
|
13
|
+
*
|
|
14
|
+
* Detection lives here (and is called from {@link GthAbstractAgent}, the invoke/stream loop over
|
|
15
|
+
* messages/chunks) because that is the only layer where a message's `response_metadata` /
|
|
16
|
+
* `additional_kwargs` — where finish/stop reasons live — are visible; `GthAgentRunner` only ever
|
|
17
|
+
* sees the rendered string.
|
|
18
|
+
*/
|
|
19
|
+
/** Read a nested record field defensively (returns undefined for non-objects / missing keys). */
|
|
20
|
+
function readField(source, key) {
|
|
21
|
+
if (!source || typeof source !== 'object')
|
|
22
|
+
return undefined;
|
|
23
|
+
return source[key];
|
|
24
|
+
}
|
|
25
|
+
/** Pull any human-readable explanation the refusal carried (message content, then reasoning). */
|
|
26
|
+
function extractRefusalText(message) {
|
|
27
|
+
const content = readField(message, 'content');
|
|
28
|
+
if (typeof content === 'string' && content.trim().length > 0)
|
|
29
|
+
return content.trim();
|
|
30
|
+
// Content-block arrays (Anthropic / Bedrock): concatenate any text parts.
|
|
31
|
+
if (Array.isArray(content)) {
|
|
32
|
+
const text = content
|
|
33
|
+
.map((part) => {
|
|
34
|
+
if (typeof part === 'string')
|
|
35
|
+
return part;
|
|
36
|
+
const t = readField(part, 'text');
|
|
37
|
+
return typeof t === 'string' ? t : '';
|
|
38
|
+
})
|
|
39
|
+
.join('')
|
|
40
|
+
.trim();
|
|
41
|
+
if (text.length > 0)
|
|
42
|
+
return text;
|
|
43
|
+
}
|
|
44
|
+
// Some refusals put the explanation only in the reasoning channel.
|
|
45
|
+
const kwargs = readField(message, 'additional_kwargs');
|
|
46
|
+
const reasoning = readField(kwargs, 'reasoning_content');
|
|
47
|
+
if (typeof reasoning === 'string' && reasoning.trim().length > 0)
|
|
48
|
+
return reasoning.trim();
|
|
49
|
+
// Anthropic exposes the declined text on a dedicated `refusal` field in some SDK shapes.
|
|
50
|
+
const refusalField = readField(kwargs, 'refusal') ?? readField(message, 'refusal');
|
|
51
|
+
if (typeof refusalField === 'string' && refusalField.trim().length > 0)
|
|
52
|
+
return refusalField.trim();
|
|
53
|
+
return '';
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Inspect a finished model message (an `AIMessage` / `AIMessageChunk`, or any object exposing
|
|
57
|
+
* `response_metadata` / `additional_kwargs`) and return a {@link RefusalInfo} when its stop/finish
|
|
58
|
+
* reason indicates a content-policy refusal, else `null`. Defensive: any non-message / unexpected
|
|
59
|
+
* shape yields `null`, so a normal turn is never mistaken for a refusal.
|
|
60
|
+
*
|
|
61
|
+
* Covered shapes:
|
|
62
|
+
* - OpenAI-family `finish_reason: 'content_filter'` (also under `additional_kwargs`).
|
|
63
|
+
* - Anthropic `stop_reason: 'refusal'`.
|
|
64
|
+
* - Bedrock Converse guardrail intervention: `stopReason`/`stop_reason`/`finish_reason` ===
|
|
65
|
+
* `'guardrail_intervened'`, or `additional_kwargs['amazon-bedrock-guardrailAction'] ===
|
|
66
|
+
* `'INTERVENED'`.
|
|
67
|
+
* - Bedrock Converse content filter: `stopReason`/`stop_reason`/`finish_reason` ===
|
|
68
|
+
* `'content_filtered'` (EXT-41 — a distinct `StopReason` enum value from `guardrail_intervened`
|
|
69
|
+
* that was previously mapped to `null`, i.e. a silent empty turn / false negative).
|
|
70
|
+
*/
|
|
71
|
+
export function detectRefusal(message) {
|
|
72
|
+
if (!message || typeof message !== 'object')
|
|
73
|
+
return null;
|
|
74
|
+
const meta = readField(message, 'response_metadata');
|
|
75
|
+
const kwargs = readField(message, 'additional_kwargs');
|
|
76
|
+
// Gather the stop/finish reason from every place providers surface it.
|
|
77
|
+
const finishReason = readField(meta, 'finish_reason') ?? readField(kwargs, 'finish_reason') ?? undefined;
|
|
78
|
+
const stopReasonSnake = readField(meta, 'stop_reason') ?? readField(kwargs, 'stop_reason') ?? undefined;
|
|
79
|
+
const stopReasonCamel = readField(meta, 'stopReason') ?? readField(kwargs, 'stopReason') ?? undefined;
|
|
80
|
+
const asString = (v) => (typeof v === 'string' ? v : undefined);
|
|
81
|
+
const finish = asString(finishReason);
|
|
82
|
+
const stopSnake = asString(stopReasonSnake);
|
|
83
|
+
const stopCamel = asString(stopReasonCamel);
|
|
84
|
+
const explanation = extractRefusalText(message);
|
|
85
|
+
// OpenAI-family content filter.
|
|
86
|
+
if (finish === 'content_filter') {
|
|
87
|
+
return { provider: 'openai', reason: 'content_filter', explanation };
|
|
88
|
+
}
|
|
89
|
+
// Anthropic refusal stop reason.
|
|
90
|
+
if (stopSnake === 'refusal' || stopCamel === 'refusal') {
|
|
91
|
+
return { provider: 'anthropic', reason: 'refusal', explanation };
|
|
92
|
+
}
|
|
93
|
+
// Bedrock Converse guardrail intervention (camelCase `stopReason`, or snake / finish variants).
|
|
94
|
+
if (stopCamel === 'guardrail_intervened' ||
|
|
95
|
+
stopSnake === 'guardrail_intervened' ||
|
|
96
|
+
finish === 'guardrail_intervened' ||
|
|
97
|
+
readField(kwargs, 'amazon-bedrock-guardrailAction') === 'INTERVENED' ||
|
|
98
|
+
readField(meta, 'amazon-bedrock-guardrailAction') === 'INTERVENED') {
|
|
99
|
+
return { provider: 'bedrock', reason: 'guardrail_intervened', explanation };
|
|
100
|
+
}
|
|
101
|
+
// EXT-41 — Bedrock Converse content filter. A distinct `StopReason` enum value from
|
|
102
|
+
// `guardrail_intervened` (both live in the same AWS Converse `StopReason` enum); previously
|
|
103
|
+
// unmapped, so a content-filtered turn returned `null` → the silent empty-turn false negative.
|
|
104
|
+
if (stopCamel === 'content_filtered' ||
|
|
105
|
+
stopSnake === 'content_filtered' ||
|
|
106
|
+
finish === 'content_filtered') {
|
|
107
|
+
return { provider: 'bedrock', reason: 'content_filtered', explanation };
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Build the clear, user-facing message shown when the model declines. Framed as the model /
|
|
113
|
+
* provider's own policy decision (not a Gaunt Sloth fault) and stated as terminal — a refusal is
|
|
114
|
+
* deterministic for the same input, so retrying as-is will not help. Any model-provided explanation
|
|
115
|
+
* is included verbatim. This string is BOTH surfaced to the console and RETURNED as the turn's
|
|
116
|
+
* answer, so the non-interactive caller writes it to the output file and exits `ok` (it is a
|
|
117
|
+
* successful, if declined, response — not a failure to be re-wrapped as "Failed to get answer").
|
|
118
|
+
*/
|
|
119
|
+
export function buildRefusalMessage(info) {
|
|
120
|
+
const head = 'The model declined to respond (safety refusal / content filter) — this is the ' +
|
|
121
|
+
"model/provider's own policy decision, not a Gaunt Sloth error.";
|
|
122
|
+
const detail = info.explanation
|
|
123
|
+
? `Model's explanation: ${info.explanation}`
|
|
124
|
+
: 'The model provided no explanation.';
|
|
125
|
+
const hint = 'A refusal is deterministic for the same input — rephrase the request or try a different ' +
|
|
126
|
+
'model rather than re-running it as-is.';
|
|
127
|
+
return `${head}\n\n${detail}\n\n${hint}`;
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=refusal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refusal.js","sourceRoot":"","sources":["../../src/core/refusal.ts"],"names":[],"mappings":"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"}
|
package/dist/core/runStats.d.ts
CHANGED
|
@@ -10,7 +10,14 @@
|
|
|
10
10
|
* message chunks / `ToolMessage`s as they arrive), across providers whose message shapes vary.
|
|
11
11
|
* Nothing here may throw into a run — a missing/odd field just means that datum is skipped.
|
|
12
12
|
*/
|
|
13
|
-
import type { GthRunStats } from '#src/core/types.js';
|
|
13
|
+
import type { GthRunStats, GthToolResult } from '#src/core/types.js';
|
|
14
|
+
/**
|
|
15
|
+
* BATCH-21 — cap on a captured tool-result `content` (characters). Keeps a giant payload (a whole
|
|
16
|
+
* file read, a long shell log) from bloating run stats; anything longer is truncated to this
|
|
17
|
+
* length. Sized so realistic structured payloads (the `gth eval` tool-result-assertion use case)
|
|
18
|
+
* survive intact.
|
|
19
|
+
*/
|
|
20
|
+
export declare const TOOL_RESULT_CONTENT_CAP = 8192;
|
|
14
21
|
/** Mutable tally behind {@link finalizeRunStats}; see {@link createRunStatsAccumulator}. */
|
|
15
22
|
export interface RunStatsAccumulator {
|
|
16
23
|
/** Running sum of input/prompt tokens. */
|
|
@@ -21,15 +28,19 @@ export interface RunStatsAccumulator {
|
|
|
21
28
|
sawUsage: boolean;
|
|
22
29
|
/** Deduplicated set of invoked tool names. */
|
|
23
30
|
tools: Set<string>;
|
|
31
|
+
/** BATCH-21 — one record per executed tool result (`ToolMessage`), in arrival order, un-deduped. */
|
|
32
|
+
toolResults: GthToolResult[];
|
|
24
33
|
}
|
|
25
34
|
/** A fresh, empty accumulator. */
|
|
26
35
|
export declare function createRunStatsAccumulator(): RunStatsAccumulator;
|
|
27
36
|
/**
|
|
28
37
|
* Fold one LangChain message (or message chunk) into the accumulator. Fail-soft: any unexpected
|
|
29
38
|
* shape is swallowed so a run is never affected. Harvests, when present:
|
|
30
|
-
* - `usage_metadata.input_tokens` / `.output_tokens` (summed; marks `sawUsage`),
|
|
39
|
+
* - `usage_metadata.input_tokens` / `.output_tokens` (summed; marks `sawUsage`),
|
|
31
40
|
* - tool names from an AIMessage's requested `tool_calls[].name` AND from a `ToolMessage`'s own
|
|
32
|
-
* `.name` (the executed tool), so both "requested" and "executed" tools are captured
|
|
41
|
+
* `.name` (the executed tool), so both "requested" and "executed" tools are captured, and
|
|
42
|
+
* - (BATCH-21) a per-`ToolMessage` result record — `name` + `isError` (from `.status`) + capped
|
|
43
|
+
* `content` — into `acc.toolResults`, so tool-RESULT assertions can grade what a tool returned.
|
|
33
44
|
*/
|
|
34
45
|
export declare function accumulateMessage(acc: RunStatsAccumulator, message: unknown): void;
|
|
35
46
|
/** Freeze the accumulator into the public {@link GthRunStats}. Tokens omitted unless observed. */
|
package/dist/core/runStats.js
CHANGED
|
@@ -1,13 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BATCH-21 — cap on a captured tool-result `content` (characters). Keeps a giant payload (a whole
|
|
3
|
+
* file read, a long shell log) from bloating run stats; anything longer is truncated to this
|
|
4
|
+
* length. Sized so realistic structured payloads (the `gth eval` tool-result-assertion use case)
|
|
5
|
+
* survive intact.
|
|
6
|
+
*/
|
|
7
|
+
export const TOOL_RESULT_CONTENT_CAP = 8192;
|
|
1
8
|
/** A fresh, empty accumulator. */
|
|
2
9
|
export function createRunStatsAccumulator() {
|
|
3
|
-
return { input: 0, output: 0, sawUsage: false, tools: new Set() };
|
|
10
|
+
return { input: 0, output: 0, sawUsage: false, tools: new Set(), toolResults: [] };
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* BATCH-21 — derive a tool result's text payload from a `ToolMessage.content`, fail-soft. A string
|
|
14
|
+
* passes through; anything else non-`undefined` is JSON-stringified (the same derivation the
|
|
15
|
+
* `tool_result` stream event uses in `GthAbstractAgent`); the result is capped at
|
|
16
|
+
* {@link TOOL_RESULT_CONTENT_CAP}. Returns `undefined` (payload omitted) when nothing textual can
|
|
17
|
+
* be derived — never throws.
|
|
18
|
+
*/
|
|
19
|
+
function toolResultContentText(content) {
|
|
20
|
+
try {
|
|
21
|
+
let text;
|
|
22
|
+
if (typeof content === 'string') {
|
|
23
|
+
text = content;
|
|
24
|
+
}
|
|
25
|
+
else if (content !== undefined) {
|
|
26
|
+
text = JSON.stringify(content);
|
|
27
|
+
}
|
|
28
|
+
if (text === undefined)
|
|
29
|
+
return undefined;
|
|
30
|
+
return text.length > TOOL_RESULT_CONTENT_CAP ? text.slice(0, TOOL_RESULT_CONTENT_CAP) : text;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
/* fail-soft: an unstringifiable payload just means no content is recorded */
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
4
36
|
}
|
|
5
37
|
/**
|
|
6
38
|
* Fold one LangChain message (or message chunk) into the accumulator. Fail-soft: any unexpected
|
|
7
39
|
* shape is swallowed so a run is never affected. Harvests, when present:
|
|
8
|
-
* - `usage_metadata.input_tokens` / `.output_tokens` (summed; marks `sawUsage`),
|
|
40
|
+
* - `usage_metadata.input_tokens` / `.output_tokens` (summed; marks `sawUsage`),
|
|
9
41
|
* - tool names from an AIMessage's requested `tool_calls[].name` AND from a `ToolMessage`'s own
|
|
10
|
-
* `.name` (the executed tool), so both "requested" and "executed" tools are captured
|
|
42
|
+
* `.name` (the executed tool), so both "requested" and "executed" tools are captured, and
|
|
43
|
+
* - (BATCH-21) a per-`ToolMessage` result record — `name` + `isError` (from `.status`) + capped
|
|
44
|
+
* `content` — into `acc.toolResults`, so tool-RESULT assertions can grade what a tool returned.
|
|
11
45
|
*/
|
|
12
46
|
export function accumulateMessage(acc, message) {
|
|
13
47
|
try {
|
|
@@ -39,6 +73,16 @@ export function accumulateMessage(acc, message) {
|
|
|
39
73
|
const type = typeof m.getType === 'function' ? m.getType() : m._getType?.();
|
|
40
74
|
if (type === 'tool' && typeof m.name === 'string' && m.name.length > 0) {
|
|
41
75
|
acc.tools.add(m.name);
|
|
76
|
+
// BATCH-21 — capture the RESULT record too (same capture site, same fail-soft discipline):
|
|
77
|
+
// `.status === 'error'` is LangChain's real tool-error signal, `.content` the returned
|
|
78
|
+
// payload (capped; omitted when no text can be derived). One record per ToolMessage, in
|
|
79
|
+
// arrival order — deliberately NOT deduplicated, unlike the name set above.
|
|
80
|
+
const content = toolResultContentText(m.content);
|
|
81
|
+
acc.toolResults.push({
|
|
82
|
+
name: m.name,
|
|
83
|
+
isError: m.status === 'error',
|
|
84
|
+
...(content !== undefined ? { content } : {}),
|
|
85
|
+
});
|
|
42
86
|
}
|
|
43
87
|
}
|
|
44
88
|
catch {
|
|
@@ -51,6 +95,7 @@ export function finalizeRunStats(acc) {
|
|
|
51
95
|
tokensInput: acc.sawUsage ? acc.input : undefined,
|
|
52
96
|
tokensOutput: acc.sawUsage ? acc.output : undefined,
|
|
53
97
|
tools: [...acc.tools],
|
|
98
|
+
toolResults: [...acc.toolResults],
|
|
54
99
|
};
|
|
55
100
|
}
|
|
56
101
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runStats.js","sourceRoot":"","sources":["../../src/core/runStats.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"runStats.js","sourceRoot":"","sources":["../../src/core/runStats.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** Legacy marker some models emit after a serialized JSON tool request. */
|
|
2
|
+
export declare const END_TOOL_REQUEST = "[END_TOOL_REQUEST]";
|
|
3
|
+
/** Harmony stream marker that introduces the target channel before a tool call. */
|
|
4
|
+
export declare const HARMONY_CHANNEL_MARKER = "<|channel|>";
|
|
5
|
+
/** Harmony stream marker that may separate the header from the JSON payload. */
|
|
6
|
+
export declare const HARMONY_MESSAGE_MARKER = "<|message|>";
|
|
7
|
+
/** Harmony stream marker that may close a serialized tool-call payload. */
|
|
8
|
+
export declare const HARMONY_CALL_MARKER = "<|call|>";
|
|
9
|
+
/**
|
|
10
|
+
* Harmony marker real gpt-oss emits in place of the reference grammar's literal `code` token —
|
|
11
|
+
* e.g. `<|constrain|>json` declares the tool-call payload format. (EXT-43.)
|
|
12
|
+
*/
|
|
13
|
+
export declare const HARMONY_CONSTRAIN_MARKER = "<|constrain|>";
|
|
14
|
+
/** Tool names in bracket/plain-text repairs intentionally match provider-safe ids only. */
|
|
15
|
+
export declare function isPlainTextToolNameChar(char: string | undefined): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Harmony tool names may be NAMESPACED with dots (real gpt-oss emits `to=functions.get_weather`),
|
|
18
|
+
* so the Harmony header scanner accepts `.` on top of the provider-safe id chars. This is a
|
|
19
|
+
* Harmony-only widening (EXT-43): the bracket/XML-ish dialects keep {@link isPlainTextToolNameChar}
|
|
20
|
+
* unchanged so their prose-safety is untouched. The final dot-segment is the tool name matched
|
|
21
|
+
* against the allow-list (see {@link finalDotSegment}).
|
|
22
|
+
*/
|
|
23
|
+
export declare function isHarmonyToolNameChar(char: string | undefined): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* The tool name for a (possibly namespaced) Harmony target: the final dot-segment.
|
|
26
|
+
* `functions.get_weather` → `get_weather`; `functions.tools.get_weather` → `get_weather`; a dotless
|
|
27
|
+
* `get_weather` → itself. Returns `''` for a trailing-dot / empty final segment (rejected upstream).
|
|
28
|
+
*/
|
|
29
|
+
export declare function finalDotSegment(name: string): string;
|
|
30
|
+
/** Skips spaces and tabs only, preserving line boundaries for grammar decisions. */
|
|
31
|
+
export declare function skipHorizontalWhitespace(text: string, start: number): number;
|
|
32
|
+
/** Skips all JavaScript whitespace when line structure is no longer meaningful. */
|
|
33
|
+
export declare function skipWhitespace(text: string, start: number): number;
|
|
34
|
+
/** Consumes either Unix or Windows line endings and returns the first offset after them. */
|
|
35
|
+
export declare function consumeLineBreak(text: string, start: number): number | null;
|
|
36
|
+
/**
|
|
37
|
+
* Finds the exclusive end offset of a balanced JSON object starting at `start`. Returns null if
|
|
38
|
+
* the object never closes, or (when `maxPayloadBytes` is set) as soon as the scan runs past the
|
|
39
|
+
* cap — the payload-size gate that keeps a runaway blob from being treated as a tool call.
|
|
40
|
+
*/
|
|
41
|
+
export declare function findJsonObjectEnd(text: string, start: number, maxPayloadBytes?: number): number | null;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// EXT-35 — plain-text tool-call repair grammar.
|
|
2
|
+
//
|
|
3
|
+
// Ported (TypeScript, gaunt-sloth house style) from the openclaw
|
|
4
|
+
// `@openclaw/tool-call-repair` reference (`packages/tool-call-repair/src/grammar.ts`).
|
|
5
|
+
// Only the low-level scanners the standalone-block parser in `./payload.ts` needs are
|
|
6
|
+
// carried over; the reference's streaming/normalizer/strip helpers are intentionally omitted.
|
|
7
|
+
//
|
|
8
|
+
// These functions recognise the fixed protocol markers small/local models emit when they
|
|
9
|
+
// serialise a tool call as assistant TEXT instead of a native tool_call. They do no allow-list
|
|
10
|
+
// or size gating themselves — that lives in `./payload.ts` / `./promote.ts`.
|
|
11
|
+
/** Legacy marker some models emit after a serialized JSON tool request. */
|
|
12
|
+
export const END_TOOL_REQUEST = '[END_TOOL_REQUEST]';
|
|
13
|
+
/** Harmony stream marker that introduces the target channel before a tool call. */
|
|
14
|
+
export const HARMONY_CHANNEL_MARKER = '<|channel|>';
|
|
15
|
+
/** Harmony stream marker that may separate the header from the JSON payload. */
|
|
16
|
+
export const HARMONY_MESSAGE_MARKER = '<|message|>';
|
|
17
|
+
/** Harmony stream marker that may close a serialized tool-call payload. */
|
|
18
|
+
export const HARMONY_CALL_MARKER = '<|call|>';
|
|
19
|
+
/**
|
|
20
|
+
* Harmony marker real gpt-oss emits in place of the reference grammar's literal `code` token —
|
|
21
|
+
* e.g. `<|constrain|>json` declares the tool-call payload format. (EXT-43.)
|
|
22
|
+
*/
|
|
23
|
+
export const HARMONY_CONSTRAIN_MARKER = '<|constrain|>';
|
|
24
|
+
/** Tool names in bracket/plain-text repairs intentionally match provider-safe ids only. */
|
|
25
|
+
export function isPlainTextToolNameChar(char) {
|
|
26
|
+
return Boolean(char && /[A-Za-z0-9_-]/.test(char));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Harmony tool names may be NAMESPACED with dots (real gpt-oss emits `to=functions.get_weather`),
|
|
30
|
+
* so the Harmony header scanner accepts `.` on top of the provider-safe id chars. This is a
|
|
31
|
+
* Harmony-only widening (EXT-43): the bracket/XML-ish dialects keep {@link isPlainTextToolNameChar}
|
|
32
|
+
* unchanged so their prose-safety is untouched. The final dot-segment is the tool name matched
|
|
33
|
+
* against the allow-list (see {@link finalDotSegment}).
|
|
34
|
+
*/
|
|
35
|
+
export function isHarmonyToolNameChar(char) {
|
|
36
|
+
return Boolean(char && /[A-Za-z0-9_.-]/.test(char));
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The tool name for a (possibly namespaced) Harmony target: the final dot-segment.
|
|
40
|
+
* `functions.get_weather` → `get_weather`; `functions.tools.get_weather` → `get_weather`; a dotless
|
|
41
|
+
* `get_weather` → itself. Returns `''` for a trailing-dot / empty final segment (rejected upstream).
|
|
42
|
+
*/
|
|
43
|
+
export function finalDotSegment(name) {
|
|
44
|
+
const lastDot = name.lastIndexOf('.');
|
|
45
|
+
return lastDot === -1 ? name : name.slice(lastDot + 1);
|
|
46
|
+
}
|
|
47
|
+
/** Skips spaces and tabs only, preserving line boundaries for grammar decisions. */
|
|
48
|
+
export function skipHorizontalWhitespace(text, start) {
|
|
49
|
+
let index = start;
|
|
50
|
+
while (index < text.length && (text[index] === ' ' || text[index] === '\t')) {
|
|
51
|
+
index += 1;
|
|
52
|
+
}
|
|
53
|
+
return index;
|
|
54
|
+
}
|
|
55
|
+
/** Skips all JavaScript whitespace when line structure is no longer meaningful. */
|
|
56
|
+
export function skipWhitespace(text, start) {
|
|
57
|
+
let index = start;
|
|
58
|
+
while (index < text.length && /\s/.test(text[index] ?? '')) {
|
|
59
|
+
index += 1;
|
|
60
|
+
}
|
|
61
|
+
return index;
|
|
62
|
+
}
|
|
63
|
+
/** Consumes either Unix or Windows line endings and returns the first offset after them. */
|
|
64
|
+
export function consumeLineBreak(text, start) {
|
|
65
|
+
if (text[start] === '\r') {
|
|
66
|
+
return text[start + 1] === '\n' ? start + 2 : start + 1;
|
|
67
|
+
}
|
|
68
|
+
if (text[start] === '\n') {
|
|
69
|
+
return start + 1;
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Finds the exclusive end offset of a balanced JSON object starting at `start`. Returns null if
|
|
75
|
+
* the object never closes, or (when `maxPayloadBytes` is set) as soon as the scan runs past the
|
|
76
|
+
* cap — the payload-size gate that keeps a runaway blob from being treated as a tool call.
|
|
77
|
+
*/
|
|
78
|
+
export function findJsonObjectEnd(text, start, maxPayloadBytes) {
|
|
79
|
+
let depth = 0;
|
|
80
|
+
let inString = false;
|
|
81
|
+
let escaped = false;
|
|
82
|
+
for (let index = start; index < text.length; index += 1) {
|
|
83
|
+
if (maxPayloadBytes !== undefined && index + 1 - start > maxPayloadBytes) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
const char = text[index];
|
|
87
|
+
if (inString) {
|
|
88
|
+
if (escaped) {
|
|
89
|
+
escaped = false;
|
|
90
|
+
}
|
|
91
|
+
else if (char === '\\') {
|
|
92
|
+
escaped = true;
|
|
93
|
+
}
|
|
94
|
+
else if (char === '"') {
|
|
95
|
+
inString = false;
|
|
96
|
+
}
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (char === '"') {
|
|
100
|
+
inString = true;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (char === '{') {
|
|
104
|
+
depth += 1;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (char === '}') {
|
|
108
|
+
depth -= 1;
|
|
109
|
+
if (depth === 0) {
|
|
110
|
+
return index + 1;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=grammar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grammar.js","sourceRoot":"","sources":["../../../src/core/toolCallRepair/grammar.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { parseStandalonePlainTextToolCallBlocks, type PlainTextToolCallBlock, type PlainTextToolCallParseOptions, } from './payload.js';
|
|
2
|
+
export { textToNativeToolCalls, promoteTextEmittedToolCallMessage, MAX_TEXT_EMITTED_TOOL_CALL_PAYLOAD_BYTES, type TextToolCallRepairOptions, } from './promote.js';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// EXT-35 — plain-text tool-call repair: promote a text-emitted tool call to a native tool_call.
|
|
2
|
+
// Ported/adapted from the openclaw `@openclaw/tool-call-repair` reference. See the sibling files
|
|
3
|
+
// for the per-dialect grammar (`grammar.ts`), the standalone-block parser (`payload.ts`), and the
|
|
4
|
+
// LangChain-message promotion + gates (`promote.ts`).
|
|
5
|
+
export { parseStandalonePlainTextToolCallBlocks, } from './payload.js';
|
|
6
|
+
export { textToNativeToolCalls, promoteTextEmittedToolCallMessage, MAX_TEXT_EMITTED_TOOL_CALL_PAYLOAD_BYTES, } from './promote.js';
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/toolCallRepair/index.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAChG,iGAAiG;AACjG,kGAAkG;AAClG,sDAAsD;AACtD,OAAO,EACL,sCAAsC,GAGvC,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,qBAAqB,EACrB,iCAAiC,EACjC,wCAAwC,GAEzC,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Parsed standalone plain-text tool call block with source offsets for repair. */
|
|
2
|
+
export type PlainTextToolCallBlock = {
|
|
3
|
+
/** Parsed JSON arguments object. */
|
|
4
|
+
arguments: Record<string, unknown>;
|
|
5
|
+
/** Exclusive end offset of the parsed block. */
|
|
6
|
+
end: number;
|
|
7
|
+
/** Tool name parsed from bracket, Harmony, or XML-ish syntax. */
|
|
8
|
+
name: string;
|
|
9
|
+
/** Original text slice that produced this block. */
|
|
10
|
+
raw: string;
|
|
11
|
+
/** Inclusive start offset of the parsed block. */
|
|
12
|
+
start: number;
|
|
13
|
+
};
|
|
14
|
+
/** Parser limits and allow-list options for plain-text tool-call repair. */
|
|
15
|
+
export type PlainTextToolCallParseOptions = {
|
|
16
|
+
/** Optional allow-list of tool names that may be repaired. */
|
|
17
|
+
allowedToolNames?: Iterable<string>;
|
|
18
|
+
/** Maximum serialized payload size accepted for one repaired call. */
|
|
19
|
+
maxPayloadBytes?: number;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* The single source of truth for the plain-text tool-call payload cap (256 KB). A serialized call
|
|
23
|
+
* larger than this is never treated as a tool call. Re-exported from `./promote.js` (and the module
|
|
24
|
+
* index) as `MAX_TEXT_EMITTED_TOOL_CALL_PAYLOAD_BYTES` — the public name — so there is ONE constant,
|
|
25
|
+
* not two duplicated literals. (EXT-43 unified the former `DEFAULT_MAX_PLAIN_TEXT_TOOL_PAYLOAD_BYTES`
|
|
26
|
+
* here with `promote.ts`'s copy.)
|
|
27
|
+
*/
|
|
28
|
+
export declare const MAX_TEXT_EMITTED_TOOL_CALL_PAYLOAD_BYTES = 256000;
|
|
29
|
+
/**
|
|
30
|
+
* Parse the WHOLE input into standalone plain-text tool-call blocks, or return null if any part of
|
|
31
|
+
* it is not such a block. This all-or-nothing walk is the standalone-only gate: prose that merely
|
|
32
|
+
* mentions a tool name, contains brackets, or embeds a call inside a sentence never parses (the
|
|
33
|
+
* first non-block character aborts the parse). Allow-list + payload-cap gating are applied per
|
|
34
|
+
* block via {@link PlainTextToolCallParseOptions}.
|
|
35
|
+
*/
|
|
36
|
+
export declare function parseStandalonePlainTextToolCallBlocks(text: string, options?: PlainTextToolCallParseOptions): PlainTextToolCallBlock[] | null;
|