@the-open-engine/zeroshot 6.36.0 → 6.38.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/cli/index.js +99 -29
- package/cli/lib/setup-scanner-worker.js +2 -9
- package/lib/agent-cli-provider/adapters/claude-recovery.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/claude-recovery.js +17 -3
- package/lib/agent-cli-provider/adapters/claude-recovery.js.map +1 -1
- package/lib/agent-cli-provider/adapters/index.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/index.js +7 -0
- package/lib/agent-cli-provider/adapters/index.js.map +1 -1
- package/lib/agent-cli-provider/adapters/pi.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/pi.js +86 -97
- package/lib/agent-cli-provider/adapters/pi.js.map +1 -1
- package/lib/agent-cli-provider/contract-parse.d.ts.map +1 -1
- package/lib/agent-cli-provider/contract-parse.js +5 -1
- package/lib/agent-cli-provider/contract-parse.js.map +1 -1
- package/lib/agent-cli-provider/invoke-evidence.d.ts.map +1 -1
- package/lib/agent-cli-provider/invoke-evidence.js +8 -1
- package/lib/agent-cli-provider/invoke-evidence.js.map +1 -1
- package/lib/agent-cli-provider/pi/credentials.d.ts +2 -0
- package/lib/agent-cli-provider/pi/credentials.d.ts.map +1 -0
- package/lib/agent-cli-provider/pi/credentials.js +51 -0
- package/lib/agent-cli-provider/pi/credentials.js.map +1 -0
- package/lib/agent-cli-provider/pi/events.d.ts +5 -0
- package/lib/agent-cli-provider/pi/events.d.ts.map +1 -0
- package/lib/agent-cli-provider/pi/events.js +124 -0
- package/lib/agent-cli-provider/pi/events.js.map +1 -0
- package/lib/agent-cli-provider/pi/protocol-state.d.ts +6 -0
- package/lib/agent-cli-provider/pi/protocol-state.d.ts.map +1 -0
- package/lib/agent-cli-provider/pi/protocol-state.js +138 -0
- package/lib/agent-cli-provider/pi/protocol-state.js.map +1 -0
- package/lib/agent-cli-provider/pi/release.d.ts +4 -0
- package/lib/agent-cli-provider/pi/release.d.ts.map +1 -0
- package/lib/agent-cli-provider/pi/release.js +7 -0
- package/lib/agent-cli-provider/pi/release.js.map +1 -0
- package/lib/agent-cli-provider/probe-availability.d.ts +5 -0
- package/lib/agent-cli-provider/probe-availability.d.ts.map +1 -0
- package/lib/agent-cli-provider/probe-availability.js +11 -0
- package/lib/agent-cli-provider/probe-availability.js.map +1 -0
- package/lib/agent-cli-provider/provider-registry.d.ts +13 -9
- package/lib/agent-cli-provider/provider-registry.d.ts.map +1 -1
- package/lib/agent-cli-provider/provider-registry.js +42 -8
- package/lib/agent-cli-provider/provider-registry.js.map +1 -1
- package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -1
- package/lib/agent-cli-provider/single-agent-runtime.js +15 -5
- package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -1
- package/lib/agent-cli-provider/types.d.ts +3 -0
- package/lib/agent-cli-provider/types.d.ts.map +1 -1
- package/lib/agent-cli-provider/types.js.map +1 -1
- package/lib/cluster-worker/engine-adapter.js +9 -1
- package/lib/docker-config.js +6 -1
- package/lib/provider-credential-path.js +36 -0
- package/lib/start-cluster.js +8 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/src/agent/agent-lifecycle.js +25 -76
- package/src/agent/agent-liveness-poll.js +93 -0
- package/src/agent/agent-task-executor.js +98 -23
- package/src/agent/output-extraction.js +115 -0
- package/src/agent/pi-terminal-lifecycle.js +242 -0
- package/src/agent/provider-control-plane.js +26 -0
- package/src/agent/provider-terminal-failure.js +49 -24
- package/src/agent-cli-provider/adapters/claude-recovery.ts +15 -2
- package/src/agent-cli-provider/adapters/index.ts +6 -0
- package/src/agent-cli-provider/adapters/pi.ts +105 -125
- package/src/agent-cli-provider/contract-parse.ts +8 -1
- package/src/agent-cli-provider/invoke-evidence.ts +11 -1
- package/src/agent-cli-provider/pi/credentials.ts +47 -0
- package/src/agent-cli-provider/pi/events.ts +148 -0
- package/src/agent-cli-provider/pi/protocol-state.ts +163 -0
- package/src/agent-cli-provider/pi/release.ts +4 -0
- package/src/agent-cli-provider/probe-availability.ts +19 -0
- package/src/agent-cli-provider/provider-registry.ts +48 -9
- package/src/agent-cli-provider/single-agent-runtime.ts +27 -6
- package/src/agent-cli-provider/types.ts +3 -0
- package/src/config-validator.js +1 -3
- package/src/foreground-benchmark-files.js +81 -0
- package/src/foreground-benchmark-result.js +233 -0
- package/src/foreground-benchmark-run.js +72 -0
- package/src/isolation-manager.js +43 -29
- package/src/orchestrator.js +33 -1
- package/task-lib/runner.js +7 -0
- package/task-lib/watcher-output-runtime.js +16 -5
|
@@ -347,6 +347,7 @@ function extractClaudeVertexModelError(output, { useVertex = false } = {}) {
|
|
|
347
347
|
* - Codex: {type:"turn.failed", error:{message:"msg"}}
|
|
348
348
|
* - Gemini: {type:"result", status:"error", error:{message:"msg"}} or {type:"error", severity:"error"}
|
|
349
349
|
* - Opencode: {type:"session.error", error:{message:"msg"}}
|
|
350
|
+
* - Pi: latest assistant message_end, finalized only by a later agent_settled
|
|
350
351
|
*
|
|
351
352
|
* @param {string} providerName - Active provider whose terminal errors may be inspected
|
|
352
353
|
* @returns {{error: string, provider: string}|null} Error info or null
|
|
@@ -397,6 +398,22 @@ function opencodeFailureFromObject(obj) {
|
|
|
397
398
|
};
|
|
398
399
|
}
|
|
399
400
|
|
|
401
|
+
function piFailureFromMessage(message) {
|
|
402
|
+
const stopReason = message.stopReason;
|
|
403
|
+
const errorMessage = message.errorMessage;
|
|
404
|
+
if (stopReason === 'deferred') {
|
|
405
|
+
return {
|
|
406
|
+
...cliErrorDetail('Pi turn deferred without completing.', 'Pi turn deferred'),
|
|
407
|
+
provider: 'pi',
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
if (stopReason !== 'error' && stopReason !== 'aborted' && !errorMessage) return null;
|
|
411
|
+
return {
|
|
412
|
+
...cliErrorDetail(errorMessage || stopReason, 'Pi turn failed'),
|
|
413
|
+
provider: 'pi',
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
400
417
|
function failureFromProviderObject(obj, providerName) {
|
|
401
418
|
if (providerName === 'claude') return claudeFailureFromObject(obj);
|
|
402
419
|
if (providerName === 'codex') return codexFailureFromObject(obj);
|
|
@@ -405,7 +422,105 @@ function failureFromProviderObject(obj, providerName) {
|
|
|
405
422
|
return null;
|
|
406
423
|
}
|
|
407
424
|
|
|
425
|
+
function piProtocolFailure(message) {
|
|
426
|
+
return { ...cliErrorDetail(message, message), provider: 'pi' };
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const PI_STOP_REASONS = new Set(['stop', 'length', 'toolUse', 'error', 'aborted', 'deferred']);
|
|
430
|
+
const PI_USAGE_FIELDS = ['input', 'output', 'cacheRead', 'cacheWrite', 'totalTokens'];
|
|
431
|
+
const PI_COST_FIELDS = ['input', 'output', 'cacheRead', 'cacheWrite', 'total'];
|
|
432
|
+
|
|
433
|
+
function parsePiProtocolObject(content) {
|
|
434
|
+
if (!content.startsWith('{')) return null;
|
|
435
|
+
try {
|
|
436
|
+
const parsed = JSON.parse(content);
|
|
437
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
|
|
438
|
+
} catch {
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function isPiRecord(value) {
|
|
444
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function hasPiNumericFields(value, fields) {
|
|
448
|
+
return isPiRecord(value) && fields.every((field) => typeof value[field] === 'number');
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function isValidPiUsage(usage) {
|
|
452
|
+
if (!hasPiNumericFields(usage, PI_USAGE_FIELDS)) return false;
|
|
453
|
+
if (!hasPiNumericFields(usage.cost, PI_COST_FIELDS)) return false;
|
|
454
|
+
return (
|
|
455
|
+
(usage.cacheWrite1h === undefined || typeof usage.cacheWrite1h === 'number') &&
|
|
456
|
+
(usage.reasoning === undefined || typeof usage.reasoning === 'number')
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function isValidPiAssistantMessage(message) {
|
|
461
|
+
return (
|
|
462
|
+
isPiRecord(message) &&
|
|
463
|
+
message.role === 'assistant' &&
|
|
464
|
+
Array.isArray(message.content) &&
|
|
465
|
+
isValidPiUsage(message.usage) &&
|
|
466
|
+
PI_STOP_REASONS.has(message.stopReason)
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function piMessageRole(message) {
|
|
471
|
+
if (!message || typeof message !== 'object' || Array.isArray(message)) return null;
|
|
472
|
+
return typeof message.role === 'string' ? message.role : null;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function applyPiProtocolLine(content, state) {
|
|
476
|
+
if (!content) return null;
|
|
477
|
+
if (
|
|
478
|
+
content.startsWith('[ZEROSHOT] Earlier provider output omitted') ||
|
|
479
|
+
content.startsWith('[ZEROSHOT] Provider output record retained') ||
|
|
480
|
+
content.startsWith('[ZEROSHOT][PROVIDER_STDERR] ')
|
|
481
|
+
) {
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
if (state.settled) return piProtocolFailure('Pi emitted output after agent_settled.');
|
|
485
|
+
const parsed = parsePiProtocolObject(content);
|
|
486
|
+
if (parsed === null) return piProtocolFailure('Pi JSON stream contained malformed output.');
|
|
487
|
+
if (typeof parsed.type !== 'string') {
|
|
488
|
+
return piProtocolFailure('Pi JSON stream contained an event without a valid type.');
|
|
489
|
+
}
|
|
490
|
+
if (parsed.type === 'message_end') {
|
|
491
|
+
const role = piMessageRole(parsed.message);
|
|
492
|
+
if (role === null) return piProtocolFailure('Pi message_end did not contain a valid message.');
|
|
493
|
+
if (role !== 'assistant') return null;
|
|
494
|
+
if (!isValidPiAssistantMessage(parsed.message)) {
|
|
495
|
+
return piProtocolFailure('Pi message_end did not contain a valid assistant message.');
|
|
496
|
+
}
|
|
497
|
+
state.latestAssistant = parsed.message;
|
|
498
|
+
} else if (parsed.type === 'agent_settled') {
|
|
499
|
+
state.settled = true;
|
|
500
|
+
}
|
|
501
|
+
return null;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function extractSettledPiFailure(lines) {
|
|
505
|
+
const state = { latestAssistant: null, settled: false };
|
|
506
|
+
for (const line of lines) {
|
|
507
|
+
const content = stripTimestamp(line);
|
|
508
|
+
const failure = applyPiProtocolLine(content, state);
|
|
509
|
+
if (failure) return failure;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
if (!state.settled) return piProtocolFailure('Pi JSON stream ended before agent_settled.');
|
|
513
|
+
if (state.latestAssistant === null) {
|
|
514
|
+
return piProtocolFailure('Pi settled without a valid assistant message_end.');
|
|
515
|
+
}
|
|
516
|
+
return piFailureFromMessage(state.latestAssistant);
|
|
517
|
+
}
|
|
518
|
+
|
|
408
519
|
function extractCliFailure(output, providerName = 'claude') {
|
|
520
|
+
if (providerName === 'pi') {
|
|
521
|
+
const lines = typeof output === 'string' ? output.split('\n') : [];
|
|
522
|
+
return extractSettledPiFailure(lines);
|
|
523
|
+
}
|
|
409
524
|
if (!output || typeof output !== 'string') return null;
|
|
410
525
|
|
|
411
526
|
const lines = output.split('\n');
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
|
|
3
|
+
const { parseProviderEvent, providerFailureFields } = require('./provider-control-plane');
|
|
4
|
+
|
|
5
|
+
const ZEROSHOT_PI_METADATA_PREFIXES = [
|
|
6
|
+
'[ZEROSHOT] Earlier provider output omitted',
|
|
7
|
+
'[ZEROSHOT][PROVIDER_STDERR] ',
|
|
8
|
+
'[ZEROSHOT][FATAL] ',
|
|
9
|
+
];
|
|
10
|
+
const ZEROSHOT_RETAINED_RECORD_PREFIX = '[ZEROSHOT] Provider output record retained';
|
|
11
|
+
const PI_STOP_REASONS = new Set(['stop', 'length', 'toolUse', 'error', 'aborted', 'deferred']);
|
|
12
|
+
const PI_REQUIRED_TOKEN_FIELDS = ['input', 'output', 'cacheRead', 'cacheWrite', 'totalTokens'];
|
|
13
|
+
const PI_OPTIONAL_TOKEN_FIELDS = ['cacheWrite1h', 'reasoning'];
|
|
14
|
+
const PI_TOKEN_FIELDS = [...PI_REQUIRED_TOKEN_FIELDS, ...PI_OPTIONAL_TOKEN_FIELDS];
|
|
15
|
+
const PI_COST_FIELDS = ['input', 'output', 'cacheRead', 'cacheWrite', 'total'];
|
|
16
|
+
const PI_PENDING_EVENT_TYPES = new Set([
|
|
17
|
+
'turn_end',
|
|
18
|
+
'agent_end',
|
|
19
|
+
'auto_retry_start',
|
|
20
|
+
'auto_retry_end',
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
function pendingFailureMetadata(failure) {
|
|
24
|
+
return {
|
|
25
|
+
zeroshot_pending_failure: {
|
|
26
|
+
provider: failure.provider,
|
|
27
|
+
category: failure.category,
|
|
28
|
+
kind: failure.classification.kind,
|
|
29
|
+
retryable: failure.classification.retryable,
|
|
30
|
+
diagnostic: failure.diagnostic,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function numericFields(fields, source) {
|
|
36
|
+
return Object.fromEntries(
|
|
37
|
+
fields.map((field) => [field, typeof source[field] === 'number' ? source[field] : 0])
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function redactedUsage(usage) {
|
|
42
|
+
const value = usage && typeof usage === 'object' && !Array.isArray(usage) ? usage : {};
|
|
43
|
+
return {
|
|
44
|
+
...numericFields(PI_REQUIRED_TOKEN_FIELDS, value),
|
|
45
|
+
...(typeof value.cacheWrite1h === 'number' ? { cacheWrite1h: value.cacheWrite1h } : {}),
|
|
46
|
+
...(typeof value.reasoning === 'number' ? { reasoning: value.reasoning } : {}),
|
|
47
|
+
cost: numericFields(PI_COST_FIELDS, isRecord(value.cost) ? value.cost : {}),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isRecord(value) {
|
|
52
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function hasNumericFields(value, fields) {
|
|
56
|
+
return isRecord(value) && fields.every((field) => typeof value[field] === 'number');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function addNumericFields(target, source, fields) {
|
|
60
|
+
for (const field of fields) {
|
|
61
|
+
if (typeof source[field] === 'number') target[field] = (target[field] || 0) + source[field];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function rememberTokenUsage(state, usage) {
|
|
66
|
+
if (!hasNumericFields(usage, PI_REQUIRED_TOKEN_FIELDS)) return false;
|
|
67
|
+
if (!hasNumericFields(usage.cost, PI_COST_FIELDS)) return false;
|
|
68
|
+
if (
|
|
69
|
+
!PI_OPTIONAL_TOKEN_FIELDS.every(
|
|
70
|
+
(field) => usage[field] === undefined || typeof usage[field] === 'number'
|
|
71
|
+
)
|
|
72
|
+
) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
state.piUsage = state.piUsage || { tokens: {}, cost: {} };
|
|
76
|
+
addNumericFields(state.piUsage.tokens, usage, PI_TOKEN_FIELDS);
|
|
77
|
+
addNumericFields(state.piUsage.cost, usage.cost, PI_COST_FIELDS);
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function getPiTokenUsage(state) {
|
|
82
|
+
if (!state?.piUsage) return null;
|
|
83
|
+
const { tokens, cost } = state.piUsage;
|
|
84
|
+
return {
|
|
85
|
+
inputTokens: tokens.input || 0,
|
|
86
|
+
outputTokens: tokens.output || 0,
|
|
87
|
+
cacheReadInputTokens: tokens.cacheRead || 0,
|
|
88
|
+
cacheCreationInputTokens: tokens.cacheWrite || 0,
|
|
89
|
+
totalCostUsd: cost.total || 0,
|
|
90
|
+
durationMs: null,
|
|
91
|
+
modelUsage: { ...tokens, cost: { ...cost } },
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function isValidAssistantMessage(message) {
|
|
96
|
+
return Array.isArray(message?.content) && PI_STOP_REASONS.has(message.stopReason);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function redactPendingFailure(failure, parsed) {
|
|
100
|
+
const pending = pendingFailureMetadata(failure);
|
|
101
|
+
if (parsed.type === 'agent_end') {
|
|
102
|
+
return JSON.stringify({
|
|
103
|
+
type: parsed.type,
|
|
104
|
+
messages: [],
|
|
105
|
+
willRetry: parsed.willRetry === true,
|
|
106
|
+
...pending,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
if (parsed.type === 'auto_retry_end') {
|
|
110
|
+
return JSON.stringify({ type: parsed.type, success: false, ...pending });
|
|
111
|
+
}
|
|
112
|
+
if (parsed.type === 'auto_retry_start') {
|
|
113
|
+
return JSON.stringify({
|
|
114
|
+
type: parsed.type,
|
|
115
|
+
...numericFields(
|
|
116
|
+
['attempt', 'maxAttempts', 'delayMs'].filter((field) => typeof parsed[field] === 'number'),
|
|
117
|
+
parsed
|
|
118
|
+
),
|
|
119
|
+
...pending,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
return JSON.stringify({
|
|
123
|
+
type: parsed.type,
|
|
124
|
+
message: {
|
|
125
|
+
role: 'assistant',
|
|
126
|
+
content: [],
|
|
127
|
+
usage: redactedUsage(parsed.message?.usage),
|
|
128
|
+
stopReason: 'error',
|
|
129
|
+
errorMessage: 'Pi provider turn failed; awaiting agent settlement.',
|
|
130
|
+
},
|
|
131
|
+
...(parsed.type === 'turn_end' ? { toolResults: [] } : {}),
|
|
132
|
+
...pending,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function finalFailureEnvelope(failure) {
|
|
137
|
+
return JSON.stringify({
|
|
138
|
+
type: 'agent_settled',
|
|
139
|
+
...providerFailureFields(failure),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function rememberProtocolFailure(state, content, extractProviderFailure) {
|
|
144
|
+
const failure = extractProviderFailure(content, 'pi');
|
|
145
|
+
if (failure) {
|
|
146
|
+
state.piProtocolFailure = failure;
|
|
147
|
+
state.providerFailure = failure;
|
|
148
|
+
}
|
|
149
|
+
return failure;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function failProtocolLine(state, content, extractProviderFailure) {
|
|
153
|
+
const failure = rememberProtocolFailure(state, content, extractProviderFailure);
|
|
154
|
+
return failure ? finalFailureEnvelope(failure) : content;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function handleMessageEnd(state, content, parsed, extractProviderFailure) {
|
|
158
|
+
const role = parsed.message?.role;
|
|
159
|
+
if (typeof role !== 'string') return failProtocolLine(state, content, extractProviderFailure);
|
|
160
|
+
const hasCountedUsage = role === 'assistant' || parsed.message?.usage !== undefined;
|
|
161
|
+
if (
|
|
162
|
+
['assistant', 'toolResult'].includes(role) &&
|
|
163
|
+
hasCountedUsage &&
|
|
164
|
+
!rememberTokenUsage(state, parsed.message?.usage)
|
|
165
|
+
) {
|
|
166
|
+
return failProtocolLine(state, 'invalid Pi usage', extractProviderFailure);
|
|
167
|
+
}
|
|
168
|
+
if (role !== 'assistant') return content;
|
|
169
|
+
if (!isValidAssistantMessage(parsed.message)) {
|
|
170
|
+
return failProtocolLine(state, content, extractProviderFailure);
|
|
171
|
+
}
|
|
172
|
+
const failure = extractProviderFailure(
|
|
173
|
+
`${content}\n${JSON.stringify({ type: 'agent_settled' })}`,
|
|
174
|
+
'pi'
|
|
175
|
+
);
|
|
176
|
+
state.piLatestAssistantObserved = true;
|
|
177
|
+
state.pendingPiFailure = failure;
|
|
178
|
+
if (failure) return redactPendingFailure(failure, parsed);
|
|
179
|
+
if (!state.piProtocolFailure) state.providerFailure = null;
|
|
180
|
+
return content;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function usageForEvent(parsed) {
|
|
184
|
+
if (parsed.type === 'compaction_end' && parsed.result?.usage !== undefined) {
|
|
185
|
+
return parsed.result.usage;
|
|
186
|
+
}
|
|
187
|
+
if (
|
|
188
|
+
parsed.type === 'entry_appended' &&
|
|
189
|
+
parsed.entry?.type === 'branch_summary' &&
|
|
190
|
+
parsed.entry.usage !== undefined
|
|
191
|
+
) {
|
|
192
|
+
return parsed.entry.usage;
|
|
193
|
+
}
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function handleSettlement(state, content, extractProviderFailure) {
|
|
198
|
+
state.piProtocolSettled = true;
|
|
199
|
+
const missingAssistantFailure = state.piLatestAssistantObserved
|
|
200
|
+
? null
|
|
201
|
+
: extractProviderFailure(JSON.stringify({ type: 'agent_settled' }), 'pi');
|
|
202
|
+
const failure = state.piProtocolFailure || state.pendingPiFailure || missingAssistantFailure;
|
|
203
|
+
state.pendingPiFailure = null;
|
|
204
|
+
state.providerFailure = failure || null;
|
|
205
|
+
return failure ? finalFailureEnvelope(failure) : content;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function redactPiFailureForControlPlane(state, content, extractProviderFailure) {
|
|
209
|
+
if (content.startsWith(ZEROSHOT_RETAINED_RECORD_PREFIX)) {
|
|
210
|
+
state.piProtocolPrefixOmitted = true;
|
|
211
|
+
return content;
|
|
212
|
+
}
|
|
213
|
+
if (ZEROSHOT_PI_METADATA_PREFIXES.some((prefix) => content.startsWith(prefix))) return content;
|
|
214
|
+
const parsed = parseProviderEvent(content);
|
|
215
|
+
if (parsed === null || typeof parsed.type !== 'string') {
|
|
216
|
+
return failProtocolLine(state, content, extractProviderFailure);
|
|
217
|
+
}
|
|
218
|
+
if (state.piProtocolSettled) {
|
|
219
|
+
return failProtocolLine(
|
|
220
|
+
state,
|
|
221
|
+
`${JSON.stringify({ type: 'agent_settled' })}\n${content}`,
|
|
222
|
+
extractProviderFailure
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
state.piProtocolObserved = true;
|
|
226
|
+
|
|
227
|
+
if (parsed.type === 'message_end') {
|
|
228
|
+
return handleMessageEnd(state, content, parsed, extractProviderFailure);
|
|
229
|
+
}
|
|
230
|
+
const usage = usageForEvent(parsed);
|
|
231
|
+
if (usage !== undefined && !rememberTokenUsage(state, usage)) {
|
|
232
|
+
return failProtocolLine(state, 'invalid Pi usage', extractProviderFailure);
|
|
233
|
+
}
|
|
234
|
+
if (state.pendingPiFailure && PI_PENDING_EVENT_TYPES.has(parsed.type)) {
|
|
235
|
+
return redactPendingFailure(state.pendingPiFailure, parsed);
|
|
236
|
+
}
|
|
237
|
+
return parsed.type === 'agent_settled'
|
|
238
|
+
? handleSettlement(state, content, extractProviderFailure)
|
|
239
|
+
: content;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
module.exports = { getPiTokenUsage, redactPiFailureForControlPlane };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
|
|
3
|
+
function parseProviderEvent(content) {
|
|
4
|
+
try {
|
|
5
|
+
const parsed = JSON.parse(content);
|
|
6
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
|
|
7
|
+
} catch {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function providerFailureFields(failure) {
|
|
13
|
+
return {
|
|
14
|
+
error: { message: failure.error },
|
|
15
|
+
zeroshot_failure: {
|
|
16
|
+
provider: failure.provider,
|
|
17
|
+
event: failure.event,
|
|
18
|
+
category: failure.category,
|
|
19
|
+
kind: failure.classification.kind,
|
|
20
|
+
retryable: failure.classification.retryable,
|
|
21
|
+
diagnostic: failure.diagnostic,
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = { parseProviderEvent, providerFailureFields };
|
|
@@ -3,12 +3,23 @@
|
|
|
3
3
|
const { getProvider } = require('../providers');
|
|
4
4
|
const { isCriticalAgent } = require('./critical-agent-policy');
|
|
5
5
|
const { extractCliFailure } = require('./output-extraction');
|
|
6
|
+
const { getPiTokenUsage, redactPiFailureForControlPlane } = require('./pi-terminal-lifecycle');
|
|
7
|
+
const { parseProviderEvent, providerFailureFields } = require('./provider-control-plane');
|
|
8
|
+
|
|
9
|
+
const AUTHENTICATION_FAILURE_PATTERNS = [
|
|
10
|
+
/invalid[_ -]?api[_ -]?key/i,
|
|
11
|
+
/api[_ -]?key.*invalid/i,
|
|
12
|
+
/(?:missing|no) api key/i,
|
|
13
|
+
/run\s*\/login/i,
|
|
14
|
+
/unauthori[sz]ed/i,
|
|
15
|
+
/forbidden|authentication|permission denied/i,
|
|
16
|
+
];
|
|
6
17
|
|
|
7
18
|
function categoryForProviderFailure(error, classification) {
|
|
8
19
|
const isPermanent = classification.retryable === false;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
20
|
+
if (isPermanent && AUTHENTICATION_FAILURE_PATTERNS.some((pattern) => pattern.test(error))) {
|
|
21
|
+
return 'authentication';
|
|
22
|
+
}
|
|
12
23
|
|
|
13
24
|
const quotaPattern = /(?:insufficient[_ -]?quota|quota exceeded|resource_exhausted)/i;
|
|
14
25
|
if (isPermanent && quotaPattern.test(error)) return 'quota';
|
|
@@ -30,6 +41,12 @@ function classifyProviderFailure(providerName, error) {
|
|
|
30
41
|
};
|
|
31
42
|
}
|
|
32
43
|
|
|
44
|
+
function providerFailureEvent(providerName) {
|
|
45
|
+
if (providerName === 'codex') return 'turn.failed';
|
|
46
|
+
if (providerName === 'pi') return 'agent_settled';
|
|
47
|
+
return 'terminal_error';
|
|
48
|
+
}
|
|
49
|
+
|
|
33
50
|
function extractProviderFailure(output, providerName) {
|
|
34
51
|
const cliError = extractCliFailure(output, providerName);
|
|
35
52
|
if (!cliError) return null;
|
|
@@ -39,41 +56,47 @@ function extractProviderFailure(output, providerName) {
|
|
|
39
56
|
return {
|
|
40
57
|
error: `Provider ${cliError.provider} failed (${category}; ${classification.kind})`,
|
|
41
58
|
provider: cliError.provider,
|
|
42
|
-
event: cliError.provider
|
|
59
|
+
event: providerFailureEvent(cliError.provider),
|
|
43
60
|
category,
|
|
44
61
|
classification,
|
|
45
62
|
diagnostic: cliError.diagnostic,
|
|
46
63
|
};
|
|
47
64
|
}
|
|
48
65
|
|
|
49
|
-
function
|
|
50
|
-
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
state.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const parsed = JSON.parse(content);
|
|
57
|
-
if (typeof parsed?.type === 'string') eventType = parsed.type;
|
|
58
|
-
} catch {
|
|
59
|
-
// extractProviderFailure already proved a supported terminal envelope.
|
|
66
|
+
function extractProviderCompletionFailure(output, providerName, state = {}) {
|
|
67
|
+
if (providerName !== 'pi') return extractProviderFailure(output, providerName);
|
|
68
|
+
if (state.providerFailure) return state.providerFailure;
|
|
69
|
+
if (state.piProtocolPrefixOmitted) return extractProviderFailure('', 'pi');
|
|
70
|
+
if (!state.piProtocolObserved) return extractProviderFailure(output, 'pi');
|
|
71
|
+
if (!state.piProtocolSettled || !state.piLatestAssistantObserved) {
|
|
72
|
+
return extractProviderFailure('', 'pi');
|
|
60
73
|
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function redactedFailureEnvelope(failure, providerName, eventType) {
|
|
61
78
|
return JSON.stringify({
|
|
62
79
|
type: eventType,
|
|
63
80
|
...(providerName === 'claude' ? { is_error: true } : {}),
|
|
64
81
|
...(providerName === 'gemini' ? { status: 'error', severity: 'error' } : {}),
|
|
65
|
-
|
|
66
|
-
zeroshot_failure: {
|
|
67
|
-
provider: failure.provider,
|
|
68
|
-
event: failure.event,
|
|
69
|
-
category: failure.category,
|
|
70
|
-
kind: failure.classification.kind,
|
|
71
|
-
retryable: failure.classification.retryable,
|
|
72
|
-
diagnostic: failure.diagnostic,
|
|
73
|
-
},
|
|
82
|
+
...providerFailureFields(failure),
|
|
74
83
|
});
|
|
75
84
|
}
|
|
76
85
|
|
|
86
|
+
function redactTerminalFailureForControlPlane(state, providerName, content) {
|
|
87
|
+
if (providerName === 'pi') {
|
|
88
|
+
return redactPiFailureForControlPlane(state, content, extractProviderFailure);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const failure = extractProviderFailure(content, providerName);
|
|
92
|
+
if (!failure) return content;
|
|
93
|
+
|
|
94
|
+
state.providerFailure = failure;
|
|
95
|
+
const parsed = parseProviderEvent(content);
|
|
96
|
+
const eventType = typeof parsed?.type === 'string' ? parsed.type : 'provider.failure';
|
|
97
|
+
return redactedFailureEnvelope(failure, providerName, eventType);
|
|
98
|
+
}
|
|
99
|
+
|
|
77
100
|
function decorateError(error, failure) {
|
|
78
101
|
if (!failure) return error;
|
|
79
102
|
error.provider = failure.provider || null;
|
|
@@ -176,7 +199,9 @@ function buildFinalFailureInfo({
|
|
|
176
199
|
module.exports = {
|
|
177
200
|
buildFinalFailureInfo,
|
|
178
201
|
decorateError,
|
|
202
|
+
extractProviderCompletionFailure,
|
|
179
203
|
extractProviderFailure,
|
|
204
|
+
getPiTokenUsage,
|
|
180
205
|
publishCriticalFailure,
|
|
181
206
|
receiptFields,
|
|
182
207
|
redactTerminalFailureForControlPlane,
|
|
@@ -25,8 +25,9 @@ export function detectProviderStreamingModeError(
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export function detectProviderFatalError(provider: string, line: unknown): string | null {
|
|
28
|
-
if (
|
|
29
|
-
return
|
|
28
|
+
if (isClaudeProvider(provider)) return detectClaudeFatalError(line);
|
|
29
|
+
if (provider.toLowerCase() === 'pi') return detectPiFatalError(line);
|
|
30
|
+
return null;
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
export function recoverProviderStructuredOutput(
|
|
@@ -84,6 +85,18 @@ function detectClaudeFatalError(line: unknown): string | null {
|
|
|
84
85
|
return null;
|
|
85
86
|
}
|
|
86
87
|
|
|
88
|
+
function detectPiFatalError(line: unknown): string | null {
|
|
89
|
+
if (typeof line !== 'string') return null;
|
|
90
|
+
const message = line.trim();
|
|
91
|
+
if (/\bno api key found\b/i.test(message)) {
|
|
92
|
+
return 'Pi authentication required: run /login';
|
|
93
|
+
}
|
|
94
|
+
if (/\bmodel\b.*\bnot found\b/i.test(message)) {
|
|
95
|
+
return 'Pi model not found: run pi --list-models';
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
87
100
|
function findSessionJsonlPath(sessionId: string): string | null {
|
|
88
101
|
const claudeDir = process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude');
|
|
89
102
|
const projectsDir = join(claudeDir, 'projects');
|
|
@@ -90,6 +90,12 @@ export function parseProviderChunk(
|
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
const finalEvent = adapter.finishParsing?.(state);
|
|
94
|
+
if (finalEvent) {
|
|
95
|
+
if (isOutputEventArray(finalEvent)) events.push(...finalEvent);
|
|
96
|
+
else events.push(finalEvent);
|
|
97
|
+
}
|
|
98
|
+
|
|
93
99
|
return events;
|
|
94
100
|
}
|
|
95
101
|
|