@parall/codex-agent 1.31.0 → 1.32.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/dist/config.d.ts.map +1 -1
- package/dist/config.js +34 -35
- package/dist/dispatch.d.ts +6 -5
- package/dist/dispatch.d.ts.map +1 -1
- package/dist/dispatch.js +101 -81
- package/dist/event-mapping.d.ts +1 -1
- package/dist/event-mapping.d.ts.map +1 -1
- package/dist/event-mapping.js +102 -82
- package/dist/index.js +154 -118
- package/dist/jsonrpc-client.d.ts +4 -4
- package/dist/jsonrpc-client.d.ts.map +1 -1
- package/dist/jsonrpc-client.js +15 -15
- package/dist/session-manager.d.ts +1 -1
- package/dist/session-manager.d.ts.map +1 -1
- package/dist/session-manager.js +7 -5
- package/dist/workspace.d.ts +1 -1
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +40 -39
- package/package.json +4 -4
- package/src/config.ts +41 -36
- package/src/dispatch.ts +139 -109
- package/src/event-mapping.ts +135 -109
- package/src/index.ts +187 -121
- package/src/jsonrpc-client.ts +22 -20
- package/src/session-manager.ts +10 -6
- package/src/workspace.ts +50 -43
package/dist/event-mapping.js
CHANGED
|
@@ -27,37 +27,37 @@ export class EventMapper {
|
|
|
27
27
|
const p = (params ?? {});
|
|
28
28
|
switch (method) {
|
|
29
29
|
// Streaming deltas are intentionally dropped — see the class comment.
|
|
30
|
-
case
|
|
31
|
-
case
|
|
32
|
-
case
|
|
30
|
+
case 'item/agentMessage/delta':
|
|
31
|
+
case 'item/reasoning/textDelta':
|
|
32
|
+
case 'item/reasoning/summaryTextDelta':
|
|
33
33
|
break;
|
|
34
|
-
case
|
|
34
|
+
case 'item/started': {
|
|
35
35
|
const item = p.item;
|
|
36
|
-
if (!item || typeof item.type !==
|
|
36
|
+
if (!item || typeof item.type !== 'string')
|
|
37
37
|
break;
|
|
38
|
-
events.push(...this.mapItem(item,
|
|
38
|
+
events.push(...this.mapItem(item, 'started'));
|
|
39
39
|
break;
|
|
40
40
|
}
|
|
41
|
-
case
|
|
41
|
+
case 'item/completed': {
|
|
42
42
|
const item = p.item;
|
|
43
|
-
if (!item || typeof item.type !==
|
|
43
|
+
if (!item || typeof item.type !== 'string')
|
|
44
44
|
break;
|
|
45
|
-
events.push(...this.mapItem(item,
|
|
45
|
+
events.push(...this.mapItem(item, 'completed'));
|
|
46
46
|
break;
|
|
47
47
|
}
|
|
48
|
-
case
|
|
48
|
+
case 'turn/completed': {
|
|
49
49
|
const turn = p.turn;
|
|
50
50
|
const status = turn ? asString(turn.status) : undefined;
|
|
51
51
|
const error = turn?.error;
|
|
52
|
-
if (status ===
|
|
53
|
-
const message = asString(error?.message) ??
|
|
54
|
-
events.push({ type:
|
|
52
|
+
if (status === 'failed') {
|
|
53
|
+
const message = asString(error?.message) ?? 'Codex turn failed';
|
|
54
|
+
events.push({ type: 'error', message });
|
|
55
55
|
}
|
|
56
56
|
break;
|
|
57
57
|
}
|
|
58
|
-
case
|
|
59
|
-
const message = asString(p.message) ??
|
|
60
|
-
events.push({ type:
|
|
58
|
+
case 'error': {
|
|
59
|
+
const message = asString(p.message) ?? 'Codex error';
|
|
60
|
+
events.push({ type: 'error', message });
|
|
61
61
|
break;
|
|
62
62
|
}
|
|
63
63
|
}
|
|
@@ -67,8 +67,8 @@ export class EventMapper {
|
|
|
67
67
|
const type = asString(item.type);
|
|
68
68
|
const id = asString(item.id);
|
|
69
69
|
const now = Date.now();
|
|
70
|
-
if (type ===
|
|
71
|
-
if (phase !==
|
|
70
|
+
if (type === 'agentMessage') {
|
|
71
|
+
if (phase !== 'completed')
|
|
72
72
|
return [];
|
|
73
73
|
const text = asString(item.text);
|
|
74
74
|
if (!text)
|
|
@@ -76,62 +76,68 @@ export class EventMapper {
|
|
|
76
76
|
// Layer 0 symmetric output contract: plain text is never auto-projected;
|
|
77
77
|
// agents must explicitly call `@parall/cli messages send` / `dm` to reach
|
|
78
78
|
// the chat. The text is still recorded as a session step for audit.
|
|
79
|
-
return [{ type:
|
|
79
|
+
return [{ type: 'text', text, project: false }];
|
|
80
80
|
}
|
|
81
|
-
if (type ===
|
|
82
|
-
if (phase !==
|
|
81
|
+
if (type === 'reasoning') {
|
|
82
|
+
if (phase !== 'completed')
|
|
83
83
|
return [];
|
|
84
84
|
const text = joinReasoningText(item);
|
|
85
85
|
if (!text)
|
|
86
86
|
return [];
|
|
87
|
-
return [{ type:
|
|
87
|
+
return [{ type: 'thinking', text }];
|
|
88
88
|
}
|
|
89
|
-
if (type ===
|
|
89
|
+
if (type === 'commandExecution') {
|
|
90
90
|
const command = formatCommand(item.command);
|
|
91
91
|
const callId = id ?? `shell-${now}`;
|
|
92
|
-
if (phase ===
|
|
92
|
+
if (phase === 'started') {
|
|
93
93
|
this.toolCallStart.set(callId, now);
|
|
94
|
-
return [
|
|
95
|
-
|
|
94
|
+
return [
|
|
95
|
+
{
|
|
96
|
+
type: 'tool_call',
|
|
96
97
|
callId,
|
|
97
|
-
toolName:
|
|
98
|
+
toolName: 'shell',
|
|
98
99
|
input: { command, cwd: asString(item.cwd) },
|
|
99
100
|
startedAt: new Date(now).toISOString(),
|
|
100
|
-
}
|
|
101
|
+
},
|
|
102
|
+
];
|
|
101
103
|
}
|
|
102
104
|
// Codex app-server uses camelCase: aggregatedOutput / exitCode / durationMs
|
|
103
105
|
// (see openai/codex codex-rs/app-server-protocol). Earlier snake_case
|
|
104
106
|
// probing dropped exit codes and split outputs that arrive as one string.
|
|
105
107
|
const durationMs = this.resolveDuration(callId, now, item.durationMs);
|
|
106
108
|
const output = asString(item.aggregatedOutput) ?? joinStreams(item.stdout, item.stderr);
|
|
107
|
-
const exitCode = typeof item.exitCode ===
|
|
109
|
+
const exitCode = typeof item.exitCode === 'number' ? item.exitCode : undefined;
|
|
108
110
|
const status = asString(item.status);
|
|
109
|
-
const failed = (exitCode !== undefined && exitCode !== 0)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
type: "tool_result",
|
|
111
|
+
const failed = (exitCode !== undefined && exitCode !== 0) || status === 'failed' || status === 'declined';
|
|
112
|
+
return [
|
|
113
|
+
{
|
|
114
|
+
type: 'tool_result',
|
|
114
115
|
callId,
|
|
115
|
-
toolName:
|
|
116
|
+
toolName: 'shell',
|
|
116
117
|
output,
|
|
117
|
-
...(failed
|
|
118
|
+
...(failed
|
|
119
|
+
? { error: output || `shell ${status ?? 'exited'} (code=${exitCode ?? '?'})` }
|
|
120
|
+
: {}),
|
|
118
121
|
...(durationMs !== undefined ? { durationMs } : {}),
|
|
119
|
-
}
|
|
122
|
+
},
|
|
123
|
+
];
|
|
120
124
|
}
|
|
121
|
-
if (type ===
|
|
125
|
+
if (type === 'mcpToolCall') {
|
|
122
126
|
const server = asString(item.server);
|
|
123
127
|
const tool = asString(item.tool);
|
|
124
|
-
const toolName = server && tool ? `${server}:${tool}` : tool ?? server ??
|
|
128
|
+
const toolName = server && tool ? `${server}:${tool}` : (tool ?? server ?? 'mcp');
|
|
125
129
|
const callId = id ?? `mcp-${now}`;
|
|
126
|
-
if (phase ===
|
|
130
|
+
if (phase === 'started') {
|
|
127
131
|
this.toolCallStart.set(callId, now);
|
|
128
|
-
return [
|
|
129
|
-
|
|
132
|
+
return [
|
|
133
|
+
{
|
|
134
|
+
type: 'tool_call',
|
|
130
135
|
callId,
|
|
131
136
|
toolName,
|
|
132
137
|
input: item.arguments ?? {},
|
|
133
138
|
startedAt: new Date(now).toISOString(),
|
|
134
|
-
}
|
|
139
|
+
},
|
|
140
|
+
];
|
|
135
141
|
}
|
|
136
142
|
const durationMs = this.resolveDuration(callId, now, item.durationMs);
|
|
137
143
|
const result = item.result;
|
|
@@ -140,62 +146,70 @@ export class EventMapper {
|
|
|
140
146
|
// success path renders an empty-quoted output and the failure path's
|
|
141
147
|
// `output || "MCP tool call failed"` fallback gets eaten because the
|
|
142
148
|
// literal `'""'` is truthy.
|
|
143
|
-
const output = result == null
|
|
144
|
-
? ""
|
|
145
|
-
: typeof result === "string" ? result : JSON.stringify(result);
|
|
149
|
+
const output = result == null ? '' : typeof result === 'string' ? result : JSON.stringify(result);
|
|
146
150
|
const success = item.isSuccess !== false;
|
|
147
|
-
return [
|
|
148
|
-
|
|
151
|
+
return [
|
|
152
|
+
{
|
|
153
|
+
type: 'tool_result',
|
|
149
154
|
callId,
|
|
150
155
|
toolName,
|
|
151
156
|
output,
|
|
152
|
-
...(success ? {} : { error: output ||
|
|
157
|
+
...(success ? {} : { error: output || 'MCP tool call failed' }),
|
|
153
158
|
...(durationMs !== undefined ? { durationMs } : {}),
|
|
154
|
-
}
|
|
159
|
+
},
|
|
160
|
+
];
|
|
155
161
|
}
|
|
156
|
-
if (type ===
|
|
162
|
+
if (type === 'fileChange') {
|
|
157
163
|
const callId = id ?? `patch-${now}`;
|
|
158
|
-
if (phase ===
|
|
164
|
+
if (phase === 'started') {
|
|
159
165
|
this.toolCallStart.set(callId, now);
|
|
160
|
-
return [
|
|
161
|
-
|
|
166
|
+
return [
|
|
167
|
+
{
|
|
168
|
+
type: 'tool_call',
|
|
162
169
|
callId,
|
|
163
|
-
toolName:
|
|
170
|
+
toolName: 'patch',
|
|
164
171
|
input: { changes: item.changes ?? [] },
|
|
165
172
|
startedAt: new Date(now).toISOString(),
|
|
166
|
-
}
|
|
173
|
+
},
|
|
174
|
+
];
|
|
167
175
|
}
|
|
168
176
|
const durationMs = this.resolveDuration(callId, now, undefined);
|
|
169
|
-
return [
|
|
170
|
-
|
|
177
|
+
return [
|
|
178
|
+
{
|
|
179
|
+
type: 'tool_result',
|
|
171
180
|
callId,
|
|
172
|
-
toolName:
|
|
173
|
-
output:
|
|
181
|
+
toolName: 'patch',
|
|
182
|
+
output: '',
|
|
174
183
|
...(durationMs !== undefined ? { durationMs } : {}),
|
|
175
|
-
}
|
|
184
|
+
},
|
|
185
|
+
];
|
|
176
186
|
}
|
|
177
|
-
if (type ===
|
|
187
|
+
if (type === 'webSearch') {
|
|
178
188
|
const callId = id ?? `search-${now}`;
|
|
179
|
-
if (phase ===
|
|
189
|
+
if (phase === 'started') {
|
|
180
190
|
this.toolCallStart.set(callId, now);
|
|
181
|
-
return [
|
|
182
|
-
|
|
191
|
+
return [
|
|
192
|
+
{
|
|
193
|
+
type: 'tool_call',
|
|
183
194
|
callId,
|
|
184
|
-
toolName:
|
|
185
|
-
input: { query: asString(item.query) ??
|
|
195
|
+
toolName: 'web_search',
|
|
196
|
+
input: { query: asString(item.query) ?? '' },
|
|
186
197
|
startedAt: new Date(now).toISOString(),
|
|
187
|
-
}
|
|
198
|
+
},
|
|
199
|
+
];
|
|
188
200
|
}
|
|
189
201
|
const durationMs = this.resolveDuration(callId, now, item.durationMs);
|
|
190
202
|
const results = Array.isArray(item.results) ? item.results : [];
|
|
191
|
-
const output = results.length > 0 ? JSON.stringify(results) :
|
|
192
|
-
return [
|
|
193
|
-
|
|
203
|
+
const output = results.length > 0 ? JSON.stringify(results) : '';
|
|
204
|
+
return [
|
|
205
|
+
{
|
|
206
|
+
type: 'tool_result',
|
|
194
207
|
callId,
|
|
195
|
-
toolName:
|
|
208
|
+
toolName: 'web_search',
|
|
196
209
|
output,
|
|
197
210
|
...(durationMs !== undefined ? { durationMs } : {}),
|
|
198
|
-
}
|
|
211
|
+
},
|
|
212
|
+
];
|
|
199
213
|
}
|
|
200
214
|
return [];
|
|
201
215
|
}
|
|
@@ -208,7 +222,7 @@ export class EventMapper {
|
|
|
208
222
|
resolveDuration(callId, now, serverDurationMs) {
|
|
209
223
|
const start = this.toolCallStart.get(callId);
|
|
210
224
|
this.toolCallStart.delete(callId);
|
|
211
|
-
if (typeof serverDurationMs ===
|
|
225
|
+
if (typeof serverDurationMs === 'number' && Number.isFinite(serverDurationMs)) {
|
|
212
226
|
return Math.max(0, serverDurationMs);
|
|
213
227
|
}
|
|
214
228
|
if (start === undefined)
|
|
@@ -217,30 +231,36 @@ export class EventMapper {
|
|
|
217
231
|
}
|
|
218
232
|
}
|
|
219
233
|
function asString(value) {
|
|
220
|
-
if (typeof value !==
|
|
234
|
+
if (typeof value !== 'string')
|
|
221
235
|
return undefined;
|
|
222
236
|
const trimmed = value.trim();
|
|
223
237
|
return trimmed.length > 0 ? trimmed : undefined;
|
|
224
238
|
}
|
|
225
239
|
function joinReasoningText(item) {
|
|
226
|
-
const summary = Array.isArray(item.summary)
|
|
227
|
-
|
|
240
|
+
const summary = Array.isArray(item.summary)
|
|
241
|
+
? item.summary.filter((x) => typeof x === 'string')
|
|
242
|
+
: [];
|
|
243
|
+
const content = Array.isArray(item.content)
|
|
244
|
+
? item.content.filter((x) => typeof x === 'string')
|
|
245
|
+
: [];
|
|
228
246
|
const parts = [...summary, ...content, asString(item.text)].filter(Boolean);
|
|
229
|
-
return parts.join(
|
|
247
|
+
return parts.join('\n').trim();
|
|
230
248
|
}
|
|
231
249
|
function joinStreams(stdout, stderr) {
|
|
232
250
|
const out = asString(stdout);
|
|
233
251
|
const err = asString(stderr);
|
|
234
252
|
if (out && err)
|
|
235
253
|
return `${out}\n${err}`;
|
|
236
|
-
return out ?? err ??
|
|
254
|
+
return out ?? err ?? '';
|
|
237
255
|
}
|
|
238
256
|
/** Codex sends `command` either as a plain string or as an argv array (e.g. ["sh", "-c", "ls"]). */
|
|
239
257
|
function formatCommand(command) {
|
|
240
|
-
if (typeof command ===
|
|
258
|
+
if (typeof command === 'string')
|
|
241
259
|
return command;
|
|
242
260
|
if (Array.isArray(command)) {
|
|
243
|
-
return command
|
|
261
|
+
return command
|
|
262
|
+
.map((part) => (typeof part === 'string' ? part : JSON.stringify(part)))
|
|
263
|
+
.join(' ');
|
|
244
264
|
}
|
|
245
|
-
return
|
|
265
|
+
return '';
|
|
246
266
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import * as os from
|
|
3
|
-
import { ParallAgentGateway, createPlatformConfigManager, createLogger, childLogger, isPlatformManagedProfile, parseShutdownDeadlineMs, parseProviderConfig, clearAllProviderCreds, llmSource } from
|
|
4
|
-
import { ApiError, ParallClient, ParallWs } from
|
|
5
|
-
import { buildCodexRuntimeKey, contextFilePathForSession, resolveCodexAgentConfig, resolveWsUrl, sessionStateFilePathForRuntime, stepIdFilePathForSession, } from
|
|
6
|
-
import { CodexAppServerAdapter } from
|
|
7
|
-
import { CodexSessionManager } from
|
|
8
|
-
import { ensureCodexWorkspace, ensureParallProvider, ensureWorkspaceTrusted, isParallProxyMode } from
|
|
9
|
-
const log = createLogger(
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import { ParallAgentGateway, createPlatformConfigManager, createLogger, createOtelLogger, childLogger, isPlatformManagedProfile, isPlatformModelOverride, resolveRuntimeModel, parseShutdownDeadlineMs, parseForkDeadlineMs, parseDispatchDeadlineMs, parseProviderConfig, clearAllProviderCreds, llmSource, initAgentTelemetry, } from '@parall/agent-core';
|
|
4
|
+
import { ApiError, ParallClient, ParallWs } from '@parall/sdk';
|
|
5
|
+
import { buildCodexRuntimeKey, contextFilePathForSession, resolveCodexAgentConfig, resolveWsUrl, sessionStateFilePathForRuntime, stepIdFilePathForSession, } from './config.js';
|
|
6
|
+
import { CodexAppServerAdapter } from './dispatch.js';
|
|
7
|
+
import { CodexSessionManager } from './session-manager.js';
|
|
8
|
+
import { ensureCodexWorkspace, ensureParallProvider, ensureWorkspaceTrusted, isParallProxyMode, } from './workspace.js';
|
|
9
|
+
const log = createLogger('codex-agent');
|
|
10
10
|
let activeLog = log;
|
|
11
11
|
async function getAgentMeWithLegacyFallback(client, orgId) {
|
|
12
12
|
try {
|
|
@@ -26,11 +26,11 @@ function resolveProviderEnv() {
|
|
|
26
26
|
return;
|
|
27
27
|
clearAllProviderCreds(process.env);
|
|
28
28
|
const source = llmSource(pc);
|
|
29
|
-
if (source ===
|
|
29
|
+
if (source === 'parall') {
|
|
30
30
|
process.env.OPENAI_API_KEY = process.env.PRLL_API_KEY;
|
|
31
31
|
process.env.OPENAI_BASE_URL = `${process.env.PRLL_API_URL}/api/llm/v1`;
|
|
32
32
|
}
|
|
33
|
-
else if (source ===
|
|
33
|
+
else if (source === 'custom') {
|
|
34
34
|
if (pc.openai_api_key)
|
|
35
35
|
process.env.OPENAI_API_KEY = pc.openai_api_key;
|
|
36
36
|
if (pc.openai_base_url)
|
|
@@ -38,117 +38,153 @@ function resolveProviderEnv() {
|
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
async function main() {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const client = new ParallClient({
|
|
44
|
-
baseUrl: config.apiUrl,
|
|
45
|
-
token: config.apiKey,
|
|
46
|
-
swimlaneName: config.swimlaneName,
|
|
47
|
-
});
|
|
48
|
-
const me = await getAgentMeWithLegacyFallback(client, config.orgId);
|
|
49
|
-
const agentUserId = me.id;
|
|
50
|
-
const agentLog = childLogger(log, agentUserId);
|
|
51
|
-
activeLog = agentLog;
|
|
52
|
-
ensureWorkspaceTrusted(config.codexHome, config.workspaceDir, agentLog);
|
|
53
|
-
const useParallProvider = isParallProxyMode();
|
|
54
|
-
if (useParallProvider) {
|
|
55
|
-
ensureParallProvider(config.codexHome, config.apiUrl, agentLog);
|
|
56
|
-
agentLog.info("parall custom provider configured (Responses API HTTP/SSE mode)");
|
|
57
|
-
}
|
|
58
|
-
ensureCodexWorkspace(config.workspaceDir, agentLog, {
|
|
59
|
-
userId: agentUserId,
|
|
60
|
-
displayName: me.display_name,
|
|
61
|
-
description: me.agent_profile?.description ?? undefined,
|
|
62
|
-
});
|
|
63
|
-
const runtimeKey = config.runtimeKey || buildCodexRuntimeKey(agentUserId);
|
|
64
|
-
const mainContextFilePath = contextFilePathForSession(config.stateDir, runtimeKey);
|
|
65
|
-
const sessionStateFilePath = sessionStateFilePathForRuntime(config.stateDir, runtimeKey);
|
|
66
|
-
const sessionManager = new CodexSessionManager(runtimeKey, sessionStateFilePath, agentLog);
|
|
67
|
-
const resolvedWsUrl = resolveWsUrl(config.apiUrl, config.wsUrl, config.swimlaneName);
|
|
68
|
-
const ws = new ParallWs({
|
|
69
|
-
getTicket: () => client.getWsTicket(),
|
|
70
|
-
wsUrl: resolvedWsUrl,
|
|
71
|
-
});
|
|
72
|
-
const configMgr = createPlatformConfigManager({ client, stateDir: config.stateDir, runtimeType: "codex", log: agentLog });
|
|
73
|
-
const platformDefaults = await configMgr.fetch();
|
|
74
|
-
let platformManaged = isPlatformManagedProfile(me.agent_profile);
|
|
75
|
-
const resolvedModel = platformManaged ? (platformDefaults.model ?? config.model) : config.model;
|
|
76
|
-
const resolvedEffort = platformManaged ? (platformDefaults.thinkingEffort ?? config.reasoningEffort) : config.reasoningEffort;
|
|
77
|
-
if (platformDefaults.model)
|
|
78
|
-
agentLog.info(`platform config: model=${platformDefaults.model} (${platformManaged ? "applied" : "ignored, model_management=self"})`);
|
|
79
|
-
if (platformDefaults.thinkingEffort)
|
|
80
|
-
agentLog.info(`platform config: reasoning_effort=${platformDefaults.thinkingEffort} (${platformManaged ? "applied" : "ignored, model_management=self"})`);
|
|
81
|
-
const adapter = new CodexAppServerAdapter({
|
|
82
|
-
codexBin: config.codexBin,
|
|
83
|
-
codexHome: config.codexHome,
|
|
84
|
-
workspaceDir: config.workspaceDir,
|
|
85
|
-
model: resolvedModel,
|
|
86
|
-
reasoningEffort: resolvedEffort,
|
|
87
|
-
sandbox: config.sandbox,
|
|
88
|
-
approvalPolicy: config.approvalPolicy,
|
|
89
|
-
sessionManager,
|
|
90
|
-
log: agentLog,
|
|
91
|
-
contextFilePath: mainContextFilePath,
|
|
92
|
-
useParallProvider,
|
|
93
|
-
});
|
|
94
|
-
const gateway = new ParallAgentGateway({
|
|
95
|
-
accountId: agentUserId,
|
|
96
|
-
client,
|
|
97
|
-
ws,
|
|
98
|
-
connectionLabel: resolvedWsUrl,
|
|
99
|
-
config: {
|
|
100
|
-
parall_url: config.apiUrl,
|
|
101
|
-
api_key: config.apiKey,
|
|
102
|
-
org_id: config.orgId,
|
|
103
|
-
},
|
|
104
|
-
agentUserId,
|
|
105
|
-
runtimeType: "codex",
|
|
106
|
-
runtimeKey,
|
|
107
|
-
runtimeRef: {
|
|
108
|
-
hostname: os.hostname(),
|
|
109
|
-
pid: process.pid,
|
|
110
|
-
workspace_dir: config.workspaceDir,
|
|
111
|
-
codex_home: config.codexHome,
|
|
112
|
-
driver: "app-server",
|
|
113
|
-
},
|
|
114
|
-
dispatchAdapter: adapter,
|
|
115
|
-
log: agentLog,
|
|
116
|
-
shutdownDeadlineMs: parseShutdownDeadlineMs(process.env.PRLL_SHUTDOWN_DEADLINE_MS),
|
|
117
|
-
contextFilePathForSession: (sessionKey) => contextFilePathForSession(config.stateDir, sessionKey),
|
|
118
|
-
stepIdFilePathForSession: (sessionKey) => stepIdFilePathForSession(config.stateDir, sessionKey),
|
|
119
|
-
onConfigUpdate: async () => {
|
|
120
|
-
const refreshed = await getAgentMeWithLegacyFallback(client, config.orgId);
|
|
121
|
-
platformManaged = isPlatformManagedProfile(refreshed.agent_profile);
|
|
122
|
-
const updated = await configMgr.fetch();
|
|
123
|
-
adapter.updateConfig({
|
|
124
|
-
model: platformManaged ? (updated.model ?? config.model) : (config.model ?? null),
|
|
125
|
-
reasoningEffort: platformManaged ? (updated.thinkingEffort ?? config.reasoningEffort) : (config.reasoningEffort ?? null),
|
|
126
|
-
});
|
|
127
|
-
},
|
|
128
|
-
onSessionReady: async () => {
|
|
129
|
-
const refreshed = await getAgentMeWithLegacyFallback(client, config.orgId);
|
|
130
|
-
platformManaged = isPlatformManagedProfile(refreshed.agent_profile);
|
|
131
|
-
const updated = await configMgr.fetch();
|
|
132
|
-
adapter.updateConfig({
|
|
133
|
-
model: platformManaged ? (updated.model ?? config.model) : (config.model ?? null),
|
|
134
|
-
reasoningEffort: platformManaged ? (updated.thinkingEffort ?? config.reasoningEffort) : (config.reasoningEffort ?? null),
|
|
135
|
-
});
|
|
136
|
-
},
|
|
137
|
-
onNewSession: () => { sessionManager.clearMainThread(); },
|
|
138
|
-
onSessionStale: () => { sessionManager.clearMainThread(); },
|
|
139
|
-
});
|
|
140
|
-
const abortController = new AbortController();
|
|
141
|
-
const abort = () => abortController.abort();
|
|
142
|
-
process.on("SIGINT", abort);
|
|
143
|
-
process.on("SIGTERM", abort);
|
|
41
|
+
const telemetry = await initAgentTelemetry('parall-codex-agent', 'codex');
|
|
42
|
+
activeLog = createOtelLogger('agent', 'codex-agent');
|
|
144
43
|
try {
|
|
145
|
-
|
|
146
|
-
|
|
44
|
+
resolveProviderEnv();
|
|
45
|
+
const config = resolveCodexAgentConfig(process.env);
|
|
46
|
+
const client = new ParallClient({
|
|
47
|
+
baseUrl: config.apiUrl,
|
|
48
|
+
token: config.apiKey,
|
|
49
|
+
swimlaneName: config.swimlaneName,
|
|
50
|
+
});
|
|
51
|
+
const me = await getAgentMeWithLegacyFallback(client, config.orgId);
|
|
52
|
+
const agentUserId = me.id;
|
|
53
|
+
const agentLog = childLogger(activeLog, agentUserId);
|
|
54
|
+
activeLog = agentLog;
|
|
55
|
+
ensureWorkspaceTrusted(config.codexHome, config.workspaceDir, agentLog);
|
|
56
|
+
const useParallProvider = isParallProxyMode();
|
|
57
|
+
if (useParallProvider) {
|
|
58
|
+
ensureParallProvider(config.codexHome, config.apiUrl, agentLog);
|
|
59
|
+
agentLog.info('parall custom provider configured (Responses API HTTP/SSE mode)');
|
|
60
|
+
}
|
|
61
|
+
ensureCodexWorkspace(config.workspaceDir, agentLog, {
|
|
62
|
+
userId: agentUserId,
|
|
63
|
+
displayName: me.display_name,
|
|
64
|
+
description: me.agent_profile?.description ?? undefined,
|
|
65
|
+
});
|
|
66
|
+
const runtimeKey = config.runtimeKey || buildCodexRuntimeKey(agentUserId);
|
|
67
|
+
const mainContextFilePath = contextFilePathForSession(config.stateDir, runtimeKey);
|
|
68
|
+
const sessionStateFilePath = sessionStateFilePathForRuntime(config.stateDir, runtimeKey);
|
|
69
|
+
const sessionManager = new CodexSessionManager(runtimeKey, sessionStateFilePath, agentLog);
|
|
70
|
+
const resolvedWsUrl = resolveWsUrl(config.apiUrl, config.wsUrl, config.swimlaneName);
|
|
71
|
+
const ws = new ParallWs({
|
|
72
|
+
getTicket: () => client.getWsTicket(),
|
|
73
|
+
wsUrl: resolvedWsUrl,
|
|
74
|
+
});
|
|
75
|
+
const configMgr = createPlatformConfigManager({
|
|
76
|
+
client,
|
|
77
|
+
stateDir: config.stateDir,
|
|
78
|
+
runtimeType: 'codex',
|
|
79
|
+
log: agentLog,
|
|
80
|
+
});
|
|
81
|
+
const platformDefaults = await configMgr.fetch();
|
|
82
|
+
let platformManaged = isPlatformManagedProfile(me.agent_profile);
|
|
83
|
+
// Model precedence: operator override (platform-managed) > env > server floor.
|
|
84
|
+
// The server delivers a model floor for Parall-proxy routes even when
|
|
85
|
+
// model_management=self, so the CLI never falls back to a built-in default
|
|
86
|
+
// the catalog might reject — but that floor must lose to an explicit env
|
|
87
|
+
// override. A profile is an operator-override only when it is platform-managed
|
|
88
|
+
// AND not explicitly self-managed: this treats legacy hosted agents
|
|
89
|
+
// (model_management=null, resolved to platform server-side) as overrides,
|
|
90
|
+
// while excluding local self agents whose delivered model is a floor.
|
|
91
|
+
// reasoning_effort stays an operator override (platform).
|
|
92
|
+
let platformModelOverride = isPlatformModelOverride(me.agent_profile);
|
|
93
|
+
const resolvedModel = resolveRuntimeModel(platformModelOverride, platformDefaults.model, config.model);
|
|
94
|
+
const resolvedEffort = platformManaged
|
|
95
|
+
? (platformDefaults.thinkingEffort ?? config.reasoningEffort)
|
|
96
|
+
: config.reasoningEffort;
|
|
97
|
+
if (platformDefaults.model)
|
|
98
|
+
agentLog.info(`platform config: model=${platformDefaults.model} → resolved=${resolvedModel ?? 'cli-default'} (${platformModelOverride ? 'platform-managed' : 'floor/self'})`);
|
|
99
|
+
if (platformDefaults.thinkingEffort)
|
|
100
|
+
agentLog.info(`platform config: reasoning_effort=${platformDefaults.thinkingEffort} (${platformModelOverride ? 'applied' : 'ignored, model_management=self'})`);
|
|
101
|
+
const adapter = new CodexAppServerAdapter({
|
|
102
|
+
codexBin: config.codexBin,
|
|
103
|
+
codexHome: config.codexHome,
|
|
104
|
+
workspaceDir: config.workspaceDir,
|
|
105
|
+
model: resolvedModel,
|
|
106
|
+
reasoningEffort: resolvedEffort,
|
|
107
|
+
sandbox: config.sandbox,
|
|
108
|
+
approvalPolicy: config.approvalPolicy,
|
|
109
|
+
sessionManager,
|
|
110
|
+
log: agentLog,
|
|
111
|
+
contextFilePath: mainContextFilePath,
|
|
112
|
+
useParallProvider,
|
|
113
|
+
});
|
|
114
|
+
const gateway = new ParallAgentGateway({
|
|
115
|
+
accountId: agentUserId,
|
|
116
|
+
client,
|
|
117
|
+
ws,
|
|
118
|
+
connectionLabel: resolvedWsUrl,
|
|
119
|
+
config: {
|
|
120
|
+
parall_url: config.apiUrl,
|
|
121
|
+
api_key: config.apiKey,
|
|
122
|
+
org_id: config.orgId,
|
|
123
|
+
},
|
|
124
|
+
agentUserId,
|
|
125
|
+
runtimeType: 'codex',
|
|
126
|
+
runtimeKey,
|
|
127
|
+
runtimeRef: {
|
|
128
|
+
hostname: os.hostname(),
|
|
129
|
+
pid: process.pid,
|
|
130
|
+
workspace_dir: config.workspaceDir,
|
|
131
|
+
codex_home: config.codexHome,
|
|
132
|
+
driver: 'app-server',
|
|
133
|
+
},
|
|
134
|
+
dispatchAdapter: adapter,
|
|
135
|
+
log: agentLog,
|
|
136
|
+
shutdownDeadlineMs: parseShutdownDeadlineMs(process.env.PRLL_SHUTDOWN_DEADLINE_MS),
|
|
137
|
+
forkDeadlineMs: parseForkDeadlineMs(process.env.PRLL_FORK_DEADLINE_MS),
|
|
138
|
+
dispatchDeadlineMs: parseDispatchDeadlineMs(process.env.PRLL_DISPATCH_DEADLINE_MS),
|
|
139
|
+
contextFilePathForSession: (sessionKey) => contextFilePathForSession(config.stateDir, sessionKey),
|
|
140
|
+
stepIdFilePathForSession: (sessionKey) => stepIdFilePathForSession(config.stateDir, sessionKey),
|
|
141
|
+
onConfigUpdate: async () => {
|
|
142
|
+
const refreshed = await getAgentMeWithLegacyFallback(client, config.orgId);
|
|
143
|
+
platformManaged = isPlatformManagedProfile(refreshed.agent_profile);
|
|
144
|
+
platformModelOverride = isPlatformModelOverride(refreshed.agent_profile);
|
|
145
|
+
const updated = await configMgr.fetch();
|
|
146
|
+
adapter.updateConfig({
|
|
147
|
+
model: resolveRuntimeModel(platformModelOverride, updated.model, config.model) ?? null,
|
|
148
|
+
reasoningEffort: platformManaged
|
|
149
|
+
? (updated.thinkingEffort ?? config.reasoningEffort)
|
|
150
|
+
: (config.reasoningEffort ?? null),
|
|
151
|
+
});
|
|
152
|
+
},
|
|
153
|
+
onSessionReady: async () => {
|
|
154
|
+
const refreshed = await getAgentMeWithLegacyFallback(client, config.orgId);
|
|
155
|
+
platformManaged = isPlatformManagedProfile(refreshed.agent_profile);
|
|
156
|
+
platformModelOverride = isPlatformModelOverride(refreshed.agent_profile);
|
|
157
|
+
const updated = await configMgr.fetch();
|
|
158
|
+
adapter.updateConfig({
|
|
159
|
+
model: resolveRuntimeModel(platformModelOverride, updated.model, config.model) ?? null,
|
|
160
|
+
reasoningEffort: platformManaged
|
|
161
|
+
? (updated.thinkingEffort ?? config.reasoningEffort)
|
|
162
|
+
: (config.reasoningEffort ?? null),
|
|
163
|
+
});
|
|
164
|
+
},
|
|
165
|
+
onNewSession: () => {
|
|
166
|
+
sessionManager.clearMainThread();
|
|
167
|
+
},
|
|
168
|
+
onSessionStale: () => {
|
|
169
|
+
sessionManager.clearMainThread();
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
const abortController = new AbortController();
|
|
173
|
+
const abort = () => abortController.abort();
|
|
174
|
+
process.on('SIGINT', abort);
|
|
175
|
+
process.on('SIGTERM', abort);
|
|
176
|
+
try {
|
|
177
|
+
agentLog.info(`starting self-hosted Codex runtime (app-server driver) for ${me.display_name} (${agentUserId})`);
|
|
178
|
+
await gateway.run(abortController.signal);
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
await adapter.stop();
|
|
182
|
+
process.off('SIGINT', abort);
|
|
183
|
+
process.off('SIGTERM', abort);
|
|
184
|
+
}
|
|
147
185
|
}
|
|
148
186
|
finally {
|
|
149
|
-
await
|
|
150
|
-
process.off("SIGINT", abort);
|
|
151
|
-
process.off("SIGTERM", abort);
|
|
187
|
+
await telemetry.shutdown();
|
|
152
188
|
}
|
|
153
189
|
}
|
|
154
190
|
main().catch((err) => {
|
package/dist/jsonrpc-client.d.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import type { ChildProcessWithoutNullStreams } from
|
|
1
|
+
import type { ChildProcessWithoutNullStreams } from 'node:child_process';
|
|
2
2
|
type JsonRpcId = number | string;
|
|
3
3
|
export type JsonRpcRequest = {
|
|
4
|
-
jsonrpc:
|
|
4
|
+
jsonrpc: '2.0';
|
|
5
5
|
id: JsonRpcId;
|
|
6
6
|
method: string;
|
|
7
7
|
params?: unknown;
|
|
8
8
|
};
|
|
9
9
|
export type JsonRpcNotification = {
|
|
10
|
-
jsonrpc:
|
|
10
|
+
jsonrpc: '2.0';
|
|
11
11
|
method: string;
|
|
12
12
|
params?: unknown;
|
|
13
13
|
};
|
|
14
14
|
export type JsonRpcResponse = {
|
|
15
|
-
jsonrpc:
|
|
15
|
+
jsonrpc: '2.0';
|
|
16
16
|
id: JsonRpcId;
|
|
17
17
|
result?: unknown;
|
|
18
18
|
error?: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jsonrpc-client.d.ts","sourceRoot":"","sources":["../src/jsonrpc-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,oBAAoB,CAAC;AAEzE,KAAK,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;AAEjC,MAAM,MAAM,cAAc,GAAG;IAC3B,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,EAAE,SAAS,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,KAAK,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,EAAE,SAAS,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CAC3D,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;AAW5E;;;;;;;GAOG;AACH,qBAAa,kBAAkB;IAQ3B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;IAT/B,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiC;IACzD,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,cAAc,CAAoC;IAC1D,OAAO,CAAC,QAAQ,CAAS;gBAGN,IAAI,EAAE,8BAA8B,EACpC,gBAAgB,GAAE,MAAmC,EACrD,WAAW,CAAC,GAAE,CAAC,IAAI,EAAE,8BAA8B,KAAK,IAAI,aAAA;IAS/E,sBAAsB,CAAC,OAAO,EAAE,mBAAmB;IAInD,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"jsonrpc-client.d.ts","sourceRoot":"","sources":["../src/jsonrpc-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,oBAAoB,CAAC;AAEzE,KAAK,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;AAEjC,MAAM,MAAM,cAAc,GAAG;IAC3B,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,EAAE,SAAS,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,KAAK,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,EAAE,SAAS,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CAC3D,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;AAW5E;;;;;;;GAOG;AACH,qBAAa,kBAAkB;IAQ3B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;IAT/B,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiC;IACzD,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,cAAc,CAAoC;IAC1D,OAAO,CAAC,QAAQ,CAAS;gBAGN,IAAI,EAAE,8BAA8B,EACpC,gBAAgB,GAAE,MAAmC,EACrD,WAAW,CAAC,GAAE,CAAC,IAAI,EAAE,8BAA8B,KAAK,IAAI,aAAA;IAS/E,sBAAsB,CAAC,OAAO,EAAE,mBAAmB;IAInD,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAsB/D,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO;IAMjD,UAAU,IAAI,OAAO;IAIrB,OAAO,CAAC,GAAG,EAAE,KAAK;IAUlB,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,aAAa;IAWrB,OAAO,CAAC,MAAM;IAWd,OAAO,CAAC,UAAU;CAyBnB"}
|