@the-open-engine/zeroshot 6.34.1 → 6.34.3

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.
@@ -16,8 +16,51 @@
16
16
  * 4. Direct JSON parse of entire output
17
17
  */
18
18
 
19
+ const { createHash } = require('node:crypto');
19
20
  const { parseProviderChunk } = require('../providers');
20
21
 
22
+ const MAX_CLI_ERROR_BYTES = 4096;
23
+ const CLI_ERROR_TRUNCATION_SUFFIX = '… [truncated]';
24
+
25
+ function truncateUtf8(text, maxBytes = MAX_CLI_ERROR_BYTES) {
26
+ if (Buffer.byteLength(text) <= maxBytes) return text;
27
+
28
+ const suffixBytes = Buffer.byteLength(CLI_ERROR_TRUNCATION_SUFFIX);
29
+ const contentBudget = Math.max(0, maxBytes - suffixBytes);
30
+ let bytes = 0;
31
+ let truncated = '';
32
+ for (const character of text) {
33
+ const characterBytes = Buffer.byteLength(character);
34
+ if (bytes + characterBytes > contentBudget) break;
35
+ truncated += character;
36
+ bytes += characterBytes;
37
+ }
38
+ return `${truncated}${CLI_ERROR_TRUNCATION_SUFFIX}`;
39
+ }
40
+
41
+ function primitiveErrorText(value) {
42
+ return typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean'
43
+ ? String(value)
44
+ : '';
45
+ }
46
+
47
+ function cliErrorDetail(value, fallback) {
48
+ const candidate = Array.isArray(value)
49
+ ? value
50
+ .map(primitiveErrorText)
51
+ .filter((message) => message.trim())
52
+ .join('; ')
53
+ : primitiveErrorText(value);
54
+ const raw = candidate.trim() ? candidate : fallback;
55
+ return {
56
+ error: truncateUtf8(raw.trim()),
57
+ diagnostic: {
58
+ byteLength: Buffer.byteLength(raw),
59
+ sha256: createHash('sha256').update(raw).digest('hex'),
60
+ },
61
+ };
62
+ }
63
+
21
64
  /**
22
65
  * Strip timestamp prefix from log lines.
23
66
  * Format: [epochMs]content or [epochMs]{json...}
@@ -308,60 +351,87 @@ function extractClaudeVertexModelError(output, { useVertex = false } = {}) {
308
351
  * @param {string} providerName - Active provider whose terminal errors may be inspected
309
352
  * @returns {{error: string, provider: string}|null} Error info or null
310
353
  */
