@the-open-engine/zeroshot 6.37.0 → 6.38.1

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.
Files changed (78) hide show
  1. package/cli/lib/setup-scanner-worker.js +2 -9
  2. package/lib/agent-cli-provider/adapters/claude-recovery.d.ts.map +1 -1
  3. package/lib/agent-cli-provider/adapters/claude-recovery.js +17 -3
  4. package/lib/agent-cli-provider/adapters/claude-recovery.js.map +1 -1
  5. package/lib/agent-cli-provider/adapters/codex.d.ts.map +1 -1
  6. package/lib/agent-cli-provider/adapters/codex.js +3 -1
  7. package/lib/agent-cli-provider/adapters/codex.js.map +1 -1
  8. package/lib/agent-cli-provider/adapters/index.d.ts.map +1 -1
  9. package/lib/agent-cli-provider/adapters/index.js +7 -0
  10. package/lib/agent-cli-provider/adapters/index.js.map +1 -1
  11. package/lib/agent-cli-provider/adapters/pi.d.ts.map +1 -1
  12. package/lib/agent-cli-provider/adapters/pi.js +86 -97
  13. package/lib/agent-cli-provider/adapters/pi.js.map +1 -1
  14. package/lib/agent-cli-provider/contract-parse.d.ts.map +1 -1
  15. package/lib/agent-cli-provider/contract-parse.js +5 -1
  16. package/lib/agent-cli-provider/contract-parse.js.map +1 -1
  17. package/lib/agent-cli-provider/invoke-evidence.d.ts.map +1 -1
  18. package/lib/agent-cli-provider/invoke-evidence.js +8 -1
  19. package/lib/agent-cli-provider/invoke-evidence.js.map +1 -1
  20. package/lib/agent-cli-provider/pi/credentials.d.ts +2 -0
  21. package/lib/agent-cli-provider/pi/credentials.d.ts.map +1 -0
  22. package/lib/agent-cli-provider/pi/credentials.js +51 -0
  23. package/lib/agent-cli-provider/pi/credentials.js.map +1 -0
  24. package/lib/agent-cli-provider/pi/events.d.ts +5 -0
  25. package/lib/agent-cli-provider/pi/events.d.ts.map +1 -0
  26. package/lib/agent-cli-provider/pi/events.js +124 -0
  27. package/lib/agent-cli-provider/pi/events.js.map +1 -0
  28. package/lib/agent-cli-provider/pi/protocol-state.d.ts +6 -0
  29. package/lib/agent-cli-provider/pi/protocol-state.d.ts.map +1 -0
  30. package/lib/agent-cli-provider/pi/protocol-state.js +138 -0
  31. package/lib/agent-cli-provider/pi/protocol-state.js.map +1 -0
  32. package/lib/agent-cli-provider/pi/release.d.ts +4 -0
  33. package/lib/agent-cli-provider/pi/release.d.ts.map +1 -0
  34. package/lib/agent-cli-provider/pi/release.js +7 -0
  35. package/lib/agent-cli-provider/pi/release.js.map +1 -0
  36. package/lib/agent-cli-provider/probe-availability.d.ts +5 -0
  37. package/lib/agent-cli-provider/probe-availability.d.ts.map +1 -0
  38. package/lib/agent-cli-provider/probe-availability.js +11 -0
  39. package/lib/agent-cli-provider/probe-availability.js.map +1 -0
  40. package/lib/agent-cli-provider/provider-registry.d.ts +13 -9
  41. package/lib/agent-cli-provider/provider-registry.d.ts.map +1 -1
  42. package/lib/agent-cli-provider/provider-registry.js +42 -8
  43. package/lib/agent-cli-provider/provider-registry.js.map +1 -1
  44. package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -1
  45. package/lib/agent-cli-provider/single-agent-runtime.js +15 -5
  46. package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -1
  47. package/lib/agent-cli-provider/types.d.ts +3 -0
  48. package/lib/agent-cli-provider/types.d.ts.map +1 -1
  49. package/lib/agent-cli-provider/types.js.map +1 -1
  50. package/lib/cluster-worker/engine-adapter.js +9 -1
  51. package/lib/docker-config.js +6 -1
  52. package/lib/provider-credential-path.js +36 -0
  53. package/lib/start-cluster.js +8 -0
  54. package/npm-shrinkwrap.json +2 -2
  55. package/package.json +1 -1
  56. package/src/agent/agent-task-executor.js +79 -20
  57. package/src/agent/output-extraction.js +115 -0
  58. package/src/agent/pi-terminal-lifecycle.js +242 -0
  59. package/src/agent/provider-control-plane.js +26 -0
  60. package/src/agent/provider-terminal-failure.js +49 -24
  61. package/src/agent-cli-provider/adapters/claude-recovery.ts +15 -2
  62. package/src/agent-cli-provider/adapters/codex.ts +3 -1
  63. package/src/agent-cli-provider/adapters/index.ts +6 -0
  64. package/src/agent-cli-provider/adapters/pi.ts +105 -125
  65. package/src/agent-cli-provider/contract-parse.ts +8 -1
  66. package/src/agent-cli-provider/invoke-evidence.ts +11 -1
  67. package/src/agent-cli-provider/pi/credentials.ts +47 -0
  68. package/src/agent-cli-provider/pi/events.ts +148 -0
  69. package/src/agent-cli-provider/pi/protocol-state.ts +163 -0
  70. package/src/agent-cli-provider/pi/release.ts +4 -0
  71. package/src/agent-cli-provider/probe-availability.ts +19 -0
  72. package/src/agent-cli-provider/provider-registry.ts +48 -9
  73. package/src/agent-cli-provider/single-agent-runtime.ts +27 -6
  74. package/src/agent-cli-provider/types.ts +3 -0
  75. package/src/config-validator.js +1 -3
  76. package/src/isolation-manager.js +43 -29
  77. package/src/orchestrator.js +33 -1
  78. package/task-lib/watcher-output-runtime.js +16 -5
