@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.
- 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/codex.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/codex.js +3 -1
- package/lib/agent-cli-provider/adapters/codex.js.map +1 -1
- package/lib/agent-cli-provider/adapters/index.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/index.js +7 -0
- package/lib/agent-cli-provider/adapters/index.js.map +1 -1
- package/lib/agent-cli-provider/adapters/pi.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/pi.js +86 -97
- package/lib/agent-cli-provider/adapters/pi.js.map +1 -1
- package/lib/agent-cli-provider/contract-parse.d.ts.map +1 -1
- package/lib/agent-cli-provider/contract-parse.js +5 -1
- package/lib/agent-cli-provider/contract-parse.js.map +1 -1
- package/lib/agent-cli-provider/invoke-evidence.d.ts.map +1 -1
- package/lib/agent-cli-provider/invoke-evidence.js +8 -1
- package/lib/agent-cli-provider/invoke-evidence.js.map +1 -1
- package/lib/agent-cli-provider/pi/credentials.d.ts +2 -0
- package/lib/agent-cli-provider/pi/credentials.d.ts.map +1 -0
- package/lib/agent-cli-provider/pi/credentials.js +51 -0
- package/lib/agent-cli-provider/pi/credentials.js.map +1 -0
- package/lib/agent-cli-provider/pi/events.d.ts +5 -0
- package/lib/agent-cli-provider/pi/events.d.ts.map +1 -0
- package/lib/agent-cli-provider/pi/events.js +124 -0
- package/lib/agent-cli-provider/pi/events.js.map +1 -0
- package/lib/agent-cli-provider/pi/protocol-state.d.ts +6 -0
- package/lib/agent-cli-provider/pi/protocol-state.d.ts.map +1 -0
- package/lib/agent-cli-provider/pi/protocol-state.js +138 -0
- package/lib/agent-cli-provider/pi/protocol-state.js.map +1 -0
- package/lib/agent-cli-provider/pi/release.d.ts +4 -0
- package/lib/agent-cli-provider/pi/release.d.ts.map +1 -0
- package/lib/agent-cli-provider/pi/release.js +7 -0
- package/lib/agent-cli-provider/pi/release.js.map +1 -0
- package/lib/agent-cli-provider/probe-availability.d.ts +5 -0
- package/lib/agent-cli-provider/probe-availability.d.ts.map +1 -0
- package/lib/agent-cli-provider/probe-availability.js +11 -0
- package/lib/agent-cli-provider/probe-availability.js.map +1 -0
- package/lib/agent-cli-provider/provider-registry.d.ts +13 -9
- package/lib/agent-cli-provider/provider-registry.d.ts.map +1 -1
- package/lib/agent-cli-provider/provider-registry.js +42 -8
- package/lib/agent-cli-provider/provider-registry.js.map +1 -1
- package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -1
- package/lib/agent-cli-provider/single-agent-runtime.js +15 -5
- package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -1
- package/lib/agent-cli-provider/types.d.ts +3 -0
- package/lib/agent-cli-provider/types.d.ts.map +1 -1
- package/lib/agent-cli-provider/types.js.map +1 -1
- package/lib/cluster-worker/engine-adapter.js +9 -1
- package/lib/docker-config.js +6 -1
- package/lib/provider-credential-path.js +36 -0
- package/lib/start-cluster.js +8 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/src/agent/agent-task-executor.js +79 -20
- package/src/agent/output-extraction.js +115 -0
- package/src/agent/pi-terminal-lifecycle.js +242 -0
- package/src/agent/provider-control-plane.js +26 -0
- package/src/agent/provider-terminal-failure.js +49 -24
- package/src/agent-cli-provider/adapters/claude-recovery.ts +15 -2
- package/src/agent-cli-provider/adapters/codex.ts +3 -1
- package/src/agent-cli-provider/adapters/index.ts +6 -0
- package/src/agent-cli-provider/adapters/pi.ts +105 -125
- package/src/agent-cli-provider/contract-parse.ts +8 -1
- package/src/agent-cli-provider/invoke-evidence.ts +11 -1
- package/src/agent-cli-provider/pi/credentials.ts +47 -0
- package/src/agent-cli-provider/pi/events.ts +148 -0
- package/src/agent-cli-provider/pi/protocol-state.ts +163 -0
- package/src/agent-cli-provider/pi/release.ts +4 -0
- package/src/agent-cli-provider/probe-availability.ts +19 -0
- package/src/agent-cli-provider/provider-registry.ts +48 -9
- package/src/agent-cli-provider/single-agent-runtime.ts +27 -6
- package/src/agent-cli-provider/types.ts +3 -0
- package/src/config-validator.js +1 -3
- package/src/isolation-manager.js +43 -29
- package/src/orchestrator.js +33 -1
- package/task-lib/watcher-output-runtime.js +16 -5
|
@@ -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
|
+
}
|
|
@@ -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(
|
package/src/isolation-manager.js
CHANGED
|
@@ -30,6 +30,7 @@ const {
|
|
|
30
30
|
isUsableEnvValue,
|
|
31
31
|
validateProviderEnvAuth,
|
|
32
32
|
} = require('../lib/docker-config');
|
|
33
|
+
const { resolveProviderCredentialPaths } = require('../lib/provider-credential-path');
|
|
33
34
|
const { getProvider } = require('./providers');
|
|
34
35
|
const { readRepoSettings } = require('../lib/repo-settings');
|
|
35
36
|
const { provisionClaudeCredentials } = require('./claude-credentials');
|
|
@@ -151,6 +152,34 @@ function expandHomePath(value) {
|
|
|
151
152
|
return value.replace(/^~(?=\/|$)/, os.homedir());
|
|
152
153
|
}
|
|
153
154
|
|
|
155
|
+
function appendCredentialMount(plan, mount, explicitContainerPaths, claudeContainerPath) {
|
|
156
|
+
if (mount.container === claudeContainerPath) {
|
|
157
|
+
console.warn(
|
|
158
|
+
`[IsolationManager] Skipping mount for ${mount.host} -> ${mount.container} ` +
|
|
159
|
+
'(Claude config is managed by zeroshot).'
|
|
160
|
+
);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const hostPath = expandHomePath(mount.host);
|
|
165
|
+
let stat;
|
|
166
|
+
try {
|
|
167
|
+
stat = fs.statSync(hostPath);
|
|
168
|
+
} catch {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (hostPath.endsWith('config') && !stat.isFile()) return;
|
|
172
|
+
|
|
173
|
+
plan.mountedHosts.push(hostPath);
|
|
174
|
+
const mountSpec = mount.readonly
|
|
175
|
+
? `${hostPath}:${mount.container}:ro`
|
|
176
|
+
: `${hostPath}:${mount.container}`;
|
|
177
|
+
plan.args.push('-v', mountSpec);
|
|
178
|
+
if (explicitContainerPaths.has(mount.container)) {
|
|
179
|
+
plan.explicitMountContainerPaths.push(mount.container);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
154
183
|
function pathContains(base, target) {
|
|
155
184
|
const resolvedBase = path.resolve(base);
|
|
156
185
|
const resolvedTarget = path.resolve(target);
|
|
@@ -325,7 +354,6 @@ class IsolationManager {
|
|
|
325
354
|
this._removeContainerByName(containerName);
|
|
326
355
|
|
|
327
356
|
workDir = await this._prepareIsolatedWorkspace(clusterId, workDir, reuseExisting);
|
|
328
|
-
|
|
329
357
|
// The cluster config dir carries Claude credentials (via provisionClaudeCredentials) and the
|
|
330
358
|
// Claude-specific AskUserQuestion-blocking hook (~/.claude/settings.json PreToolUse), which
|
|
331
359
|
// only the `claude` CLI reads. Creating and mounting it for every provider — regardless of
|
|
@@ -534,33 +562,7 @@ class IsolationManager {
|
|
|
534
562
|
const claudeContainerPath = path.posix.join(containerHome, '.claude');
|
|
535
563
|
|
|
536
564
|
for (const mount of mounts) {
|
|
537
|
-
|
|
538
|
-
console.warn(
|
|
539
|
-
`[IsolationManager] Skipping mount for ${mount.host} -> ${mount.container} ` +
|
|
540
|
-
'(Claude config is managed by zeroshot).'
|
|
541
|
-
);
|
|
542
|
-
continue;
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
const hostPath = expandHomePath(mount.host);
|
|
546
|
-
|
|
547
|
-
try {
|
|
548
|
-
const stat = fs.statSync(hostPath);
|
|
549
|
-
if (hostPath.endsWith('config') && !stat.isFile()) {
|
|
550
|
-
continue;
|
|
551
|
-
}
|
|
552
|
-
} catch {
|
|
553
|
-
continue;
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
const mountSpec = mount.readonly
|
|
557
|
-
? `${hostPath}:${mount.container}:ro`
|
|
558
|
-
: `${hostPath}:${mount.container}`;
|
|
559
|
-
plan.args.push('-v', mountSpec);
|
|
560
|
-
plan.mountedHosts.push(hostPath);
|
|
561
|
-
if (explicitContainerPaths.has(mount.container)) {
|
|
562
|
-
plan.explicitMountContainerPaths.push(mount.container);
|
|
563
|
-
}
|
|
565
|
+
appendCredentialMount(plan, mount, explicitContainerPaths, claudeContainerPath);
|
|
564
566
|
}
|
|
565
567
|
|
|
566
568
|
const { envToPass, explicitNames } = this._collectDockerEnvVars(
|
|
@@ -568,6 +570,16 @@ class IsolationManager {
|
|
|
568
570
|
userMountConfig,
|
|
569
571
|
settings
|
|
570
572
|
);
|
|
573
|
+
const hostOnlyEnvs = new Set(
|
|
574
|
+
mountConfig
|
|
575
|
+
.filter((preset) => typeof preset === 'string')
|
|
576
|
+
.map((preset) => MOUNT_PRESETS[preset]?.hostEnv)
|
|
577
|
+
.filter((name) => typeof name === 'string')
|
|
578
|
+
);
|
|
579
|
+
for (const hostOnlyEnv of hostOnlyEnvs) {
|
|
580
|
+
delete envToPass[hostOnlyEnv];
|
|
581
|
+
explicitNames.delete(hostOnlyEnv);
|
|
582
|
+
}
|
|
571
583
|
for (const [key, value] of Object.entries(envToPass)) {
|
|
572
584
|
plan.args.push('-e', `${key}=${value}`);
|
|
573
585
|
plan.forwardedEnv[key] = value;
|
|
@@ -680,7 +692,9 @@ class IsolationManager {
|
|
|
680
692
|
|
|
681
693
|
// A mount only counts if it carries the secret (credentialInMount !== false).
|
|
682
694
|
const credentialPaths = provider.getCredentialPaths ? provider.getCredentialPaths() : [];
|
|
683
|
-
const expandedCreds =
|
|
695
|
+
const expandedCreds = resolveProviderCredentialPaths({ ...metadata, credentialPaths }).filter(
|
|
696
|
+
(credPath) => fs.existsSync(credPath)
|
|
697
|
+
);
|
|
684
698
|
const hasCredentialMount =
|
|
685
699
|
docker.credentialInMount !== false &&
|
|
686
700
|
mountedHosts.some((hostPath) =>
|