@wix/pathgrade 1.0.17 → 1.0.19
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/README.md +7 -4
- package/dist/agents/claude/sdk-message-projector.js +12 -4
- package/dist/agents/claude/tool-results.d.ts +1 -2
- package/dist/agents/claude/tool-results.js +9 -40
- package/dist/agents/claude.js +6 -4
- package/dist/agents/codex-app-server/agent.d.ts +5 -0
- package/dist/agents/codex-app-server/agent.js +145 -179
- package/dist/agents/codex-app-server/item-lifecycle.d.ts +30 -0
- package/dist/agents/codex-app-server/item-lifecycle.js +95 -0
- package/dist/agents/codex-app-server/item-projection.d.ts +62 -0
- package/dist/agents/codex-app-server/item-projection.js +135 -0
- package/dist/agents/codex-app-server/managed-auth.d.ts +12 -0
- package/dist/agents/codex-app-server/managed-auth.js +53 -0
- package/dist/agents/codex-app-server/transport.d.ts +2 -0
- package/dist/agents/codex-app-server/transport.js +30 -3
- package/dist/agents/opencode/host-safety.d.ts +3 -0
- package/dist/agents/opencode/host-safety.js +30 -0
- package/dist/agents/opencode.d.ts +2 -4
- package/dist/agents/opencode.js +47 -38
- package/dist/openai-oauth/chatgpt-oauth-llm.d.ts +25 -0
- package/dist/openai-oauth/chatgpt-oauth-llm.js +398 -0
- package/dist/openai-oauth/codex-auth-broker.d.ts +32 -0
- package/dist/openai-oauth/codex-auth-broker.js +110 -0
- package/dist/openai-oauth/index.d.ts +2 -0
- package/dist/openai-oauth/index.js +1 -0
- package/dist/providers/credentials.d.ts +4 -1
- package/dist/providers/credentials.js +4 -3
- package/dist/providers/sandbox.d.ts +2 -0
- package/dist/providers/scripted-mcp-mock-host.js +6 -3
- package/dist/providers/workspace.d.ts +1 -0
- package/dist/providers/workspace.js +9 -1
- package/dist/sdk/agent-result-log.js +4 -2
- package/dist/sdk/agent.js +7 -0
- package/dist/sdk/judge-tools.js +14 -3
- package/dist/sdk/managed-session.d.ts +2 -0
- package/dist/sdk/managed-session.js +22 -5
- package/dist/sdk/mcp-safety.js +2 -18
- package/dist/sdk/snapshots.d.ts +1 -0
- package/dist/sdk/snapshots.js +3 -2
- package/dist/sdk/tool-event-log.js +5 -2
- package/dist/sdk/tool-event-secrets.d.ts +4 -0
- package/dist/sdk/tool-event-secrets.js +14 -0
- package/dist/sdk/turn-result-secrets.d.ts +5 -0
- package/dist/sdk/turn-result-secrets.js +12 -0
- package/dist/tool-event-results.d.ts +10 -0
- package/dist/tool-event-results.js +171 -0
- package/dist/types.d.ts +2 -0
- package/dist/utils/llm.js +11 -0
- package/docs/OPENAI_OAUTH_JUDGE.md +91 -0
- package/package.json +13 -2
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { buildSummary, extractSkillNameFromPath, inferCodexExecAction, } from '../../tool-events.js';
|
|
2
|
+
import { sanitizeToolEventResult } from '../../tool-event-results.js';
|
|
3
|
+
import { attachOriginalMcpInput } from '../../sdk/mcp-event-input.js';
|
|
4
|
+
export function projectItemIntoTurn(item, turn, sensitiveValues, timing = {}) {
|
|
5
|
+
if (item.type === 'agentMessage') {
|
|
6
|
+
const message = item;
|
|
7
|
+
if (message.text)
|
|
8
|
+
turn.assistantMessageParts.push(message.text);
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
if (item.type === 'commandExecution') {
|
|
12
|
+
projectCommand(item, turn, sensitiveValues, timing);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if (item.type === 'fileChange') {
|
|
16
|
+
const changes = item.changes;
|
|
17
|
+
for (const change of changes ?? []) {
|
|
18
|
+
turn.nonAskToolEvents.push({
|
|
19
|
+
action: 'edit_file', provider: 'codex', providerToolName: 'fileChange',
|
|
20
|
+
turnNumber: turn.turnNumber, arguments: { file_path: change.path },
|
|
21
|
+
summary: `edit_file: ${change.path}`, confidence: 'high',
|
|
22
|
+
rawSnippet: JSON.stringify(change),
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (item.type === 'mcpToolCall') {
|
|
28
|
+
projectMcpCall(item, turn, sensitiveValues, timing);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function projectCommand(cmd, turn, sensitiveValues, timing) {
|
|
32
|
+
const action = inferCodexExecAction(cmd.command);
|
|
33
|
+
const skillPath = extractSkillPathFromText(cmd.command);
|
|
34
|
+
const args = { command: cmd.command, ...(skillPath ? { path: skillPath } : {}) };
|
|
35
|
+
const exitCode = finiteNumber(cmd.exitCode);
|
|
36
|
+
const failed = cmd.status === 'failed' || cmd.status === 'declined'
|
|
37
|
+
|| (exitCode !== undefined && exitCode !== 0);
|
|
38
|
+
const status = failed ? 'error' : cmd.status === 'completed' ? 'completed' : 'incomplete';
|
|
39
|
+
const timingFields = projectToolTiming(timing, finiteNumber(cmd.durationMs));
|
|
40
|
+
turn.nonAskToolEvents.push({
|
|
41
|
+
action, provider: 'codex', providerToolName: 'commandExecution', toolUseId: cmd.id,
|
|
42
|
+
turnNumber: turn.turnNumber, arguments: args, status, ...timingFields,
|
|
43
|
+
...(typeof cmd.aggregatedOutput === 'string' || exitCode !== undefined ? {
|
|
44
|
+
result: sanitizeToolEventResult({
|
|
45
|
+
...(typeof cmd.aggregatedOutput === 'string' ? { content: cmd.aggregatedOutput } : {}),
|
|
46
|
+
...(exitCode !== undefined ? { exitCode } : {}),
|
|
47
|
+
}, sensitiveValues),
|
|
48
|
+
} : {}),
|
|
49
|
+
summary: buildSummary(action, 'commandExecution', args), confidence: 'high',
|
|
50
|
+
rawSnippet: JSON.stringify({
|
|
51
|
+
type: cmd.type, id: cmd.id, command: cmd.command, status: cmd.status,
|
|
52
|
+
cwd: cmd.cwd, commandActions: cmd.commandActions,
|
|
53
|
+
}),
|
|
54
|
+
});
|
|
55
|
+
const recordedSkills = new Set();
|
|
56
|
+
for (const commandAction of cmd.commandActions ?? []) {
|
|
57
|
+
const skillName = extractCommandActionSkillName(commandAction) ?? extractSkillNameFromText(cmd.command);
|
|
58
|
+
if (!skillName || recordedSkills.has(skillName))
|
|
59
|
+
continue;
|
|
60
|
+
recordedSkills.add(skillName);
|
|
61
|
+
turn.nonAskToolEvents.push({
|
|
62
|
+
action: 'use_skill', provider: 'codex',
|
|
63
|
+
providerToolName: `commandExecution.commandActions.${commandAction.type ?? 'unknown'}`,
|
|
64
|
+
turnNumber: turn.turnNumber, arguments: { path: commandAction.path, name: commandAction.name },
|
|
65
|
+
summary: `use_skill ${skillName}`, confidence: 'high', rawSnippet: JSON.stringify(commandAction), skillName,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function projectMcpCall(call, turn, sensitiveValues, timing) {
|
|
70
|
+
if (turn.nonAskToolEvents.some((event) => event.action === 'mcp_tool_call'
|
|
71
|
+
&& event.toolUseId === call.id && event.mcp?.invocation === 'not_invoked'))
|
|
72
|
+
return;
|
|
73
|
+
const args = recordFromUnknown(call.arguments);
|
|
74
|
+
const providerToolName = `${call.server}.${call.tool}`;
|
|
75
|
+
const error = typeof call.error?.message === 'string' ? call.error.message : undefined;
|
|
76
|
+
const status = call.status === 'completed' && error === undefined
|
|
77
|
+
? 'completed' : call.status === 'failed' || error !== undefined ? 'error' : 'incomplete';
|
|
78
|
+
const resultContent = call.result === undefined ? undefined : JSON.stringify(call.result);
|
|
79
|
+
turn.nonAskToolEvents.push(attachOriginalMcpInput({
|
|
80
|
+
action: 'mcp_tool_call', provider: 'codex', providerToolName, toolUseId: call.id,
|
|
81
|
+
turnNumber: turn.turnNumber, status, ...projectToolTiming(timing, finiteNumber(call.durationMs)),
|
|
82
|
+
arguments: { ...args, server: call.server, tool: call.tool, status: call.status ?? 'unknown' },
|
|
83
|
+
summary: `MCP tool ${providerToolName} ${call.status ?? 'unknown'}`, confidence: 'high',
|
|
84
|
+
rawSnippet: JSON.stringify({
|
|
85
|
+
type: call.type, id: call.id, server: call.server, tool: call.tool,
|
|
86
|
+
status: call.status, arguments: call.arguments, durationMs: call.durationMs,
|
|
87
|
+
}),
|
|
88
|
+
...(resultContent !== undefined || error !== undefined ? {
|
|
89
|
+
result: sanitizeToolEventResult({
|
|
90
|
+
...(resultContent !== undefined ? { content: resultContent } : {}),
|
|
91
|
+
...(error !== undefined ? { content: error } : {}),
|
|
92
|
+
}, sensitiveValues),
|
|
93
|
+
} : {}),
|
|
94
|
+
}, args));
|
|
95
|
+
}
|
|
96
|
+
/** Provider duration is authoritative; observed monotonic duration is the fallback. */
|
|
97
|
+
export function projectToolTiming(timing, providerDurationMs) {
|
|
98
|
+
const startedAtMs = finiteTimestamp(timing.startedAtWallMs);
|
|
99
|
+
const completedAtMs = finiteTimestamp(timing.completedAtWallMs);
|
|
100
|
+
const observedDurationMs = finiteTimestamp(timing.observedDurationMs);
|
|
101
|
+
const validProviderDurationMs = finiteTimestamp(providerDurationMs);
|
|
102
|
+
return {
|
|
103
|
+
...(startedAtMs !== undefined ? { startedAt: new Date(startedAtMs).toISOString() } : {}),
|
|
104
|
+
...(completedAtMs !== undefined ? { completedAt: new Date(completedAtMs).toISOString() } : {}),
|
|
105
|
+
...(validProviderDurationMs !== undefined
|
|
106
|
+
? { durationMs: validProviderDurationMs }
|
|
107
|
+
: observedDurationMs !== undefined ? { durationMs: observedDurationMs } : {}),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function extractCommandActionSkillName(action) {
|
|
111
|
+
if (typeof action.path === 'string') {
|
|
112
|
+
const direct = extractSkillNameFromPath(action.path);
|
|
113
|
+
if (direct)
|
|
114
|
+
return direct;
|
|
115
|
+
const embedded = extractSkillNameFromText(action.path);
|
|
116
|
+
if (embedded)
|
|
117
|
+
return embedded;
|
|
118
|
+
}
|
|
119
|
+
return typeof action.command === 'string' ? extractSkillNameFromText(action.command) : undefined;
|
|
120
|
+
}
|
|
121
|
+
function extractSkillNameFromText(value) {
|
|
122
|
+
return value?.match(/(?:^|[/\s"'])\.(?:agents|claude)\/skills\/([^/\s"']+)\/SKILL\.md(?:$|[\s"'])/)?.[1];
|
|
123
|
+
}
|
|
124
|
+
function extractSkillPathFromText(value) {
|
|
125
|
+
return value?.match(/(?:^|[\s"'])(?<path>(?:\/|\.{1,2}\/)?[^\s"']*(?:\.agents|\.claude)\/skills\/[^/\s"']+\/SKILL\.md)(?:$|[\s"'])/)?.groups?.path;
|
|
126
|
+
}
|
|
127
|
+
function recordFromUnknown(value) {
|
|
128
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
129
|
+
}
|
|
130
|
+
function finiteTimestamp(value) {
|
|
131
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
132
|
+
}
|
|
133
|
+
function finiteNumber(value) {
|
|
134
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
135
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createDefaultCodexAuthBroker, type CodexAuthBroker } from '../../openai-oauth/codex-auth-broker.js';
|
|
2
|
+
import type { AppServerTransport } from './transport.js';
|
|
3
|
+
export interface CodexManagedAuth {
|
|
4
|
+
login(transport: AppServerTransport): Promise<void>;
|
|
5
|
+
respondToRefresh(requestId: number | string, transport: AppServerTransport, failTurn: (message: string) => void): void;
|
|
6
|
+
}
|
|
7
|
+
export declare function createCodexManagedAuth(input: {
|
|
8
|
+
enabled: boolean;
|
|
9
|
+
broker?: CodexAuthBroker;
|
|
10
|
+
codexHome?: string;
|
|
11
|
+
createBroker?: typeof createDefaultCodexAuthBroker;
|
|
12
|
+
}): CodexManagedAuth;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { createDefaultCodexAuthBroker, } from '../../openai-oauth/codex-auth-broker.js';
|
|
2
|
+
const UNSUPPORTED_MESSAGE = 'codex app-server requires OPENAI_API_KEY for pathgrade and honors OPENAI_BASE_URL when set; ChatGPT/cached auth unsupported under transport=app-server';
|
|
3
|
+
export function createCodexManagedAuth(input) {
|
|
4
|
+
let brokerPromise;
|
|
5
|
+
const broker = () => brokerPromise ??= input.broker
|
|
6
|
+
? Promise.resolve(input.broker)
|
|
7
|
+
: (input.createBroker ?? createDefaultCodexAuthBroker)({
|
|
8
|
+
env: {
|
|
9
|
+
...process.env,
|
|
10
|
+
...(input.codexHome ? { CODEX_HOME: input.codexHome } : {}),
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
let refreshPromise;
|
|
14
|
+
const refresh = () => refreshPromise ??= broker()
|
|
15
|
+
.then((value) => value.acquireAccessSession({ refresh: true }))
|
|
16
|
+
.finally(() => { refreshPromise = undefined; });
|
|
17
|
+
return {
|
|
18
|
+
async login(transport) {
|
|
19
|
+
if (!input.enabled)
|
|
20
|
+
return;
|
|
21
|
+
try {
|
|
22
|
+
const session = await (await broker()).acquireAccessSession({ refresh: false });
|
|
23
|
+
await transport.sendRequest('account/login/start', {
|
|
24
|
+
type: 'chatgptAuthTokens',
|
|
25
|
+
accessToken: session.accessToken,
|
|
26
|
+
chatgptAccountId: session.accountId,
|
|
27
|
+
chatgptPlanType: null,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
throw new Error('Codex-managed ChatGPT authentication unavailable');
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
respondToRefresh(requestId, transport, failTurn) {
|
|
35
|
+
if (!input.enabled) {
|
|
36
|
+
transport.sendErrorResponse(requestId, -32001, UNSUPPORTED_MESSAGE);
|
|
37
|
+
failTurn(UNSUPPORTED_MESSAGE);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
void refresh()
|
|
41
|
+
.then((session) => transport.sendResponse(requestId, {
|
|
42
|
+
accessToken: session.accessToken,
|
|
43
|
+
chatgptAccountId: session.accountId,
|
|
44
|
+
chatgptPlanType: null,
|
|
45
|
+
}))
|
|
46
|
+
.catch(() => {
|
|
47
|
+
const message = 'Codex-managed ChatGPT authentication refresh failed';
|
|
48
|
+
transport.sendErrorResponse(requestId, -32001, message);
|
|
49
|
+
failTurn(message);
|
|
50
|
+
});
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -44,6 +44,8 @@ export interface SpawnAppServerTransportInput {
|
|
|
44
44
|
* Exposed for tests; defaults to 2s for production spawns.
|
|
45
45
|
*/
|
|
46
46
|
killGracePeriodMs?: number;
|
|
47
|
+
/** Disable stderr logging for secret-bearing control-plane uses. */
|
|
48
|
+
logStderrOnExit?: boolean;
|
|
47
49
|
}
|
|
48
50
|
export declare function buildAppServerSpawnArgs(args?: readonly string[], env?: NodeJS.ProcessEnv): string[];
|
|
49
51
|
/**
|
|
@@ -73,6 +73,12 @@ function createNdjsonTransportInternal(cfg) {
|
|
|
73
73
|
return;
|
|
74
74
|
output.write(JSON.stringify(obj) + '\n');
|
|
75
75
|
};
|
|
76
|
+
const rejectPending = () => {
|
|
77
|
+
for (const resolver of pendingRequests.values()) {
|
|
78
|
+
resolver({ error: { code: -32_000, message: 'AppServerTransport closed' } });
|
|
79
|
+
}
|
|
80
|
+
pendingRequests.clear();
|
|
81
|
+
};
|
|
76
82
|
return {
|
|
77
83
|
sendRequest(method, params) {
|
|
78
84
|
if (closed)
|
|
@@ -121,9 +127,10 @@ function createNdjsonTransportInternal(cfg) {
|
|
|
121
127
|
return;
|
|
122
128
|
closed = true;
|
|
123
129
|
rl.close();
|
|
124
|
-
|
|
130
|
+
rejectPending();
|
|
125
131
|
},
|
|
126
132
|
notifyClose(info) {
|
|
133
|
+
rejectPending();
|
|
127
134
|
for (const h of closeHandlers) {
|
|
128
135
|
try {
|
|
129
136
|
h(info);
|
|
@@ -234,15 +241,35 @@ export function spawnAppServerTransport(cfg = {}) {
|
|
|
234
241
|
input: child.stdout,
|
|
235
242
|
pid: child.pid,
|
|
236
243
|
});
|
|
244
|
+
let spawnFailed = false;
|
|
245
|
+
child.on('error', () => {
|
|
246
|
+
spawnFailed = true;
|
|
247
|
+
transport.notifyClose({ exitCode: null, signal: null, pid: child.pid });
|
|
248
|
+
});
|
|
237
249
|
child.on('exit', (exitCode, signal) => {
|
|
238
|
-
if (stderrBuf.trim().length > 0) {
|
|
250
|
+
if (cfg.logStderrOnExit !== false && stderrBuf.trim().length > 0) {
|
|
239
251
|
console.error(`[codex app-server pid=${child.pid}] exited with code=${exitCode} signal=${signal}. stderr:\n${stderrBuf}`);
|
|
240
252
|
}
|
|
241
253
|
transport.notifyClose({ exitCode, signal, pid: child.pid });
|
|
242
254
|
});
|
|
255
|
+
const sessionChild = {
|
|
256
|
+
pid: child.pid,
|
|
257
|
+
kill: (signal) => child.kill(signal),
|
|
258
|
+
once: (_event, listener) => {
|
|
259
|
+
const finish = () => {
|
|
260
|
+
child.off('exit', finish);
|
|
261
|
+
child.off('error', finish);
|
|
262
|
+
listener();
|
|
263
|
+
};
|
|
264
|
+
child.once('exit', finish);
|
|
265
|
+
child.once('error', finish);
|
|
266
|
+
},
|
|
267
|
+
get exitCode() { return spawnFailed ? -1 : child.exitCode; },
|
|
268
|
+
get signalCode() { return child.signalCode; },
|
|
269
|
+
};
|
|
243
270
|
return createAppServerSessionHandle({
|
|
244
271
|
transport,
|
|
245
|
-
child,
|
|
272
|
+
child: sessionChild,
|
|
246
273
|
...(cfg.killGracePeriodMs !== undefined ? { killGracePeriodMs: cfg.killGracePeriodMs } : {}),
|
|
247
274
|
});
|
|
248
275
|
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare function sha256File(filename: string): Promise<string>;
|
|
2
|
+
export declare function managedOpenCodeConfigPaths(platform?: NodeJS.Platform, username?: string): string[];
|
|
3
|
+
export declare function assertCleanManagedOpenCodeHost(candidates?: string[]): Promise<void>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as os from 'node:os';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { createReadStream } from 'node:fs';
|
|
5
|
+
export function sha256File(filename) {
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
const hash = createHash('sha256');
|
|
8
|
+
const stream = createReadStream(filename);
|
|
9
|
+
stream.on('error', reject);
|
|
10
|
+
stream.on('data', (chunk) => hash.update(chunk));
|
|
11
|
+
stream.on('end', () => resolve(hash.digest('hex')));
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
export function managedOpenCodeConfigPaths(platform = process.platform, username = os.userInfo().username) {
|
|
15
|
+
return platform === 'linux'
|
|
16
|
+
? ['/etc/opencode/opencode.json', '/etc/opencode/opencode.jsonc']
|
|
17
|
+
: [
|
|
18
|
+
'/Library/Application Support/opencode/opencode.json',
|
|
19
|
+
'/Library/Application Support/opencode/opencode.jsonc',
|
|
20
|
+
`/Library/Managed Preferences/${username}/ai.opencode.managed.plist`,
|
|
21
|
+
'/Library/Managed Preferences/ai.opencode.managed.plist',
|
|
22
|
+
];
|
|
23
|
+
}
|
|
24
|
+
export async function assertCleanManagedOpenCodeHost(candidates = managedOpenCodeConfigPaths()) {
|
|
25
|
+
for (const candidate of candidates) {
|
|
26
|
+
if (await fs.pathExists(candidate)) {
|
|
27
|
+
throw new Error(`OpenCode managed host configuration is not supported: ${candidate}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BaseAgent, type AgentCommandRunner, type AgentSession, type AgentSessionOptions, type AgentTurnResult, type EnvironmentHandle } from '../types.js';
|
|
2
|
+
export { assertCleanManagedOpenCodeHost, managedOpenCodeConfigPaths } from './opencode/host-safety.js';
|
|
2
3
|
interface SpawnResult {
|
|
3
4
|
stdout: string;
|
|
4
5
|
exitCode: number | null;
|
|
@@ -18,10 +19,7 @@ interface ParsedOpenCodeTurn {
|
|
|
18
19
|
result: AgentTurnResult;
|
|
19
20
|
sessionId: string;
|
|
20
21
|
}
|
|
21
|
-
export declare function parseOpenCodeOutput(stdout: string, processResult: Pick<SpawnResult, 'exitCode' | 'overflow' | 'aborted'>, mcpToolNames: ReadonlySet<string
|
|
22
|
-
export declare function managedOpenCodeConfigPaths(platform?: NodeJS.Platform, username?: string): string[];
|
|
23
|
-
export declare function assertCleanManagedOpenCodeHost(candidates?: string[]): Promise<void>;
|
|
22
|
+
export declare function parseOpenCodeOutput(stdout: string, processResult: Pick<SpawnResult, 'exitCode' | 'overflow' | 'aborted'>, mcpToolNames: ReadonlySet<string>, sensitiveValues?: readonly string[]): ParsedOpenCodeTurn;
|
|
24
23
|
export declare class OpenCodeAgent extends BaseAgent {
|
|
25
24
|
createSession(runtime: EnvironmentHandle, _runCommand: AgentCommandRunner, options?: AgentSessionOptions): Promise<AgentSession>;
|
|
26
25
|
}
|
|
27
|
-
export {};
|
package/dist/agents/opencode.js
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
import { createHash } from 'node:crypto';
|
|
3
|
-
import { createReadStream } from 'node:fs';
|
|
4
|
-
import * as os from 'node:os';
|
|
5
2
|
import * as path from 'node:path';
|
|
6
3
|
import fs from 'fs-extra';
|
|
7
4
|
import { BaseAgent, getRuntimeEnv, getWorkspacePath, } from '../types.js';
|
|
8
5
|
import { buildSummary, enrichSkillEvents } from '../tool-events.js';
|
|
6
|
+
import { collectSensitiveEnvValues, sanitizePersistenceValue, sanitizeToolEventResult, } from '../tool-event-results.js';
|
|
9
7
|
import { readStagedMcpServers } from '../providers/mcp-config.js';
|
|
10
8
|
import { removeSandboxRoot } from '../providers/sandbox-lifecycle.js';
|
|
9
|
+
import { attachTurnResultSensitiveValues } from '../sdk/turn-result-secrets.js';
|
|
10
|
+
import { attachOriginalMcpInput } from '../sdk/mcp-event-input.js';
|
|
11
|
+
import { attachToolEventSensitiveValues } from '../sdk/tool-event-secrets.js';
|
|
11
12
|
import { currentOpenCodePlatformKey, OPENCODE_RUNTIME_LOCK, } from './opencode/contract.js';
|
|
12
13
|
import { OpenCodeRuntimePolicy, OPENCODE_PERMISSION } from './opencode/runtime-policy.js';
|
|
13
14
|
import { killOpenCodeProcessGroup, registerOpenCodeProcessGroup, unregisterOpenCodeProcessGroup, } from './opencode/process-groups.js';
|
|
15
|
+
import { assertCleanManagedOpenCodeHost, sha256File } from './opencode/host-safety.js';
|
|
16
|
+
export { assertCleanManagedOpenCodeHost, managedOpenCodeConfigPaths } from './opencode/host-safety.js';
|
|
14
17
|
const OUTPUT_CAP_BYTES = 16 * 1024 * 1024;
|
|
15
18
|
const NATIVE_TOOL_ACTIONS = {
|
|
16
19
|
bash: 'run_shell',
|
|
@@ -147,7 +150,7 @@ function sanitizedProviderError(event) {
|
|
|
147
150
|
const retryable = typeof data.isRetryable === 'boolean' ? ` retryable=${data.isRetryable}` : '';
|
|
148
151
|
return new Error(`OpenCode provider error${status}${retryable}`);
|
|
149
152
|
}
|
|
150
|
-
export function parseOpenCodeOutput(stdout, processResult, mcpToolNames) {
|
|
153
|
+
export function parseOpenCodeOutput(stdout, processResult, mcpToolNames, sensitiveValues = []) {
|
|
151
154
|
if (processResult.overflow)
|
|
152
155
|
throw new Error('OpenCode output exceeded the 16 MiB limit');
|
|
153
156
|
if (processResult.aborted)
|
|
@@ -216,28 +219,52 @@ export function parseOpenCodeOutput(stdout, processResult, mcpToolNames) {
|
|
|
216
219
|
if (state.status !== 'completed' && state.status !== 'error') {
|
|
217
220
|
throw new Error(`OpenCode protocol error: incomplete tool ${tool}`);
|
|
218
221
|
}
|
|
222
|
+
const projectedStatus = state.status;
|
|
219
223
|
const input = state.input === undefined ? undefined : record(state.input, 'tool_use state.input');
|
|
220
224
|
const time = state.time === undefined ? undefined : record(state.time, 'tool_use state.time');
|
|
221
225
|
const startedAtMs = time === undefined ? undefined : finiteNumber(time.start, 'tool_use state.time.start');
|
|
222
226
|
const completedAtMs = time === undefined ? undefined : finiteNumber(time.end, 'tool_use state.time.end');
|
|
227
|
+
const callId = typeof part.callID === 'string' ? part.callID : undefined;
|
|
228
|
+
const metadata = state.metadata === undefined ? undefined : record(state.metadata, 'tool_use state.metadata');
|
|
229
|
+
const output = state.status === 'completed' && typeof state.output === 'string' ? state.output : undefined;
|
|
230
|
+
const error = state.status === 'error' && typeof state.error === 'string' ? state.error : undefined;
|
|
231
|
+
const exitCode = metadata && typeof metadata.exit === 'number' && Number.isFinite(metadata.exit)
|
|
232
|
+
? metadata.exit
|
|
233
|
+
: undefined;
|
|
234
|
+
const status = projectedStatus === 'completed' && exitCode !== undefined && exitCode !== 0
|
|
235
|
+
? 'error'
|
|
236
|
+
: projectedStatus;
|
|
223
237
|
const action = mcpToolNames.has(tool)
|
|
224
238
|
? 'mcp_tool_call'
|
|
225
239
|
: NATIVE_TOOL_ACTIONS[tool] ?? 'unknown';
|
|
226
|
-
|
|
240
|
+
const toolEvent = {
|
|
227
241
|
action,
|
|
228
242
|
provider: 'opencode',
|
|
229
243
|
providerToolName: tool,
|
|
244
|
+
status,
|
|
245
|
+
...(callId ? { toolUseId: callId } : {}),
|
|
230
246
|
...(input ? { arguments: input } : {}),
|
|
231
247
|
...(startedAtMs !== undefined ? { startedAt: new Date(startedAtMs).toISOString() } : {}),
|
|
232
248
|
...(completedAtMs !== undefined ? { completedAt: new Date(completedAtMs).toISOString() } : {}),
|
|
233
249
|
...(startedAtMs !== undefined && completedAtMs !== undefined
|
|
234
250
|
? { durationMs: Math.max(0, completedAtMs - startedAtMs) }
|
|
235
251
|
: {}),
|
|
252
|
+
...(output !== undefined || error !== undefined || exitCode !== undefined || metadata?.truncated === true
|
|
253
|
+
? { result: sanitizeToolEventResult({
|
|
254
|
+
...(output !== undefined ? { content: output } : {}),
|
|
255
|
+
...(error !== undefined ? { content: error } : {}),
|
|
256
|
+
...(exitCode !== undefined ? { exitCode } : {}),
|
|
257
|
+
...(metadata?.truncated === true ? { truncated: true } : {}),
|
|
258
|
+
}, sensitiveValues) }
|
|
259
|
+
: {}),
|
|
236
260
|
summary: buildSummary(action, tool, input),
|
|
237
261
|
confidence: action === 'unknown' ? 'low' : 'high',
|
|
238
262
|
rawSnippet: JSON.stringify({ tool, status: state.status, input }).slice(0, 2_000),
|
|
239
|
-
}
|
|
240
|
-
|
|
263
|
+
};
|
|
264
|
+
toolEvents.push(action === 'mcp_tool_call' && input
|
|
265
|
+
? attachOriginalMcpInput(toolEvent, input)
|
|
266
|
+
: toolEvent);
|
|
267
|
+
sanitizedTrace.push({ type, tool, status, input });
|
|
241
268
|
continue;
|
|
242
269
|
}
|
|
243
270
|
if (type === 'step_finish') {
|
|
@@ -274,34 +301,28 @@ export function parseOpenCodeOutput(stdout, processResult, mcpToolNames) {
|
|
|
274
301
|
if (stepFinishCount === 0)
|
|
275
302
|
throw new Error('OpenCode protocol error: missing step_finish');
|
|
276
303
|
const assistantMessage = textParts.join('');
|
|
277
|
-
const traceOutput = sanitizedTrace
|
|
304
|
+
const traceOutput = sanitizePersistenceValue(sanitizedTrace, sensitiveValues)
|
|
305
|
+
.map((event) => JSON.stringify(event))
|
|
306
|
+
.join('\n');
|
|
278
307
|
return {
|
|
279
308
|
sessionId,
|
|
280
|
-
result: {
|
|
309
|
+
result: attachTurnResultSensitiveValues({
|
|
281
310
|
rawOutput: traceOutput,
|
|
282
311
|
traceOutput,
|
|
283
312
|
assistantMessage,
|
|
284
313
|
visibleAssistantMessage: assistantMessage,
|
|
285
314
|
visibleAssistantMessageSource: 'assistant_message',
|
|
286
315
|
exitCode: 0,
|
|
287
|
-
toolEvents: enrichSkillEvents(toolEvents)
|
|
316
|
+
toolEvents: enrichSkillEvents(toolEvents)
|
|
317
|
+
.map((event) => attachToolEventSensitiveValues(event, sensitiveValues)),
|
|
288
318
|
inputTokens,
|
|
289
319
|
outputTokens,
|
|
290
320
|
cacheCreationInputTokens,
|
|
291
321
|
cacheReadInputTokens,
|
|
292
322
|
costUsd,
|
|
293
|
-
},
|
|
323
|
+
}, sensitiveValues),
|
|
294
324
|
};
|
|
295
325
|
}
|
|
296
|
-
function sha256File(filename) {
|
|
297
|
-
return new Promise((resolve, reject) => {
|
|
298
|
-
const hash = createHash('sha256');
|
|
299
|
-
const stream = createReadStream(filename);
|
|
300
|
-
stream.on('error', reject);
|
|
301
|
-
stream.on('data', (chunk) => hash.update(chunk));
|
|
302
|
-
stream.on('end', () => resolve(hash.digest('hex')));
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
326
|
async function assertNoProjectConfig(workspacePath) {
|
|
306
327
|
for (const name of ['opencode.json', 'opencode.jsonc', '.opencode']) {
|
|
307
328
|
if (await fs.pathExists(path.join(workspacePath, name))) {
|
|
@@ -309,23 +330,6 @@ async function assertNoProjectConfig(workspacePath) {
|
|
|
309
330
|
}
|
|
310
331
|
}
|
|
311
332
|
}
|
|
312
|
-
export function managedOpenCodeConfigPaths(platform = process.platform, username = os.userInfo().username) {
|
|
313
|
-
return platform === 'linux'
|
|
314
|
-
? ['/etc/opencode/opencode.json', '/etc/opencode/opencode.jsonc']
|
|
315
|
-
: [
|
|
316
|
-
'/Library/Application Support/opencode/opencode.json',
|
|
317
|
-
'/Library/Application Support/opencode/opencode.jsonc',
|
|
318
|
-
`/Library/Managed Preferences/${username}/ai.opencode.managed.plist`,
|
|
319
|
-
'/Library/Managed Preferences/ai.opencode.managed.plist',
|
|
320
|
-
];
|
|
321
|
-
}
|
|
322
|
-
export async function assertCleanManagedOpenCodeHost(candidates = managedOpenCodeConfigPaths()) {
|
|
323
|
-
for (const candidate of candidates) {
|
|
324
|
-
if (await fs.pathExists(candidate)) {
|
|
325
|
-
throw new Error(`OpenCode managed host configuration is not supported: ${candidate}`);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
333
|
async function projectMcpConfig(workspacePath, mcpConfigPath) {
|
|
330
334
|
if (!mcpConfigPath)
|
|
331
335
|
return {};
|
|
@@ -355,6 +359,7 @@ class OpenCodeSession {
|
|
|
355
359
|
getAbortSignal;
|
|
356
360
|
getRemainingMs;
|
|
357
361
|
requestedModel;
|
|
362
|
+
sensitiveValues;
|
|
358
363
|
xdgDirs;
|
|
359
364
|
runtimePolicy;
|
|
360
365
|
resolvedExecutable;
|
|
@@ -374,6 +379,10 @@ class OpenCodeSession {
|
|
|
374
379
|
this.getAbortSignal = options.getAbortSignal ?? (() => options.abortSignal);
|
|
375
380
|
this.getRemainingMs = options.getRemainingMs ?? (() => 0);
|
|
376
381
|
this.requestedModel = options.model;
|
|
382
|
+
this.sensitiveValues = [...new Set([
|
|
383
|
+
...collectSensitiveEnvValues(this.runtimeEnv),
|
|
384
|
+
...(options.sensitiveValues ?? []),
|
|
385
|
+
])];
|
|
377
386
|
const home = this.runtimeEnv.HOME;
|
|
378
387
|
if (!home)
|
|
379
388
|
throw new Error('OpenCode requires a managed HOME');
|
|
@@ -436,7 +445,7 @@ class OpenCodeSession {
|
|
|
436
445
|
finally {
|
|
437
446
|
await this.runtimePolicy.afterTurn();
|
|
438
447
|
}
|
|
439
|
-
const parsed = parseOpenCodeOutput(processResult.stdout, processResult, this.mcpToolNames);
|
|
448
|
+
const parsed = parseOpenCodeOutput(processResult.stdout, processResult, this.mcpToolNames, this.sensitiveValues);
|
|
440
449
|
if (this.sessionId && parsed.sessionId !== this.sessionId) {
|
|
441
450
|
throw new Error('OpenCode protocol error: resumed session ID changed');
|
|
442
451
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ToolCapableLLMPort } from '../utils/llm-types.js';
|
|
2
|
+
import { type CodexAuthBroker } from './codex-auth-broker.js';
|
|
3
|
+
export interface ChatGptOAuthJudgeOptions {
|
|
4
|
+
model: string;
|
|
5
|
+
reasoningEffort: 'low' | 'medium' | 'high';
|
|
6
|
+
requestTimeoutMs?: number;
|
|
7
|
+
}
|
|
8
|
+
export type ChatGptOAuthJudgeErrorCode = 'OAUTH_CONFIG_INVALID' | 'OAUTH_DEPENDENCY_UNAVAILABLE' | 'OAUTH_CODEX_UNAVAILABLE' | 'OAUTH_CODEX_LOGIN_REQUIRED' | 'OAUTH_AUTH_TIMEOUT' | 'OAUTH_MODEL_MISMATCH' | 'OAUTH_MODEL_UNAVAILABLE' | 'OAUTH_REQUEST_TIMEOUT' | 'OAUTH_UPSTREAM_RATE_LIMITED' | 'OAUTH_UPSTREAM_FAILED' | 'OAUTH_PROTOCOL_RESPONSE_INVALID';
|
|
9
|
+
export declare class ChatGptOAuthJudgeError extends Error {
|
|
10
|
+
readonly code: ChatGptOAuthJudgeErrorCode;
|
|
11
|
+
constructor(code: ChatGptOAuthJudgeErrorCode, message?: string);
|
|
12
|
+
}
|
|
13
|
+
type CoreModule = typeof import('@openai-oauth/core');
|
|
14
|
+
export interface ChatGptOAuthJudgeDependencies {
|
|
15
|
+
broker?: CodexAuthBroker;
|
|
16
|
+
fetch?: typeof globalThis.fetch;
|
|
17
|
+
loadCore?: () => Promise<CoreModule>;
|
|
18
|
+
codexBinary?: string;
|
|
19
|
+
codexEnv?: NodeJS.ProcessEnv;
|
|
20
|
+
authTimeoutMs?: number;
|
|
21
|
+
}
|
|
22
|
+
export declare function createChatGptOAuthJudgeLLM(options: ChatGptOAuthJudgeOptions): ToolCapableLLMPort;
|
|
23
|
+
/** Internal deterministic seam; intentionally omitted from the package subpath exports. */
|
|
24
|
+
export declare function createChatGptOAuthJudgeLLMWithDependencies(options: ChatGptOAuthJudgeOptions, dependencies?: ChatGptOAuthJudgeDependencies): ToolCapableLLMPort;
|
|
25
|
+
export {};
|