@@ -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
- const authenticationPattern =
10
- /(?:invalid[_ -]?api[_ -]?key|api[_ -]?key.*invalid|unauthori[sz]ed|forbidden|authentication|permission denied)/i;
11
- if (isPermanent && authenticationPattern.test(error)) return 'authentication';
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 === 'codex' ? 'turn.failed' : 'terminal_error',
59
+ event: providerFailureEvent(cliError.provider),
43
60
  category,
44
61
  classification,
45
62
  diagnostic: cliError.diagnostic,
46
63
  };
47
64
  }
48
65
 
49
- function redactTerminalFailureForControlPlane(state, providerName, content) {
50
- const failure = extractProviderFailure(content, providerName);
51
- if (!failure) return content;
52
-
53
- state.providerFailure = failure;
54
- let eventType = 'provider.failure';
55
- try {
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
- error: { message: failure.error },
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 (!isClaudeProvider(provider)) return null;
29
- return detectClaudeFatalError(line);
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');
@@ -222,6 +222,8 @@ function buildCommand(context: string, options: BuildProviderCommandOptions = {}
222
222
  : null;
223
223
  addWebSearchArgs(args, options);
224
224
  if (resumeSessionId) {
225
+ // Codex 0.147 parses --sandbox on `exec`, not on the `resume` subcommand.
226
+ addAutoApproveArgs(args, options);
225
227
  args.push('resume');
226
228
  }
227
229
 
@@ -229,8 +231,8 @@ function buildCommand(context: string, options: BuildProviderCommandOptions = {}
229
231
  addModelArgs(args, options);
230
232
  if (!resumeSessionId) {
231
233
  addCwdArgs(args, options);
234
+ addAutoApproveArgs(args, options);
232
235
  }
233
- addAutoApproveArgs(args, options);
234
236
  addSkipGitArgs(args, options);
235
237
  const finalContext = applySchemaArgs(args, cleanup, context, options);
236
238
 
@@ -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
 
@@ -1,15 +1,9 @@
1
1
  import { appendJsonSchemaPrompt } from '../schema';
2
2
  import { contractError } from '../contract-errors';
3
- import {
4
- emitAssistantSnapshot,
5
- parseAssistantDelta,
6
- parseToolExecutionEnd,
7
- parseToolExecutionStart,
8
- parseToolExecutionUpdate,
9
- parseTurnEndResult,
10
- resetAssistantSnapshot,
11
- } from '../assistant-stream';
12
- import { getRecord, getString, isRecord, tryParseJson, unknownToMessage } from '../json';
3
+ import { unknownToMessage } from '../json';
4
+ import { PI_CREDENTIAL_ENV_KEYS } from '../pi/credentials';
5
+ import { createPiParserState, finishPiParsing, parsePiEvent } from '../pi/events';
6
+ import { PI_SUPPORTED_VERSION } from '../pi/release';
13
7
  import {
14
8
  type BuildProviderCommandOptions,
15
9
  type CommandSpec,
@@ -18,17 +12,15 @@ import {
18
12
  type LevelOverrides,
19
13
  type ModelCatalogEntry,
20
14
  type ModelLevel,
21
- type OutputEvent,
22
15
  type PiCliFeatures,
23
16
  type ProviderAdapter,
24
- type ProviderParserState,
25
17
  type ResolvedModelSpec,
26
18
  type WarningMetadata,
27
19
  } from '../types';
28
20
  import {
29
21
  classifyBaseProviderError,
30
22
  commandSpec,
31
- createParserState,
23
+ isCliVersionAtLeast,
32
24
  optionFeatures,
33
25
  resolveModelSpecWithConfig,
34
26
  warning,
@@ -37,45 +29,104 @@ import {
37
29
  const MODEL_CATALOG: Readonly<Record<string, ModelCatalogEntry>> = {};
38
30
 
39
31
  const LEVEL_MAPPING: Readonly<Record<ModelLevel, LevelModelSpec>> = {
40
- level1: { rank: 1, model: null },
41
- level2: { rank: 2, model: null },
42
- level3: { rank: 3, model: null },
32
+ level1: { rank: 1, model: null, reasoningEffort: 'low' },
33
+ level2: { rank: 2, model: null, reasoningEffort: 'medium' },
34
+ level3: { rank: 3, model: null, reasoningEffort: 'high' },
43
35
  };
44
36
 
45
- const IGNORED_EVENT_TYPES = new Set([
46
- 'session',
47
- 'agent_start',
48
- 'agent_end',
49
- 'turn_start',
50
- 'queue_update',
51
- 'compaction_start',
52
- 'compaction_end',
53
- 'auto_retry_start',
54
- 'auto_retry_end',
55
- ]);
37
+ function supportsFlag(
38
+ help: string,
39
+ assumeCurrent: boolean,
40
+ versionMatches: boolean,
41
+ pattern: RegExp
42
+ ): boolean {
43
+ return versionMatches && (assumeCurrent || pattern.test(help));
44
+ }
56
45
 
57
- function detectCliFeatures(helpText?: string | null): PiCliFeatures {
46
+ function detectCliFeatures(helpText?: string | null, versionText?: string | null): PiCliFeatures {
58
47
  const help = helpText ?? '';
59
- const unknown = !help;
48
+ const versionMatches = isCliVersionAtLeast(versionText, PI_SUPPORTED_VERSION);
49
+ const assumeCurrent = !help && versionMatches;
60
50
  return {
61
51
  provider: 'pi',
62
- supportsJsonMode: unknown ? true : /--mode\b/.test(help) && /\bjson\b/.test(help),
63
- supportsModel: unknown ? true : /--model\b/.test(help),
64
- supportsNoSession: unknown ? true : /--no-session\b/.test(help),
65
- supportsNoExtensions: unknown ? true : /--no-extensions\b/.test(help),
66
- supportsNoSkills: unknown ? true : /--no-skills\b/.test(help),
67
- supportsNoPromptTemplates: unknown ? true : /--no-prompt-templates\b/.test(help),
68
- supportsNoContextFiles: unknown ? true : /--no-context-files\b/.test(help),
69
- supportsNoApprove: unknown ? true : /--no-approve\b/.test(help),
70
- unknown,
52
+ versionMatches,
53
+ supportsJsonMode:
54
+ supportsFlag(help, assumeCurrent, versionMatches, /--mode\b/) &&
55
+ (assumeCurrent || /\bjson\b/.test(help)),
56
+ supportsModel: supportsFlag(help, assumeCurrent, versionMatches, /--model\b/),
57
+ supportsThinking: supportsFlag(help, assumeCurrent, versionMatches, /--thinking\b/),
58
+ supportsNoSession: supportsFlag(help, assumeCurrent, versionMatches, /--no-session\b/),
59
+ supportsNoExtensions: supportsFlag(help, assumeCurrent, versionMatches, /--no-extensions\b/),
60
+ supportsNoSkills: supportsFlag(help, assumeCurrent, versionMatches, /--no-skills\b/),
61
+ supportsNoPromptTemplates: supportsFlag(
62
+ help,
63
+ assumeCurrent,
64
+ versionMatches,
65
+ /--no-prompt-templates\b/
66
+ ),
67
+ supportsNoContextFiles: supportsFlag(
68
+ help,
69
+ assumeCurrent,
70
+ versionMatches,
71
+ /--no-context-files\b/
72
+ ),
73
+ supportsNoApprove: supportsFlag(help, assumeCurrent, versionMatches, /--no-approve\b/),
74
+ unknown: !help || !(versionText ?? '').trim(),
71
75
  };
72
76
  }
73
77
 
78
+ function assertRequiredControls(options: BuildProviderCommandOptions): void {
79
+ const features = optionFeatures(options);
80
+ if (features.versionMatches !== true) {
81
+ throw contractError({
82
+ code: 'invalid-field',
83
+ field: 'options.cliFeatures.versionMatches',
84
+ exitCode: 2,
85
+ message:
86
+ `Pi ${PI_SUPPORTED_VERSION} or newer is required for the agent_settled JSON protocol. ` +
87
+ `Upgrade with: npm install -g --ignore-scripts ` +
88
+ `@earendil-works/pi-coding-agent@${PI_SUPPORTED_VERSION}`,
89
+ });
90
+ }
91
+ const required = [
92
+ ['supportsJsonMode', '--mode json'],
93
+ ['supportsNoSession', '--no-session'],
94
+ ['supportsNoSkills', '--no-skills'],
95
+ ['supportsNoPromptTemplates', '--no-prompt-templates'],
96
+ ['supportsNoContextFiles', '--no-context-files'],
97
+ ['supportsNoApprove', '--no-approve'],
98
+ ] as const;
99
+ for (const [field, flag] of required) {
100
+ if (features[field] !== false) continue;
101
+ throw contractError({
102
+ code: 'invalid-field',
103
+ field: `options.cliFeatures.${field}`,
104
+ exitCode: 2,
105
+ message: `Pi ${PI_SUPPORTED_VERSION}+ support for ${flag} is required for Zeroshot execution.`,
106
+ });
107
+ }
108
+ if (options.modelSpec?.model && features.supportsModel === false) {
109
+ throw contractError({
110
+ code: 'invalid-field',
111
+ field: 'options.cliFeatures.supportsModel',
112
+ exitCode: 2,
113
+ message: `Pi ${PI_SUPPORTED_VERSION}+ support for --model is required for explicit model selection.`,
114
+ });
115
+ }
116
+ if (options.modelSpec?.reasoningEffort && features.supportsThinking === false) {
117
+ throw contractError({
118
+ code: 'invalid-field',
119
+ field: 'options.cliFeatures.supportsThinking',
120
+ exitCode: 2,
121
+ message: `Pi ${PI_SUPPORTED_VERSION}+ support for --thinking is required for reasoning effort.`,
122
+ });
123
+ }
124
+ }
125
+
74
126
  function addRequiredArgs(args: string[], options: BuildProviderCommandOptions): void {
75
127
  const features = optionFeatures(options);
76
128
  if (features.supportsJsonMode !== false) args.push('--mode', 'json');
77
129
  if (features.supportsNoSession !== false) args.push('--no-session');
78
- if (features.supportsNoExtensions !== false) args.push('--no-extensions');
79
130
  if (features.supportsNoSkills !== false) args.push('--no-skills');
80
131
  if (features.supportsNoPromptTemplates !== false) args.push('--no-prompt-templates');
81
132
  if (features.supportsNoContextFiles !== false) args.push('--no-context-files');
@@ -87,6 +138,9 @@ function addOptionalArgs(args: string[], options: BuildProviderCommandOptions):
87
138
  if (options.modelSpec?.model && features.supportsModel !== false) {
88
139
  args.push('--model', options.modelSpec.model);
89
140
  }
141
+ if (options.modelSpec?.reasoningEffort && features.supportsThinking !== false) {
142
+ args.push('--thinking', options.modelSpec.reasoningEffort);
143
+ }
90
144
  }
91
145
 
92
146
  function failClosedUnsupportedSessionControl(options: BuildProviderCommandOptions): void {
@@ -98,12 +152,11 @@ function failClosedUnsupportedSessionControl(options: BuildProviderCommandOption
98
152
  field,
99
153
  exitCode: 2,
100
154
  message:
101
- 'Pi CLI does not support resume/continue session control; fail closed and start a fresh run instead.',
155
+ 'Pi CLI does not support resume/continue session control with a task-owned identity boundary.',
102
156
  });
103
157
  }
104
158
 
105
159
  function collectWarnings(options: BuildProviderCommandOptions): WarningMetadata[] {
106
- const features = optionFeatures(options);
107
160
  const warnings: WarningMetadata[] = [];
108
161
 
109
162
  if (options.jsonSchema) {
@@ -115,16 +168,12 @@ function collectWarnings(options: BuildProviderCommandOptions): WarningMetadata[
115
168
  )
116
169
  );
117
170
  }
118
- if (features.supportsJsonMode === false) {
119
- warnings.push(
120
- warning('pi', 'pi-json-mode', 'Pi CLI does not advertise --mode json; continuing anyway.')
121
- );
122
- }
123
171
  return warnings;
124
172
  }
125
173
 
126
174
  function buildCommand(context: string, options: BuildProviderCommandOptions = {}): CommandSpec {
127
175
  failClosedUnsupportedSessionControl(options);
176
+ assertRequiredControls(options);
128
177
  const finalContext = options.jsonSchema
129
178
  ? appendJsonSchemaPrompt(context, options.jsonSchema)
130
179
  : context;
@@ -132,7 +181,9 @@ function buildCommand(context: string, options: BuildProviderCommandOptions = {}
132
181
 
133
182
  addRequiredArgs(args, options);
134
183
  addOptionalArgs(args, options);
135
- args.push(finalContext);
184
+ // Pi has no `--` end-of-options sentinel and interprets leading `-`/`@` positional values as
185
+ // flags or file arguments. A leading space keeps arbitrary Zeroshot context in prompt mode.
186
+ args.push(` ${finalContext}`);
136
187
 
137
188
  return commandSpec({
138
189
  binary: 'pi',
@@ -143,80 +194,6 @@ function buildCommand(context: string, options: BuildProviderCommandOptions = {}
143
194
  });
144
195
  }
145
196
 
146
- function createPiState(): ProviderParserState {
147
- return {
148
- ...createParserState('pi'),
149
- lastAssistantText: '',
150
- lastAssistantThinking: '',
151
- };
152
- }
153
-
154
- function parsePiTurnEnd(
155
- event: Record<string, unknown>,
156
- state: ProviderParserState
157
- ): readonly OutputEvent[] {
158
- const message = getRecord(event, 'message');
159
- const events = message === null ? [] : [...emitAssistantSnapshot(message, state)];
160
- events.push(parseTurnEndResult(event, { failureMessage: 'Pi turn failed' }));
161
- return events;
162
- }
163
-
164
- function parseMessageEvent(
165
- type: string,
166
- event: Record<string, unknown>,
167
- state: ProviderParserState
168
- ): readonly OutputEvent[] | OutputEvent | null | undefined {
169
- if (type === 'message_start') {
170
- resetAssistantSnapshot(getRecord(event, 'message'), state);
171
- return null;
172
- }
173
-
174
- if (type === 'message_update') {
175
- const assistantMessageEvent = getRecord(event, 'assistantMessageEvent');
176
- if (assistantMessageEvent !== null) {
177
- const assistantEvent = parseAssistantDelta(assistantMessageEvent, state);
178
- if (assistantEvent !== null) return assistantEvent;
179
- }
180
- const message = getRecord(event, 'message');
181
- return message === null ? null : emitAssistantSnapshot(message, state);
182
- }
183
-
184
- if (type !== 'message_end') return undefined;
185
- const message = getRecord(event, 'message');
186
- return message === null ? null : emitAssistantSnapshot(message, state);
187
- }
188
-
189
- function parseToolEvent(
190
- type: string,
191
- event: Record<string, unknown>,
192
- state: ProviderParserState
193
- ): OutputEvent | null | undefined {
194
- if (type === 'tool_execution_start') return parseToolExecutionStart(event, state);
195
- if (type === 'tool_execution_update') return parseToolExecutionUpdate(event, state);
196
- if (type === 'tool_execution_end') return parseToolExecutionEnd(event, state);
197
- return undefined;
198
- }
199
-
200
- function parseEvent(
201
- line: string,
202
- state: ProviderParserState
203
- ): readonly OutputEvent[] | OutputEvent | null {
204
- const parsed = tryParseJson(line);
205
- if (!isRecord(parsed)) return null;
206
-
207
- const type = getString(parsed, 'type');
208
- if (type === null || IGNORED_EVENT_TYPES.has(type)) return null;
209
-
210
- const messageEvent = parseMessageEvent(type, parsed, state);
211
- if (messageEvent !== undefined) return messageEvent;
212
-
213
- const toolEvent = parseToolEvent(type, parsed, state);
214
- if (toolEvent !== undefined) return toolEvent;
215
-
216
- if (type === 'turn_end') return parsePiTurnEnd(parsed, state);
217
- return null;
218
- }
219
-
220
197
  function resolveModelSpec(level: ModelLevel, overrides?: LevelOverrides): ResolvedModelSpec {
221
198
  return resolveModelSpecWithConfig({
222
199
  mapping: LEVEL_MAPPING,
@@ -250,7 +227,9 @@ function classifyError(error: unknown): ErrorClassification {
250
227
  /\b(cancelled|canceled|aborted|interrupted)\b/i,
251
228
  /\brun\s*\/login\b/i,
252
229
  /\bmissing api key\b/i,
230
+ /\bno api key found\b/i,
253
231
  /\bno valid authentication\b/i,
232
+ /\bmodel\b.*\bnot found\b/i,
254
233
  /\bunknown option\b/i,
255
234
  /\bfailed to load\b/i,
256
235
  /\bcannot find module\b/i,
@@ -263,8 +242,8 @@ export const piAdapter: ProviderAdapter = {
263
242
  id: 'pi',
264
243
  displayName: 'Pi',
265
244
  binary: 'pi',
266
- adapterVersion: '1',
267
- credentialEnvKeys: [],
245
+ adapterVersion: '2',
246
+ credentialEnvKeys: PI_CREDENTIAL_ENV_KEYS,
268
247
  modelCatalog: MODEL_CATALOG,
269
248
  levelMapping: LEVEL_MAPPING,
270
249
  defaultLevel: 'level2',
@@ -272,8 +251,9 @@ export const piAdapter: ProviderAdapter = {
272
251
  defaultMinLevel: 'level1',
273
252
  detectCliFeatures,
274
253
  buildCommand,
275
- parseEvent,
276
- createParserState: createPiState,
254
+ parseEvent: parsePiEvent,
255
+ finishParsing: finishPiParsing,
256
+ createParserState: createPiParserState,
277
257
  resolveModelSpec,
278
258
  validateModelId,
279
259
  classifyError,
@@ -62,9 +62,16 @@ export function parseOutputEvents(
62
62
  }
63
63
  ): { readonly events: readonly OutputEvent[]; readonly diagnostics: readonly ContractEvidence[] } {
64
64
  const diagnostics = [...collectParseDiagnostics(fragments.sources)];
65
+ const protocolChunk =
66
+ adapter.id === 'pi'
67
+ ? ((
68
+ fragments.sources.find((source) => source.name === 'stdout') ??
69
+ fragments.sources.find((source) => source.name === 'jsonl')
70
+ )?.value ?? '')
71
+ : fragments.chunk;
65
72
  try {
66
73
  return {
67
- events: parseProviderChunk(adapter.id, fragments.chunk),
74
+ events: parseProviderChunk(adapter.id, protocolChunk),
68
75
  diagnostics,
69
76
  };
70
77
  } catch (error) {
@@ -20,6 +20,16 @@ function classifyParsedFailure(
20
20
  });
21
21
  }
22
22
 
23
+ function classifyMissingPiSettlement(
24
+ adapter: ProviderAdapter,
25
+ events: readonly OutputEvent[]
26
+ ): ErrorClassification | null {
27
+ if (adapter.id !== 'pi' || events.some((event) => event.type === 'result')) return null;
28
+ return classifyProviderError('pi', {
29
+ message: 'Pi JSON stream ended before agent_settled.',
30
+ });
31
+ }
32
+
23
33
  export function providerFailureClassification(
24
34
  adapter: ProviderAdapter,
25
35
  result: ProcessResult,
@@ -31,7 +41,7 @@ export function providerFailureClassification(
31
41
  });
32
42
  }
33
43
  if (result.exitCode === 0 && result.signal === null) {
34
- return classifyParsedFailure(adapter, events);
44
+ return classifyParsedFailure(adapter, events) ?? classifyMissingPiSettlement(adapter, events);
35
45
  }
36
46
  return classifyProviderError(adapter.id, {
37
47
  message: result.stderr || result.stdout || `Provider exited with code ${result.exitCode ?? ''}`,
@@ -0,0 +1,47 @@
1
+ export const PI_CREDENTIAL_ENV_KEYS = [
2
+ 'ANTHROPIC_AUTH_TOKEN',
3
+ 'ANTHROPIC_OAUTH_TOKEN',
4
+ 'ANTHROPIC_API_KEY',
5
+ 'ANT_LING_API_KEY',
6
+ 'QWEN_TOKEN_PLAN_API_KEY',
7
+ 'QWEN_TOKEN_PLAN_CN_API_KEY',
8
+ 'OPENAI_API_KEY',
9
+ 'AZURE_OPENAI_API_KEY',
10
+ 'NVIDIA_API_KEY',
11
+ 'DEEPSEEK_API_KEY',
12
+ 'GEMINI_API_KEY',
13
+ 'GOOGLE_CLOUD_API_KEY',
14
+ 'GROQ_API_KEY',
15
+ 'CEREBRAS_API_KEY',
16
+ 'XAI_API_KEY',
17
+ 'RADIUS_API_KEY',
18
+ 'OPENROUTER_API_KEY',
19
+ 'AI_GATEWAY_API_KEY',
20
+ 'ZAI_API_KEY',
21
+ 'ZAI_CODING_CN_API_KEY',
22
+ 'MISTRAL_API_KEY',
23
+ 'MINIMAX_API_KEY',
24
+ 'MINIMAX_CN_API_KEY',
25
+ 'MOONSHOT_API_KEY',
26
+ 'HF_TOKEN',
27
+ 'FIREWORKS_API_KEY',
28
+ 'TOGETHER_API_KEY',
29
+ 'BASETEN_API_KEY',
30
+ 'OPENCODE_API_KEY',
31
+ 'KIMI_API_KEY',
32
+ 'CLOUDFLARE_API_KEY',
33
+ 'XIAOMI_API_KEY',
34
+ 'XIAOMI_TOKEN_PLAN_CN_API_KEY',
35
+ 'XIAOMI_TOKEN_PLAN_AMS_API_KEY',
36
+ 'XIAOMI_TOKEN_PLAN_SGP_API_KEY',
37
+ 'COPILOT_GITHUB_TOKEN',
38
+ 'AWS_ACCESS_KEY_ID',
39
+ 'AWS_SECRET_ACCESS_KEY',
40
+ 'AWS_SESSION_TOKEN',
41
+ 'AWS_BEARER_TOKEN_BEDROCK',
42
+ 'AWS_PROFILE',
43
+ 'AWS_CONTAINER_CREDENTIALS_RELATIVE_URI',
44
+ 'AWS_CONTAINER_CREDENTIALS_FULL_URI',
45
+ 'AWS_WEB_IDENTITY_TOKEN_FILE',
46
+ 'GOOGLE_APPLICATION_CREDENTIALS',
47
+ ] as const;