@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.
Files changed (81) hide show
  1. package/cli/index.js +99 -29
  2. package/cli/lib/setup-scanner-worker.js +2 -9
  3. package/lib/agent-cli-provider/adapters/claude-recovery.d.ts.map +1 -1
  4. package/lib/agent-cli-provider/adapters/claude-recovery.js +17 -3
  5. package/lib/agent-cli-provider/adapters/claude-recovery.js.map +1 -1
  6. package/lib/agent-cli-provider/adapters/index.d.ts.map +1 -1
  7. package/lib/agent-cli-provider/adapters/index.js +7 -0
  8. package/lib/agent-cli-provider/adapters/index.js.map +1 -1
  9. package/lib/agent-cli-provider/adapters/pi.d.ts.map +1 -1
  10. package/lib/agent-cli-provider/adapters/pi.js +86 -97
  11. package/lib/agent-cli-provider/adapters/pi.js.map +1 -1
  12. package/lib/agent-cli-provider/contract-parse.d.ts.map +1 -1
  13. package/lib/agent-cli-provider/contract-parse.js +5 -1
  14. package/lib/agent-cli-provider/contract-parse.js.map +1 -1
  15. package/lib/agent-cli-provider/invoke-evidence.d.ts.map +1 -1
  16. package/lib/agent-cli-provider/invoke-evidence.js +8 -1
  17. package/lib/agent-cli-provider/invoke-evidence.js.map +1 -1
  18. package/lib/agent-cli-provider/pi/credentials.d.ts +2 -0
  19. package/lib/agent-cli-provider/pi/credentials.d.ts.map +1 -0
  20. package/lib/agent-cli-provider/pi/credentials.js +51 -0
  21. package/lib/agent-cli-provider/pi/credentials.js.map +1 -0
  22. package/lib/agent-cli-provider/pi/events.d.ts +5 -0
  23. package/lib/agent-cli-provider/pi/events.d.ts.map +1 -0
  24. package/lib/agent-cli-provider/pi/events.js +124 -0
  25. package/lib/agent-cli-provider/pi/events.js.map +1 -0
  26. package/lib/agent-cli-provider/pi/protocol-state.d.ts +6 -0
  27. package/lib/agent-cli-provider/pi/protocol-state.d.ts.map +1 -0
  28. package/lib/agent-cli-provider/pi/protocol-state.js +138 -0
  29. package/lib/agent-cli-provider/pi/protocol-state.js.map +1 -0
  30. package/lib/agent-cli-provider/pi/release.d.ts +4 -0
  31. package/lib/agent-cli-provider/pi/release.d.ts.map +1 -0
  32. package/lib/agent-cli-provider/pi/release.js +7 -0
  33. package/lib/agent-cli-provider/pi/release.js.map +1 -0
  34. package/lib/agent-cli-provider/probe-availability.d.ts +5 -0
  35. package/lib/agent-cli-provider/probe-availability.d.ts.map +1 -0
  36. package/lib/agent-cli-provider/probe-availability.js +11 -0
  37. package/lib/agent-cli-provider/probe-availability.js.map +1 -0
  38. package/lib/agent-cli-provider/provider-registry.d.ts +13 -9
  39. package/lib/agent-cli-provider/provider-registry.d.ts.map +1 -1
  40. package/lib/agent-cli-provider/provider-registry.js +42 -8
  41. package/lib/agent-cli-provider/provider-registry.js.map +1 -1
  42. package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -1
  43. package/lib/agent-cli-provider/single-agent-runtime.js +15 -5
  44. package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -1
  45. package/lib/agent-cli-provider/types.d.ts +3 -0
  46. package/lib/agent-cli-provider/types.d.ts.map +1 -1
  47. package/lib/agent-cli-provider/types.js.map +1 -1
  48. package/lib/cluster-worker/engine-adapter.js +9 -1
  49. package/lib/docker-config.js +6 -1
  50. package/lib/provider-credential-path.js +36 -0
  51. package/lib/start-cluster.js +8 -0
  52. package/npm-shrinkwrap.json +2 -2
  53. package/package.json +1 -1
  54. package/src/agent/agent-lifecycle.js +25 -76
  55. package/src/agent/agent-liveness-poll.js +93 -0
  56. package/src/agent/agent-task-executor.js +98 -23
  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/index.ts +6 -0
  63. package/src/agent-cli-provider/adapters/pi.ts +105 -125
  64. package/src/agent-cli-provider/contract-parse.ts +8 -1
  65. package/src/agent-cli-provider/invoke-evidence.ts +11 -1
  66. package/src/agent-cli-provider/pi/credentials.ts +47 -0
  67. package/src/agent-cli-provider/pi/events.ts +148 -0
  68. package/src/agent-cli-provider/pi/protocol-state.ts +163 -0
  69. package/src/agent-cli-provider/pi/release.ts +4 -0
  70. package/src/agent-cli-provider/probe-availability.ts +19 -0
  71. package/src/agent-cli-provider/provider-registry.ts +48 -9
  72. package/src/agent-cli-provider/single-agent-runtime.ts +27 -6
  73. package/src/agent-cli-provider/types.ts +3 -0
  74. package/src/config-validator.js +1 -3
  75. package/src/foreground-benchmark-files.js +81 -0
  76. package/src/foreground-benchmark-result.js +233 -0
  77. package/src/foreground-benchmark-run.js +72 -0
  78. package/src/isolation-manager.js +43 -29
  79. package/src/orchestrator.js +33 -1
  80. package/task-lib/runner.js +7 -0
  81. package/task-lib/watcher-output-runtime.js +16 -5
