@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
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { parseTurnEndResult } from '../assistant-stream';
|
|
2
|
+
import { getNumber, getRecord, getString } from '../json';
|
|
3
|
+
import type { ProviderParserState, ResultEvent } from '../types';
|
|
4
|
+
|
|
5
|
+
const STOP_REASONS = new Set(['stop', 'length', 'toolUse', 'error', 'aborted', 'deferred']);
|
|
6
|
+
const REQUIRED_USAGE_FIELDS = [
|
|
7
|
+
'input',
|
|
8
|
+
'output',
|
|
9
|
+
'cacheRead',
|
|
10
|
+
'cacheWrite',
|
|
11
|
+
'totalTokens',
|
|
12
|
+
] as const;
|
|
13
|
+
const OPTIONAL_USAGE_FIELDS = ['cacheWrite1h', 'reasoning'] as const;
|
|
14
|
+
const COST_FIELDS = ['input', 'output', 'cacheRead', 'cacheWrite', 'total'] as const;
|
|
15
|
+
|
|
16
|
+
interface PiProtocolState {
|
|
17
|
+
latestAssistant?: Record<string, unknown>;
|
|
18
|
+
invalid?: string;
|
|
19
|
+
usage: Record<string, number>;
|
|
20
|
+
cost: Record<string, number>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const protocolStates = new WeakMap<ProviderParserState, PiProtocolState>();
|
|
24
|
+
|
|
25
|
+
function protocolState(state: ProviderParserState): PiProtocolState {
|
|
26
|
+
const current = protocolStates.get(state);
|
|
27
|
+
if (current !== undefined) return current;
|
|
28
|
+
const created = { usage: {}, cost: {} };
|
|
29
|
+
protocolStates.set(state, created);
|
|
30
|
+
return created;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function invalidatePiStream(state: ProviderParserState, error: string): void {
|
|
34
|
+
protocolState(state).invalid = error;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function validateUsage(usage: Record<string, unknown>, label: string): string | null {
|
|
38
|
+
for (const field of REQUIRED_USAGE_FIELDS) {
|
|
39
|
+
if (getNumber(usage, field) === null) return `${label}.${field} is missing.`;
|
|
40
|
+
}
|
|
41
|
+
for (const field of OPTIONAL_USAGE_FIELDS) {
|
|
42
|
+
if (usage[field] !== undefined && getNumber(usage, field) === null) {
|
|
43
|
+
return `${label}.${field} is invalid.`;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const cost = getRecord(usage, 'cost');
|
|
47
|
+
if (cost === null) return `${label}.cost is missing.`;
|
|
48
|
+
for (const field of COST_FIELDS) {
|
|
49
|
+
if (getNumber(cost, field) === null) return `${label}.cost.${field} is missing.`;
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function validateAssistant(message: Record<string, unknown>): string | null {
|
|
55
|
+
if (!Array.isArray(message.content)) return 'Pi assistant message content is missing.';
|
|
56
|
+
const stopReason = getString(message, 'stopReason');
|
|
57
|
+
if (stopReason === null || !STOP_REASONS.has(stopReason)) {
|
|
58
|
+
return 'Pi assistant message stopReason is missing or unsupported.';
|
|
59
|
+
}
|
|
60
|
+
const usage = getRecord(message, 'usage');
|
|
61
|
+
return usage === null
|
|
62
|
+
? 'Pi assistant message usage is missing.'
|
|
63
|
+
: validateUsage(usage, 'Pi assistant usage');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function addNumbers(
|
|
67
|
+
target: Record<string, number>,
|
|
68
|
+
source: Record<string, unknown>,
|
|
69
|
+
fields: readonly string[]
|
|
70
|
+
): void {
|
|
71
|
+
for (const field of fields) {
|
|
72
|
+
const value = getNumber(source, field);
|
|
73
|
+
if (value !== null) target[field] = (target[field] ?? 0) + value;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function rememberPiAssistant(
|
|
78
|
+
message: Record<string, unknown> | null,
|
|
79
|
+
state: ProviderParserState
|
|
80
|
+
): void {
|
|
81
|
+
if (message === null) return invalidatePiStream(state, 'Pi message_end was missing its message.');
|
|
82
|
+
const role = getString(message, 'role');
|
|
83
|
+
if (role === null) return invalidatePiStream(state, 'Pi message_end was missing its role.');
|
|
84
|
+
if (role !== 'assistant') return;
|
|
85
|
+
const error = validateAssistant(message);
|
|
86
|
+
if (error !== null) return invalidatePiStream(state, error);
|
|
87
|
+
const current = protocolState(state);
|
|
88
|
+
current.latestAssistant = message;
|
|
89
|
+
rememberPiUsage(getRecord(message, 'usage'), state, 'Pi assistant usage');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function rememberPiUsage(
|
|
93
|
+
usage: Record<string, unknown> | null,
|
|
94
|
+
state: ProviderParserState,
|
|
95
|
+
label: string
|
|
96
|
+
): void {
|
|
97
|
+
if (usage === null) return invalidatePiStream(state, `${label} is invalid.`);
|
|
98
|
+
const error = validateUsage(usage, label);
|
|
99
|
+
if (error !== null) return invalidatePiStream(state, error);
|
|
100
|
+
const current = protocolState(state);
|
|
101
|
+
addNumbers(current.usage, usage, [...REQUIRED_USAGE_FIELDS, ...OPTIONAL_USAGE_FIELDS]);
|
|
102
|
+
const cost = getRecord(usage, 'cost');
|
|
103
|
+
if (cost !== null) addNumbers(current.cost, cost, COST_FIELDS);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function usageResult(
|
|
107
|
+
current: PiProtocolState
|
|
108
|
+
): Pick<
|
|
109
|
+
ResultEvent,
|
|
110
|
+
| 'inputTokens'
|
|
111
|
+
| 'outputTokens'
|
|
112
|
+
| 'cacheReadInputTokens'
|
|
113
|
+
| 'cacheCreationInputTokens'
|
|
114
|
+
| 'cost'
|
|
115
|
+
| 'modelUsage'
|
|
116
|
+
> {
|
|
117
|
+
const cost = Object.keys(current.cost).length === 0 ? null : current.cost;
|
|
118
|
+
return {
|
|
119
|
+
inputTokens: current.usage.input ?? 0,
|
|
120
|
+
outputTokens: current.usage.output ?? 0,
|
|
121
|
+
cacheReadInputTokens: current.usage.cacheRead ?? 0,
|
|
122
|
+
cacheCreationInputTokens: current.usage.cacheWrite ?? 0,
|
|
123
|
+
cost: cost?.total ?? null,
|
|
124
|
+
modelUsage: { ...current.usage, ...(cost === null ? {} : { cost }) },
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function failedResult(error: string, current: PiProtocolState): ResultEvent {
|
|
129
|
+
return {
|
|
130
|
+
type: 'result',
|
|
131
|
+
success: false,
|
|
132
|
+
result: null,
|
|
133
|
+
error,
|
|
134
|
+
...usageResult(current),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function settlePiStream(state: ProviderParserState): ResultEvent {
|
|
139
|
+
const current = protocolState(state);
|
|
140
|
+
if (current.invalid) {
|
|
141
|
+
return failedResult(`Invalid Pi JSON stream: ${current.invalid}`, current);
|
|
142
|
+
}
|
|
143
|
+
if (!current.latestAssistant) {
|
|
144
|
+
return failedResult('Pi settled without an assistant message_end.', current);
|
|
145
|
+
}
|
|
146
|
+
const parsedResult = parseTurnEndResult(
|
|
147
|
+
{ message: current.latestAssistant },
|
|
148
|
+
{ failureMessage: 'Pi turn failed' }
|
|
149
|
+
);
|
|
150
|
+
const result =
|
|
151
|
+
getString(current.latestAssistant, 'stopReason') === 'deferred'
|
|
152
|
+
? {
|
|
153
|
+
...parsedResult,
|
|
154
|
+
success: false,
|
|
155
|
+
result: null,
|
|
156
|
+
error: 'Pi turn deferred without completing.',
|
|
157
|
+
}
|
|
158
|
+
: parsedResult;
|
|
159
|
+
return {
|
|
160
|
+
...result,
|
|
161
|
+
...usageResult(current),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ProviderCliFeatures } from './types';
|
|
2
|
+
|
|
3
|
+
type AvailabilityProbe = 'command' | 'help-or-version' | 'supported-version' | undefined;
|
|
4
|
+
|
|
5
|
+
export function runtimeProbeIsAvailable(
|
|
6
|
+
availabilityProbe: AvailabilityProbe,
|
|
7
|
+
evidenceAvailable: boolean | undefined,
|
|
8
|
+
helpText: string,
|
|
9
|
+
versionText: string,
|
|
10
|
+
capabilities: ProviderCliFeatures
|
|
11
|
+
): boolean {
|
|
12
|
+
const probeAvailable =
|
|
13
|
+
evidenceAvailable ??
|
|
14
|
+
(availabilityProbe === 'command' ? true : Boolean(helpText || versionText));
|
|
15
|
+
const versionSupported =
|
|
16
|
+
availabilityProbe !== 'supported-version' ||
|
|
17
|
+
('versionMatches' in capabilities && capabilities.versionMatches === true);
|
|
18
|
+
return probeAvailable && versionSupported;
|
|
19
|
+
}
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
OMP_INSTALL_COMMAND,
|
|
19
19
|
} from './omp/release';
|
|
20
20
|
import { OMP_SDK_SETTINGS_DEFAULTS, validateOmpSdkSettings } from './omp/sdk-settings';
|
|
21
|
+
import { PI_INSTALL_COMMAND } from './pi/release';
|
|
21
22
|
import type { OmpSettingsValidationContext } from './omp/sdk-settings';
|
|
22
23
|
import type { ModelLevel, ProviderAdapter, StructuredOutputRecoveryAdapter } from './types';
|
|
23
24
|
|
|
@@ -73,6 +74,9 @@ export interface ProviderDocsMetadata {
|
|
|
73
74
|
|
|
74
75
|
export interface ProviderDockerMountPreset {
|
|
75
76
|
readonly host: string;
|
|
77
|
+
// Optional host-only config-root override. Its value selects the mount source but is never
|
|
78
|
+
// forwarded into the container, where `container` remains the canonical provider path.
|
|
79
|
+
readonly hostEnv?: string;
|
|
76
80
|
readonly container: string;
|
|
77
81
|
readonly readonly: boolean;
|
|
78
82
|
}
|
|
@@ -130,7 +134,7 @@ interface ProviderRegistryEntryBase {
|
|
|
130
134
|
settings: Record<string, unknown>,
|
|
131
135
|
context?: OmpSettingsValidationContext
|
|
132
136
|
) => string | null;
|
|
133
|
-
readonly availabilityProbe?: 'command' | 'help-or-version';
|
|
137
|
+
readonly availabilityProbe?: 'command' | 'help-or-version' | 'supported-version';
|
|
134
138
|
readonly docs: ProviderDocsMetadata;
|
|
135
139
|
readonly docker: ProviderDockerMetadata;
|
|
136
140
|
readonly defaultLevels: Readonly<{
|
|
@@ -186,6 +190,35 @@ const CLAUDE_DOCKER_ENV_PASSTHROUGH = [
|
|
|
186
190
|
'CLAUDE_CODE_USE_BEDROCK',
|
|
187
191
|
] as const;
|
|
188
192
|
|
|
193
|
+
const PI_EXPLICIT_DOCKER_CREDENTIALS = new Set([
|
|
194
|
+
'AWS_CONTAINER_CREDENTIALS_FULL_URI',
|
|
195
|
+
'AWS_CONTAINER_CREDENTIALS_RELATIVE_URI',
|
|
196
|
+
'AWS_PROFILE',
|
|
197
|
+
'AWS_WEB_IDENTITY_TOKEN_FILE',
|
|
198
|
+
'GOOGLE_APPLICATION_CREDENTIALS',
|
|
199
|
+
]);
|
|
200
|
+
|
|
201
|
+
const PI_DOCKER_CONFIGURATION_ENV = [
|
|
202
|
+
'AWS_BEDROCK_FORCE_CACHE',
|
|
203
|
+
'AWS_BEDROCK_FORCE_HTTP1',
|
|
204
|
+
'AWS_BEDROCK_SKIP_AUTH',
|
|
205
|
+
'AWS_DEFAULT_REGION',
|
|
206
|
+
'AWS_ENDPOINT_URL_BEDROCK_RUNTIME',
|
|
207
|
+
'AWS_REGION',
|
|
208
|
+
'AZURE_OPENAI_API_VERSION',
|
|
209
|
+
'AZURE_OPENAI_BASE_URL',
|
|
210
|
+
'AZURE_OPENAI_DEPLOYMENT_NAME_MAP',
|
|
211
|
+
'AZURE_OPENAI_RESOURCE_NAME',
|
|
212
|
+
'CLOUDFLARE_ACCOUNT_ID',
|
|
213
|
+
'CLOUDFLARE_GATEWAY_ID',
|
|
214
|
+
'GCLOUD_PROJECT',
|
|
215
|
+
'GOOGLE_CLOUD_LOCATION',
|
|
216
|
+
'GOOGLE_CLOUD_PROJECT',
|
|
217
|
+
'KIMI_CODE_OAUTH_HOST',
|
|
218
|
+
'KIMI_OAUTH_HOST',
|
|
219
|
+
'PI_CACHE_RETENTION',
|
|
220
|
+
] as const;
|
|
221
|
+
|
|
189
222
|
const SPAWN_INVOKE = Object.freeze({ lane: 'spawn' }) as SpawnProviderInvokeSpec;
|
|
190
223
|
const ACP_STDIO_INVOKE = Object.freeze({
|
|
191
224
|
lane: 'acp-stdio',
|
|
@@ -444,17 +477,17 @@ export const providerRegistry = [
|
|
|
444
477
|
binary: 'pi',
|
|
445
478
|
command: { kind: 'fixed', command: 'pi', args: [] },
|
|
446
479
|
invoke: SPAWN_INVOKE,
|
|
447
|
-
installInstructions:
|
|
480
|
+
installInstructions: PI_INSTALL_COMMAND,
|
|
448
481
|
authInstructions: 'pi\n/login',
|
|
449
|
-
credentialPaths: ['~/.pi'],
|
|
482
|
+
credentialPaths: ['$PI_CODING_AGENT_DIR/auth.json', '~/.pi/agent/auth.json'],
|
|
450
483
|
credentialEnvKeys: piAdapter.credentialEnvKeys,
|
|
451
484
|
settingsFields: [],
|
|
452
|
-
availabilityProbe: '
|
|
485
|
+
availabilityProbe: 'supported-version',
|
|
453
486
|
capabilities: {
|
|
454
487
|
...STANDARD_CAPABILITIES,
|
|
455
488
|
mcpServers: false,
|
|
456
489
|
jsonSchema: false,
|
|
457
|
-
reasoningEffort:
|
|
490
|
+
reasoningEffort: true,
|
|
458
491
|
},
|
|
459
492
|
docs: {
|
|
460
493
|
label: 'Pi',
|
|
@@ -462,11 +495,17 @@ export const providerRegistry = [
|
|
|
462
495
|
},
|
|
463
496
|
docker: {
|
|
464
497
|
mount: {
|
|
465
|
-
host: '~/.pi',
|
|
466
|
-
|
|
467
|
-
|
|
498
|
+
host: '~/.pi/agent',
|
|
499
|
+
hostEnv: 'PI_CODING_AGENT_DIR',
|
|
500
|
+
container: '$HOME/.pi/agent',
|
|
501
|
+
readonly: false,
|
|
468
502
|
},
|
|
469
|
-
|
|
503
|
+
install: PI_INSTALL_COMMAND,
|
|
504
|
+
configRoots: ['$HOME/.pi/agent'],
|
|
505
|
+
envPassthrough: [
|
|
506
|
+
...piAdapter.credentialEnvKeys.filter((name) => !PI_EXPLICIT_DOCKER_CREDENTIALS.has(name)),
|
|
507
|
+
...PI_DOCKER_CONFIGURATION_ENV,
|
|
508
|
+
],
|
|
470
509
|
},
|
|
471
510
|
defaultLevels: {
|
|
472
511
|
min: piAdapter.defaultMinLevel,
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
supportsProviderCapability,
|
|
30
30
|
supportsProviderOutputReformatting,
|
|
31
31
|
} from './provider-registry';
|
|
32
|
+
import { runtimeProbeIsAvailable } from './probe-availability';
|
|
32
33
|
import type {
|
|
33
34
|
BuildProviderCommandOptions,
|
|
34
35
|
CliFeatureOverrides,
|
|
@@ -577,6 +578,10 @@ function resolveRuntimeCliFeatures(
|
|
|
577
578
|
supportsWebSearch: false,
|
|
578
579
|
};
|
|
579
580
|
}
|
|
581
|
+
if (provider === 'pi') {
|
|
582
|
+
const detected = detectRuntimeProviderCliFeatures(provider, runtimeSettings);
|
|
583
|
+
return overrides === undefined ? detected : mergePiFailClosedCliFeatures(detected, overrides);
|
|
584
|
+
}
|
|
580
585
|
if (getProviderRegistryEntry(provider).invoke.lane === 'acp-stdio') {
|
|
581
586
|
const detected = detectRuntimeProviderCliFeatures(provider, runtimeSettings);
|
|
582
587
|
if (overrides === undefined) return detected;
|
|
@@ -595,6 +600,18 @@ function resolveRuntimeCliFeatures(
|
|
|
595
600
|
};
|
|
596
601
|
}
|
|
597
602
|
|
|
603
|
+
function mergePiFailClosedCliFeatures(
|
|
604
|
+
detected: ProviderCliFeatures,
|
|
605
|
+
overrides: CliFeatureOverrides
|
|
606
|
+
): CliFeatureOverrides {
|
|
607
|
+
return Object.fromEntries(
|
|
608
|
+
Object.entries(detected).map(([field, value]) => [
|
|
609
|
+
field,
|
|
610
|
+
typeof value === 'boolean' ? value && Reflect.get(overrides, field) !== false : value,
|
|
611
|
+
])
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
|
|
598
615
|
function mergeAcpFailClosedCliFeatures(
|
|
599
616
|
detected: ProviderCliFeatures,
|
|
600
617
|
overrides: CliFeatureOverrides
|
|
@@ -679,7 +696,9 @@ export function probeRuntimeProviderCli(
|
|
|
679
696
|
|
|
680
697
|
const helpText =
|
|
681
698
|
evidence?.helpText ??
|
|
682
|
-
|
|
699
|
+
(adapter.id === 'pi'
|
|
700
|
+
? ''
|
|
701
|
+
: stringResult(getHelpOutputFn(helpCommand.command, helpCommand.args)).trim());
|
|
683
702
|
const versionText =
|
|
684
703
|
evidence?.versionText ??
|
|
685
704
|
stringResult(
|
|
@@ -691,11 +710,13 @@ export function probeRuntimeProviderCli(
|
|
|
691
710
|
const capabilities = attestedCliFeatures(adapter, helpText, versionText);
|
|
692
711
|
|
|
693
712
|
return {
|
|
694
|
-
available:
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
713
|
+
available: runtimeProbeIsAvailable(
|
|
714
|
+
registryEntry.availabilityProbe,
|
|
715
|
+
evidence?.available,
|
|
716
|
+
helpText,
|
|
717
|
+
versionText,
|
|
718
|
+
capabilities
|
|
719
|
+
),
|
|
699
720
|
helpText,
|
|
700
721
|
versionText,
|
|
701
722
|
capabilities,
|
|
@@ -158,8 +158,10 @@ export interface OpencodeCliFeatures extends BaseCliFeatures {
|
|
|
158
158
|
|
|
159
159
|
export interface PiCliFeatures extends BaseCliFeatures {
|
|
160
160
|
readonly provider: 'pi';
|
|
161
|
+
readonly versionMatches: boolean;
|
|
161
162
|
readonly supportsJsonMode: boolean;
|
|
162
163
|
readonly supportsModel: boolean;
|
|
164
|
+
readonly supportsThinking: boolean;
|
|
163
165
|
readonly supportsNoSession: boolean;
|
|
164
166
|
readonly supportsNoExtensions: boolean;
|
|
165
167
|
readonly supportsNoSkills: boolean;
|
|
@@ -573,6 +575,7 @@ export interface ProviderAdapter {
|
|
|
573
575
|
buildCommand(context: string, options?: BuildProviderCommandOptions): CommandSpec;
|
|
574
576
|
extractSessionId?(line: string): string | null;
|
|
575
577
|
parseEvent(line: string, state: ProviderParserState): ProviderParseResult;
|
|
578
|
+
finishParsing?(state: ProviderParserState): ProviderParseResult;
|
|
576
579
|
createParserState(): ProviderParserState;
|
|
577
580
|
resolveModelSpec(level: ModelLevel, overrides?: LevelOverrides): ResolvedModelSpec;
|
|
578
581
|
validateModelId(modelId: string | null | undefined): string | null | undefined;
|
package/src/config-validator.js
CHANGED
|
@@ -2058,9 +2058,7 @@ function validateProviderSettings(provider, providerSettings) {
|
|
|
2058
2058
|
providerModule.resolveModelSpec(level, { [level]: override });
|
|
2059
2059
|
}
|
|
2060
2060
|
if (override?.reasoningEffort && !providerSupportsCapability(provider, 'reasoningEffort')) {
|
|
2061
|
-
throw new Error(
|
|
2062
|
-
`reasoningEffort overrides are only supported for Claude, Codex, and Opencode`
|
|
2063
|
-
);
|
|
2061
|
+
throw new Error(`Provider "${provider}" does not support reasoningEffort overrides`);
|
|
2064
2062
|
}
|
|
2065
2063
|
if (override?.reasoningEffort && !REASONING_EFFORTS.includes(override.reasoningEffort)) {
|
|
2066
2064
|
throw new Error(
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const crypto = require('node:crypto');
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
|
|
5
|
+
const { buildTelemetry } = require('./foreground-benchmark-result');
|
|
6
|
+
|
|
7
|
+
function serialized(value) {
|
|
8
|
+
return Buffer.from(`${JSON.stringify(value)}\n`, 'utf8');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function atomicWriteNew(targetPath, content) {
|
|
12
|
+
const directory = path.dirname(targetPath);
|
|
13
|
+
const temporary = path.join(
|
|
14
|
+
directory,
|
|
15
|
+
`.${path.basename(targetPath)}.${process.pid}.${crypto.randomBytes(8).toString('hex')}.tmp`
|
|
16
|
+
);
|
|
17
|
+
let descriptor;
|
|
18
|
+
let published = false;
|
|
19
|
+
try {
|
|
20
|
+
descriptor = fs.openSync(temporary, 'wx', 0o600);
|
|
21
|
+
fs.writeFileSync(descriptor, content);
|
|
22
|
+
fs.fsyncSync(descriptor);
|
|
23
|
+
fs.closeSync(descriptor);
|
|
24
|
+
descriptor = undefined;
|
|
25
|
+
fs.linkSync(temporary, targetPath);
|
|
26
|
+
published = true;
|
|
27
|
+
fs.unlinkSync(temporary);
|
|
28
|
+
const directoryDescriptor = fs.openSync(directory, fs.constants.O_RDONLY);
|
|
29
|
+
try {
|
|
30
|
+
fs.fsyncSync(directoryDescriptor);
|
|
31
|
+
} finally {
|
|
32
|
+
fs.closeSync(directoryDescriptor);
|
|
33
|
+
}
|
|
34
|
+
} catch (error) {
|
|
35
|
+
error.atomicTargetPublished = published;
|
|
36
|
+
throw error;
|
|
37
|
+
} finally {
|
|
38
|
+
if (descriptor !== undefined) fs.closeSync(descriptor);
|
|
39
|
+
try {
|
|
40
|
+
fs.unlinkSync(temporary);
|
|
41
|
+
} catch {
|
|
42
|
+
// Preserve the primary write error; a leftover randomized temp is non-authoritative.
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function removeOrphanTelemetry(telemetryPath, primaryError) {
|
|
48
|
+
try {
|
|
49
|
+
fs.unlinkSync(telemetryPath);
|
|
50
|
+
} catch (cleanupError) {
|
|
51
|
+
if (cleanupError.code !== 'ENOENT') primaryError.cleanupError = cleanupError;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function writeBenchmarkResultBundle(resultPath, result, snapshot) {
|
|
56
|
+
if (typeof resultPath !== 'string' || resultPath.length === 0) {
|
|
57
|
+
throw new Error('result path must be non-empty text');
|
|
58
|
+
}
|
|
59
|
+
const resolvedResult = path.resolve(resultPath);
|
|
60
|
+
const telemetryPath = `${resolvedResult}.telemetry.json`;
|
|
61
|
+
const telemetry = buildTelemetry(result.runId, snapshot);
|
|
62
|
+
const telemetryBytes = serialized(telemetry);
|
|
63
|
+
atomicWriteNew(telemetryPath, telemetryBytes);
|
|
64
|
+
const receipt = {
|
|
65
|
+
...result,
|
|
66
|
+
telemetry: {
|
|
67
|
+
artifact: path.basename(telemetryPath),
|
|
68
|
+
byteLength: telemetryBytes.length,
|
|
69
|
+
sha256: crypto.createHash('sha256').update(telemetryBytes).digest('hex'),
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
try {
|
|
73
|
+
atomicWriteNew(resolvedResult, serialized(receipt));
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (!error.atomicTargetPublished) removeOrphanTelemetry(telemetryPath, error);
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
return receipt;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = { writeBenchmarkResultBundle };
|