@the-open-engine/zeroshot 6.36.0 → 6.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/index.js +99 -29
- package/cli/lib/setup-scanner-worker.js +2 -9
- package/lib/agent-cli-provider/adapters/claude-recovery.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/claude-recovery.js +17 -3
- package/lib/agent-cli-provider/adapters/claude-recovery.js.map +1 -1
- package/lib/agent-cli-provider/adapters/index.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/index.js +7 -0
- package/lib/agent-cli-provider/adapters/index.js.map +1 -1
- package/lib/agent-cli-provider/adapters/pi.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/pi.js +86 -97
- package/lib/agent-cli-provider/adapters/pi.js.map +1 -1
- package/lib/agent-cli-provider/contract-parse.d.ts.map +1 -1
- package/lib/agent-cli-provider/contract-parse.js +5 -1
- package/lib/agent-cli-provider/contract-parse.js.map +1 -1
- package/lib/agent-cli-provider/invoke-evidence.d.ts.map +1 -1
- package/lib/agent-cli-provider/invoke-evidence.js +8 -1
- package/lib/agent-cli-provider/invoke-evidence.js.map +1 -1
- package/lib/agent-cli-provider/pi/credentials.d.ts +2 -0
- package/lib/agent-cli-provider/pi/credentials.d.ts.map +1 -0
- package/lib/agent-cli-provider/pi/credentials.js +51 -0
- package/lib/agent-cli-provider/pi/credentials.js.map +1 -0
- package/lib/agent-cli-provider/pi/events.d.ts +5 -0
- package/lib/agent-cli-provider/pi/events.d.ts.map +1 -0
- package/lib/agent-cli-provider/pi/events.js +124 -0
- package/lib/agent-cli-provider/pi/events.js.map +1 -0
- package/lib/agent-cli-provider/pi/protocol-state.d.ts +6 -0
- package/lib/agent-cli-provider/pi/protocol-state.d.ts.map +1 -0
- package/lib/agent-cli-provider/pi/protocol-state.js +138 -0
- package/lib/agent-cli-provider/pi/protocol-state.js.map +1 -0
- package/lib/agent-cli-provider/pi/release.d.ts +4 -0
- package/lib/agent-cli-provider/pi/release.d.ts.map +1 -0
- package/lib/agent-cli-provider/pi/release.js +7 -0
- package/lib/agent-cli-provider/pi/release.js.map +1 -0
- package/lib/agent-cli-provider/probe-availability.d.ts +5 -0
- package/lib/agent-cli-provider/probe-availability.d.ts.map +1 -0
- package/lib/agent-cli-provider/probe-availability.js +11 -0
- package/lib/agent-cli-provider/probe-availability.js.map +1 -0
- package/lib/agent-cli-provider/provider-registry.d.ts +13 -9
- package/lib/agent-cli-provider/provider-registry.d.ts.map +1 -1
- package/lib/agent-cli-provider/provider-registry.js +42 -8
- package/lib/agent-cli-provider/provider-registry.js.map +1 -1
- package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -1
- package/lib/agent-cli-provider/single-agent-runtime.js +15 -5
- package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -1
- package/lib/agent-cli-provider/types.d.ts +3 -0
- package/lib/agent-cli-provider/types.d.ts.map +1 -1
- package/lib/agent-cli-provider/types.js.map +1 -1
- package/lib/cluster-worker/engine-adapter.js +9 -1
- package/lib/docker-config.js +6 -1
- package/lib/provider-credential-path.js +36 -0
- package/lib/start-cluster.js +8 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/src/agent/agent-lifecycle.js +25 -76
- package/src/agent/agent-liveness-poll.js +93 -0
- package/src/agent/agent-task-executor.js +98 -23
- package/src/agent/output-extraction.js +115 -0
- package/src/agent/pi-terminal-lifecycle.js +242 -0
- package/src/agent/provider-control-plane.js +26 -0
- package/src/agent/provider-terminal-failure.js +49 -24
- package/src/agent-cli-provider/adapters/claude-recovery.ts +15 -2
- package/src/agent-cli-provider/adapters/index.ts +6 -0
- package/src/agent-cli-provider/adapters/pi.ts +105 -125
- package/src/agent-cli-provider/contract-parse.ts +8 -1
- package/src/agent-cli-provider/invoke-evidence.ts +11 -1
- package/src/agent-cli-provider/pi/credentials.ts +47 -0
- package/src/agent-cli-provider/pi/events.ts +148 -0
- package/src/agent-cli-provider/pi/protocol-state.ts +163 -0
- package/src/agent-cli-provider/pi/release.ts +4 -0
- package/src/agent-cli-provider/probe-availability.ts +19 -0
- package/src/agent-cli-provider/provider-registry.ts +48 -9
- package/src/agent-cli-provider/single-agent-runtime.ts +27 -6
- package/src/agent-cli-provider/types.ts +3 -0
- package/src/config-validator.js +1 -3
- package/src/foreground-benchmark-files.js +81 -0
- package/src/foreground-benchmark-result.js +233 -0
- package/src/foreground-benchmark-run.js +72 -0
- package/src/isolation-manager.js +43 -29
- package/src/orchestrator.js +33 -1
- package/task-lib/runner.js +7 -0
- package/task-lib/watcher-output-runtime.js +16 -5
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
import { appendJsonSchemaPrompt } from '../schema';
|
|
2
2
|
import { contractError } from '../contract-errors';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
|
48
|
+
const versionMatches = isCliVersionAtLeast(versionText, PI_SUPPORTED_VERSION);
|
|
49
|
+
const assumeCurrent = !help && versionMatches;
|
|
60
50
|
return {
|
|
61
51
|
provider: 'pi',
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
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
|
-
|
|
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: '
|
|
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
|
-
|
|
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,
|
|
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
|
+
}
|