@@ -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;
@@ -0,0 +1,148 @@
1
+ import {
2
+ emitAssistantSnapshot,
3
+ parseAssistantDelta,
4
+ parseToolExecutionEnd,
5
+ parseToolExecutionStart,
6
+ parseToolExecutionUpdate,
7
+ resetAssistantSnapshot,
8
+ } from '../assistant-stream';
9
+ import { getRecord, getString, isRecord, tryParseJson } from '../json';
10
+ import type { OutputEvent, ProviderParserState } from '../types';
11
+ import {
12
+ invalidatePiStream,
13
+ rememberPiAssistant,
14
+ rememberPiUsage,
15
+ settlePiStream,
16
+ } from './protocol-state';
17
+
18
+ const IGNORED_EVENT_TYPES = new Set([
19
+ 'session',
20
+ 'agent_start',
21
+ 'agent_end',
22
+ 'turn_start',
23
+ 'queue_update',
24
+ 'compaction_start',
25
+ 'compaction_end',
26
+ 'auto_retry_start',
27
+ 'auto_retry_end',
28
+ ]);
29
+ const settledStates = new WeakSet<ProviderParserState>();
30
+
31
+ export function createPiParserState(): ProviderParserState {
32
+ return {
33
+ provider: 'pi',
34
+ lastToolId: undefined,
35
+ lastAssistantText: '',
36
+ lastAssistantThinking: '',
37
+ };
38
+ }
39
+
40
+ function parseMessageEvent(
41
+ type: string,
42
+ event: Record<string, unknown>,
43
+ state: ProviderParserState
44
+ ): readonly OutputEvent[] | OutputEvent | null | undefined {
45
+ if (type === 'message_start') {
46
+ resetAssistantSnapshot(getRecord(event, 'message'), state);
47
+ return null;
48
+ }
49
+ if (type === 'message_update') {
50
+ const assistantMessageEvent = getRecord(event, 'assistantMessageEvent');
51
+ if (assistantMessageEvent !== null) {
52
+ const assistantEvent = parseAssistantDelta(assistantMessageEvent, state);
53
+ if (assistantEvent !== null) return assistantEvent;
54
+ }
55
+ const message = getRecord(event, 'message');
56
+ return message === null ? null : emitAssistantSnapshot(message, state);
57
+ }
58
+ if (type !== 'message_end') return undefined;
59
+
60
+ const message = getRecord(event, 'message');
61
+ if (
62
+ message !== null &&
63
+ getString(message, 'role') === 'toolResult' &&
64
+ Object.hasOwn(message, 'usage')
65
+ ) {
66
+ rememberPiUsage(getRecord(message, 'usage'), state, 'Pi tool usage');
67
+ }
68
+ rememberPiAssistant(message, state);
69
+ return message === null || getString(message, 'role') !== 'assistant'
70
+ ? null
71
+ : emitAssistantSnapshot(message, state);
72
+ }
73
+
74
+ function parseToolEvent(
75
+ type: string,
76
+ event: Record<string, unknown>,
77
+ state: ProviderParserState
78
+ ): OutputEvent | null | undefined {
79
+ if (type === 'tool_execution_start') return parseToolExecutionStart(event, state);
80
+ if (type === 'tool_execution_update') return parseToolExecutionUpdate(event, state);
81
+ if (type === 'tool_execution_end') return parseToolExecutionEnd(event, state);
82
+ return undefined;
83
+ }
84
+
85
+ function parseKnownPiEvent(
86
+ type: string,
87
+ parsed: Record<string, unknown>,
88
+ state: ProviderParserState
89
+ ): readonly OutputEvent[] | OutputEvent | null {
90
+ if (type === 'compaction_end') {
91
+ const result = getRecord(parsed, 'result');
92
+ if (result !== null && Object.hasOwn(result, 'usage')) {
93
+ rememberPiUsage(getRecord(result, 'usage'), state, 'Pi compaction usage');
94
+ }
95
+ return null;
96
+ }
97
+ if (type === 'entry_appended') {
98
+ const entry = getRecord(parsed, 'entry');
99
+ if (
100
+ entry !== null &&
101
+ getString(entry, 'type') === 'branch_summary' &&
102
+ Object.hasOwn(entry, 'usage')
103
+ ) {
104
+ rememberPiUsage(getRecord(entry, 'usage'), state, 'Pi branch summary usage');
105
+ }
106
+ return null;
107
+ }
108
+ if (IGNORED_EVENT_TYPES.has(type)) return null;
109
+ const messageEvent = parseMessageEvent(type, parsed, state);
110
+ if (messageEvent !== undefined) return messageEvent;
111
+ const toolEvent = parseToolEvent(type, parsed, state);
112
+ if (toolEvent !== undefined) return toolEvent;
113
+ if (type === 'turn_end') {
114
+ const message = getRecord(parsed, 'message');
115
+ return message === null ? [] : emitAssistantSnapshot(message, state);
116
+ }
117
+ if (type !== 'agent_settled') return null;
118
+ settledStates.add(state);
119
+ return settlePiStream(state);
120
+ }
121
+
122
+ export function parsePiEvent(
123
+ line: string,
124
+ state: ProviderParserState
125
+ ): readonly OutputEvent[] | OutputEvent | null {
126
+ if (line.trim() && settledStates.has(state)) {
127
+ invalidatePiStream(state, 'stdout contained output after agent_settled.');
128
+ return settlePiStream(state);
129
+ }
130
+ const parsed = tryParseJson(line);
131
+ if (!isRecord(parsed)) {
132
+ if (line.trim()) invalidatePiStream(state, 'stdout contained malformed JSON.');
133
+ return null;
134
+ }
135
+
136
+ const type = getString(parsed, 'type');
137
+ if (type === null) {
138
+ invalidatePiStream(state, 'an event was missing its type.');
139
+ return null;
140
+ }
141
+ return parseKnownPiEvent(type, parsed, state);
142
+ }
143
+
144
+ export function finishPiParsing(state: ProviderParserState): OutputEvent | null {
145
+ if (settledStates.has(state)) return null;
146
+ invalidatePiStream(state, 'stdout ended before agent_settled.');
147
+ return settlePiStream(state);
148
+ }