@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.
Files changed (50) hide show
  1. package/README.md +7 -4
  2. package/dist/agents/claude/sdk-message-projector.js +12 -4
  3. package/dist/agents/claude/tool-results.d.ts +1 -2
  4. package/dist/agents/claude/tool-results.js +9 -40
  5. package/dist/agents/claude.js +6 -4
  6. package/dist/agents/codex-app-server/agent.d.ts +5 -0
  7. package/dist/agents/codex-app-server/agent.js +145 -179
  8. package/dist/agents/codex-app-server/item-lifecycle.d.ts +30 -0
  9. package/dist/agents/codex-app-server/item-lifecycle.js +95 -0
  10. package/dist/agents/codex-app-server/item-projection.d.ts +62 -0
  11. package/dist/agents/codex-app-server/item-projection.js +135 -0
  12. package/dist/agents/codex-app-server/managed-auth.d.ts +12 -0
  13. package/dist/agents/codex-app-server/managed-auth.js +53 -0
  14. package/dist/agents/codex-app-server/transport.d.ts +2 -0
  15. package/dist/agents/codex-app-server/transport.js +30 -3
  16. package/dist/agents/opencode/host-safety.d.ts +3 -0
  17. package/dist/agents/opencode/host-safety.js +30 -0
  18. package/dist/agents/opencode.d.ts +2 -4
  19. package/dist/agents/opencode.js +47 -38
  20. package/dist/openai-oauth/chatgpt-oauth-llm.d.ts +25 -0
  21. package/dist/openai-oauth/chatgpt-oauth-llm.js +398 -0
  22. package/dist/openai-oauth/codex-auth-broker.d.ts +32 -0
  23. package/dist/openai-oauth/codex-auth-broker.js +110 -0
  24. package/dist/openai-oauth/index.d.ts +2 -0
  25. package/dist/openai-oauth/index.js +1 -0
  26. package/dist/providers/credentials.d.ts +4 -1
  27. package/dist/providers/credentials.js +4 -3
  28. package/dist/providers/sandbox.d.ts +2 -0
  29. package/dist/providers/scripted-mcp-mock-host.js +6 -3
  30. package/dist/providers/workspace.d.ts +1 -0
  31. package/dist/providers/workspace.js +9 -1
  32. package/dist/sdk/agent-result-log.js +4 -2
  33. package/dist/sdk/agent.js +7 -0
  34. package/dist/sdk/judge-tools.js +14 -3
  35. package/dist/sdk/managed-session.d.ts +2 -0
  36. package/dist/sdk/managed-session.js +22 -5
  37. package/dist/sdk/mcp-safety.js +2 -18
  38. package/dist/sdk/snapshots.d.ts +1 -0
  39. package/dist/sdk/snapshots.js +3 -2
  40. package/dist/sdk/tool-event-log.js +5 -2
  41. package/dist/sdk/tool-event-secrets.d.ts +4 -0
  42. package/dist/sdk/tool-event-secrets.js +14 -0
  43. package/dist/sdk/turn-result-secrets.d.ts +5 -0
  44. package/dist/sdk/turn-result-secrets.js +12 -0
  45. package/dist/tool-event-results.d.ts +10 -0
  46. package/dist/tool-event-results.js +171 -0
  47. package/dist/types.d.ts +2 -0
  48. package/dist/utils/llm.js +11 -0
  49. package/docs/OPENAI_OAUTH_JUDGE.md +91 -0
  50. package/package.json +13 -2
