@gaunt-sloth/core 2.0.0-beta.3 → 2.0.0-beta.4

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.
@@ -1,5 +1,15 @@
1
1
  /**
2
- * EXT-37 — content-policy refusal detection for the agent run loop.
2
+ * Stop/finish-reason detection for the agent run loop — the **metadata feeder** of the
3
+ * [[EXT-159]] termination taxonomy.
4
+ *
5
+ * This module is the one reader of a message's stop/finish reason. It normalizes the per-provider
6
+ * spellings into two shapes: {@link detectRefusal}, whose {@link RefusalInfo} also drives the
7
+ * user-facing notice, and {@link detectStopMetadata}, which classifies the same metadata into the
8
+ * shared taxonomy for every consumer that only needs to know *why the turn ended*. There is
9
+ * deliberately no second reader of `response_metadata` beside it.
10
+ *
11
+ * Its counterpart is the exception classifier in `core/terminationReason.ts`: a reason that arrives
12
+ * as a thrown error is not in `response_metadata` at all, so nothing here can see it.
3
13
  *
4
14
  * A *successful* model response (HTTP 200) can carry a stop/finish reason that means the model — or
5
15
  * the provider's safety system — declined to answer. The content is usually empty, so without this
@@ -112,6 +122,104 @@ export function detectRefusal(message) {
112
122
  }
113
123
  return null;
114
124
  }
125
+ /**
126
+ * The provider spellings of "the answer hit the output cap", lower-cased.
127
+ *
128
+ * The same four places {@link detectRefusal} reads carry this one too, spelled per family: OpenAI's
129
+ * `finish_reason: 'length'`, Anthropic's and Bedrock's `stop_reason: 'max_tokens'`, Gemini's
130
+ * `finishReason: 'MAX_TOKENS'`, Ollama's `done_reason: 'length'`. LangChain normalizes none of it.
131
+ *
132
+ * Unlike a refusal, a truncation carries **no provider**: the token does not identify one. Both
133
+ * Anthropic and Gemini spell it `max_tokens` once case is normalised, and both OpenAI and Ollama
134
+ * spell it `length` — so naming a family from the token would be a guess stated as a fact. The raw
135
+ * token goes in `detail`, which is what is actually known.
136
+ */
137
+ const TRUNCATION_REASONS = new Set([
138
+ 'length',
139
+ 'max_tokens',
140
+ 'maxtokens',
141
+ 'model_length',
142
+ ]);
143
+ /**
144
+ * Every stop/finish reason token a message carries, lower-cased, from both `response_metadata` and
145
+ * `additional_kwargs` and in both snake and camel case — the same four places {@link detectRefusal}
146
+ * looks, so the two detections can never come to disagree about where the metadata lives.
147
+ */
148
+ function stopReasonTokens(message) {
149
+ const meta = readField(message, 'response_metadata');
150
+ const kwargs = readField(message, 'additional_kwargs');
151
+ const tokens = [];
152
+ for (const key of ['finish_reason', 'finishReason', 'stop_reason', 'stopReason', 'done_reason']) {
153
+ for (const source of [meta, kwargs, message]) {
154
+ const value = readField(source, key);
155
+ if (typeof value === 'string' && value.length > 0)
156
+ tokens.push(value.toLowerCase());
157
+ }
158
+ }
159
+ return tokens;
160
+ }
161
+ /**
162
+ * [[EXT-159]] — the provider's own stop/finish reason for a finished message, or `null` when the
163
+ * message carried none.
164
+ *
165
+ * **`null` is a recorded fact, not a missing one.** No `finish_reason` was written to any log
166
+ * anywhere, so a turn that ended without the provider saying why was indistinguishable from one
167
+ * that ended normally. A caller records the `null` as explicitly as it records a token.
168
+ *
169
+ * Built on the SAME reader {@link detectStopMetadata} uses rather than a second one beside it: a
170
+ * separate scan of `response_metadata` is how two readers come to disagree about what the provider
171
+ * said, and the whole point of this node is that the two halves of the classification share one
172
+ * source. The first token wins, in the key order that shared reader walks, and it is lower-cased
173
+ * there — this is diagnostic detail, never the carrier of a classification.
174
+ */
175
+ export function readStopReasonToken(message) {
176
+ if (!message || typeof message !== 'object')
177
+ return null;
178
+ return stopReasonTokens(message)[0] ?? null;
179
+ }
180
+ /**
181
+ * Inspect a finished model message and return an output-truncation classification when its
182
+ * stop/finish reason says the answer hit the output cap, else `null`.
183
+ *
184
+ * A truncated answer is not a refused one and not an error: the turn returns a *successful*
185
+ * response that simply stops mid-thought. Nothing is surfaced from here — the classification exists
186
+ * so a turn that ended this way is not indistinguishable from one the model finished.
187
+ */
188
+ export function detectOutputTruncation(message) {
189
+ if (!message || typeof message !== 'object')
190
+ return null;
191
+ for (const token of stopReasonTokens(message)) {
192
+ if (TRUNCATION_REASONS.has(token)) {
193
+ return { category: 'output_truncated', detail: token };
194
+ }
195
+ }
196
+ return null;
197
+ }
198
+ /**
199
+ * The metadata feeder, as one call: classify a finished model message's stop/finish reason into the
200
+ * [[EXT-159]] taxonomy, or `null` when it says nothing worth recording.
201
+ *
202
+ * `null` for an ordinary end is deliberate. Only a *terminal and interesting* reason is reported —
203
+ * a refusal or a truncation — because every other end of a turn is classified by the site that
204
+ * actually ends it, and a reader that reported `completed` off a mid-turn tool round would pin the
205
+ * wrong reason before the real one happened.
206
+ */
207
+ export function detectStopMetadata(message) {
208
+ const refusal = detectRefusal(message);
209
+ if (refusal)
210
+ return classifyRefusal(refusal);
211
+ return detectOutputTruncation(message);
212
+ }
213
+ /**
214
+ * The taxonomy classification a {@link RefusalInfo} stands for.
215
+ *
216
+ * Exported so a call site that already holds the detected {@link RefusalInfo} — because it needs
217
+ * the explanation text for the user-facing notice — records the same classification
218
+ * {@link detectStopMetadata} would, rather than re-deriving the mapping beside it.
219
+ */
220
+ export function classifyRefusal(info) {
221
+ return { category: 'content_refusal', provider: info.provider, detail: info.reason };
222
+ }
115
223
  /**
116
224
  * Build the clear, user-facing message shown when the model declines. Framed as the model /
117
225
  * provider's own policy decision (not a Gaunt Sloth fault) and stated as terminal — a refusal is
@@ -1 +1 @@
1
- {"version":3,"file":"refusal.js","sourceRoot":"","sources":["../../src/core/refusal.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"refusal.js","sourceRoot":"","sources":["../../src/core/refusal.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * [[EXT-159]] — getting the typed termination reason to the person looking at the screen.
4
+ *
5
+ * `core/terminationReason.ts` decides *what* ended a run. This module decides *how that fact is
6
+ * said*, once, for every surface — the Ink TUI, the readline session, the non-interactive verbs,
7
+ * ACP and AG-UI — so a run states why it ended in the same vocabulary wherever it is being watched.
8
+ *
9
+ * **Recording it better is not the deliverable.** Most people never make a debug dump; the ones
10
+ * most likely to hit a silent stop have already quit, taking the live session state with them; and
11
+ * reading a dump is a skill that fails even for a maintainer holding one. So the reason has to
12
+ * reach the SESSION, at the moment it happens, on the surface in front of the user.
13
+ *
14
+ * **The prose is never the carrier.** Every surface commits the {@link GthTerminationReason} value
15
+ * itself and derives the words from it here, so a test — and a consumer — reads the classification
16
+ * structurally instead of matching a sentence. That is also why {@link terminationCode} exists: a
17
+ * user quoting one short token in a bug report hands a maintainer the fact that makes the report
18
+ * tractable, which is precisely what "the agent just stopped" could not.
19
+ *
20
+ * **An ABSENT reason is a signal, not a blank to fill.** The taxonomy's contract is that a turn
21
+ * with no reason is a site nobody classified, which is why an ordinary completion is recorded
22
+ * rather than left empty. Nothing here invents a category, a placeholder or a default for `null`:
23
+ * {@link terminationLogLine} states the absence as an absence, and
24
+ * {@link displayTermination} says nothing to the user, because "we did not classify this" is a
25
+ * defect report aimed at us and not an explanation aimed at them.
26
+ */
27
+ import type { GthTerminationReason } from '#src/core/terminationReason.js';
28
+ /**
29
+ * The opening of every termination notice title.
30
+ *
31
+ * Exported because two surfaces put the notice into a channel that also carries other messages —
32
+ * ACP's `agent_message`, where the agent speaks about the session — so something has to be able to
33
+ * tell one from the other without transcribing the wording. A consumer keying on this constant
34
+ * moves with the copy; one keying on a copy of the string does not, and finds out when the two have
35
+ * silently disagreed for a while.
36
+ */
37
+ export declare const TERMINATION_NOTICE_TITLE_PREFIX = "Run ended: ";
38
+ /** A termination reason, rendered for a surface that shows a title and body lines. */
39
+ export interface GthTerminationNotice {
40
+ /**
41
+ * **The carrier.** The classification travels as this value; the strings below are derived from
42
+ * it and are never the only place it exists.
43
+ */
44
+ reason: GthTerminationReason;
45
+ /** The one line that says what happened. */
46
+ title: string;
47
+ /** The supporting lines: the quotable code, what was seen, and what may help. */
48
+ lines: string[];
49
+ }
50
+ /**
51
+ * The short token a user can quote and a maintainer can act on: the category and the site that
52
+ * classified it.
53
+ *
54
+ * Both halves, always. Several sites share a category and several categories reach one site, so
55
+ * either alone loses the fact that separates two failures which look identical on screen — the
56
+ * standing example being an `empty_response` whose as-is retry has already been spent at
57
+ * `runner.empty-after-fallback` and one where it has not been spent at all.
58
+ */
59
+ export declare function terminationCode(reason: GthTerminationReason): string;
60
+ /**
61
+ * Is this reason worth telling the person watching?
62
+ *
63
+ * Two categories are not. `completed` is the ordinary end of a turn, and announcing it would put a
64
+ * line under every successful answer. `suspended` is a run that has PAUSED rather than ended — the
65
+ * graph is parked on a tool-approval interrupt and about to continue — so announcing it would
66
+ * report the middle of a working turn as its end.
67
+ *
68
+ * Everything else is announced, deliberately including the ones that look self-explanatory.
69
+ * `cancelled` is announced because a turn cancelled by a stray escape sequence is exactly the shape
70
+ * that gets misattributed to the provider for months ([[TUI-C62]]), and a user who did not knowingly
71
+ * press anything is owed the fact that a cancellation is what happened. `approval_stop` is announced
72
+ * because the gate's own prose explains the DECISION while this states the CLASSIFICATION, which is
73
+ * the half a bug report needs.
74
+ *
75
+ * Announcing is a separate decision from recording: everything is recorded, including the
76
+ * categories this returns `false` for.
77
+ */
78
+ export declare function shouldAnnounceTermination(reason: GthTerminationReason): boolean;
79
+ /**
80
+ * Render a reason for a surface that shows a title and body lines.
81
+ *
82
+ * **The `??` on the label lookup is a runtime floor, and it takes nothing away from the build-time
83
+ * one.** That a 23rd category fails the build comes from the category-label table being a `Record`
84
+ * over the whole union, which this expression cannot weaken. What it covers is the case the type
85
+ * system never saw: a reason that arrived from outside it — read back off an ACP `_meta`, handed
86
+ * over by an embedder, or revived from a dump — whose category is a string the table has no entry
87
+ * for. Without the fallback that renders as the literal word `undefined` in the one line the user
88
+ * is being shown, which is a worse answer than "the cause was not recognised" and is exactly the
89
+ * kind of second failure this module refuses to add to a first one.
90
+ */
91
+ export declare function terminationNotice(reason: GthTerminationReason): GthTerminationNotice;
92
+ /**
93
+ * One line for the debug log, stating either the whole classification or the absence of one.
94
+ *
95
+ * `null` is the case this function exists for. A turn that ended with nothing classifying it is the
96
+ * taxonomy's own defect signal, and it has to be written down as that — never as `completed`, never
97
+ * as `unknown`, and never omitted, because a missing line is indistinguishable from a session where
98
+ * this was never reached.
99
+ */
100
+ export declare function terminationLogLine(reason: GthTerminationReason | null): string;
101
+ /**
102
+ * Say why the run ended on a console surface (the readline session and the non-interactive verbs),
103
+ * and write the same fact to the debug log either way.
104
+ *
105
+ * Returns whether anything was shown, so a caller can tell "said nothing because the turn simply
106
+ * finished" from "said nothing because nothing classified it" without re-deriving the rule.
107
+ *
108
+ * Fail-soft in the strongest sense: explaining a failure must never become a second failure, so
109
+ * every path here is wrapped and a throw is swallowed.
110
+ */
111
+ export declare function displayTermination(reason: GthTerminationReason | null): boolean;
@@ -0,0 +1,209 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * [[EXT-159]] — getting the typed termination reason to the person looking at the screen.
4
+ *
5
+ * `core/terminationReason.ts` decides *what* ended a run. This module decides *how that fact is
6
+ * said*, once, for every surface — the Ink TUI, the readline session, the non-interactive verbs,
7
+ * ACP and AG-UI — so a run states why it ended in the same vocabulary wherever it is being watched.
8
+ *
9
+ * **Recording it better is not the deliverable.** Most people never make a debug dump; the ones
10
+ * most likely to hit a silent stop have already quit, taking the live session state with them; and
11
+ * reading a dump is a skill that fails even for a maintainer holding one. So the reason has to
12
+ * reach the SESSION, at the moment it happens, on the surface in front of the user.
13
+ *
14
+ * **The prose is never the carrier.** Every surface commits the {@link GthTerminationReason} value
15
+ * itself and derives the words from it here, so a test — and a consumer — reads the classification
16
+ * structurally instead of matching a sentence. That is also why {@link terminationCode} exists: a
17
+ * user quoting one short token in a bug report hands a maintainer the fact that makes the report
18
+ * tractable, which is precisely what "the agent just stopped" could not.
19
+ *
20
+ * **An ABSENT reason is a signal, not a blank to fill.** The taxonomy's contract is that a turn
21
+ * with no reason is a site nobody classified, which is why an ordinary completion is recorded
22
+ * rather than left empty. Nothing here invents a category, a placeholder or a default for `null`:
23
+ * {@link terminationLogLine} states the absence as an absence, and
24
+ * {@link displayTermination} says nothing to the user, because "we did not classify this" is a
25
+ * defect report aimed at us and not an explanation aimed at them.
26
+ */
27
+ import { display, displayWarning } from '#src/utils/consoleUtils.js';
28
+ import { debugLog } from '#src/utils/debugUtils.js';
29
+ /**
30
+ * What each category is called in a sentence a user reads.
31
+ *
32
+ * An exhaustive `Record` rather than a `switch` with a default: a 23rd category is then a build
33
+ * failure here instead of a run that silently reports the new cause in the old words.
34
+ */
35
+ const CATEGORY_LABEL = {
36
+ completed: 'the model finished',
37
+ empty_response: 'the model returned nothing',
38
+ content_refusal: 'the model declined to answer',
39
+ output_truncated: 'the answer hit the output limit',
40
+ context_overflow: 'the conversation outgrew the model input window',
41
+ rate_limited: 'the provider rate-limited the request',
42
+ auth_failed: 'the provider rejected the credentials',
43
+ invalid_request: 'the provider rejected the request',
44
+ provider_error: 'the provider failed',
45
+ network_error: 'the connection failed',
46
+ timeout: 'a deadline elapsed',
47
+ cancelled: 'it was cancelled',
48
+ approval_stop: 'the approvals gate stopped it',
49
+ tool_error_budget: 'the tool-error budget ended the run',
50
+ tool_loop_guard: 'the tool-loop guard ended a repeating call',
51
+ interrupt_drain_guard: 'too many tool approvals in one turn',
52
+ tool_error: 'a tool failed',
53
+ suspended: 'it is waiting to be resumed',
54
+ recursion_limit: 'it hit the graph step limit',
55
+ abandoned: 'the client stopped listening',
56
+ unknown: 'the cause was not recognised',
57
+ };
58
+ /**
59
+ * What each remedy asks the user to actually do.
60
+ *
61
+ * Exhaustive for the same reason {@link CATEGORY_LABEL} is: the posture table names a remedy, and a
62
+ * remedy nobody worded is a retry hint the user never gets.
63
+ */
64
+ const REMEDY_LABEL = {
65
+ 'reduce-context': 'Send less — clear or compact the conversation, then try again.',
66
+ 'back-off': 'Wait a moment, then send the same request again.',
67
+ 'change-request': 'Try a different or narrower request.',
68
+ 'change-model': 'Try a different model.',
69
+ 'fix-credentials': 'Check the API credentials or configuration, then try again.',
70
+ resume: 'Nothing is wrong — the run is parked and can be continued where it stopped.',
71
+ };
72
+ /**
73
+ * The opening of every termination notice title.
74
+ *
75
+ * Exported because two surfaces put the notice into a channel that also carries other messages —
76
+ * ACP's `agent_message`, where the agent speaks about the session — so something has to be able to
77
+ * tell one from the other without transcribing the wording. A consumer keying on this constant
78
+ * moves with the copy; one keying on a copy of the string does not, and finds out when the two have
79
+ * silently disagreed for a while.
80
+ */
81
+ export const TERMINATION_NOTICE_TITLE_PREFIX = 'Run ended: ';
82
+ /**
83
+ * The short token a user can quote and a maintainer can act on: the category and the site that
84
+ * classified it.
85
+ *
86
+ * Both halves, always. Several sites share a category and several categories reach one site, so
87
+ * either alone loses the fact that separates two failures which look identical on screen — the
88
+ * standing example being an `empty_response` whose as-is retry has already been spent at
89
+ * `runner.empty-after-fallback` and one where it has not been spent at all.
90
+ */
91
+ export function terminationCode(reason) {
92
+ return `${reason.category}@${reason.site}`;
93
+ }
94
+ /**
95
+ * Is this reason worth telling the person watching?
96
+ *
97
+ * Two categories are not. `completed` is the ordinary end of a turn, and announcing it would put a
98
+ * line under every successful answer. `suspended` is a run that has PAUSED rather than ended — the
99
+ * graph is parked on a tool-approval interrupt and about to continue — so announcing it would
100
+ * report the middle of a working turn as its end.
101
+ *
102
+ * Everything else is announced, deliberately including the ones that look self-explanatory.
103
+ * `cancelled` is announced because a turn cancelled by a stray escape sequence is exactly the shape
104
+ * that gets misattributed to the provider for months ([[TUI-C62]]), and a user who did not knowingly
105
+ * press anything is owed the fact that a cancellation is what happened. `approval_stop` is announced
106
+ * because the gate's own prose explains the DECISION while this states the CLASSIFICATION, which is
107
+ * the half a bug report needs.
108
+ *
109
+ * Announcing is a separate decision from recording: everything is recorded, including the
110
+ * categories this returns `false` for.
111
+ */
112
+ export function shouldAnnounceTermination(reason) {
113
+ return reason.category !== 'completed' && reason.category !== 'suspended';
114
+ }
115
+ /**
116
+ * Render a reason for a surface that shows a title and body lines.
117
+ *
118
+ * **The `??` on the label lookup is a runtime floor, and it takes nothing away from the build-time
119
+ * one.** That a 23rd category fails the build comes from the category-label table being a `Record`
120
+ * over the whole union, which this expression cannot weaken. What it covers is the case the type
121
+ * system never saw: a reason that arrived from outside it — read back off an ACP `_meta`, handed
122
+ * over by an embedder, or revived from a dump — whose category is a string the table has no entry
123
+ * for. Without the fallback that renders as the literal word `undefined` in the one line the user
124
+ * is being shown, which is a worse answer than "the cause was not recognised" and is exactly the
125
+ * kind of second failure this module refuses to add to a first one.
126
+ */
127
+ export function terminationNotice(reason) {
128
+ const lines = [`Reason code: ${terminationCode(reason)}`];
129
+ const seen = [];
130
+ if (reason.provider)
131
+ seen.push(`provider ${reason.provider}`);
132
+ if (reason.detail)
133
+ seen.push(`reported ${reason.detail}`);
134
+ if (seen.length > 0)
135
+ lines.push(`What was seen: ${seen.join(', ')}.`);
136
+ lines.push(retryAdvice(reason));
137
+ return {
138
+ reason,
139
+ title: `${TERMINATION_NOTICE_TITLE_PREFIX}${CATEGORY_LABEL[reason.category] ?? CATEGORY_LABEL.unknown}`,
140
+ lines,
141
+ };
142
+ }
143
+ /**
144
+ * The retry advice for a reason, read off the posture the taxonomy already decided.
145
+ *
146
+ * Read from the reason's own posture fields rather than re-deriving one from the category, so this
147
+ * cannot come to disagree with the single posture table the taxonomy exists to keep.
148
+ */
149
+ function retryAdvice(reason) {
150
+ if (reason.retryableAsIs)
151
+ return 'Sending the same request again may work.';
152
+ if (reason.retryableAfterRemedy && reason.remedy)
153
+ return REMEDY_LABEL[reason.remedy];
154
+ return 'Sending the same request again will not help.';
155
+ }
156
+ /**
157
+ * One line for the debug log, stating either the whole classification or the absence of one.
158
+ *
159
+ * `null` is the case this function exists for. A turn that ended with nothing classifying it is the
160
+ * taxonomy's own defect signal, and it has to be written down as that — never as `completed`, never
161
+ * as `unknown`, and never omitted, because a missing line is indistinguishable from a session where
162
+ * this was never reached.
163
+ */
164
+ export function terminationLogLine(reason) {
165
+ if (!reason) {
166
+ return ('EXT-159 termination: UNCLASSIFIED — no site classified how this turn ended. ' +
167
+ 'An absent reason means a termination site we missed, not a turn that went well.');
168
+ }
169
+ const parts = [
170
+ `category=${reason.category}`,
171
+ `site=${reason.site}`,
172
+ `source=${reason.source}`,
173
+ `retryableAsIs=${reason.retryableAsIs}`,
174
+ `retryableAfterRemedy=${reason.retryableAfterRemedy}`,
175
+ ];
176
+ if (reason.remedy)
177
+ parts.push(`remedy=${reason.remedy}`);
178
+ if (reason.provider)
179
+ parts.push(`provider=${reason.provider}`);
180
+ if (reason.detail)
181
+ parts.push(`detail=${reason.detail}`);
182
+ return `EXT-159 termination: ${parts.join(' ')}`;
183
+ }
184
+ /**
185
+ * Say why the run ended on a console surface (the readline session and the non-interactive verbs),
186
+ * and write the same fact to the debug log either way.
187
+ *
188
+ * Returns whether anything was shown, so a caller can tell "said nothing because the turn simply
189
+ * finished" from "said nothing because nothing classified it" without re-deriving the rule.
190
+ *
191
+ * Fail-soft in the strongest sense: explaining a failure must never become a second failure, so
192
+ * every path here is wrapped and a throw is swallowed.
193
+ */
194
+ export function displayTermination(reason) {
195
+ try {
196
+ debugLog(terminationLogLine(reason));
197
+ if (!reason || !shouldAnnounceTermination(reason))
198
+ return false;
199
+ const notice = terminationNotice(reason);
200
+ displayWarning(notice.title);
201
+ for (const line of notice.lines)
202
+ display(` ${line}`);
203
+ return true;
204
+ }
205
+ catch {
206
+ return false;
207
+ }
208
+ }
209
+ //# sourceMappingURL=terminationNotice.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminationNotice.js","sourceRoot":"","sources":["../../src/core/terminationNotice.ts"],"names":[],"mappings":"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"}