@gaunt-sloth/core 2.0.0-alpha.8 → 2.0.0-beta.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/.gsloth.review.md +2 -0
- package/README.md +71 -20
- package/dist/config/colour.d.ts +38 -0
- package/dist/config/colour.js +36 -0
- package/dist/config/colour.js.map +1 -0
- package/dist/config/configDiscovery.d.ts +79 -0
- package/dist/config/configDiscovery.js +80 -0
- package/dist/config/configDiscovery.js.map +1 -0
- package/dist/config/defaults.d.ts +20 -20
- package/dist/config/defaults.js +10 -8
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/filesystem-tools.d.ts +41 -0
- package/dist/config/filesystem-tools.js +56 -0
- package/dist/config/filesystem-tools.js.map +1 -0
- package/dist/config/loader.d.ts +171 -19
- package/dist/config/loader.js +1054 -144
- package/dist/config/loader.js.map +1 -1
- package/dist/config/mouse.d.ts +50 -0
- package/dist/config/mouse.js +44 -0
- package/dist/config/mouse.js.map +1 -0
- 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/providerKeys.d.ts +69 -0
- package/dist/config/providerKeys.js +69 -0
- package/dist/config/providerKeys.js.map +1 -0
- package/dist/config/schema.d.ts +2671 -138
- package/dist/config/schema.js +1361 -85
- package/dist/config/schema.js.map +1 -1
- package/dist/config/shell-policy.d.ts +899 -111
- package/dist/config/shell-policy.js +800 -70
- package/dist/config/shell-policy.js.map +1 -1
- package/dist/config/tool-descriptions.d.ts +211 -0
- package/dist/config/tool-descriptions.js +272 -0
- package/dist/config/tool-descriptions.js.map +1 -0
- package/dist/config/types.d.ts +352 -41
- package/dist/config/types.js +1 -0
- package/dist/config/types.js.map +1 -1
- package/dist/config.d.ts +35 -1
- package/dist/config.js +16 -1
- package/dist/config.js.map +1 -1
- package/dist/constants.d.ts +28 -1
- package/dist/constants.js +28 -1
- package/dist/constants.js.map +1 -1
- package/dist/core/GthAbstractAgent.d.ts +166 -11
- package/dist/core/GthAbstractAgent.js +484 -45
- package/dist/core/GthAbstractAgent.js.map +1 -1
- package/dist/core/GthAgentRunner.d.ts +543 -57
- package/dist/core/GthAgentRunner.js +1494 -140
- package/dist/core/GthAgentRunner.js.map +1 -1
- package/dist/core/GthLangChainAgent.d.ts +117 -2
- package/dist/core/GthLangChainAgent.js +602 -29
- package/dist/core/GthLangChainAgent.js.map +1 -1
- package/dist/core/approvals/annotations.d.ts +122 -0
- package/dist/core/approvals/annotations.js +137 -0
- package/dist/core/approvals/annotations.js.map +1 -0
- package/dist/core/approvals/grants.d.ts +216 -0
- package/dist/core/approvals/grants.js +469 -0
- package/dist/core/approvals/grants.js.map +1 -0
- package/dist/core/approvals/matcher.d.ts +202 -0
- package/dist/core/approvals/matcher.js +267 -0
- package/dist/core/approvals/matcher.js.map +1 -0
- package/dist/core/approvals/mcpSubjects.d.ts +40 -0
- package/dist/core/approvals/mcpSubjects.js +99 -0
- package/dist/core/approvals/mcpSubjects.js.map +1 -0
- package/dist/core/approvals/promptHeader.d.ts +28 -0
- package/dist/core/approvals/promptHeader.js +62 -0
- package/dist/core/approvals/promptHeader.js.map +1 -0
- package/dist/core/approvals/toolAnnotationSources.d.ts +105 -0
- package/dist/core/approvals/toolAnnotationSources.js +277 -0
- package/dist/core/approvals/toolAnnotationSources.js.map +1 -0
- package/dist/core/approvals/toolHost.d.ts +46 -0
- package/dist/core/approvals/toolHost.js +108 -0
- package/dist/core/approvals/toolHost.js.map +1 -0
- package/dist/core/debugCapture.d.ts +74 -0
- package/dist/core/debugCapture.js +100 -0
- package/dist/core/debugCapture.js.map +1 -0
- package/dist/core/gthLeanAgentFactory.d.ts +4 -4
- package/dist/core/gthLeanAgentFactory.js +4 -4
- package/dist/core/launchBanner.d.ts +127 -0
- package/dist/core/launchBanner.js +414 -0
- package/dist/core/launchBanner.js.map +1 -0
- package/dist/core/modelLabel.d.ts +19 -0
- package/dist/core/modelLabel.js +26 -0
- package/dist/core/modelLabel.js.map +1 -0
- package/dist/core/plainToolIndication.d.ts +15 -0
- package/dist/core/plainToolIndication.js +174 -0
- package/dist/core/plainToolIndication.js.map +1 -0
- package/dist/core/reasoningBlocks.d.ts +65 -0
- package/dist/core/reasoningBlocks.js +103 -0
- package/dist/core/reasoningBlocks.js.map +1 -0
- package/dist/core/refusal.d.ts +53 -0
- package/dist/core/refusal.js +133 -0
- package/dist/core/refusal.js.map +1 -0
- package/dist/core/runHeader.d.ts +38 -0
- package/dist/core/runHeader.js +42 -0
- package/dist/core/runHeader.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/shell/ShellCommandFailedError.d.ts +3 -4
- package/dist/core/shell/ShellCommandFailedError.js +3 -4
- package/dist/core/shell/ShellCommandFailedError.js.map +1 -1
- package/dist/core/shell/abstention.d.ts +88 -0
- package/dist/core/shell/abstention.js +184 -0
- package/dist/core/shell/abstention.js.map +1 -0
- package/dist/core/shell/approvalCapture.d.ts +271 -0
- package/dist/core/shell/approvalCapture.js +108 -0
- package/dist/core/shell/approvalCapture.js.map +1 -0
- package/dist/core/shell/approvalStop.d.ts +123 -0
- package/dist/core/shell/approvalStop.js +269 -0
- package/dist/core/shell/approvalStop.js.map +1 -0
- package/dist/core/shell/arity.d.ts +6 -0
- package/dist/core/shell/arity.js +20 -6
- package/dist/core/shell/arity.js.map +1 -1
- package/dist/core/shell/denylist.d.ts +11 -0
- package/dist/core/shell/denylist.js +37 -0
- package/dist/core/shell/denylist.js.map +1 -0
- package/dist/core/shell/escalationSeverity.d.ts +141 -0
- package/dist/core/shell/escalationSeverity.js +89 -0
- package/dist/core/shell/escalationSeverity.js.map +1 -0
- package/dist/core/shell/framing.d.ts +190 -0
- package/dist/core/shell/framing.js +633 -0
- package/dist/core/shell/framing.js.map +1 -0
- package/dist/core/shell/hardline.d.ts +103 -0
- package/dist/core/shell/hardline.js +780 -0
- package/dist/core/shell/hardline.js.map +1 -0
- package/dist/core/shell/negotiation.d.ts +328 -0
- package/dist/core/shell/negotiation.js +488 -0
- package/dist/core/shell/negotiation.js.map +1 -0
- package/dist/core/shell/normalize.d.ts +44 -4
- package/dist/core/shell/normalize.js +61 -7
- package/dist/core/shell/normalize.js.map +1 -1
- package/dist/core/shell/openWorld.d.ts +263 -0
- package/dist/core/shell/openWorld.js +1188 -0
- package/dist/core/shell/openWorld.js.map +1 -0
- package/dist/core/shell/rater.d.ts +873 -0
- package/dist/core/shell/rater.js +1454 -0
- package/dist/core/shell/rater.js.map +1 -0
- package/dist/core/shell/raterModel.d.ts +41 -0
- package/dist/core/shell/raterModel.js +51 -0
- package/dist/core/shell/raterModel.js.map +1 -0
- package/dist/core/shell/raterVocabulary.d.ts +121 -0
- package/dist/core/shell/raterVocabulary.js +116 -0
- package/dist/core/shell/raterVocabulary.js.map +1 -0
- package/dist/core/shell/rejection.d.ts +69 -0
- package/dist/core/shell/rejection.js +38 -0
- package/dist/core/shell/rejection.js.map +1 -0
- 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 +123 -0
- package/dist/core/toolDisplay.js +451 -0
- package/dist/core/toolDisplay.js.map +1 -0
- package/dist/core/toolOutputChannel.d.ts +95 -0
- package/dist/core/toolOutputChannel.js +165 -0
- package/dist/core/toolOutputChannel.js.map +1 -0
- package/dist/core/types.d.ts +349 -16
- package/dist/core/types.js.map +1 -1
- package/dist/history/historyFormat.d.ts +12 -3
- package/dist/history/historyFormat.js +50 -8
- package/dist/history/historyFormat.js.map +1 -1
- package/dist/history/historyStore.d.ts +77 -0
- package/dist/history/historyStore.js +173 -6
- package/dist/history/historyStore.js.map +1 -1
- package/dist/history/recordSession.d.ts +10 -1
- package/dist/history/recordSession.js +27 -0
- package/dist/history/recordSession.js.map +1 -1
- package/dist/providers/anthropic.js +12 -0
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/configurationPassthrough.d.ts +107 -0
- package/dist/providers/configurationPassthrough.js +148 -0
- package/dist/providers/configurationPassthrough.js.map +1 -0
- package/dist/providers/geminiSchemaSanitizer.d.ts +55 -0
- package/dist/providers/geminiSchemaSanitizer.js +347 -0
- package/dist/providers/geminiSchemaSanitizer.js.map +1 -0
- package/dist/providers/geminiThinking.d.ts +60 -0
- package/dist/providers/geminiThinking.js +92 -0
- package/dist/providers/geminiThinking.js.map +1 -0
- package/dist/providers/google-genai.js +18 -1
- package/dist/providers/google-genai.js.map +1 -1
- package/dist/providers/groq.js +12 -0
- package/dist/providers/groq.js.map +1 -1
- package/dist/providers/huggingface.d.ts +25 -0
- package/dist/providers/huggingface.js +69 -0
- package/dist/providers/huggingface.js.map +1 -0
- package/dist/providers/modelCatalog.d.ts +109 -0
- package/dist/providers/modelCatalog.js +245 -0
- package/dist/providers/modelCatalog.js.map +1 -0
- package/dist/providers/modelDiscovery.d.ts +99 -5
- package/dist/providers/modelDiscovery.js +191 -35
- package/dist/providers/modelDiscovery.js.map +1 -1
- package/dist/providers/ollama.d.ts +18 -4
- package/dist/providers/ollama.js +67 -37
- package/dist/providers/ollama.js.map +1 -1
- package/dist/providers/openai.js +34 -0
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/openrouter.d.ts +26 -4
- package/dist/providers/openrouter.js +83 -26
- package/dist/providers/openrouter.js.map +1 -1
- package/dist/providers/vertexai.js +19 -1
- package/dist/providers/vertexai.js.map +1 -1
- package/dist/providers/xai.js +20 -0
- package/dist/providers/xai.js.map +1 -1
- package/dist/runtime/askStructured.d.ts +105 -0
- package/dist/runtime/askStructured.js +120 -0
- package/dist/runtime/askStructured.js.map +1 -0
- package/dist/runtime/conversation.d.ts +64 -0
- package/dist/runtime/conversation.js +171 -0
- package/dist/runtime/conversation.js.map +1 -0
- package/dist/runtime/singleShot.d.ts +37 -6
- package/dist/runtime/singleShot.js +113 -67
- package/dist/runtime/singleShot.js.map +1 -1
- package/dist/runtime/structuredOutput.d.ts +104 -0
- package/dist/runtime/structuredOutput.js +393 -0
- package/dist/runtime/structuredOutput.js.map +1 -0
- package/dist/utils/ProgressIndicator.d.ts +21 -0
- package/dist/utils/ProgressIndicator.js +30 -3
- package/dist/utils/ProgressIndicator.js.map +1 -1
- package/dist/utils/aiignoreUtils.js.map +1 -1
- package/dist/utils/binaryOutputUtils.js.map +1 -1
- package/dist/utils/consoleUtils.d.ts +95 -0
- package/dist/utils/consoleUtils.js +112 -2
- package/dist/utils/consoleUtils.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 +134 -0
- package/dist/utils/debugDump.js +381 -0
- package/dist/utils/debugDump.js.map +1 -0
- package/dist/utils/debugUtils.d.ts +13 -4
- package/dist/utils/debugUtils.js +36 -13
- package/dist/utils/debugUtils.js.map +1 -1
- package/dist/utils/displayWidth.d.ts +53 -0
- package/dist/utils/displayWidth.js +195 -0
- package/dist/utils/displayWidth.js.map +1 -0
- package/dist/utils/fileUtils.js.map +1 -1
- package/dist/utils/globalConfigUtils.d.ts +14 -2
- package/dist/utils/globalConfigUtils.js +22 -4
- package/dist/utils/globalConfigUtils.js.map +1 -1
- package/dist/utils/llmUtils.d.ts +39 -8
- package/dist/utils/llmUtils.js +76 -8
- package/dist/utils/llmUtils.js.map +1 -1
- package/dist/utils/redactSecrets.d.ts +63 -0
- package/dist/utils/redactSecrets.js +286 -0
- package/dist/utils/redactSecrets.js.map +1 -0
- package/dist/utils/systemPromptNotes.d.ts +222 -0
- package/dist/utils/systemPromptNotes.js +338 -0
- package/dist/utils/systemPromptNotes.js.map +1 -0
- package/dist/utils/systemUtils.d.ts +18 -1
- package/dist/utils/systemUtils.js +38 -3
- package/dist/utils/systemUtils.js.map +1 -1
- package/dist/utils/toolMatching.d.ts +30 -0
- package/dist/utils/toolMatching.js +44 -0
- package/dist/utils/toolMatching.js.map +1 -0
- package/dist/utils/untrustedText.d.ts +86 -0
- package/dist/utils/untrustedText.js +101 -0
- package/dist/utils/untrustedText.js.map +1 -0
- package/package.json +21 -7
- package/schema/gsloth-config.schema.json +1921 -326
- package/dist/core/shell/allowlist.d.ts +0 -75
- package/dist/core/shell/allowlist.js +0 -187
- package/dist/core/shell/allowlist.js.map +0 -1
- package/dist/core/shell/judge.d.ts +0 -161
- package/dist/core/shell/judge.js +0 -261
- package/dist/core/shell/judge.js.map +0 -1
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module plainToolIndication
|
|
3
|
+
* TUI-C30 — compact tool-call indication for the PLAIN surface (`--no-tui` readline sessions,
|
|
4
|
+
* piped/single-shot `ask`/`exec`/`review`/`pr`). The Ink TUI renders tool calls from the typed
|
|
5
|
+
* event stream; the plain surface streams strings, so until now a tool call only surfaced
|
|
6
|
+
* through the tools' own transient notices (`📁 Reading file: …`, `🔧 Executing …` + raw child
|
|
7
|
+
* output via the tool-output channel's default sink). This module watches the SAME LangGraph
|
|
8
|
+
* message stream the string path already iterates and, when each `ToolMessage` lands, prints
|
|
9
|
+
* one compact indication built from the shared {@link toolDisplay} registry:
|
|
10
|
+
*
|
|
11
|
+
* ✓ 📁 read_file(path=README.md)
|
|
12
|
+
* # Readme ← up to the canonical 10 preview lines, dim
|
|
13
|
+
* … (+42 more lines)
|
|
14
|
+
*
|
|
15
|
+
* Stream discipline (matches how the plain surface prints tool activity today): the block is
|
|
16
|
+
* emitted at INFO level through `displayToolIndication` — same stdout channel, same
|
|
17
|
+
* `consoleLevel` gate and session-log treatment as the existing tool notices — so scripted
|
|
18
|
+
* consumers that already silence INFO chatter silence this too. Colour is used exactly when the
|
|
19
|
+
* resolved `useColour` (the CFG-30 ladder in `config/colour.ts`) says so — TUI-C35 removed the
|
|
20
|
+
* local `&& stdout.isTTY` narrowing this module used to apply on top, which was redundant against
|
|
21
|
+
* the ladder's own rung-4 TTY auto-detection everywhere except `FORCE_COLOR` on a pipe, the one
|
|
22
|
+
* case that variable exists to serve. An ordinary piped run is therefore still clean monochrome
|
|
23
|
+
* (DL-7) — rung 4 decides that — with diff lines readable via their `+`/`-` prefixes.
|
|
24
|
+
*
|
|
25
|
+
* Live-output dedupe: shell-shaped results (`<COMMAND_OUTPUT>`) belong to tools whose child
|
|
26
|
+
* output ALREADY streamed raw via the channel's default sink, so those render with
|
|
27
|
+
* `liveOutputAlreadyShown` and show only the closing status line — never a repeat of output
|
|
28
|
+
* the user just watched.
|
|
29
|
+
*/
|
|
30
|
+
import { AIMessage, AIMessageChunk, ToolMessage } from '@langchain/core/messages';
|
|
31
|
+
import { buildToolPreviewLines, getToolGlyph, isShellShapedResult, renderToolLineAnsi, summariseToolCall, } from '#src/core/toolDisplay.js';
|
|
32
|
+
import { displayToolIndication } from '#src/utils/consoleUtils.js';
|
|
33
|
+
import { getUseColour } from '#src/utils/systemUtils.js';
|
|
34
|
+
const INDENT = ' ';
|
|
35
|
+
/**
|
|
36
|
+
* Create the per-stream observer. State is scoped to one stream (one `agent.stream()` call);
|
|
37
|
+
* tool_call deltas are accumulated from `tool_call_chunks` (keyed by the provider's chunk
|
|
38
|
+
* `index`, which restarts per LLM round — the map is flushed into the by-id map whenever a
|
|
39
|
+
* `ToolMessage` arrives, mirroring `processEventStream`'s reset-per-round). Deliberately does
|
|
40
|
+
* NOT `concat()` whole `AIMessageChunk`s: only the tool-call slices are needed, which also
|
|
41
|
+
* sidesteps the TUI-C29 `__raw_response` aggregation-growth trap entirely.
|
|
42
|
+
*
|
|
43
|
+
* `emit` is injectable for tests; production uses the INFO-level `displayToolIndication`.
|
|
44
|
+
*/
|
|
45
|
+
export function createPlainToolIndication(emit = displayToolIndication) {
|
|
46
|
+
/** Streaming tool-call deltas for the CURRENT round, keyed by tool_call_chunk index. */
|
|
47
|
+
const streaming = new Map();
|
|
48
|
+
/** Completed calls awaiting their ToolMessage, keyed by tool call id. */
|
|
49
|
+
const byId = new Map();
|
|
50
|
+
const flushStreamingIntoById = () => {
|
|
51
|
+
for (const call of streaming.values()) {
|
|
52
|
+
if (call.id)
|
|
53
|
+
byId.set(call.id, call);
|
|
54
|
+
}
|
|
55
|
+
streaming.clear();
|
|
56
|
+
};
|
|
57
|
+
const renderToolMessage = (message) => {
|
|
58
|
+
const id = typeof message.tool_call_id === 'string' ? message.tool_call_id : '';
|
|
59
|
+
const tracked = id ? byId.get(id) : undefined;
|
|
60
|
+
if (id)
|
|
61
|
+
byId.delete(id);
|
|
62
|
+
const name = tracked?.name || (typeof message.name === 'string' ? message.name : '') || '';
|
|
63
|
+
const result = typeof message.content === 'string' ? message.content : JSON.stringify(message.content);
|
|
64
|
+
const isError = message.status === 'error';
|
|
65
|
+
// TUI-C35 — colour is exactly what the resolved ladder says, with no local narrowing.
|
|
66
|
+
// This used to AND in `stdout.isTTY`, which was redundant in every case but one: rung 4 of
|
|
67
|
+
// `config/colour.ts` already auto-detects from stdout's TTY status, so an unconfigured piped
|
|
68
|
+
// run is monochrome either way. The one case it changed was `FORCE_COLOR` into a pipe — which
|
|
69
|
+
// it suppressed, defeating the only thing that variable is for.
|
|
70
|
+
const colour = getUseColour();
|
|
71
|
+
const statusGlyph = isError
|
|
72
|
+
? colour
|
|
73
|
+
? '\x1b[31m✗\x1b[0m'
|
|
74
|
+
: '✗'
|
|
75
|
+
: colour
|
|
76
|
+
? '\x1b[32m✓\x1b[0m'
|
|
77
|
+
: '✓';
|
|
78
|
+
const summary = summariseToolCall(name, tracked?.argsText);
|
|
79
|
+
const summaryText = colour ? `\x1b[2m${summary}\x1b[0m` : summary;
|
|
80
|
+
const head = `${statusGlyph} ${getToolGlyph(name)} ${summaryText}`;
|
|
81
|
+
const preview = buildToolPreviewLines({
|
|
82
|
+
name,
|
|
83
|
+
argsText: tracked?.argsText,
|
|
84
|
+
result,
|
|
85
|
+
isError,
|
|
86
|
+
// Shell-shaped results stream their child output live through the channel's default
|
|
87
|
+
// sink on this surface — suppress the duplicated body, keep the status tail. TUI-C32
|
|
88
|
+
// residual c: gate on the tool NAME + shape (not shape alone), so a non-shell tool whose
|
|
89
|
+
// result merely quotes `<COMMAND_OUTPUT>` keeps its preview body instead of being suppressed.
|
|
90
|
+
liveOutputAlreadyShown: isShellShapedResult(name, result),
|
|
91
|
+
});
|
|
92
|
+
const body = preview.map((line) => INDENT + renderToolLineAnsi(line, colour));
|
|
93
|
+
// Leading newline mirrors the historical notice framing (the model text stream may have
|
|
94
|
+
// left the cursor mid-line).
|
|
95
|
+
emit(['', head, ...body].join('\n'));
|
|
96
|
+
};
|
|
97
|
+
return {
|
|
98
|
+
observe(chunk) {
|
|
99
|
+
// Order matters: AIMessageChunk extends AIMessage, so test the chunk shape first
|
|
100
|
+
// (mirrors processEventStream).
|
|
101
|
+
if (AIMessageChunk.isInstance(chunk)) {
|
|
102
|
+
// TUI-C32 residual e — fail-soft, matching the ToolMessage branch: accumulating tool-call
|
|
103
|
+
// deltas (`JSON.stringify(tc.args)` can throw on an unserialisable arg, e.g. a BigInt) must
|
|
104
|
+
// never break the run's stream loop. On any error we simply skip this chunk's tracking.
|
|
105
|
+
try {
|
|
106
|
+
const c = chunk;
|
|
107
|
+
const deltas = c.tool_call_chunks ?? [];
|
|
108
|
+
if (deltas.length > 0) {
|
|
109
|
+
for (const delta of deltas) {
|
|
110
|
+
const index = typeof delta.index === 'number' ? delta.index : 0;
|
|
111
|
+
const entry = streaming.get(index) ?? { name: '', argsText: '' };
|
|
112
|
+
if (delta.id)
|
|
113
|
+
entry.id = delta.id;
|
|
114
|
+
if (delta.name)
|
|
115
|
+
entry.name = entry.name || delta.name;
|
|
116
|
+
if (delta.args)
|
|
117
|
+
entry.argsText += delta.args;
|
|
118
|
+
streaming.set(index, entry);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
// Some providers surface COMPLETE tool_calls on a chunk instead of deltas.
|
|
123
|
+
for (const tc of c.tool_calls ?? []) {
|
|
124
|
+
if (tc.id) {
|
|
125
|
+
byId.set(tc.id, {
|
|
126
|
+
id: tc.id,
|
|
127
|
+
name: tc.name,
|
|
128
|
+
argsText: JSON.stringify(tc.args ?? {}),
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
/* indication is best-effort; the model-facing stream is untouched */
|
|
136
|
+
}
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (AIMessage.isInstance(chunk)) {
|
|
140
|
+
// A non-chunk AIMessage (resumed/checkpoint-replayed runs) carries final tool_calls.
|
|
141
|
+
// TUI-C32 residual e — same fail-soft wrap as above/the ToolMessage branch.
|
|
142
|
+
try {
|
|
143
|
+
const m = chunk;
|
|
144
|
+
for (const tc of m.tool_calls ?? []) {
|
|
145
|
+
if (tc.id) {
|
|
146
|
+
byId.set(tc.id, {
|
|
147
|
+
id: tc.id,
|
|
148
|
+
name: tc.name,
|
|
149
|
+
argsText: JSON.stringify(tc.args ?? {}),
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
/* indication is best-effort; the model-facing stream is untouched */
|
|
156
|
+
}
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (chunk instanceof ToolMessage) {
|
|
160
|
+
// The round is over: park any streamed calls under their ids (chunk indexes restart
|
|
161
|
+
// next round), then render the arrived result. Fail-soft — rendering must never break
|
|
162
|
+
// the run.
|
|
163
|
+
try {
|
|
164
|
+
flushStreamingIntoById();
|
|
165
|
+
renderToolMessage(chunk);
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
/* indication is best-effort; the model-facing stream is untouched */
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=plainToolIndication.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plainToolIndication.js","sourceRoot":"","sources":["../../src/core/plainToolIndication.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* CFG-33 — the bridge between a provider's reasoning SHAPE and gaunt-sloth's reasoning channel, for
|
|
4
|
+
* the providers that carry thinking INSIDE `content` rather than beside it.
|
|
5
|
+
*
|
|
6
|
+
* Most providers hand thinking over out-of-band, in `additional_kwargs.reasoning_content` (or
|
|
7
|
+
* `reasoning`), which {@link pickReasoningDelta} in `GthAbstractAgent` reads. Google Gemini does not:
|
|
8
|
+
* a thought summary arrives as a content BLOCK marked `thought: true` and typed exactly like an
|
|
9
|
+
* answer block (`type: 'text'`). Two consequences follow, and the second is the dangerous one:
|
|
10
|
+
*
|
|
11
|
+
* 1. `additional_kwargs` carries nothing, so the reasoning channel stays empty — the `/reasoning`
|
|
12
|
+
* panel shows nothing at all on `google-genai`/`vertexai`.
|
|
13
|
+
* 2. `BaseMessage.text` maps every `type: 'text'` block, so the thought summary is folded into the
|
|
14
|
+
* ANSWER — it would print inline in the answer on every surface, and into `writeOutputToFile`.
|
|
15
|
+
*
|
|
16
|
+
* These helpers classify content once, so each consumer can take the half it wants. They are pure,
|
|
17
|
+
* shape-driven and provider-agnostic: content that carries no reasoning block is passed through with
|
|
18
|
+
* byte-identical results (and, for {@link stripReasoningBlocks}, the very same array reference), so
|
|
19
|
+
* no other provider's rendering changes.
|
|
20
|
+
*/
|
|
21
|
+
/** One classified slice of assistant output: answer prose vs. the model's thinking. */
|
|
22
|
+
export type ThinkSegment = {
|
|
23
|
+
kind: 'answer' | 'reasoning';
|
|
24
|
+
text: string;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Is this content block the model's THINKING rather than its answer?
|
|
28
|
+
*
|
|
29
|
+
* Deliberately narrow: ONLY Gemini's `thought: true` marker on an otherwise ordinary text block.
|
|
30
|
+
* Providers whose thinking arrives as a distinct block TYPE (Anthropic's `thinking` blocks) are NOT
|
|
31
|
+
* matched here — `BaseMessage.text` already excludes them from the answer, and their reasoning
|
|
32
|
+
* already reaches the channel via `additional_kwargs.reasoning_content`, so matching them too would
|
|
33
|
+
* emit that provider's thinking TWICE.
|
|
34
|
+
*/
|
|
35
|
+
export declare function isReasoningContentBlock(block: unknown): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Classify an assistant message's `content` into ORDERED answer/reasoning segments, preserving the
|
|
38
|
+
* order the model emitted them in (a thought summary that precedes the answer stays before it).
|
|
39
|
+
*
|
|
40
|
+
* The concatenation of the `answer` segments is EXACTLY what `BaseMessage.text` returns once the
|
|
41
|
+
* reasoning blocks are removed — the same block rules, the same empty join — so for content that
|
|
42
|
+
* carries no reasoning block this is a drop-in for `.text`.
|
|
43
|
+
*/
|
|
44
|
+
export declare function segmentAssistantContent(content: unknown): ThinkSegment[];
|
|
45
|
+
/**
|
|
46
|
+
* The ANSWER text of an assistant message's content — what `BaseMessage.text` would return minus any
|
|
47
|
+
* reasoning block. Used by the surfaces that render answer text directly and have never shown
|
|
48
|
+
* reasoning (the plain console stream), so a thought summary cannot leak into the answer there.
|
|
49
|
+
*/
|
|
50
|
+
export declare function answerTextOf(content: unknown): string;
|
|
51
|
+
/**
|
|
52
|
+
* The same content with its reasoning blocks removed, for consumers that render the block ARRAY
|
|
53
|
+
* rather than its text (`renderAssistantContent` / `materializeBinaryOutputs` on the non-streaming
|
|
54
|
+
* path). Returns the INPUT REFERENCE unchanged whenever nothing matched, so every other provider's
|
|
55
|
+
* content object is untouched and identity comparisons still hold.
|
|
56
|
+
*
|
|
57
|
+
* A pure content transform with no opinion about graph state — what a caller does with the result is
|
|
58
|
+
* the caller's argument to make. Every caller in this package only READS the result — rendering it,
|
|
59
|
+
* or pulling a refusal's explanation out of it — so the message kept in state stays whole and its
|
|
60
|
+
* thought parts (and any `thoughtSignature` riding with them) still replay to the provider as
|
|
61
|
+
* history. The one caller that rewrites state instead is the subagent thought
|
|
62
|
+
* redaction in `@gaunt-sloth/agent`, which strips a FINISHED subagent's own messages; why that costs
|
|
63
|
+
* nothing is argued there.
|
|
64
|
+
*/
|
|
65
|
+
export declare function stripReasoningBlocks(content: unknown): unknown;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* CFG-33 — the bridge between a provider's reasoning SHAPE and gaunt-sloth's reasoning channel, for
|
|
4
|
+
* the providers that carry thinking INSIDE `content` rather than beside it.
|
|
5
|
+
*
|
|
6
|
+
* Most providers hand thinking over out-of-band, in `additional_kwargs.reasoning_content` (or
|
|
7
|
+
* `reasoning`), which {@link pickReasoningDelta} in `GthAbstractAgent` reads. Google Gemini does not:
|
|
8
|
+
* a thought summary arrives as a content BLOCK marked `thought: true` and typed exactly like an
|
|
9
|
+
* answer block (`type: 'text'`). Two consequences follow, and the second is the dangerous one:
|
|
10
|
+
*
|
|
11
|
+
* 1. `additional_kwargs` carries nothing, so the reasoning channel stays empty — the `/reasoning`
|
|
12
|
+
* panel shows nothing at all on `google-genai`/`vertexai`.
|
|
13
|
+
* 2. `BaseMessage.text` maps every `type: 'text'` block, so the thought summary is folded into the
|
|
14
|
+
* ANSWER — it would print inline in the answer on every surface, and into `writeOutputToFile`.
|
|
15
|
+
*
|
|
16
|
+
* These helpers classify content once, so each consumer can take the half it wants. They are pure,
|
|
17
|
+
* shape-driven and provider-agnostic: content that carries no reasoning block is passed through with
|
|
18
|
+
* byte-identical results (and, for {@link stripReasoningBlocks}, the very same array reference), so
|
|
19
|
+
* no other provider's rendering changes.
|
|
20
|
+
*/
|
|
21
|
+
function isContentBlock(value) {
|
|
22
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Is this content block the model's THINKING rather than its answer?
|
|
26
|
+
*
|
|
27
|
+
* Deliberately narrow: ONLY Gemini's `thought: true` marker on an otherwise ordinary text block.
|
|
28
|
+
* Providers whose thinking arrives as a distinct block TYPE (Anthropic's `thinking` blocks) are NOT
|
|
29
|
+
* matched here — `BaseMessage.text` already excludes them from the answer, and their reasoning
|
|
30
|
+
* already reaches the channel via `additional_kwargs.reasoning_content`, so matching them too would
|
|
31
|
+
* emit that provider's thinking TWICE.
|
|
32
|
+
*/
|
|
33
|
+
export function isReasoningContentBlock(block) {
|
|
34
|
+
return isContentBlock(block) && block.thought === true && block.type === 'text';
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Classify an assistant message's `content` into ORDERED answer/reasoning segments, preserving the
|
|
38
|
+
* order the model emitted them in (a thought summary that precedes the answer stays before it).
|
|
39
|
+
*
|
|
40
|
+
* The concatenation of the `answer` segments is EXACTLY what `BaseMessage.text` returns once the
|
|
41
|
+
* reasoning blocks are removed — the same block rules, the same empty join — so for content that
|
|
42
|
+
* carries no reasoning block this is a drop-in for `.text`.
|
|
43
|
+
*/
|
|
44
|
+
export function segmentAssistantContent(content) {
|
|
45
|
+
if (typeof content === 'string') {
|
|
46
|
+
return content.length > 0 ? [{ kind: 'answer', text: content }] : [];
|
|
47
|
+
}
|
|
48
|
+
if (!Array.isArray(content)) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
const segments = [];
|
|
52
|
+
for (const block of content) {
|
|
53
|
+
if (typeof block === 'string') {
|
|
54
|
+
if (block.length > 0)
|
|
55
|
+
segments.push({ kind: 'answer', text: block });
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (!isContentBlock(block))
|
|
59
|
+
continue;
|
|
60
|
+
const text = typeof block.text === 'string' ? block.text : '';
|
|
61
|
+
if (text.length === 0)
|
|
62
|
+
continue;
|
|
63
|
+
if (isReasoningContentBlock(block)) {
|
|
64
|
+
segments.push({ kind: 'reasoning', text });
|
|
65
|
+
}
|
|
66
|
+
else if (block.type === 'text') {
|
|
67
|
+
segments.push({ kind: 'answer', text });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return segments;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The ANSWER text of an assistant message's content — what `BaseMessage.text` would return minus any
|
|
74
|
+
* reasoning block. Used by the surfaces that render answer text directly and have never shown
|
|
75
|
+
* reasoning (the plain console stream), so a thought summary cannot leak into the answer there.
|
|
76
|
+
*/
|
|
77
|
+
export function answerTextOf(content) {
|
|
78
|
+
return segmentAssistantContent(content)
|
|
79
|
+
.filter((segment) => segment.kind === 'answer')
|
|
80
|
+
.map((segment) => segment.text)
|
|
81
|
+
.join('');
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The same content with its reasoning blocks removed, for consumers that render the block ARRAY
|
|
85
|
+
* rather than its text (`renderAssistantContent` / `materializeBinaryOutputs` on the non-streaming
|
|
86
|
+
* path). Returns the INPUT REFERENCE unchanged whenever nothing matched, so every other provider's
|
|
87
|
+
* content object is untouched and identity comparisons still hold.
|
|
88
|
+
*
|
|
89
|
+
* A pure content transform with no opinion about graph state — what a caller does with the result is
|
|
90
|
+
* the caller's argument to make. Every caller in this package only READS the result — rendering it,
|
|
91
|
+
* or pulling a refusal's explanation out of it — so the message kept in state stays whole and its
|
|
92
|
+
* thought parts (and any `thoughtSignature` riding with them) still replay to the provider as
|
|
93
|
+
* history. The one caller that rewrites state instead is the subagent thought
|
|
94
|
+
* redaction in `@gaunt-sloth/agent`, which strips a FINISHED subagent's own messages; why that costs
|
|
95
|
+
* nothing is argued there.
|
|
96
|
+
*/
|
|
97
|
+
export function stripReasoningBlocks(content) {
|
|
98
|
+
if (!Array.isArray(content))
|
|
99
|
+
return content;
|
|
100
|
+
const kept = content.filter((block) => !isReasoningContentBlock(block));
|
|
101
|
+
return kept.length === content.length ? content : kept;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=reasoningBlocks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reasoningBlocks.js","sourceRoot":"","sources":["../../src/core/reasoningBlocks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAOH,SAAS,cAAc,CAAC,KAAc;IACpC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAAc;IACpD,OAAO,cAAc,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,CAAC;AAClF,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAAgB;IACtD,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAChC,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACvE,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,QAAQ,GAAmB,EAAE,CAAC;IACpC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;gBAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YACrE,SAAS;QACX,CAAC;QACD,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC;YAAE,SAAS;QACrC,MAAM,IAAI,GAAG,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9D,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QAChC,IAAI,uBAAuB,CAAC,KAAK,CAAC,EAAE,CAAC;YACnC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7C,CAAC;aAAM,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YACjC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,OAAgB;IAC3C,OAAO,uBAAuB,CAAC,OAAO,CAAC;SACpC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,QAAQ,CAAC;SAC9C,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;SAC9B,IAAI,CAAC,EAAE,CAAC,CAAC;AACd,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAgB;IACnD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QAAE,OAAO,OAAO,CAAC;IAC5C,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,uBAAuB,CAAC,KAAK,CAAC,CAAC,CAAC;IACxE,OAAO,IAAI,CAAC,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;AACzD,CAAC"}
|
|
@@ -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,133 @@
|
|
|
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
|
+
import { stripReasoningBlocks } from '#src/core/reasoningBlocks.js';
|
|
20
|
+
/** Read a nested record field defensively (returns undefined for non-objects / missing keys). */
|
|
21
|
+
function readField(source, key) {
|
|
22
|
+
if (!source || typeof source !== 'object')
|
|
23
|
+
return undefined;
|
|
24
|
+
return source[key];
|
|
25
|
+
}
|
|
26
|
+
/** Pull any human-readable explanation the refusal carried (message content, then reasoning). */
|
|
27
|
+
function extractRefusalText(message) {
|
|
28
|
+
const content = readField(message, 'content');
|
|
29
|
+
if (typeof content === 'string' && content.trim().length > 0)
|
|
30
|
+
return content.trim();
|
|
31
|
+
// Content-block arrays (Anthropic / Bedrock): concatenate any text parts. CFG-33 — minus the
|
|
32
|
+
// model's own thinking: Gemini marks a thought summary `thought: true` and types it exactly like
|
|
33
|
+
// an answer part, so without this it would be pasted verbatim into the refusal notice as the
|
|
34
|
+
// model's "explanation".
|
|
35
|
+
if (Array.isArray(content)) {
|
|
36
|
+
const text = stripReasoningBlocks(content)
|
|
37
|
+
.map((part) => {
|
|
38
|
+
if (typeof part === 'string')
|
|
39
|
+
return part;
|
|
40
|
+
const t = readField(part, 'text');
|
|
41
|
+
return typeof t === 'string' ? t : '';
|
|
42
|
+
})
|
|
43
|
+
.join('')
|
|
44
|
+
.trim();
|
|
45
|
+
if (text.length > 0)
|
|
46
|
+
return text;
|
|
47
|
+
}
|
|
48
|
+
// Some refusals put the explanation only in the reasoning channel.
|
|
49
|
+
const kwargs = readField(message, 'additional_kwargs');
|
|
50
|
+
const reasoning = readField(kwargs, 'reasoning_content');
|
|
51
|
+
if (typeof reasoning === 'string' && reasoning.trim().length > 0)
|
|
52
|
+
return reasoning.trim();
|
|
53
|
+
// Anthropic exposes the declined text on a dedicated `refusal` field in some SDK shapes.
|
|
54
|
+
const refusalField = readField(kwargs, 'refusal') ?? readField(message, 'refusal');
|
|
55
|
+
if (typeof refusalField === 'string' && refusalField.trim().length > 0)
|
|
56
|
+
return refusalField.trim();
|
|
57
|
+
return '';
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Inspect a finished model message (an `AIMessage` / `AIMessageChunk`, or any object exposing
|
|
61
|
+
* `response_metadata` / `additional_kwargs`) and return a {@link RefusalInfo} when its stop/finish
|
|
62
|
+
* reason indicates a content-policy refusal, else `null`. Defensive: any non-message / unexpected
|
|
63
|
+
* shape yields `null`, so a normal turn is never mistaken for a refusal.
|
|
64
|
+
*
|
|
65
|
+
* Covered shapes:
|
|
66
|
+
* - OpenAI-family `finish_reason: 'content_filter'` (also under `additional_kwargs`).
|
|
67
|
+
* - Anthropic `stop_reason: 'refusal'`.
|
|
68
|
+
* - Bedrock Converse guardrail intervention: `stopReason`/`stop_reason`/`finish_reason` ===
|
|
69
|
+
* `'guardrail_intervened'`, or `additional_kwargs['amazon-bedrock-guardrailAction'] ===
|
|
70
|
+
* `'INTERVENED'`.
|
|
71
|
+
* - Bedrock Converse content filter: `stopReason`/`stop_reason`/`finish_reason` ===
|
|
72
|
+
* `'content_filtered'` (EXT-41 — a distinct `StopReason` enum value from `guardrail_intervened`
|
|
73
|
+
* that was previously mapped to `null`, i.e. a silent empty turn / false negative).
|
|
74
|
+
*/
|
|
75
|
+
export function detectRefusal(message) {
|
|
76
|
+
if (!message || typeof message !== 'object')
|
|
77
|
+
return null;
|
|
78
|
+
const meta = readField(message, 'response_metadata');
|
|
79
|
+
const kwargs = readField(message, 'additional_kwargs');
|
|
80
|
+
// Gather the stop/finish reason from every place providers surface it.
|
|
81
|
+
const finishReason = readField(meta, 'finish_reason') ?? readField(kwargs, 'finish_reason') ?? undefined;
|
|
82
|
+
const stopReasonSnake = readField(meta, 'stop_reason') ?? readField(kwargs, 'stop_reason') ?? undefined;
|
|
83
|
+
const stopReasonCamel = readField(meta, 'stopReason') ?? readField(kwargs, 'stopReason') ?? undefined;
|
|
84
|
+
const asString = (v) => (typeof v === 'string' ? v : undefined);
|
|
85
|
+
const finish = asString(finishReason);
|
|
86
|
+
const stopSnake = asString(stopReasonSnake);
|
|
87
|
+
const stopCamel = asString(stopReasonCamel);
|
|
88
|
+
const explanation = extractRefusalText(message);
|
|
89
|
+
// OpenAI-family content filter.
|
|
90
|
+
if (finish === 'content_filter') {
|
|
91
|
+
return { provider: 'openai', reason: 'content_filter', explanation };
|
|
92
|
+
}
|
|
93
|
+
// Anthropic refusal stop reason.
|
|
94
|
+
if (stopSnake === 'refusal' || stopCamel === 'refusal') {
|
|
95
|
+
return { provider: 'anthropic', reason: 'refusal', explanation };
|
|
96
|
+
}
|
|
97
|
+
// Bedrock Converse guardrail intervention (camelCase `stopReason`, or snake / finish variants).
|
|
98
|
+
if (stopCamel === 'guardrail_intervened' ||
|
|
99
|
+
stopSnake === 'guardrail_intervened' ||
|
|
100
|
+
finish === 'guardrail_intervened' ||
|
|
101
|
+
readField(kwargs, 'amazon-bedrock-guardrailAction') === 'INTERVENED' ||
|
|
102
|
+
readField(meta, 'amazon-bedrock-guardrailAction') === 'INTERVENED') {
|
|
103
|
+
return { provider: 'bedrock', reason: 'guardrail_intervened', explanation };
|
|
104
|
+
}
|
|
105
|
+
// EXT-41 — Bedrock Converse content filter. A distinct `StopReason` enum value from
|
|
106
|
+
// `guardrail_intervened` (both live in the same AWS Converse `StopReason` enum); previously
|
|
107
|
+
// unmapped, so a content-filtered turn returned `null` → the silent empty-turn false negative.
|
|
108
|
+
if (stopCamel === 'content_filtered' ||
|
|
109
|
+
stopSnake === 'content_filtered' ||
|
|
110
|
+
finish === 'content_filtered') {
|
|
111
|
+
return { provider: 'bedrock', reason: 'content_filtered', explanation };
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Build the clear, user-facing message shown when the model declines. Framed as the model /
|
|
117
|
+
* provider's own policy decision (not a Gaunt Sloth fault) and stated as terminal — a refusal is
|
|
118
|
+
* deterministic for the same input, so retrying as-is will not help. Any model-provided explanation
|
|
119
|
+
* is included verbatim. This string is BOTH surfaced to the console and RETURNED as the turn's
|
|
120
|
+
* answer, so the non-interactive caller writes it to the output file and exits `ok` (it is a
|
|
121
|
+
* successful, if declined, response — not a failure to be re-wrapped as "Failed to get answer").
|
|
122
|
+
*/
|
|
123
|
+
export function buildRefusalMessage(info) {
|
|
124
|
+
const head = 'The model declined to respond (safety refusal / content filter) — this is the ' +
|
|
125
|
+
"model/provider's own policy decision, not a Gaunt Sloth error.";
|
|
126
|
+
const detail = info.explanation
|
|
127
|
+
? `Model's explanation: ${info.explanation}`
|
|
128
|
+
: 'The model provided no explanation.';
|
|
129
|
+
const hint = 'A refusal is deterministic for the same input — rephrase the request or try a different ' +
|
|
130
|
+
'model rather than re-running it as-is.';
|
|
131
|
+
return `${head}\n\n${detail}\n\n${hint}`;
|
|
132
|
+
}
|
|
133
|
+
//# 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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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module runHeader
|
|
3
|
+
* GS2-95 — the ONE spelling of the run header, the single line every command opens with.
|
|
4
|
+
*
|
|
5
|
+
* Two unrelated writers render it: the agent's `compact` rung
|
|
6
|
+
* (`GthAbstractAgent#compactHeaderStatus`) and the review document's opening line
|
|
7
|
+
* (`@gaunt-sloth/review`'s `reviewHeadingBlock`). They emit at different moments, through different
|
|
8
|
+
* helpers, into different sinks — so the only thing that can keep them saying the same thing is a
|
|
9
|
+
* shared builder rather than two templates that happen to match today.
|
|
10
|
+
*
|
|
11
|
+
* It is a leaf with **no imports at all**, for the reason `modelLabel` is: the agent reaches it
|
|
12
|
+
* during module load, and pulling console/filesystem helpers in to format one line would drag that
|
|
13
|
+
* graph along.
|
|
14
|
+
*
|
|
15
|
+
* The header carries no markdown, in any mode. The honest condition for a `##` prefix is not "this
|
|
16
|
+
* run is under GitHub Actions" but "the consumer renders markdown", and those differ in both
|
|
17
|
+
* directions — the Ink TUI renders markdown, a GHA *job log* does not while a GHA *step summary*
|
|
18
|
+
* does. That property belongs to the SINK, and one run has several at once (the TUI on screen, a
|
|
19
|
+
* posted PR comment, a session log on disk), so no single flag can be right for all of them.
|
|
20
|
+
* Formatting a header is not worth a per-sink capability, so there is no prefix anywhere.
|
|
21
|
+
*/
|
|
22
|
+
/** The product name the header opens with — the part a reader is meant to recognise across runs. */
|
|
23
|
+
export declare const RUN_HEADER_NAME = "Gaunt Sloth";
|
|
24
|
+
/** Separates the header's parts: U+00B7 MIDDLE DOT, padded with a space on each side. */
|
|
25
|
+
export declare const RUN_HEADER_SEPARATOR = " \u00B7 ";
|
|
26
|
+
/**
|
|
27
|
+
* Build the run header: `Gaunt Sloth · <command> · <model> (<provider>)`.
|
|
28
|
+
*
|
|
29
|
+
* `command` is the name of the command the USER typed (`review`, `exec`, `eval`), never the init
|
|
30
|
+
* verb that selects the agent's mode prompt — the two differ for every command that runs its work
|
|
31
|
+
* through another verb's prompt, and a header naming the verb misnames the run.
|
|
32
|
+
*
|
|
33
|
+
* `label` is the caller's already-resolved `model (provider)` half — `modelProviderLabel`'s one
|
|
34
|
+
* spelling of "which model served this run" (DL-6). It is resolved by the caller rather than here
|
|
35
|
+
* because the two writers gate it differently, and it is **dropped rather than faked** when nothing
|
|
36
|
+
* resolves (DL-7): the line then ends after the command, with no placeholder and no empty `()`.
|
|
37
|
+
*/
|
|
38
|
+
export declare function runHeaderLine(command: string, label?: string): string;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module runHeader
|
|
3
|
+
* GS2-95 — the ONE spelling of the run header, the single line every command opens with.
|
|
4
|
+
*
|
|
5
|
+
* Two unrelated writers render it: the agent's `compact` rung
|
|
6
|
+
* (`GthAbstractAgent#compactHeaderStatus`) and the review document's opening line
|
|
7
|
+
* (`@gaunt-sloth/review`'s `reviewHeadingBlock`). They emit at different moments, through different
|
|
8
|
+
* helpers, into different sinks — so the only thing that can keep them saying the same thing is a
|
|
9
|
+
* shared builder rather than two templates that happen to match today.
|
|
10
|
+
*
|
|
11
|
+
* It is a leaf with **no imports at all**, for the reason `modelLabel` is: the agent reaches it
|
|
12
|
+
* during module load, and pulling console/filesystem helpers in to format one line would drag that
|
|
13
|
+
* graph along.
|
|
14
|
+
*
|
|
15
|
+
* The header carries no markdown, in any mode. The honest condition for a `##` prefix is not "this
|
|
16
|
+
* run is under GitHub Actions" but "the consumer renders markdown", and those differ in both
|
|
17
|
+
* directions — the Ink TUI renders markdown, a GHA *job log* does not while a GHA *step summary*
|
|
18
|
+
* does. That property belongs to the SINK, and one run has several at once (the TUI on screen, a
|
|
19
|
+
* posted PR comment, a session log on disk), so no single flag can be right for all of them.
|
|
20
|
+
* Formatting a header is not worth a per-sink capability, so there is no prefix anywhere.
|
|
21
|
+
*/
|
|
22
|
+
/** The product name the header opens with — the part a reader is meant to recognise across runs. */
|
|
23
|
+
export const RUN_HEADER_NAME = 'Gaunt Sloth';
|
|
24
|
+
/** Separates the header's parts: U+00B7 MIDDLE DOT, padded with a space on each side. */
|
|
25
|
+
export const RUN_HEADER_SEPARATOR = ' · ';
|
|
26
|
+
/**
|
|
27
|
+
* Build the run header: `Gaunt Sloth · <command> · <model> (<provider>)`.
|
|
28
|
+
*
|
|
29
|
+
* `command` is the name of the command the USER typed (`review`, `exec`, `eval`), never the init
|
|
30
|
+
* verb that selects the agent's mode prompt — the two differ for every command that runs its work
|
|
31
|
+
* through another verb's prompt, and a header naming the verb misnames the run.
|
|
32
|
+
*
|
|
33
|
+
* `label` is the caller's already-resolved `model (provider)` half — `modelProviderLabel`'s one
|
|
34
|
+
* spelling of "which model served this run" (DL-6). It is resolved by the caller rather than here
|
|
35
|
+
* because the two writers gate it differently, and it is **dropped rather than faked** when nothing
|
|
36
|
+
* resolves (DL-7): the line then ends after the command, with no placeholder and no empty `()`.
|
|
37
|
+
*/
|
|
38
|
+
export function runHeaderLine(command, label) {
|
|
39
|
+
const head = RUN_HEADER_NAME + RUN_HEADER_SEPARATOR + command;
|
|
40
|
+
return label ? head + RUN_HEADER_SEPARATOR + label : head;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=runHeader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runHeader.js","sourceRoot":"","sources":["../../src/core/runHeader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,oGAAoG;AACpG,MAAM,CAAC,MAAM,eAAe,GAAG,aAAa,CAAC;AAE7C,yFAAyF;AACzF,MAAM,CAAC,MAAM,oBAAoB,GAAG,KAAK,CAAC;AAE1C;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,aAAa,CAAC,OAAe,EAAE,KAAc;IAC3D,MAAM,IAAI,GAAG,eAAe,GAAG,oBAAoB,GAAG,OAAO,CAAC;IAC9D,OAAO,KAAK,CAAC,CAAC,CAAC,IAAI,GAAG,oBAAoB,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AAC5D,CAAC"}
|