@@ -0,0 +1,398 @@
1
+ import { CodexAuthBrokerError, createCodexAuthBroker, } from './codex-auth-broker.js';
2
+ const CORE_VERSION = '2.0.0';
3
+ const DEFAULT_REQUEST_TIMEOUT_MS = 60_000;
4
+ export class ChatGptOAuthJudgeError extends Error {
5
+ code;
6
+ constructor(code, message = code) {
7
+ super(message);
8
+ this.code = code;
9
+ this.name = 'ChatGptOAuthJudgeError';
10
+ }
11
+ }
12
+ const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
13
+ const oauthError = (code) => new ChatGptOAuthJudgeError(code);
14
+ const mapBrokerError = (error) => {
15
+ if (error instanceof ChatGptOAuthJudgeError)
16
+ return error;
17
+ if (error instanceof CodexAuthBrokerError)
18
+ return oauthError(error.code);
19
+ if (error instanceof Error && error.name === 'AbortError')
20
+ return oauthError('OAUTH_REQUEST_TIMEOUT');
21
+ return oauthError('OAUTH_CODEX_UNAVAILABLE');
22
+ };
23
+ const normalizeProfile = (options) => {
24
+ if (options.model.trim().length === 0 ||
25
+ !['low', 'medium', 'high'].includes(options.reasoningEffort)) {
26
+ throw oauthError('OAUTH_CONFIG_INVALID');
27
+ }
28
+ const requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
29
+ if (!Number.isFinite(requestTimeoutMs) || !Number.isInteger(requestTimeoutMs) || requestTimeoutMs <= 0) {
30
+ throw oauthError('OAUTH_CONFIG_INVALID');
31
+ }
32
+ return Object.freeze({
33
+ model: options.model,
34
+ reasoningEffort: options.reasoningEffort,
35
+ requestTimeoutMs,
36
+ });
37
+ };
38
+ const loadCore = async () => {
39
+ try {
40
+ return await import('@openai-oauth/core');
41
+ }
42
+ catch {
43
+ throw new ChatGptOAuthJudgeError('OAUTH_DEPENDENCY_UNAVAILABLE', `OAUTH_DEPENDENCY_UNAVAILABLE: install @openai-oauth/core@${CORE_VERSION}`);
44
+ }
45
+ };
46
+ const createBoundedFetch = (timeoutMs, sourceFetch = globalThis.fetch) => async (input, init) => {
47
+ if (typeof sourceFetch !== 'function') {
48
+ throw oauthError('OAUTH_UPSTREAM_FAILED');
49
+ }
50
+ const timeoutController = new AbortController();
51
+ const timeout = setTimeout(() => timeoutController.abort(), timeoutMs);
52
+ timeout.unref?.();
53
+ const signal = init?.signal
54
+ ? AbortSignal.any([init.signal, timeoutController.signal])
55
+ : timeoutController.signal;
56
+ try {
57
+ return await sourceFetch(input, { ...init, signal });
58
+ }
59
+ catch (error) {
60
+ if (error instanceof ChatGptOAuthJudgeError ||
61
+ (error instanceof Error && error.name === 'AbortError')) {
62
+ throw error;
63
+ }
64
+ throw oauthError('OAUTH_UPSTREAM_FAILED');
65
+ }
66
+ finally {
67
+ clearTimeout(timeout);
68
+ }
69
+ };
70
+ const classifyStatus = (status) => {
71
+ if (status === 401 || status === 403)
72
+ return oauthError('OAUTH_CODEX_LOGIN_REQUIRED');
73
+ if (status === 429)
74
+ return oauthError('OAUTH_UPSTREAM_RATE_LIMITED');
75
+ return oauthError('OAUTH_UPSTREAM_FAILED');
76
+ };
77
+ const assertModel = (profile, requested) => {
78
+ if (requested !== undefined && requested !== profile.model) {
79
+ throw oauthError('OAUTH_MODEL_MISMATCH');
80
+ }
81
+ };
82
+ const requestBody = (profile, input, instructions, tools) => ({
83
+ model: profile.model,
84
+ reasoning: { effort: profile.reasoningEffort },
85
+ instructions,
86
+ input,
87
+ ...(tools === undefined ? {} : {
88
+ tools,
89
+ tool_choice: tools.length === 0 ? 'none' : 'auto',
90
+ parallel_tool_calls: true,
91
+ }),
92
+ });
93
+ const toResponsesInput = (messages) => {
94
+ const input = [];
95
+ for (const message of messages) {
96
+ if (typeof message.content === 'string') {
97
+ input.push({
98
+ role: message.role,
99
+ content: [{
100
+ type: message.role === 'assistant' ? 'output_text' : 'input_text',
101
+ text: message.content,
102
+ }],
103
+ });
104
+ continue;
105
+ }
106
+ let text = [];
107
+ const flushText = () => {
108
+ if (text.length === 0)
109
+ return;
110
+ input.push({
111
+ role: message.role,
112
+ content: text.map((block) => ({
113
+ type: message.role === 'assistant' ? 'output_text' : 'input_text',
114
+ text: block.text,
115
+ })),
116
+ });
117
+ text = [];
118
+ };
119
+ for (const block of message.content) {
120
+ if (block.type === 'text') {
121
+ text.push(block);
122
+ continue;
123
+ }
124
+ flushText();
125
+ if (block.type === 'tool_use') {
126
+ if (message.role !== 'assistant' || block.id.length === 0 || block.name.length === 0 ||
127
+ !isRecord(block.input)) {
128
+ throw oauthError('OAUTH_PROTOCOL_RESPONSE_INVALID');
129
+ }
130
+ input.push({
131
+ type: 'function_call', call_id: block.id,
132
+ name: block.name, arguments: JSON.stringify(block.input),
133
+ });
134
+ }
135
+ else if (block.type === 'tool_result') {
136
+ if (message.role !== 'user' || block.tool_use_id.length === 0) {
137
+ throw oauthError('OAUTH_PROTOCOL_RESPONSE_INVALID');
138
+ }
139
+ input.push({
140
+ type: 'function_call_output', call_id: block.tool_use_id,
141
+ output: block.content,
142
+ });
143
+ }
144
+ }
145
+ flushText();
146
+ }
147
+ return input;
148
+ };
149
+ const safeToken = (value) => Number.isInteger(value) && value >= 0 ? value : undefined;
150
+ const parseResponse = async (response) => {
151
+ if (!response.ok)
152
+ throw classifyStatus(response.status);
153
+ try {
154
+ const parsed = await response.json();
155
+ if (!isRecord(parsed) || parsed.status !== 'completed' || !Array.isArray(parsed.output)) {
156
+ throw oauthError('OAUTH_PROTOCOL_RESPONSE_INVALID');
157
+ }
158
+ for (const item of parsed.output)
159
+ validateOutputItem(item);
160
+ return parsed;
161
+ }
162
+ catch (error) {
163
+ if (error instanceof ChatGptOAuthJudgeError)
164
+ throw error;
165
+ throw oauthError('OAUTH_PROTOCOL_RESPONSE_INVALID');
166
+ }
167
+ };
168
+ const validateOutputItem = (value) => {
169
+ if (!isRecord(value) || !['reasoning', 'message', 'function_call'].includes(String(value.type))) {
170
+ throw oauthError('OAUTH_PROTOCOL_RESPONSE_INVALID');
171
+ }
172
+ if (value.type === 'function_call') {
173
+ if (value.status !== undefined && value.status !== 'completed') {
174
+ throw oauthError('OAUTH_PROTOCOL_RESPONSE_INVALID');
175
+ }
176
+ return;
177
+ }
178
+ if (value.type === 'reasoning')
179
+ return;
180
+ if (value.role !== 'assistant' || !Array.isArray(value.content)) {
181
+ throw oauthError('OAUTH_PROTOCOL_RESPONSE_INVALID');
182
+ }
183
+ for (const content of value.content) {
184
+ if (!isRecord(content) || content.type !== 'output_text' || typeof content.text !== 'string') {
185
+ throw oauthError('OAUTH_PROTOCOL_RESPONSE_INVALID');
186
+ }
187
+ }
188
+ };
189
+ const outputText = (result) => (result.output ?? []).flatMap((item) => Array.isArray(item.content) ? item.content : [])
190
+ .filter((item) => isRecord(item) && item.type === 'output_text')
191
+ .map((item) => typeof item.text === 'string' ? item.text : '')
192
+ .join('');
193
+ const toolBlocks = (result, allowedNames) => {
194
+ const seenIds = new Set();
195
+ return (result.output ?? [])
196
+ .filter((item) => item.type === 'function_call')
197
+ .map((item) => {
198
+ if (typeof item.call_id !== 'string' || item.call_id.length === 0 ||
199
+ typeof item.name !== 'string' || item.name.length === 0 ||
200
+ !allowedNames.has(item.name) || typeof item.arguments !== 'string' ||
201
+ seenIds.has(item.call_id)) {
202
+ throw oauthError('OAUTH_PROTOCOL_RESPONSE_INVALID');
203
+ }
204
+ seenIds.add(item.call_id);
205
+ let input;
206
+ try {
207
+ input = JSON.parse(item.arguments);
208
+ }
209
+ catch {
210
+ throw oauthError('OAUTH_PROTOCOL_RESPONSE_INVALID');
211
+ }
212
+ if (!isRecord(input))
213
+ throw oauthError('OAUTH_PROTOCOL_RESPONSE_INVALID');
214
+ return { type: 'tool_use', id: item.call_id, name: item.name, input };
215
+ });
216
+ };
217
+ export function createChatGptOAuthJudgeLLM(options) {
218
+ return createChatGptOAuthJudgeLLMWithDependencies(options);
219
+ }
220
+ /** Internal deterministic seam; intentionally omitted from the package subpath exports. */
221
+ export function createChatGptOAuthJudgeLLMWithDependencies(options, dependencies = {}) {
222
+ const profile = normalizeProfile(options);
223
+ const boundedFetch = createBoundedFetch(profile.requestTimeoutMs, dependencies.fetch);
224
+ let corePromise;
225
+ let brokerPromise;
226
+ let session;
227
+ let sessionGeneration = 0;
228
+ let acquirePromise;
229
+ let refreshPromise;
230
+ let transportPromise;
231
+ let preflight;
232
+ let inputTokens = 0;
233
+ let outputTokens = 0;
234
+ const core = () => corePromise ??= (dependencies.loadCore ?? loadCore)();
235
+ const broker = () => brokerPromise ??= dependencies.broker
236
+ ? Promise.resolve(dependencies.broker)
237
+ : core().then((loaded) => createCodexAuthBroker({
238
+ ...(dependencies.codexBinary !== undefined ? { binary: dependencies.codexBinary } : {}),
239
+ ...(dependencies.codexEnv !== undefined ? { env: dependencies.codexEnv } : {}),
240
+ ...(dependencies.authTimeoutMs !== undefined ? { authTimeoutMs: dependencies.authTimeoutMs } : {}),
241
+ deriveAccountId: loaded.deriveAccountId,
242
+ deriveIsFedRamp: loaded.deriveChatGptAccountIsFedRamp,
243
+ }));
244
+ const getSession = async () => {
245
+ if (session)
246
+ return session;
247
+ acquirePromise ??= broker()
248
+ .then((value) => value.acquireAccessSession({ refresh: false }))
249
+ .then((value) => {
250
+ session = value;
251
+ sessionGeneration += 1;
252
+ return value;
253
+ })
254
+ .catch((error) => { throw mapBrokerError(error); })
255
+ .finally(() => { acquirePromise = undefined; });
256
+ return acquirePromise;
257
+ };
258
+ const refreshAfterUnauthorized = async (staleGeneration) => {
259
+ if (session && sessionGeneration !== staleGeneration)
260
+ return session;
261
+ refreshPromise ??= broker()
262
+ .then((value) => value.acquireAccessSession({ refresh: true }))
263
+ .then((value) => {
264
+ session = value;
265
+ sessionGeneration += 1;
266
+ // The core transport caches its model catalog by account. A catalog
267
+ // request made with an expired token can therefore cache the failure;
268
+ // rebuild it after refresh so the retry uses both the new token and a
269
+ // fresh catalog resolver.
270
+ transportPromise = undefined;
271
+ return value;
272
+ })
273
+ .catch((error) => { throw mapBrokerError(error); })
274
+ .finally(() => { refreshPromise = undefined; });
275
+ return refreshPromise;
276
+ };
277
+ const transport = () => transportPromise ??= core().then((loaded) => loaded.createOpenAIOAuthTransport({
278
+ auth: getSession,
279
+ codexVersion: '0.144.1',
280
+ fetch: boundedFetch,
281
+ responsesState: false,
282
+ }));
283
+ const requestWithAuthRetry = async (path, init, refreshStatuses = [401]) => {
284
+ await getSession();
285
+ const generation = sessionGeneration;
286
+ let response = await (await transport()).request(path, init);
287
+ if (!refreshStatuses.includes(response.status))
288
+ return response;
289
+ await refreshAfterUnauthorized(generation);
290
+ response = await (await transport()).request(path, init);
291
+ return response;
292
+ };
293
+ const ensurePreflight = () => preflight ??= (async () => {
294
+ const response = await requestWithAuthRetry('/v1/models', {
295
+ method: 'GET',
296
+ signal: AbortSignal.timeout(profile.requestTimeoutMs),
297
+ }, [401, 502]);
298
+ if (!response.ok)
299
+ throw classifyStatus(response.status);
300
+ let parsed;
301
+ try {
302
+ parsed = await response.json();
303
+ }
304
+ catch {
305
+ throw oauthError('OAUTH_PROTOCOL_RESPONSE_INVALID');
306
+ }
307
+ // @openai-oauth/core normalizes the upstream Codex `models[].slug`
308
+ // catalog into this OpenAI-compatible `data[].id` response.
309
+ const compatibleModels = isRecord(parsed) && Array.isArray(parsed.data) ? parsed.data : [];
310
+ if (!compatibleModels.some((entry) => isRecord(entry) && entry.id === profile.model)) {
311
+ throw oauthError('OAUTH_MODEL_UNAVAILABLE');
312
+ }
313
+ })().catch((error) => {
314
+ preflight = undefined;
315
+ if (error instanceof ChatGptOAuthJudgeError)
316
+ throw error;
317
+ if (error instanceof Error && error.name === 'AbortError') {
318
+ throw oauthError('OAUTH_REQUEST_TIMEOUT');
319
+ }
320
+ throw oauthError('OAUTH_UPSTREAM_FAILED');
321
+ });
322
+ const send = async (body) => {
323
+ await ensurePreflight();
324
+ try {
325
+ const response = await requestWithAuthRetry('/v1/responses', {
326
+ method: 'POST',
327
+ headers: { 'content-type': 'application/json' },
328
+ body: JSON.stringify(body),
329
+ signal: AbortSignal.timeout(profile.requestTimeoutMs),
330
+ });
331
+ const result = await parseResponse(response);
332
+ inputTokens += safeToken(result.usage?.input_tokens) ?? 0;
333
+ outputTokens += safeToken(result.usage?.output_tokens) ?? 0;
334
+ return result;
335
+ }
336
+ catch (error) {
337
+ if (error instanceof ChatGptOAuthJudgeError)
338
+ throw error;
339
+ if (error instanceof Error && error.name === 'AbortError') {
340
+ throw oauthError('OAUTH_REQUEST_TIMEOUT');
341
+ }
342
+ throw oauthError('OAUTH_PROTOCOL_RESPONSE_INVALID');
343
+ }
344
+ };
345
+ const port = {
346
+ get tokenUsage() { return { inputTokens, outputTokens }; },
347
+ get supportsToolUse() { return true; },
348
+ async measure(fn) {
349
+ const beforeInput = inputTokens;
350
+ const beforeOutput = outputTokens;
351
+ const result = await fn();
352
+ return {
353
+ result,
354
+ tokens: {
355
+ inputTokens: inputTokens - beforeInput,
356
+ outputTokens: outputTokens - beforeOutput,
357
+ },
358
+ costUsd: 0,
359
+ };
360
+ },
361
+ async call(prompt, callOptions = {}) {
362
+ assertModel(profile, callOptions.model);
363
+ const result = await send(requestBody(profile, [{
364
+ role: 'user', content: [{ type: 'input_text', text: prompt }],
365
+ }], ''));
366
+ if ((result.output ?? []).some((item) => item.type === 'function_call')) {
367
+ throw oauthError('OAUTH_PROTOCOL_RESPONSE_INVALID');
368
+ }
369
+ return {
370
+ text: outputText(result),
371
+ inputTokens: safeToken(result.usage?.input_tokens),
372
+ outputTokens: safeToken(result.usage?.output_tokens),
373
+ provider: 'openai',
374
+ model: profile.model,
375
+ };
376
+ },
377
+ async callWithTools(messages, callOptions) {
378
+ assertModel(profile, callOptions.model);
379
+ const tools = callOptions.tools.map((tool) => ({
380
+ type: 'function',
381
+ name: tool.name,
382
+ description: tool.description,
383
+ parameters: tool.input_schema,
384
+ }));
385
+ const result = await send(requestBody(profile, toResponsesInput(messages), callOptions.system ?? '', tools));
386
+ const blocks = toolBlocks(result, new Set(callOptions.tools.map((tool) => tool.name)));
387
+ const text = outputText(result);
388
+ const usage = {
389
+ inputTokens: safeToken(result.usage?.input_tokens),
390
+ outputTokens: safeToken(result.usage?.output_tokens),
391
+ };
392
+ return blocks.length > 0
393
+ ? { kind: 'tool_use', blocks, text: text || undefined, ...usage }
394
+ : { kind: 'final', text, ...usage };
395
+ },
396
+ };
397
+ return Object.freeze(port);
398
+ }
@@ -0,0 +1,32 @@
1
+ import type { OpenAIOAuthSession } from '@openai-oauth/core';
2
+ import { type AppServerSessionHandle } from '../agents/codex-app-server/transport.js';
3
+ export type CodexAuthBrokerErrorCode = 'OAUTH_CODEX_UNAVAILABLE' | 'OAUTH_CODEX_LOGIN_REQUIRED' | 'OAUTH_AUTH_TIMEOUT';
4
+ export declare class CodexAuthBrokerError extends Error {
5
+ readonly code: CodexAuthBrokerErrorCode;
6
+ constructor(code: CodexAuthBrokerErrorCode);
7
+ }
8
+ export interface CodexAuthBroker {
9
+ acquireAccessSession(input: {
10
+ refresh: boolean;
11
+ signal?: AbortSignal;
12
+ }): Promise<OpenAIOAuthSession>;
13
+ }
14
+ export interface CodexAuthBrokerDependencies {
15
+ binary?: string;
16
+ env?: NodeJS.ProcessEnv;
17
+ authTimeoutMs?: number;
18
+ spawn?: (input: {
19
+ binary?: string;
20
+ env: NodeJS.ProcessEnv;
21
+ }) => AppServerSessionHandle;
22
+ deriveAccountId: (token: string | undefined) => string | undefined;
23
+ deriveIsFedRamp: (token: string | undefined) => boolean;
24
+ }
25
+ export declare const sanitizeCodexAuthEnvironment: (source?: NodeJS.ProcessEnv) => NodeJS.ProcessEnv;
26
+ export declare function createCodexAuthBroker(deps: CodexAuthBrokerDependencies): CodexAuthBroker;
27
+ /** Build the production broker lazily so @openai-oauth/core stays an optional runtime dependency. */
28
+ export declare function createDefaultCodexAuthBroker(input?: {
29
+ binary?: string;
30
+ env?: NodeJS.ProcessEnv;
31
+ authTimeoutMs?: number;
32
+ }): Promise<CodexAuthBroker>;
@@ -0,0 +1,110 @@
1
+ import { spawnAppServerTransport, } from '../agents/codex-app-server/transport.js';
2
+ const DEFAULT_AUTH_TIMEOUT_MS = 10_000;
3
+ const AUTH_ENV_ALLOWLIST = new Set([
4
+ 'PATH', 'HOME', 'CODEX_HOME', 'SHELL', 'USER', 'LOGNAME',
5
+ 'LANG', 'LC_ALL', 'LC_CTYPE', 'TERM', 'TMPDIR', 'TMP', 'TEMP',
6
+ 'SYSTEMROOT', 'WINDIR', 'COMSPEC', 'PATHEXT',
7
+ ]);
8
+ export class CodexAuthBrokerError extends Error {
9
+ code;
10
+ constructor(code) {
11
+ super(code);
12
+ this.code = code;
13
+ this.name = 'CodexAuthBrokerError';
14
+ }
15
+ }
16
+ const brokerError = (code) => new CodexAuthBrokerError(code);
17
+ export const sanitizeCodexAuthEnvironment = (source = process.env) => Object.fromEntries(Object.entries(source).filter(([name]) => AUTH_ENV_ALLOWLIST.has(name.toUpperCase())));
18
+ const abortError = () => new DOMException('Aborted', 'AbortError');
19
+ async function withDeadline(promise, timeoutMs, signal) {
20
+ if (signal?.aborted)
21
+ throw abortError();
22
+ let timeout;
23
+ let onAbort;
24
+ const deadline = new Promise((_resolve, reject) => {
25
+ timeout = setTimeout(() => reject(brokerError('OAUTH_AUTH_TIMEOUT')), timeoutMs);
26
+ timeout.unref?.();
27
+ if (signal) {
28
+ onAbort = () => reject(abortError());
29
+ signal.addEventListener('abort', onAbort, { once: true });
30
+ }
31
+ });
32
+ try {
33
+ return await Promise.race([promise, deadline]);
34
+ }
35
+ finally {
36
+ if (timeout)
37
+ clearTimeout(timeout);
38
+ if (signal && onAbort)
39
+ signal.removeEventListener('abort', onAbort);
40
+ }
41
+ }
42
+ export function createCodexAuthBroker(deps) {
43
+ const timeoutMs = deps.authTimeoutMs ?? DEFAULT_AUTH_TIMEOUT_MS;
44
+ const spawn = deps.spawn ?? ((input) => spawnAppServerTransport({
45
+ ...input,
46
+ logStderrOnExit: false,
47
+ }));
48
+ return {
49
+ async acquireAccessSession({ refresh, signal }) {
50
+ let session;
51
+ try {
52
+ session = spawn({
53
+ ...(deps.binary !== undefined ? { binary: deps.binary } : {}),
54
+ env: sanitizeCodexAuthEnvironment(deps.env),
55
+ });
56
+ const operation = (async () => {
57
+ await session.transport.sendRequest('initialize', {
58
+ clientInfo: { name: 'pathgrade-auth', version: '1.0.0', title: null },
59
+ capabilities: { experimentalApi: true, optOutNotificationMethods: null },
60
+ });
61
+ session.transport.sendNotification('initialized', null);
62
+ const status = await session.transport.sendRequest('getAuthStatus', {
63
+ includeToken: true,
64
+ refreshToken: refresh,
65
+ });
66
+ if (status?.authMethod !== 'chatgpt') {
67
+ throw brokerError('OAUTH_CODEX_LOGIN_REQUIRED');
68
+ }
69
+ if (typeof status.authToken !== 'string' || status.authToken.length === 0) {
70
+ throw brokerError('OAUTH_CODEX_LOGIN_REQUIRED');
71
+ }
72
+ const accountId = deps.deriveAccountId(status.authToken);
73
+ if (!accountId)
74
+ throw brokerError('OAUTH_CODEX_LOGIN_REQUIRED');
75
+ return {
76
+ accessToken: status.authToken,
77
+ accountId,
78
+ isFedRamp: deps.deriveIsFedRamp(status.authToken),
79
+ };
80
+ })();
81
+ return await withDeadline(operation, timeoutMs, signal);
82
+ }
83
+ catch (error) {
84
+ if (error instanceof CodexAuthBrokerError ||
85
+ (error instanceof Error && error.name === 'AbortError')) {
86
+ throw error;
87
+ }
88
+ throw brokerError('OAUTH_CODEX_UNAVAILABLE');
89
+ }
90
+ finally {
91
+ await session?.close().catch(() => undefined);
92
+ }
93
+ },
94
+ };
95
+ }
96
+ /** Build the production broker lazily so @openai-oauth/core stays an optional runtime dependency. */
97
+ export async function createDefaultCodexAuthBroker(input = {}) {
98
+ let core;
99
+ try {
100
+ core = await import('@openai-oauth/core');
101
+ }
102
+ catch {
103
+ throw brokerError('OAUTH_CODEX_UNAVAILABLE');
104
+ }
105
+ return createCodexAuthBroker({
106
+ ...input,
107
+ deriveAccountId: core.deriveAccountId,
108
+ deriveIsFedRamp: core.deriveChatGptAccountIsFedRamp,
109
+ });
110
+ }
@@ -0,0 +1,2 @@
1
+ export { ChatGptOAuthJudgeError, createChatGptOAuthJudgeLLM, } from './chatgpt-oauth-llm.js';
2
+ export type { ChatGptOAuthJudgeErrorCode, ChatGptOAuthJudgeOptions, } from './chatgpt-oauth-llm.js';
@@ -0,0 +1 @@
1
+ export { ChatGptOAuthJudgeError, createChatGptOAuthJudgeLLM, } from './chatgpt-oauth-llm.js';
@@ -1,4 +1,4 @@
1
- import type { AgentName } from '../sdk/types.js';
1
+ import type { AgentName, AgentTransport } from '../sdk/types.js';
2
2
  export interface CredentialPorts {
3
3
  /** Read a host environment variable. */
4
4
  hostEnv(key: string): string | undefined;
@@ -39,10 +39,13 @@ export interface CredentialResult {
39
39
  linkFromHome?: string[];
40
40
  /** Filtered sensitive files to create inside the isolated HOME. */
41
41
  sensitiveHomeFiles?: SensitiveHomeFile[];
42
+ /** Runtime-only values that persistence sinks must redact. */
43
+ sensitiveValues?: string[];
42
44
  }
43
45
  /** Default ports using real process.env, Keychain, and filesystem. */
44
46
  export declare function defaultPorts(): CredentialPorts;
45
47
  export interface CredentialContext {
46
48
  model?: string;
49
+ transport?: AgentTransport;
47
50
  }
48
51
  export declare function resolveCredentials(agent: AgentName, userEnv: Record<string, string>, ports?: CredentialPorts, context?: CredentialContext): Promise<CredentialResult>;
@@ -61,7 +61,7 @@ export async function resolveCredentials(agent, userEnv, ports, context = {}) {
61
61
  case 'claude':
62
62
  return resolveClaude(userEnv, p);
63
63
  case 'codex':
64
- return resolveCodex(userEnv, p);
64
+ return resolveCodex(userEnv, p, context.transport);
65
65
  case 'cursor':
66
66
  return resolveCursor(userEnv, p);
67
67
  case 'opencode':
@@ -152,6 +152,7 @@ function filterOpenCodeOAuthRecord(raw) {
152
152
  const sanitized = { ...record, refresh: OPENCODE_DISABLED_REFRESH_TOKEN };
153
153
  return {
154
154
  env: {}, setupCommands: [], copyFromHome: [],
155
+ sensitiveValues: [record.access],
155
156
  sensitiveHomeFiles: [{
156
157
  relativePath: path.join('.local', 'share', 'opencode', 'auth.json'),
157
158
  content: JSON.stringify({ openai: sanitized }),
@@ -259,7 +260,7 @@ async function resolveCursor(userEnv, ports) {
259
260
  }
260
261
  return { env, setupCommands: [], copyFromHome: [], linkFromHome };
261
262
  }
262
- async function resolveCodex(userEnv, ports) {
263
+ async function resolveCodex(userEnv, ports, transport) {
263
264
  const env = {};
264
265
  const setupCommands = [];
265
266
  const copyFromHome = [];
@@ -294,7 +295,7 @@ async function resolveCodex(userEnv, ports) {
294
295
  setupCommands.push('if [ ! -d "$HOME/.codex" ] && [ -n "${OPENAI_API_KEY:-}" ]; then printenv OPENAI_API_KEY | codex login --with-api-key >/dev/null 2>&1; fi');
295
296
  }
296
297
  }
297
- else {
298
+ else if (transport !== 'app-server') {
298
299
  // No key — check for cached auth.json
299
300
  const authCachePath = path.join(ports.homedir, '.codex', 'auth.json');
300
301
  if (await ports.fileExists(authCachePath)) {
@@ -2,6 +2,8 @@ export interface SandboxConfig {
2
2
  agent: import('../sdk/types.js').AgentName;
3
3
  /** Internal provider/model context; ignored by sandbox creation itself. */
4
4
  model?: string;
5
+ /** Internal Codex transport context used while resolving credentials. */
6
+ transport?: import('../sdk/types.js').AgentTransport;
5
7
  workspace?: string;
6
8
  skillDir?: string;
7
9
  copyFromHome?: string[];
@@ -7,6 +7,7 @@ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprot
7
7
  import { compileScriptedMcpSchemaForProvider } from '../core/mcp-schema-profile.js';
8
8
  import { MCP_ANNOTATION_PROTOCOL_FLOOR } from '../core/generated-mcp-protocol.js';
9
9
  import { getOriginalMcpInput } from '../sdk/mcp-event-input.js';
10
+ import { cloneToolEventWithRuntimeMetadata } from '../sdk/tool-event-secrets.js';
10
11
  import { createMcpArgumentDigest, createMcpReceiptKey, matchesMcpApprovalArguments, } from '../sdk/mcp-mock-approvals.js';
11
12
  let testObserver = null;
12
13
  /** Non-public acceptance seam. It observes only authenticated manifest-member calls. */
@@ -332,9 +333,11 @@ function eventInput(event) {
332
333
  }
333
334
  function withInvocation(event, identity, invocation) {
334
335
  const status = String(event.arguments?.status ?? 'unknown').toLowerCase();
335
- return invocation === 'confirmed'
336
- ? { ...event, mcp: { ...identity, invocation, outcome: status.includes('fail') || status.includes('error') ? 'tool_error' : 'completed' } }
337
- : { ...event, mcp: { ...identity, invocation, outcome: 'unknown' } };
336
+ return cloneToolEventWithRuntimeMetadata(event, {
337
+ mcp: invocation === 'confirmed'
338
+ ? { ...identity, invocation, outcome: status.includes('fail') || status.includes('error') ? 'tool_error' : 'completed' }
339
+ : { ...identity, invocation, outcome: 'unknown' },
340
+ });
338
341
  }
339
342
  async function readJsonBody(req) {
340
343
  const chunks = [];
@@ -6,6 +6,7 @@ export interface Workspace {
6
6
  readonly mcpConfigPath: string | undefined;
7
7
  readonly env: Record<string, string>;
8
8
  readonly setupCommands: string[];
9
+ readonly sensitiveValues?: readonly string[];
9
10
  exec(command: string, opts?: {
10
11
  signal?: AbortSignal;
11
12
  }): Promise<CommandResult>;
@@ -7,6 +7,7 @@ import { stageMcpConfig } from './mcp-config.js';
7
7
  import { sandboxExec } from './sandbox-exec.js';
8
8
  import { resolveCredentials } from './credentials.js';
9
9
  import { isPortableCopyEntry } from './copy-filter.js';
10
+ import { collectSensitiveEnvValues } from '../tool-event-results.js';
10
11
  async function copyPathsFromHostHome(pathsToCopy, sandboxHomePath) {
11
12
  const realHome = os.homedir();
12
13
  for (const relPath of pathsToCopy) {
@@ -47,7 +48,10 @@ export async function prepareWorkspace(spec) {
47
48
  try {
48
49
  // Resolve credentials: pass user's original env (not sandboxEnv) so
49
50
  // the resolver can distinguish explicit user intent from auto-resolved values.
50
- const creds = await resolveCredentials(spec.agent, spec.env ?? {}, undefined, { model: spec.model });
51
+ const creds = await resolveCredentials(spec.agent, spec.env ?? {}, undefined, {
52
+ model: spec.model,
53
+ transport: spec.transport,
54
+ });
51
55
  Object.assign(sandboxEnv, creds.env);
52
56
  await copyPathsFromHostHome(creds.copyFromHome, homePath);
53
57
  await linkPathsFromHostHome(creds.linkFromHome ?? [], homePath);
@@ -59,6 +63,10 @@ export async function prepareWorkspace(spec) {
59
63
  mcpConfigPath,
60
64
  env: sandboxEnv,
61
65
  setupCommands: creds.setupCommands,
66
+ sensitiveValues: [...new Set([
67
+ ...collectSensitiveEnvValues(sandboxEnv),
68
+ ...(creds.sensitiveValues ?? []),
69
+ ])],
62
70
  exec: (command, opts) => sandboxExec(command, { cwd: workspacePath, env: sandboxEnv }, opts),
63
71
  async dispose() {
64
72
  if (disposed)