@the-open-engine/zeroshot 6.37.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/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-task-executor.js +79 -20
- 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/isolation-manager.js +43 -29
- package/src/orchestrator.js +33 -1
- package/task-lib/watcher-output-runtime.js +16 -5
|
@@ -56,6 +56,8 @@ const {
|
|
|
56
56
|
const { extractClaudeVertexModelError } = require('./output-extraction');
|
|
57
57
|
const {
|
|
58
58
|
extractProviderFailure,
|
|
59
|
+
extractProviderCompletionFailure,
|
|
60
|
+
getPiTokenUsage,
|
|
59
61
|
redactTerminalFailureForControlPlane,
|
|
60
62
|
} = require('./provider-terminal-failure');
|
|
61
63
|
const {
|
|
@@ -377,7 +379,9 @@ function extractErrorContext({
|
|
|
377
379
|
}
|
|
378
380
|
}
|
|
379
381
|
|
|
380
|
-
const terminalFailure =
|
|
382
|
+
const terminalFailure =
|
|
383
|
+
providerFailure ||
|
|
384
|
+
(providerName === 'pi' ? null : extractProviderFailure(output, providerName));
|
|
381
385
|
if (terminalFailure) {
|
|
382
386
|
return sanitizeErrorMessage(terminalFailure.error);
|
|
383
387
|
}
|
|
@@ -449,8 +453,12 @@ function extractErrorContext({
|
|
|
449
453
|
* @param {string} output - Full NDJSON output from Claude CLI
|
|
450
454
|
* @returns {Object|null} Token usage data or null if not found
|
|
451
455
|
*/
|
|
452
|
-
function extractTokenUsage(output, providerName = getDefaultProviderId()) {
|
|
456
|
+
function extractTokenUsage(output, providerName = getDefaultProviderId(), state = null) {
|
|
453
457
|
if (!output) return null;
|
|
458
|
+
if (providerName === 'pi') {
|
|
459
|
+
const retainedUsage = getPiTokenUsage(state);
|
|
460
|
+
if (retainedUsage !== null) return retainedUsage;
|
|
461
|
+
}
|
|
454
462
|
|
|
455
463
|
const events = parseProviderChunk(providerName, output);
|
|
456
464
|
const resultEvent = events.find((event) => event.type === 'result');
|
|
@@ -1465,21 +1473,38 @@ function lookupLogFilePath(ctPath, taskId) {
|
|
|
1465
1473
|
function parseTimestampedLine(line) {
|
|
1466
1474
|
let timestamp = Date.now();
|
|
1467
1475
|
let content = line.replace(/\r$/, '');
|
|
1476
|
+
let timestamped = false;
|
|
1468
1477
|
|
|
1469
1478
|
const timestampMatch = content.match(/^\[(\d{13})\](.*)$/);
|
|
1470
1479
|
if (timestampMatch) {
|
|
1471
1480
|
timestamp = parseInt(timestampMatch[1], 10);
|
|
1472
1481
|
content = timestampMatch[2];
|
|
1482
|
+
timestamped = true;
|
|
1473
1483
|
}
|
|
1474
1484
|
|
|
1475
|
-
return { timestamp, content };
|
|
1485
|
+
return { timestamp, content, timestamped };
|
|
1476
1486
|
}
|
|
1477
1487
|
|
|
1478
|
-
function shouldSkipLogLine(content) {
|
|
1488
|
+
function shouldSkipLogLine(content, providerName, timestamped) {
|
|
1489
|
+
if (providerName === 'pi') {
|
|
1490
|
+
if (
|
|
1491
|
+
content.startsWith('[ZEROSHOT][PROVIDER_STDERR] ') ||
|
|
1492
|
+
content.startsWith('[ZEROSHOT][FATAL] ')
|
|
1493
|
+
) {
|
|
1494
|
+
return true;
|
|
1495
|
+
}
|
|
1496
|
+
if (timestamped) return false;
|
|
1497
|
+
return (
|
|
1498
|
+
/^={50}$/.test(content) ||
|
|
1499
|
+
/^Finished: \d{4}-\d{2}-\d{2}T[^\s]+Z$/.test(content) ||
|
|
1500
|
+
/^Exit code: (?:null|-?\d+), Signal: (?:null|[A-Z0-9]+)$/.test(content)
|
|
1501
|
+
);
|
|
1502
|
+
}
|
|
1479
1503
|
return (
|
|
1480
1504
|
content.startsWith('===') ||
|
|
1481
1505
|
content.startsWith('Finished:') ||
|
|
1482
1506
|
content.startsWith('Exit code:') ||
|
|
1507
|
+
content.startsWith('[ZEROSHOT][PROVIDER_STDERR] ') ||
|
|
1483
1508
|
(content.includes('"type":"system"') && content.includes('"subtype":"init"'))
|
|
1484
1509
|
);
|
|
1485
1510
|
}
|
|
@@ -1520,8 +1545,8 @@ function broadcastAgentLine({ agent, providerName, state, line }) {
|
|
|
1520
1545
|
const followerState = ensureControlPlaneOutputState(state);
|
|
1521
1546
|
if (!line.trim()) return;
|
|
1522
1547
|
|
|
1523
|
-
const { timestamp, content } = parseTimestampedLine(line);
|
|
1524
|
-
if (shouldSkipLogLine(content)) {
|
|
1548
|
+
const { timestamp, content, timestamped } = parseTimestampedLine(line);
|
|
1549
|
+
if (shouldSkipLogLine(content, providerName, timestamped)) {
|
|
1525
1550
|
return;
|
|
1526
1551
|
}
|
|
1527
1552
|
const controlPlaneContent = redactTerminalFailureForControlPlane(
|
|
@@ -1764,7 +1789,8 @@ function buildFailureContext({ agent, taskId, providerName, state, stdout, provi
|
|
|
1764
1789
|
function resolveCompletionFailure({ classified, agent, taskId, providerName, state, stdout }) {
|
|
1765
1790
|
if (classified.success) return { providerFailure: null, errorContext: classified.error };
|
|
1766
1791
|
const providerFailure =
|
|
1767
|
-
state.providerFailure ||
|
|
1792
|
+
state.providerFailure ||
|
|
1793
|
+
(providerName === 'pi' ? null : extractProviderFailure(state.output, providerName));
|
|
1768
1794
|
return {
|
|
1769
1795
|
providerFailure,
|
|
1770
1796
|
errorContext:
|
|
@@ -1774,6 +1800,14 @@ function resolveCompletionFailure({ classified, agent, taskId, providerName, sta
|
|
|
1774
1800
|
};
|
|
1775
1801
|
}
|
|
1776
1802
|
|
|
1803
|
+
function enforceProviderCompletionTruth({ providerName, output, state, success, error }) {
|
|
1804
|
+
if (providerName !== 'pi' || !success) return { success, error };
|
|
1805
|
+
const failure = extractProviderCompletionFailure(output, providerName, state);
|
|
1806
|
+
if (!failure) return { success, error };
|
|
1807
|
+
state.providerFailure = failure;
|
|
1808
|
+
return { success: false, error: failure.error };
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1777
1811
|
async function buildCompletionResult({
|
|
1778
1812
|
agent,
|
|
1779
1813
|
taskId,
|
|
@@ -1784,7 +1818,7 @@ async function buildCompletionResult({
|
|
|
1784
1818
|
taskInfo = getTask(taskId),
|
|
1785
1819
|
}) {
|
|
1786
1820
|
const { isCompleted } = parseStatusFlags(stripAnsiCodes(stdout));
|
|
1787
|
-
|
|
1821
|
+
let classified = state.skipStructuredResultCheck
|
|
1788
1822
|
? { success, error: null }
|
|
1789
1823
|
: await evaluateStructuredSuccess({
|
|
1790
1824
|
agent,
|
|
@@ -1793,6 +1827,12 @@ async function buildCompletionResult({
|
|
|
1793
1827
|
success,
|
|
1794
1828
|
allowRecovery: isCompleted,
|
|
1795
1829
|
});
|
|
1830
|
+
classified = enforceProviderCompletionTruth({
|
|
1831
|
+
providerName,
|
|
1832
|
+
output: state.output,
|
|
1833
|
+
state,
|
|
1834
|
+
...classified,
|
|
1835
|
+
});
|
|
1796
1836
|
const resumeIdentityError = classified.success ? validateCompletedResumeIdentity(taskInfo) : null;
|
|
1797
1837
|
if (resumeIdentityError) {
|
|
1798
1838
|
classified.success = false;
|
|
@@ -1823,7 +1863,7 @@ async function buildCompletionResult({
|
|
|
1823
1863
|
// so downstream hooks and {{result.*}} substitution never re-parse.
|
|
1824
1864
|
parsedResult: state._cachedParsedResult || null,
|
|
1825
1865
|
error: errorContext,
|
|
1826
|
-
tokenUsage: extractTokenUsage(state.output, providerName),
|
|
1866
|
+
tokenUsage: extractTokenUsage(state.output, providerName, state),
|
|
1827
1867
|
providerSession: providerSessionFromCompletedTask({
|
|
1828
1868
|
agent,
|
|
1829
1869
|
providerName,
|
|
@@ -2060,7 +2100,7 @@ function buildKillHandler({ agent, taskId, state, providerName, resolve }) {
|
|
|
2060
2100
|
error: reason,
|
|
2061
2101
|
code: details.code || null,
|
|
2062
2102
|
taskId,
|
|
2063
|
-
tokenUsage: extractTokenUsage(state.output, providerName),
|
|
2103
|
+
tokenUsage: extractTokenUsage(state.output, providerName, state),
|
|
2064
2104
|
});
|
|
2065
2105
|
},
|
|
2066
2106
|
};
|
|
@@ -2734,6 +2774,7 @@ async function captureIsolatedFinalOutputTail(
|
|
|
2734
2774
|
]);
|
|
2735
2775
|
if (finalReadResult.code === 0 && finalReadResult.stdout) {
|
|
2736
2776
|
if (missingBytes > readBytes) {
|
|
2777
|
+
if (providerName === 'pi') state.piProtocolPrefixOmitted = true;
|
|
2737
2778
|
resetControlPlaneTail(state.controlPlaneOutput, { prefixOmitted: true });
|
|
2738
2779
|
state.lineBuffer = createLogRecordBuffer();
|
|
2739
2780
|
}
|
|
@@ -2760,19 +2801,21 @@ function resolveIsolatedFailure({
|
|
|
2760
2801
|
providerName,
|
|
2761
2802
|
state,
|
|
2762
2803
|
status,
|
|
2804
|
+
statusOutput,
|
|
2763
2805
|
isNotFound,
|
|
2764
2806
|
clusterId,
|
|
2765
2807
|
logFilePath,
|
|
2766
2808
|
}) {
|
|
2767
2809
|
if (success) return { providerFailure: null, errorContext: structuredError };
|
|
2768
2810
|
const providerFailure =
|
|
2769
|
-
state.providerFailure ||
|
|
2811
|
+
state.providerFailure ||
|
|
2812
|
+
(providerName === 'pi' ? null : extractProviderFailure(state.fullOutput, providerName));
|
|
2770
2813
|
const errorContext =
|
|
2771
2814
|
providerFailure?.error ||
|
|
2772
2815
|
structuredError ||
|
|
2773
2816
|
extractErrorContext({
|
|
2774
2817
|
output: state.fullOutput,
|
|
2775
|
-
statusOutput: status ? `Status: ${status}` : '',
|
|
2818
|
+
statusOutput: statusOutput || (status ? `Status: ${status}` : ''),
|
|
2776
2819
|
taskId,
|
|
2777
2820
|
isNotFound,
|
|
2778
2821
|
providerName,
|
|
@@ -2797,6 +2840,7 @@ function settleIsolatedTerminalStatus({
|
|
|
2797
2840
|
taskId,
|
|
2798
2841
|
providerName,
|
|
2799
2842
|
status,
|
|
2843
|
+
statusOutput = '',
|
|
2800
2844
|
isNotFound = false,
|
|
2801
2845
|
state,
|
|
2802
2846
|
cleanup,
|
|
@@ -2805,7 +2849,6 @@ function settleIsolatedTerminalStatus({
|
|
|
2805
2849
|
}) {
|
|
2806
2850
|
if (state.resolved) return Promise.resolve();
|
|
2807
2851
|
if (state.terminalSettlementPromise) return state.terminalSettlementPromise;
|
|
2808
|
-
|
|
2809
2852
|
state.taskExited = true;
|
|
2810
2853
|
if (status) {
|
|
2811
2854
|
state.durableTaskTerminal = true;
|
|
@@ -2842,6 +2885,15 @@ function settleIsolatedTerminalStatus({
|
|
|
2842
2885
|
success = evaluated.success;
|
|
2843
2886
|
structuredError = evaluated.error;
|
|
2844
2887
|
}
|
|
2888
|
+
const completionTruth = enforceProviderCompletionTruth({
|
|
2889
|
+
providerName,
|
|
2890
|
+
output: state.fullOutput,
|
|
2891
|
+
state,
|
|
2892
|
+
success,
|
|
2893
|
+
error: structuredError,
|
|
2894
|
+
});
|
|
2895
|
+
success = completionTruth.success;
|
|
2896
|
+
structuredError = completionTruth.error;
|
|
2845
2897
|
const { providerFailure, errorContext } = resolveIsolatedFailure({
|
|
2846
2898
|
success,
|
|
2847
2899
|
structuredError,
|
|
@@ -2850,6 +2902,7 @@ function settleIsolatedTerminalStatus({
|
|
|
2850
2902
|
providerName,
|
|
2851
2903
|
state,
|
|
2852
2904
|
status,
|
|
2905
|
+
statusOutput,
|
|
2853
2906
|
isNotFound,
|
|
2854
2907
|
clusterId,
|
|
2855
2908
|
logFilePath,
|
|
@@ -2860,7 +2913,6 @@ function settleIsolatedTerminalStatus({
|
|
|
2860
2913
|
? state._cachedParsedResult
|
|
2861
2914
|
: await agent._parseResultOutput(state.fullOutput);
|
|
2862
2915
|
}
|
|
2863
|
-
|
|
2864
2916
|
settleIsolatedFollower({
|
|
2865
2917
|
agent,
|
|
2866
2918
|
state,
|
|
@@ -2872,7 +2924,7 @@ function settleIsolatedTerminalStatus({
|
|
|
2872
2924
|
taskId,
|
|
2873
2925
|
parsedResult,
|
|
2874
2926
|
error: errorContext,
|
|
2875
|
-
tokenUsage: extractTokenUsage(state.fullOutput, providerName),
|
|
2927
|
+
tokenUsage: extractTokenUsage(state.fullOutput, providerName, state),
|
|
2876
2928
|
providerFailure,
|
|
2877
2929
|
vertexModelError,
|
|
2878
2930
|
},
|
|
@@ -2908,7 +2960,7 @@ function buildIsolatedLifecycleHandle({
|
|
|
2908
2960
|
error: reason,
|
|
2909
2961
|
code: details.code || null,
|
|
2910
2962
|
taskId,
|
|
2911
|
-
tokenUsage: extractTokenUsage(state.fullOutput, providerName),
|
|
2963
|
+
tokenUsage: extractTokenUsage(state.fullOutput, providerName, state),
|
|
2912
2964
|
},
|
|
2913
2965
|
});
|
|
2914
2966
|
};
|
|
@@ -2971,10 +3023,14 @@ function buildIsolatedLifecycleHandle({
|
|
|
2971
3023
|
}
|
|
2972
3024
|
|
|
2973
3025
|
function parseIsolatedLogLine(line) {
|
|
2974
|
-
const timestampMatch = line.match(/^\[(\d{4}-\d{2}-\d{2}T[^\]]+)\]\s*(.*)$/);
|
|
2975
|
-
const timestamp = timestampMatch
|
|
3026
|
+
const timestampMatch = line.match(/^\[(\d{13}|\d{4}-\d{2}-\d{2}T[^\]]+)\]\s*(.*)$/);
|
|
3027
|
+
const timestamp = timestampMatch
|
|
3028
|
+
? /^\d{13}$/.test(timestampMatch[1])
|
|
3029
|
+
? Number.parseInt(timestampMatch[1], 10)
|
|
3030
|
+
: new Date(timestampMatch[1]).getTime()
|
|
3031
|
+
: Date.now();
|
|
2976
3032
|
const content = timestampMatch ? timestampMatch[2] : line;
|
|
2977
|
-
return { timestamp, content };
|
|
3033
|
+
return { timestamp, content, timestamped: timestampMatch !== null };
|
|
2978
3034
|
}
|
|
2979
3035
|
|
|
2980
3036
|
function publishIsolatedOutputRecord(agent, providerName, taskId, record) {
|
|
@@ -2996,7 +3052,8 @@ function publishIsolatedOutputRecord(agent, providerName, taskId, record) {
|
|
|
2996
3052
|
}
|
|
2997
3053
|
|
|
2998
3054
|
function retainIsolatedLine(state, providerName, line) {
|
|
2999
|
-
const { timestamp, content } = parseIsolatedLogLine(line);
|
|
3055
|
+
const { timestamp, content, timestamped } = parseIsolatedLogLine(line);
|
|
3056
|
+
if (!content.trim() || shouldSkipLogLine(content, providerName, timestamped)) return null;
|
|
3000
3057
|
const controlPlaneContent = redactTerminalFailureForControlPlane(state, providerName, content);
|
|
3001
3058
|
return appendControlPlaneRecord(state.controlPlaneOutput, {
|
|
3002
3059
|
content: controlPlaneContent,
|
|
@@ -3008,6 +3065,7 @@ function retainIsolatedLine(state, providerName, line) {
|
|
|
3008
3065
|
function broadcastIsolatedLine({ agent, providerName, taskId, state, line }) {
|
|
3009
3066
|
const followerState = ensureControlPlaneOutputState(state);
|
|
3010
3067
|
const record = retainIsolatedLine(followerState, providerName, line);
|
|
3068
|
+
if (record === null) return;
|
|
3011
3069
|
publishLiveControlPlaneRecord(followerState.controlPlaneOutput, record, (item) =>
|
|
3012
3070
|
publishIsolatedOutputRecord(agent, providerName, taskId, item)
|
|
3013
3071
|
);
|
|
@@ -3134,6 +3192,7 @@ async function checkIsolatedStatus({
|
|
|
3134
3192
|
taskId,
|
|
3135
3193
|
providerName,
|
|
3136
3194
|
status,
|
|
3195
|
+
statusOutput,
|
|
3137
3196
|
isNotFound,
|
|
3138
3197
|
state,
|
|
3139
3198
|
cleanup,
|
|
@@ -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 };
|