@gaunt-sloth/core 2.0.0-beta.2 → 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.
- package/dist/config/shell-policy.d.ts +2 -2
- package/dist/config/shell-policy.js +3 -3
- package/dist/config/tool-descriptions.d.ts +5 -5
- package/dist/config/tool-descriptions.js +3 -3
- package/dist/core/GthAbstractAgent.d.ts +67 -1
- package/dist/core/GthAbstractAgent.js +248 -4
- package/dist/core/GthAbstractAgent.js.map +1 -1
- package/dist/core/GthAgentRunner.d.ts +91 -10
- package/dist/core/GthAgentRunner.js +327 -21
- package/dist/core/GthAgentRunner.js.map +1 -1
- package/dist/core/GthLangChainAgent.d.ts +3 -2
- package/dist/core/GthLangChainAgent.js +25 -5
- package/dist/core/GthLangChainAgent.js.map +1 -1
- package/dist/core/reasoningBlocks.d.ts +4 -6
- package/dist/core/reasoningBlocks.js +4 -6
- package/dist/core/reasoningBlocks.js.map +1 -1
- package/dist/core/refusal.d.ts +54 -1
- package/dist/core/refusal.js +109 -1
- package/dist/core/refusal.js.map +1 -1
- package/dist/core/shell/ShellCommandFailedError.d.ts +4 -4
- package/dist/core/shell/ShellCommandFailedError.js +4 -4
- package/dist/core/shell/openWorld.d.ts +4 -3
- package/dist/core/shell/openWorld.js +4 -3
- package/dist/core/shell/openWorld.js.map +1 -1
- package/dist/core/terminationNotice.d.ts +111 -0
- package/dist/core/terminationNotice.js +209 -0
- package/dist/core/terminationNotice.js.map +1 -0
- package/dist/core/terminationReason.d.ts +271 -0
- package/dist/core/terminationReason.js +405 -0
- package/dist/core/terminationReason.js.map +1 -0
- package/dist/core/toolDisplay.d.ts +49 -2
- package/dist/core/toolDisplay.js +137 -19
- package/dist/core/toolDisplay.js.map +1 -1
- package/dist/core/types.d.ts +33 -6
- package/dist/core/types.js.map +1 -1
- package/dist/providers/geminiSchemaSanitizer.d.ts +2 -2
- package/dist/providers/geminiSchemaSanitizer.js +2 -2
- package/dist/providers/geminiThinking.d.ts +10 -5
- package/dist/providers/geminiThinking.js +10 -5
- package/dist/providers/geminiThinking.js.map +1 -1
- package/dist/runtime/askStructured.d.ts +22 -0
- package/dist/runtime/askStructured.js +53 -0
- package/dist/runtime/askStructured.js.map +1 -1
- package/dist/runtime/conversation.d.ts +11 -0
- package/dist/runtime/conversation.js +13 -1
- package/dist/runtime/conversation.js.map +1 -1
- package/dist/runtime/singleShot.d.ts +11 -0
- package/dist/runtime/singleShot.js +21 -1
- package/dist/runtime/singleShot.js.map +1 -1
- package/dist/utils/aiignoreUtils.d.ts +6 -0
- package/dist/utils/aiignoreUtils.js +69 -1
- package/dist/utils/aiignoreUtils.js.map +1 -1
- package/dist/utils/debugDump.d.ts +54 -0
- package/dist/utils/debugDump.js +32 -0
- package/dist/utils/debugDump.js.map +1 -1
- package/dist/utils/displayWidth.d.ts +10 -5
- package/dist/utils/displayWidth.js +148 -54
- package/dist/utils/displayWidth.js.map +1 -1
- package/dist/utils/llmUtils.d.ts +1 -1
- package/dist/utils/llmUtils.js +1 -1
- package/dist/utils/systemPromptNotes.d.ts +3 -3
- package/dist/utils/systemPromptNotes.js +3 -3
- package/dist/utils/vertexaiUtils.js +100 -1
- package/dist/utils/vertexaiUtils.js.map +1 -1
- package/package.json +4 -3
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* EXT-159 — the typed reason a run ended.
|
|
4
|
+
*
|
|
5
|
+
* A run can stop for a dozen unrelated causes — a rate limit, a provider-side fault, a full context
|
|
6
|
+
* window, a content-policy refusal, the approvals gate, a tool-error budget, the user pressing Esc
|
|
7
|
+
* — and every one of them used to reach the surface as one untyped sentence. This module is the
|
|
8
|
+
* single taxonomy those causes are classified into, so the fact "why did this end" is carried as a
|
|
9
|
+
* value rather than reconstructed from prose.
|
|
10
|
+
*
|
|
11
|
+
* **Two feeders converge here, and they are not interchangeable.**
|
|
12
|
+
*
|
|
13
|
+
* - A **metadata reader** (`detectStopMetadata` in `core/refusal.ts`, called from
|
|
14
|
+
* `GthAbstractAgent`) handles reasons that arrive *on a message* — a stop/finish reason in
|
|
15
|
+
* `response_metadata` or
|
|
16
|
+
* `additional_kwargs`. It sits at that layer because that is the only place the metadata is
|
|
17
|
+
* visible.
|
|
18
|
+
* - An **exception classifier** ({@link classifyThrownTermination}, called from the runner's
|
|
19
|
+
* catches) handles reasons that arrive as a *thrown error*. Those are not in `response_metadata`
|
|
20
|
+
* at all, so no metadata reader can ever see them.
|
|
21
|
+
*
|
|
22
|
+
* Built as one metadata reader the whole thrown-error half of the class falls outside it; built as
|
|
23
|
+
* two taxonomies every consumer grows its own. Hence: two feeders, one taxonomy.
|
|
24
|
+
*
|
|
25
|
+
* **Retryability is two facts, never a boolean.** `@langchain/core` exports a typed
|
|
26
|
+
* `ContextOverflowError` that stamps itself non-retryable in its own constructor. That is right for
|
|
27
|
+
* "send the same prompt again" and exactly backwards for the remedy this cause actually has, which
|
|
28
|
+
* is to send a *smaller* one. {@link GthTerminationReason} therefore carries
|
|
29
|
+
* {@link GthTerminationReason#retryableAsIs} and
|
|
30
|
+
* {@link GthTerminationReason#retryableAfterRemedy} separately, with the remedy named.
|
|
31
|
+
*
|
|
32
|
+
* Classification only. Nothing here surfaces anything, formats anything, or changes what a run
|
|
33
|
+
* does; the user-facing strings stay where they are and keep their own wording.
|
|
34
|
+
*/
|
|
35
|
+
import { ContextOverflowError } from '@langchain/core/errors';
|
|
36
|
+
/**
|
|
37
|
+
* The single posture table.
|
|
38
|
+
*
|
|
39
|
+
* One place decides what a category means for retrying, so the three consumers this taxonomy exists
|
|
40
|
+
* for — a retry posture, a "never retry a 400, a 429 is a different case" ruling, a nudge-or-back-off
|
|
41
|
+
* decision — read the same answer instead of each deriving its own.
|
|
42
|
+
*/
|
|
43
|
+
const POSTURE = {
|
|
44
|
+
// Nothing went wrong; there is nothing to retry.
|
|
45
|
+
completed: { retryableAsIs: false, retryableAfterRemedy: false },
|
|
46
|
+
// The one cause the runtime already retries as-is, and it is right to: an empty turn is usually
|
|
47
|
+
// transient. A model that keeps returning nothing needs a different model, not another attempt.
|
|
48
|
+
empty_response: { retryableAsIs: true, retryableAfterRemedy: true, remedy: 'change-model' },
|
|
49
|
+
// A refusal is deterministic for the same input, so the same prompt refuses again.
|
|
50
|
+
content_refusal: { retryableAsIs: false, retryableAfterRemedy: true, remedy: 'change-request' },
|
|
51
|
+
// The answer was cut off, not refused: asking for less, or for a continuation, gets the rest.
|
|
52
|
+
output_truncated: { retryableAsIs: false, retryableAfterRemedy: true, remedy: 'change-request' },
|
|
53
|
+
// THE case the two fields exist for. `ContextOverflowError.getRetryable()` is false, which is
|
|
54
|
+
// right for the same prompt and exactly wrong for the smaller one compaction exists to send.
|
|
55
|
+
context_overflow: { retryableAsIs: false, retryableAfterRemedy: true, remedy: 'reduce-context' },
|
|
56
|
+
// A 429 answered immediately is a 429 again; waiting is the whole remedy.
|
|
57
|
+
rate_limited: { retryableAsIs: false, retryableAfterRemedy: true, remedy: 'back-off' },
|
|
58
|
+
auth_failed: { retryableAsIs: false, retryableAfterRemedy: true, remedy: 'fix-credentials' },
|
|
59
|
+
// A rejected request is rejected identically every time, and a repaired request is a new request
|
|
60
|
+
// rather than a retry — so neither field is true and no remedy is named.
|
|
61
|
+
invalid_request: { retryableAsIs: false, retryableAfterRemedy: false },
|
|
62
|
+
// A provider-side fault is the transient case: the same request often succeeds on the next try.
|
|
63
|
+
provider_error: { retryableAsIs: true, retryableAfterRemedy: true, remedy: 'back-off' },
|
|
64
|
+
network_error: { retryableAsIs: true, retryableAfterRemedy: true, remedy: 'back-off' },
|
|
65
|
+
timeout: { retryableAsIs: true, retryableAfterRemedy: true, remedy: 'back-off' },
|
|
66
|
+
// The user chose to stop. Retrying without being asked overrides the one decision they made.
|
|
67
|
+
cancelled: { retryableAsIs: false, retryableAfterRemedy: false },
|
|
68
|
+
// The gate refused. Re-running the refused command automatically is the failure the gate exists
|
|
69
|
+
// to prevent, so neither field offers it.
|
|
70
|
+
approval_stop: { retryableAsIs: false, retryableAfterRemedy: false },
|
|
71
|
+
// Both guards end a loop that is going nowhere. Repeating it goes nowhere again; a changed
|
|
72
|
+
// approach is exactly what each guard's own notice asks the model for.
|
|
73
|
+
tool_error_budget: { retryableAsIs: false, retryableAfterRemedy: true, remedy: 'change-request' },
|
|
74
|
+
tool_loop_guard: { retryableAsIs: false, retryableAfterRemedy: true, remedy: 'change-request' },
|
|
75
|
+
// The same turn re-suspends the same way, so repeating it exhausts the same bound. Asking for
|
|
76
|
+
// less gated work in one turn is what gets under it.
|
|
77
|
+
interrupt_drain_guard: {
|
|
78
|
+
retryableAsIs: false,
|
|
79
|
+
retryableAfterRemedy: true,
|
|
80
|
+
remedy: 'change-request',
|
|
81
|
+
},
|
|
82
|
+
tool_error: { retryableAsIs: false, retryableAfterRemedy: true, remedy: 'change-request' },
|
|
83
|
+
// Not a failure at all: the run is parked mid-flight and continues where it stopped.
|
|
84
|
+
suspended: { retryableAsIs: false, retryableAfterRemedy: true, remedy: 'resume' },
|
|
85
|
+
recursion_limit: { retryableAsIs: false, retryableAfterRemedy: true, remedy: 'change-request' },
|
|
86
|
+
abandoned: { retryableAsIs: false, retryableAfterRemedy: false },
|
|
87
|
+
// Unclassified is not "probably fine": nothing is known, so nothing is offered.
|
|
88
|
+
unknown: { retryableAsIs: false, retryableAfterRemedy: false },
|
|
89
|
+
};
|
|
90
|
+
/** The retry posture of a category. */
|
|
91
|
+
export function terminationPosture(category) {
|
|
92
|
+
return POSTURE[category] ?? POSTURE.unknown;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Build a {@link GthTerminationReason}: attach a site and a feeder to a classification and fill in
|
|
96
|
+
* the posture from the one table. Every site builds through here, so no site can invent a posture.
|
|
97
|
+
*/
|
|
98
|
+
export function terminationReason(site, source, classification) {
|
|
99
|
+
const resolved = typeof classification === 'string' ? { category: classification } : classification;
|
|
100
|
+
return {
|
|
101
|
+
category: resolved.category,
|
|
102
|
+
site,
|
|
103
|
+
source,
|
|
104
|
+
...terminationPosture(resolved.category),
|
|
105
|
+
...(resolved.provider === undefined ? {} : { provider: resolved.provider }),
|
|
106
|
+
...(resolved.detail === undefined ? {} : { detail: resolved.detail }),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Substrings providers use when the input exceeds the model's window, matched case-insensitively.
|
|
111
|
+
*
|
|
112
|
+
* These sit **beside** `@langchain/core`'s own detection rather than behind it. LangChain types the
|
|
113
|
+
* error by substring-matching the provider's English prose in each provider package, so a provider
|
|
114
|
+
* rewording its 400 drops the typed class with nothing going red — and it covers only half our
|
|
115
|
+
* providers to begin with. A fallback that repeats the match here is what keeps the classification
|
|
116
|
+
* from quietly un-typing itself on a dependency bump.
|
|
117
|
+
*/
|
|
118
|
+
const CONTEXT_OVERFLOW_PATTERNS = [
|
|
119
|
+
'context_length_exceeded',
|
|
120
|
+
'context length exceeded',
|
|
121
|
+
'maximum context length',
|
|
122
|
+
'exceeds the context window',
|
|
123
|
+
'exceed the context window',
|
|
124
|
+
'input tokens exceed the configured limit',
|
|
125
|
+
'prompt is too long',
|
|
126
|
+
'too many tokens',
|
|
127
|
+
'reduce the length of the messages',
|
|
128
|
+
'request too large',
|
|
129
|
+
];
|
|
130
|
+
/** Substrings that mean the provider refused for rate or quota reasons. */
|
|
131
|
+
const RATE_LIMIT_PATTERNS = [
|
|
132
|
+
'rate limit',
|
|
133
|
+
'rate_limit',
|
|
134
|
+
'ratelimit',
|
|
135
|
+
'too many requests',
|
|
136
|
+
'quota exceeded',
|
|
137
|
+
'resource_exhausted',
|
|
138
|
+
'resource exhausted',
|
|
139
|
+
'overloaded_error',
|
|
140
|
+
];
|
|
141
|
+
/** Substrings that mean the caller was not authorised. */
|
|
142
|
+
const AUTH_PATTERNS = [
|
|
143
|
+
'unauthorized',
|
|
144
|
+
'unauthenticated',
|
|
145
|
+
'invalid api key',
|
|
146
|
+
'invalid_api_key',
|
|
147
|
+
'incorrect api key',
|
|
148
|
+
'api key not valid',
|
|
149
|
+
'permission denied',
|
|
150
|
+
'permission_denied',
|
|
151
|
+
'authentication_error',
|
|
152
|
+
'invalid_grant',
|
|
153
|
+
'forbidden',
|
|
154
|
+
];
|
|
155
|
+
/** Substrings that mean the fault was on the provider's side. */
|
|
156
|
+
const PROVIDER_ERROR_PATTERNS = [
|
|
157
|
+
'internal error',
|
|
158
|
+
'internal server error',
|
|
159
|
+
'internal_server_error',
|
|
160
|
+
'service unavailable',
|
|
161
|
+
'bad gateway',
|
|
162
|
+
'server_error',
|
|
163
|
+
'overloaded',
|
|
164
|
+
'model is overloaded',
|
|
165
|
+
'try again later',
|
|
166
|
+
];
|
|
167
|
+
/** Substrings that mean the request never completed at the transport level. */
|
|
168
|
+
const NETWORK_PATTERNS = [
|
|
169
|
+
'econnreset',
|
|
170
|
+
'econnrefused',
|
|
171
|
+
'enotfound',
|
|
172
|
+
'epipe',
|
|
173
|
+
'eai_again',
|
|
174
|
+
'socket hang up',
|
|
175
|
+
'fetch failed',
|
|
176
|
+
'network error',
|
|
177
|
+
'connection error',
|
|
178
|
+
'terminated',
|
|
179
|
+
];
|
|
180
|
+
/** Substrings that mean a deadline elapsed. */
|
|
181
|
+
const TIMEOUT_PATTERNS = [
|
|
182
|
+
'etimedout',
|
|
183
|
+
'timed out',
|
|
184
|
+
'timeout',
|
|
185
|
+
'deadline exceeded',
|
|
186
|
+
'deadline_exceeded',
|
|
187
|
+
];
|
|
188
|
+
/** Substrings that mean the provider rejected the request itself. */
|
|
189
|
+
const INVALID_REQUEST_PATTERNS = [
|
|
190
|
+
'invalid_request_error',
|
|
191
|
+
'invalid request',
|
|
192
|
+
'bad request',
|
|
193
|
+
'invalid argument',
|
|
194
|
+
'invalid_argument',
|
|
195
|
+
];
|
|
196
|
+
/** Read a property off an unknown value without asserting anything about its shape. */
|
|
197
|
+
function field(source, key) {
|
|
198
|
+
if (!source || (typeof source !== 'object' && typeof source !== 'function'))
|
|
199
|
+
return undefined;
|
|
200
|
+
return source[key];
|
|
201
|
+
}
|
|
202
|
+
/** Whether `haystack` contains any of `patterns` (both compared lower-cased). */
|
|
203
|
+
function containsAny(haystack, patterns) {
|
|
204
|
+
return patterns.some((pattern) => haystack.includes(pattern));
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Every text an error carries that a classification may read: its message, its name, and the
|
|
208
|
+
* nested provider payloads SDKs hang off `error`, `cause`, `body` and `response.data`. Bounded to
|
|
209
|
+
* one nesting level per branch so a self-referential payload cannot spin.
|
|
210
|
+
*/
|
|
211
|
+
function errorText(error) {
|
|
212
|
+
const parts = [];
|
|
213
|
+
const push = (value) => {
|
|
214
|
+
if (typeof value === 'string')
|
|
215
|
+
parts.push(value);
|
|
216
|
+
else if (typeof value === 'number')
|
|
217
|
+
parts.push(String(value));
|
|
218
|
+
};
|
|
219
|
+
push(field(error, 'message'));
|
|
220
|
+
push(field(error, 'name'));
|
|
221
|
+
push(field(error, 'code'));
|
|
222
|
+
push(field(error, 'type'));
|
|
223
|
+
if (typeof error === 'string')
|
|
224
|
+
parts.push(error);
|
|
225
|
+
for (const key of ['error', 'cause', 'body', 'data', 'response']) {
|
|
226
|
+
const nested = field(error, key);
|
|
227
|
+
if (typeof nested === 'string') {
|
|
228
|
+
parts.push(nested);
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
push(field(nested, 'message'));
|
|
232
|
+
push(field(nested, 'type'));
|
|
233
|
+
push(field(nested, 'code'));
|
|
234
|
+
const inner = field(nested, 'error');
|
|
235
|
+
push(field(inner, 'message'));
|
|
236
|
+
push(field(inner, 'type'));
|
|
237
|
+
push(field(inner, 'code'));
|
|
238
|
+
}
|
|
239
|
+
// The separator is deliberately not a plain space: the prose patterns below are multi-word
|
|
240
|
+
// English, and joining two adjacent fragments with a space lets a pattern match ACROSS them —
|
|
241
|
+
// a fragment ending in "rate" beside one starting with "limit" would read as a rate limit.
|
|
242
|
+
// A newline cannot occur mid-pattern, so it breaks that adjacency without hiding anything.
|
|
243
|
+
return parts.join(' \n ').toLowerCase();
|
|
244
|
+
}
|
|
245
|
+
/** The HTTP status an SDK error carries, wherever it hangs it. Undefined when there is none. */
|
|
246
|
+
function httpStatus(error) {
|
|
247
|
+
for (const holder of [error, field(error, 'response'), field(error, 'error')]) {
|
|
248
|
+
for (const key of ['status', 'statusCode', 'code']) {
|
|
249
|
+
const value = field(holder, key);
|
|
250
|
+
if (typeof value === 'number' && value >= 100 && value < 600)
|
|
251
|
+
return value;
|
|
252
|
+
if (typeof value === 'string' && /^[1-5]\d{2}$/.test(value))
|
|
253
|
+
return Number(value);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return undefined;
|
|
257
|
+
}
|
|
258
|
+
/** The `name` of an error, or `undefined` for anything that carries none. */
|
|
259
|
+
function errorName(error) {
|
|
260
|
+
const name = field(error, 'name');
|
|
261
|
+
return typeof name === 'string' && name.length > 0 ? name : undefined;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Whether a thrown value is a context overflow.
|
|
265
|
+
*
|
|
266
|
+
* The predicate is `ContextOverflowError.isInstance`, never `lc_error_code`: the code is set
|
|
267
|
+
* asymmetrically across providers (Anthropic stamps both the class and the code, OpenAI only the
|
|
268
|
+
* class), so keying on it silently misses most of where the typed class actually works. The
|
|
269
|
+
* substring fallback then covers the providers LangChain does not type at all, and the case where
|
|
270
|
+
* a reworded provider message drops the class.
|
|
271
|
+
*/
|
|
272
|
+
export function isContextOverflow(error) {
|
|
273
|
+
try {
|
|
274
|
+
if (ContextOverflowError.isInstance(error))
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
/* fail-soft: a dependency that stops exporting the predicate must not break classification */
|
|
279
|
+
}
|
|
280
|
+
if (errorName(error) === 'ContextOverflowError')
|
|
281
|
+
return true;
|
|
282
|
+
return containsAny(errorText(error), CONTEXT_OVERFLOW_PATTERNS);
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* The exception feeder: classify a thrown value into the taxonomy.
|
|
286
|
+
*
|
|
287
|
+
* Order matters. The typed and named cases are decided first, because a context overflow is also an
|
|
288
|
+
* HTTP 400 and an abort is also a `DOMException`; only once those are excluded does the status code
|
|
289
|
+
* and then the prose get a say. Never throws: an unclassifiable value is `unknown`, which is a
|
|
290
|
+
* recorded fact rather than a guess.
|
|
291
|
+
*/
|
|
292
|
+
export function classifyThrownTermination(error) {
|
|
293
|
+
try {
|
|
294
|
+
const name = errorName(error);
|
|
295
|
+
const text = errorText(error);
|
|
296
|
+
const status = httpStatus(error);
|
|
297
|
+
// Typed / named first — these are unambiguous and several of them also carry a status that
|
|
298
|
+
// would classify them wrongly.
|
|
299
|
+
if (isContextOverflow(error)) {
|
|
300
|
+
return { category: 'context_overflow', detail: name ?? 'ContextOverflowError' };
|
|
301
|
+
}
|
|
302
|
+
if (name === 'AbortError' || name === 'ModelAbortError' || name === 'APIUserAbortError') {
|
|
303
|
+
return { category: 'cancelled', detail: name };
|
|
304
|
+
}
|
|
305
|
+
if (name === 'GraphInterrupt') {
|
|
306
|
+
return { category: 'suspended', detail: name };
|
|
307
|
+
}
|
|
308
|
+
if (name === 'ToolException') {
|
|
309
|
+
return { category: 'tool_error', detail: name };
|
|
310
|
+
}
|
|
311
|
+
if (name === 'GraphRecursionError' || text.includes('recursion limit')) {
|
|
312
|
+
return { category: 'recursion_limit', detail: name ?? 'recursion limit' };
|
|
313
|
+
}
|
|
314
|
+
if (name === 'TimeoutError' || name === 'APITimeoutError') {
|
|
315
|
+
return { category: 'timeout', detail: name };
|
|
316
|
+
}
|
|
317
|
+
if (name === 'APIConnectionError') {
|
|
318
|
+
return { category: 'network_error', detail: name };
|
|
319
|
+
}
|
|
320
|
+
// Status codes next: a number the provider set is stronger evidence than prose we matched.
|
|
321
|
+
if (status === 429)
|
|
322
|
+
return { category: 'rate_limited', detail: '429' };
|
|
323
|
+
if (status === 401 || status === 403)
|
|
324
|
+
return { category: 'auth_failed', detail: String(status) };
|
|
325
|
+
if (status === 408 || status === 504)
|
|
326
|
+
return { category: 'timeout', detail: String(status) };
|
|
327
|
+
if (status !== undefined && status >= 500) {
|
|
328
|
+
return { category: 'provider_error', detail: String(status) };
|
|
329
|
+
}
|
|
330
|
+
// Prose last, and in the order that keeps a specific signal from being eaten by a generic one:
|
|
331
|
+
// "quota exceeded" is a rate limit before it is an invalid request, and an auth failure often
|
|
332
|
+
// arrives as a 400 whose body says `invalid_grant`.
|
|
333
|
+
if (containsAny(text, RATE_LIMIT_PATTERNS))
|
|
334
|
+
return { category: 'rate_limited' };
|
|
335
|
+
if (containsAny(text, AUTH_PATTERNS))
|
|
336
|
+
return { category: 'auth_failed' };
|
|
337
|
+
if (containsAny(text, TIMEOUT_PATTERNS))
|
|
338
|
+
return { category: 'timeout' };
|
|
339
|
+
if (containsAny(text, NETWORK_PATTERNS))
|
|
340
|
+
return { category: 'network_error' };
|
|
341
|
+
if (containsAny(text, PROVIDER_ERROR_PATTERNS))
|
|
342
|
+
return { category: 'provider_error' };
|
|
343
|
+
if (status === 400 || containsAny(text, INVALID_REQUEST_PATTERNS)) {
|
|
344
|
+
// `detail` is the raw token the classification was made from, so it states the status the
|
|
345
|
+
// response ACTUALLY had. A 402 or 409 whose prose matches the patterns reaches this branch
|
|
346
|
+
// too (it is past the 429/401/403/408/5xx arms), and stamping a flat '400' on one would put
|
|
347
|
+
// a false statement in the field that exists to record what was seen.
|
|
348
|
+
return {
|
|
349
|
+
category: 'invalid_request',
|
|
350
|
+
detail: status === undefined ? undefined : String(status),
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
return { category: 'unknown', ...(name === undefined ? {} : { detail: name }) };
|
|
354
|
+
}
|
|
355
|
+
catch {
|
|
356
|
+
// Classification must never be the thing that breaks a run that was already failing.
|
|
357
|
+
return { category: 'unknown' };
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* The property a reason is carried on when it rides a thrown error.
|
|
362
|
+
*
|
|
363
|
+
* A run that ends by throwing crosses layers the runner does not own, and the message is not the
|
|
364
|
+
* carrier — that is the whole defect this taxonomy exists to fix. Attaching the value to the error
|
|
365
|
+
* lets any catcher upstream read the classification without re-deriving it from prose.
|
|
366
|
+
*/
|
|
367
|
+
const TERMINATION_REASON_KEY = 'gthTerminationReason';
|
|
368
|
+
/**
|
|
369
|
+
* Attach a reason to a thrown value and return it, so a `throw` site reads as one expression.
|
|
370
|
+
*
|
|
371
|
+
* Non-enumerable, so the reason never widens what an error serialises to (a logged or
|
|
372
|
+
* JSON-stringified error keeps exactly the shape it had), and first-write-wins so a re-throw
|
|
373
|
+
* through an outer wrapper cannot overwrite the inner, truer classification. Fail-soft: a frozen or
|
|
374
|
+
* primitive throw value is returned unchanged rather than turning a failure into a different one.
|
|
375
|
+
*/
|
|
376
|
+
export function attachTerminationReason(error, reason) {
|
|
377
|
+
try {
|
|
378
|
+
if (!error || (typeof error !== 'object' && typeof error !== 'function'))
|
|
379
|
+
return error;
|
|
380
|
+
if (field(error, TERMINATION_REASON_KEY) !== undefined)
|
|
381
|
+
return error;
|
|
382
|
+
Object.defineProperty(error, TERMINATION_REASON_KEY, {
|
|
383
|
+
value: reason,
|
|
384
|
+
enumerable: false,
|
|
385
|
+
writable: true,
|
|
386
|
+
configurable: true,
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
catch {
|
|
390
|
+
/* fail-soft */
|
|
391
|
+
}
|
|
392
|
+
return error;
|
|
393
|
+
}
|
|
394
|
+
/** The reason attached to a thrown value, following one `cause` link. Undefined when none is. */
|
|
395
|
+
export function terminationReasonOf(error) {
|
|
396
|
+
const own = field(error, TERMINATION_REASON_KEY);
|
|
397
|
+
if (own && typeof own === 'object')
|
|
398
|
+
return own;
|
|
399
|
+
const cause = field(error, 'cause');
|
|
400
|
+
const inherited = field(cause, TERMINATION_REASON_KEY);
|
|
401
|
+
if (inherited && typeof inherited === 'object')
|
|
402
|
+
return inherited;
|
|
403
|
+
return undefined;
|
|
404
|
+
}
|
|
405
|
+
//# sourceMappingURL=terminationReason.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminationReason.js","sourceRoot":"","sources":["../../src/core/terminationReason.ts"],"names":[],"mappings":"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"}
|
|
@@ -140,15 +140,62 @@ export declare function summariseToolCall(name: string, argsText: string | undef
|
|
|
140
140
|
/**
|
|
141
141
|
* The FULL (uncapped) body lines for a call: the registry formatter when one applies, else the
|
|
142
142
|
* shape-based shell formatter, else the generic fallback (live output lines, then the final
|
|
143
|
-
* result — both dim). Every line is secret-redacted. Used by the TUI's
|
|
144
|
-
* with {@link capToolDisplayLines} for the collapsed preview.
|
|
143
|
+
* result — both dim). Every line is secret-redacted and then neutralised. Used by the TUI's
|
|
144
|
+
* EXPANDED panel; cap it with {@link capToolDisplayLines} for the collapsed preview.
|
|
145
145
|
*/
|
|
146
146
|
export declare function buildToolBodyLines(input: ToolCallDisplayInput, secrets?: readonly string[]): ToolDisplayLine[];
|
|
147
|
+
/** The strings the expanded panel paints beside the body, ready to draw. */
|
|
148
|
+
export interface ToolExpansionText {
|
|
149
|
+
/** The streamed args text as one row's content, or `null` when the call carries no args. */
|
|
150
|
+
args: string | null;
|
|
151
|
+
/** The routed notice, one element per row the expansion draws. */
|
|
152
|
+
noticeLines: string[];
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* [[TUI-C102]] — the two untrusted strings the EXPANDED tool panel paints OUTSIDE the body: the
|
|
156
|
+
* raw streamed args text and the routed "🔧 Executing …" notice. Neither passes through
|
|
157
|
+
* {@link buildToolBodyLines}, and neither is ours: `argsText` is whatever the model streamed —
|
|
158
|
+
* under prompt injection, attacker-chosen, and for a shell call it is the command itself — and a
|
|
159
|
+
* notice quotes that same command back. Under [[TUI-C99]] this expansion is the route by which a
|
|
160
|
+
* human inspects a call they are being asked to permit, so it gets the treatment the rest of the
|
|
161
|
+
* display path already has.
|
|
162
|
+
*
|
|
163
|
+
* **Both the renderer and the row-count oracle must call this, on the same input.** Neutralised
|
|
164
|
+
* text is WIDER than the raw text it replaces, so a renderer that neutralises beside an estimator
|
|
165
|
+
* that measures the raw string disagree about how many rows the panel occupies — and a row
|
|
166
|
+
* miscount shows as content in the wrong place on screen rather than as an error anywhere. One
|
|
167
|
+
* helper for both sides is what stops them drifting apart again.
|
|
168
|
+
*
|
|
169
|
+
* Same order as everywhere else on this path: **redact the raw text, then neutralise.** A secret
|
|
170
|
+
* literal that carries a control character stops literal-matching the moment that character is
|
|
171
|
+
* rewritten. Redaction is here at all because the collapsed summary above this row already
|
|
172
|
+
* redacts, and an expansion that did not would print, under `/verbose`, the secret the row above
|
|
173
|
+
* it hid.
|
|
174
|
+
*
|
|
175
|
+
* The notice splits through the module's own `toLines`, so a `\r\n` inside a quoted command is one
|
|
176
|
+
* break there too; the args text is a single value and keeps its own newlines as visible `\x0a`,
|
|
177
|
+
* which is what a streamed JSON buffer carrying a real newline actually contains.
|
|
178
|
+
*/
|
|
179
|
+
export declare function buildToolExpansionText(input: {
|
|
180
|
+
argsText?: string;
|
|
181
|
+
notice?: string;
|
|
182
|
+
}, secrets?: readonly string[]): ToolExpansionText;
|
|
147
183
|
/**
|
|
148
184
|
* Apply the canonical render cap: at most `maxLines` lines (each char-capped at
|
|
149
185
|
* {@link TOOL_PREVIEW_LINE_MAX_CHARS} with `…`), plus a dim `… (+N more lines)` overflow
|
|
150
186
|
* marker when anything was cut. The marker line is IN ADDITION to the cap so exactly how much
|
|
151
187
|
* was hidden is always stated (DL-4 transparency).
|
|
188
|
+
*
|
|
189
|
+
* This stays a GENERIC width utility and does not neutralise: it measures a line by what it
|
|
190
|
+
* RENDERS as, so a caller passing genuinely styled text (the renderer's own SGR) gets a cap that
|
|
191
|
+
* counts the columns the user sees rather than the escapes' own bytes.
|
|
192
|
+
*
|
|
193
|
+
* **No production caller passes styled text today.** {@link buildToolPreviewLines} is the only
|
|
194
|
+
* one, and its lines arrive already neutralised from {@link buildToolBodyLines}, so on the tool
|
|
195
|
+
* path there is nothing left to discount. The discount is kept because this is public API — core
|
|
196
|
+
* exports every module — and a width utility that mismeasured styled text would be wrong for any
|
|
197
|
+
* caller that did pass some. It is a property of the utility, not a live requirement of the tool
|
|
198
|
+
* path, and the spec covering it says the same.
|
|
152
199
|
*/
|
|
153
200
|
export declare function capToolDisplayLines(lines: ToolDisplayLine[], maxLines?: number): ToolDisplayLine[];
|
|
154
201
|
/**
|