311
- function extractCliError(output, providerName = 'claude') {
354
+ function claudeFailureFromObject(obj) {
355
+ if (obj.type !== 'result') return null;
356
+ if (obj.is_error === true) {
357
+ const detail = cliErrorDetail(
358
+ Array.isArray(obj.errors) ? obj.errors : obj.error || obj.result,
359
+ 'Unknown CLI error'
360
+ );
361
+ return { ...detail, provider: 'claude' };
362
+ }
363
+ if (obj.subtype !== 'error') return null;
364
+ return {
365
+ ...cliErrorDetail(obj.error || obj.result, 'CLI returned error'),
366
+ provider: 'claude',
367
+ };
368
+ }
369
+
370
+ function codexFailureFromObject(obj) {
371
+ if (obj.type !== 'turn.failed') return null;
372
+ return {
373
+ ...cliErrorDetail(obj.error?.message || obj.error?.code || obj.error, 'Turn failed'),
374
+ provider: 'codex',
375
+ };
376
+ }
377
+
378
+ function geminiFailureFromObject(obj) {
379
+ const geminiFailure =
380
+ (obj.type === 'result' && obj.status === 'error') ||
381
+ (obj.type === 'error' && obj.severity === 'error');
382
+ if (!geminiFailure) return null;
383
+ return {
384
+ ...cliErrorDetail(obj.error?.message || obj.message, 'Gemini CLI error'),
385
+ provider: 'gemini',
386
+ };
387
+ }
388
+
389
+ function opencodeFailureFromObject(obj) {
390
+ if (obj.type !== 'session.error' && obj.type !== 'error') return null;
391
+ return {
392
+ ...cliErrorDetail(
393
+ obj.error?.data?.message || obj.error?.message || obj.error?.name,
394
+ 'Session error'
395
+ ),
396
+ provider: 'opencode',
397
+ };
398
+ }
399
+
400
+ function failureFromProviderObject(obj, providerName) {
401
+ if (providerName === 'claude') return claudeFailureFromObject(obj);
402
+ if (providerName === 'codex') return codexFailureFromObject(obj);
403
+ if (providerName === 'gemini') return geminiFailureFromObject(obj);
404
+ if (providerName === 'opencode') return opencodeFailureFromObject(obj);
405
+ return null;
406
+ }
407
+
408
+ function extractCliFailure(output, providerName = 'claude') {
312
409
  if (!output || typeof output !== 'string') return null;
313
410
 
314
411
  const lines = output.split('\n');
412
+ // Codex terminal truth is a turn.failed record at the newest end of JSONL output.
413
+ // Search it newest-first so a preserved terminal tail wins over older provider chatter.
414
+ if (providerName === 'codex') lines.reverse();
315
415
 
316
416
  for (const line of lines) {
317
417
  const content = stripTimestamp(line);
318
418
  if (!content.startsWith('{')) continue;
319
-
320
- let obj;
321
419
  try {
322
- obj = JSON.parse(content);
420
+ const failure = failureFromProviderObject(JSON.parse(content), providerName);
421
+ if (failure) return failure;
323
422
  } catch {
324
- continue;
325
- }
326
-
327
- if (providerName === 'claude' && obj.type === 'result' && obj.is_error === true) {
328
- const errorMsg = Array.isArray(obj.errors)
329
- ? obj.errors.join('; ')
330
- : obj.error || obj.result || 'Unknown CLI error';
331
- return { error: errorMsg, provider: 'claude' };
332
- }
333
-
334
- if (providerName === 'claude' && obj.type === 'result' && obj.subtype === 'error') {
335
- const errorMsg = obj.error || obj.result || 'CLI returned error';
336
- return { error: errorMsg, provider: 'claude' };
337
- }
338
-
339
- if (providerName === 'codex' && obj.type === 'turn.failed') {
340
- const errorMsg = obj.error?.message || obj.error || 'Turn failed';
341
- return { error: errorMsg, provider: 'codex' };
342
- }
343
-
344
- if (
345
- providerName === 'gemini' &&
346
- ((obj.type === 'result' && obj.status === 'error') ||
347
- (obj.type === 'error' && obj.severity === 'error'))
348
- ) {
349
- return {
350
- error: obj.error?.message || obj.message || 'Gemini CLI error',
351
- provider: 'gemini',
352
- };
353
- }
354
-
355
- if (providerName === 'opencode' && (obj.type === 'session.error' || obj.type === 'error')) {
356
- const errorMsg =
357
- obj.error?.data?.message || obj.error?.message || obj.error?.name || 'Session error';
358
- return { error: errorMsg, provider: 'opencode' };
423
+ // Ignore non-JSON output lines.
359
424
  }
360
425
  }
361
426
 
362
427
  return null;
363
428
  }
364
429
 
430
+ function extractCliError(output, providerName = 'claude') {
431
+ const failure = extractCliFailure(output, providerName);
432
+ return failure ? { error: failure.error, provider: failure.provider } : null;
433
+ }
434
+
365
435
  /**
366
436
  * Detects fatal standalone output lines that indicate no task output was produced.
367
437
  * Only matches when the line itself is the fatal message (not when it appears inside JSON).
@@ -419,8 +489,10 @@ function extractJsonFromOutput(output, providerName = 'claude') {
419
489
  }
420
490
 
421
491
  module.exports = {
492
+ MAX_CLI_ERROR_BYTES,
422
493
  extractJsonFromOutput,
423
494
  extractModelTextFromOutput,
495
+ extractCliFailure,
424
496
  extractCliError,
425
497
  extractClaudeVertexModelError,
426
498
  extractFromResultWrapper,
@@ -0,0 +1,186 @@
1
+ // @ts-nocheck
2
+
3
+ const { getProvider } = require('../providers');
4
+ const { extractCliFailure } = require('./output-extraction');
5
+
6
+ function categoryForProviderFailure(error, classification) {
7
+ const isPermanent = classification.retryable === false;
8
+ const authenticationPattern =
9
+ /(?:invalid[_ -]?api[_ -]?key|api[_ -]?key.*invalid|unauthori[sz]ed|forbidden|authentication|permission denied)/i;
10
+ if (isPermanent && authenticationPattern.test(error)) return 'authentication';
11
+
12
+ const quotaPattern = /(?:insufficient[_ -]?quota|quota exceeded|resource_exhausted)/i;
13
+ if (isPermanent && quotaPattern.test(error)) return 'quota';
14
+ if (isPermanent) return 'permanent';
15
+ return classification.kind === 'unknown-retryable' ? 'unknown' : 'transient';
16
+ }
17
+
18
+ function classifyProviderFailure(providerName, error) {
19
+ let rawClassification = { retryable: true, kind: 'unknown-retryable' };
20
+ try {
21
+ rawClassification = getProvider(providerName).adapter.classifyError(new Error(error));
22
+ } catch {
23
+ // Extraction remains available if a provider adapter cannot be loaded.
24
+ }
25
+ return {
26
+ retryable: rawClassification?.retryable !== false,
27
+ kind:
28
+ typeof rawClassification?.kind === 'string' ? rawClassification.kind : 'unknown-retryable',
29
+ };
30
+ }
31
+
32
+ function extractProviderFailure(output, providerName) {
33
+ const cliError = extractCliFailure(output, providerName);
34
+ if (!cliError) return null;
35
+
36
+ const classification = classifyProviderFailure(providerName, cliError.error);
37
+ const category = categoryForProviderFailure(cliError.error, classification);
38
+ return {
39
+ error: `Provider ${cliError.provider} failed (${category}; ${classification.kind})`,
40
+ provider: cliError.provider,
41
+ event: cliError.provider === 'codex' ? 'turn.failed' : 'terminal_error',
42
+ category,
43
+ classification,
44
+ diagnostic: cliError.diagnostic,
45
+ };
46
+ }
47
+
48
+ function redactTerminalFailureForControlPlane(state, providerName, content) {
49
+ const failure = extractProviderFailure(content, providerName);
50
+ if (!failure) return content;
51
+
52
+ state.providerFailure = failure;
53
+ let eventType = 'provider.failure';
54
+ try {
55
+ const parsed = JSON.parse(content);
56
+ if (typeof parsed?.type === 'string') eventType = parsed.type;
57
+ } catch {
58
+ // extractProviderFailure already proved a supported terminal envelope.
59
+ }
60
+ return JSON.stringify({
61
+ type: eventType,
62
+ ...(providerName === 'claude' ? { is_error: true } : {}),
63
+ ...(providerName === 'gemini' ? { status: 'error', severity: 'error' } : {}),
64
+ error: { message: failure.error },
65
+ zeroshot_failure: {
66
+ provider: failure.provider,
67
+ event: failure.event,
68
+ category: failure.category,
69
+ kind: failure.classification.kind,
70
+ retryable: failure.classification.retryable,
71
+ diagnostic: failure.diagnostic,
72
+ },
73
+ });
74
+ }
75
+
76
+ function decorateError(error, failure) {
77
+ if (!failure) return error;
78
+ error.provider = failure.provider || null;
79
+ error.providerEvent = failure.event || null;
80
+ error.providerCategory = failure.category || null;
81
+ error.classification = failure.classification || null;
82
+ error.providerDiagnostic = failure.diagnostic || null;
83
+ if (failure.classification?.retryable === false) error.permanent = true;
84
+ return error;
85
+ }
86
+
87
+ function receiptFields(error) {
88
+ if (!error?.provider) return {};
89
+ return {
90
+ provider: error.provider,
91
+ event: error.providerEvent,
92
+ category: error.providerCategory,
93
+ kind: error.classification?.kind,
94
+ retryable: error.classification?.retryable,
95
+ diagnostic: error.providerDiagnostic,
96
+ };
97
+ }
98
+
99
+ function workerFailure(error) {
100
+ const authenticationFailure =
101
+ error?.provider &&
102
+ error?.classification?.retryable === false &&
103
+ error?.providerCategory === 'authentication';
104
+ return authenticationFailure
105
+ ? { code: 'refusal', reason: 'authentication_required' }
106
+ : { code: 'crash', reason: 'declared_failure' };
107
+ }
108
+
109
+ function publishCriticalFailure({
110
+ agent,
111
+ error,
112
+ attempts,
113
+ worker,
114
+ unsupportedCapability,
115
+ structuredOutputInvalid,
116
+ }) {
117
+ const specific =
118
+ error?.hookFailure ||
119
+ structuredOutputInvalid ||
120
+ unsupportedCapability ||
121
+ error?.vertexModelError ||
122
+ error?.terminationExhausted;
123
+ const critical =
124
+ agent.role === 'implementation' ||
125
+ agent.role === 'coordinator' ||
126
+ agent.id === 'consensus-coordinator';
127
+ if (!critical || specific) return worker;
128
+
129
+ agent._publish({
130
+ topic: 'CLUSTER_FAILED',
131
+ receiver: 'broadcast',
132
+ content: {
133
+ text: `Critical agent ${agent.id} exhausted its retry budget`,
134
+ data: {
135
+ reason: error?.provider ? 'provider_execution_failed' : 'critical_agent_exhausted',
136
+ agentId: agent.id,
137
+ role: agent.role,
138
+ attempts,
139
+ code: worker.code,
140
+ workerReason: worker.reason,
141
+ ...receiptFields(error),
142
+ },
143
+ },
144
+ });
145
+ return worker;
146
+ }
147
+
148
+ function buildFinalFailureInfo({
149
+ agent,
150
+ error,
151
+ attempts,
152
+ worker,
153
+ unsupportedCapability,
154
+ structuredOutputInvalid,
155
+ }) {
156
+ return {
157
+ ...(error?.terminationExhausted ? agent.cluster.failureInfo : {}),
158
+ agentId: agent.id,
159
+ taskId: error?.taskId || agent.currentTaskId,
160
+ iteration: agent.iteration,
161
+ error: error.message,
162
+ attempts,
163
+ ...receiptFields(error),
164
+ ...(error?.provider ? { code: worker.code, workerReason: worker.reason } : {}),
165
+ ...(unsupportedCapability
166
+ ? {
167
+ code: error.code,
168
+ permanent: true,
169
+ provider: error.provider,
170
+ capability: error.capability,
171
+ }
172
+ : {}),
173
+ ...(structuredOutputInvalid ? { code: error.code, details: error.details ?? null } : {}),
174
+ timestamp: Date.now(),
175
+ };
176
+ }
177
+
178
+ module.exports = {
179
+ buildFinalFailureInfo,
180
+ decorateError,
181
+ extractProviderFailure,
182
+ publishCriticalFailure,
183
+ receiptFields,
184
+ redactTerminalFailureForControlPlane,
185
+ workerFailure,
186
+ };