@makaio/adapter-codex-app-server 1.0.0-dev-1779051654000

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/index.mjs ADDED
@@ -0,0 +1,3787 @@
1
+ import { t as __exportAll } from "./chunk-DQk6qfdC.mjs";
2
+ import { AIAdapter, AIAgent, AIAgentConnector, BaseConnectorTurn, UserMessageQueue, createAdapterNamespace, extractMcpCallTarget, formatContextBlocksAsText, formatMessageHistoryAsTranscript, isMcpCallTool, resolveConformanceTestPreset, resolveDisabledNativeTools, resolveTestConfig, serializeBlockToText, serializeTurnContext } from "@makaio/framework/adapters";
3
+ import { z } from "zod";
4
+ import { AgentSubjects, ClientSubjects, ToolSubjects } from "@makaio/framework/contracts";
5
+ import { MakaioBus } from "@makaio/framework/bus";
6
+ import { buildClientSessionBase, emitBestEffort } from "@makaio/framework/clients";
7
+ import { filterToolsWithSchema, loadToolsFromRegistry } from "@makaio/framework/adapters/stream-session";
8
+ import { spawn } from "node:child_process";
9
+ import * as path from "node:path";
10
+ import { createAdapterConfigFactory, resolveSessionEnvironment } from "@makaio/framework/adapters/config";
11
+
12
+ //#region src/namespaces/schemas/thread-lifecycle.ts
13
+ /**
14
+ * Schema for thread.started event
15
+ * Emitted when a new thread is successfully started.
16
+ *
17
+ * Note: agentId is required for bus filtering to work correctly.
18
+ * The filteredBus.on() filters events by agentId.
19
+ */
20
+ const ThreadStartedSchema = z.object({
21
+ agentId: z.string(),
22
+ threadId: z.string(),
23
+ timestamp: z.number()
24
+ });
25
+ /**
26
+ * Schema for thread.completed event
27
+ * Emitted when a thread is archived or completed.
28
+ *
29
+ * Note: agentId is required for bus filtering to work correctly.
30
+ */
31
+ const ThreadCompletedSchema = z.object({
32
+ agentId: z.string(),
33
+ threadId: z.string(),
34
+ timestamp: z.number()
35
+ });
36
+
37
+ //#endregion
38
+ //#region src/namespaces/schemas/turn-lifecycle.ts
39
+ /**
40
+ * Turn state for Codex App-Server connector.
41
+ *
42
+ * State machine follows the app-server protocol:
43
+ * - idle → active (turn/start request sent)
44
+ * - active → processing_started (turn/started notification received)
45
+ * - processing_started → turn_started (first item/started notification)
46
+ * - turn_started → step_started (command/file item starts)
47
+ * - step_started → step_finished (item completes)
48
+ * - step_finished → turn_finished (turn/completed received)
49
+ * - turn_finished → idle (cleanup, ready for next turn)
50
+ *
51
+ * Immediate mode: active → interrupted → idle (cancelled, new turn with merged content)
52
+ */
53
+ const CodexAppServerTurnStateSchema = z.enum([
54
+ "idle",
55
+ "active",
56
+ "processing_started",
57
+ "turn_started",
58
+ "step_started",
59
+ "step_finished",
60
+ "turn_finished",
61
+ "interrupted"
62
+ ]);
63
+ /**
64
+ * Turn state change event
65
+ * Emitted whenever turn state transitions
66
+ */
67
+ const TurnStateChangedSchema = z.object({
68
+ adapterId: z.string(),
69
+ agentId: z.string(),
70
+ oldState: CodexAppServerTurnStateSchema,
71
+ newState: CodexAppServerTurnStateSchema,
72
+ timestamp: z.number()
73
+ });
74
+ /**
75
+ * Schema for turn.started event
76
+ * Emitted when a turn begins processing (turn_started state).
77
+ *
78
+ * Note: agentId is required for bus filtering to work correctly.
79
+ */
80
+ const TurnStartedSchema = z.object({
81
+ agentId: z.string(),
82
+ threadId: z.string(),
83
+ turnId: z.string(),
84
+ timestamp: z.number()
85
+ });
86
+ /**
87
+ * Schema for turn.completed event
88
+ * Emitted when a turn completes (turn_finished state).
89
+ *
90
+ * Note: agentId is required for bus filtering to work correctly.
91
+ */
92
+ const TurnCompletedSchema = z.object({
93
+ agentId: z.string(),
94
+ threadId: z.string(),
95
+ turnId: z.string(),
96
+ timestamp: z.number()
97
+ });
98
+ /**
99
+ * Schema for turn.step_started event
100
+ * Emitted when a step/item begins execution.
101
+ *
102
+ * Note: agentId is required for bus filtering to work correctly.
103
+ */
104
+ const TurnStepStartedSchema = z.object({
105
+ agentId: z.string(),
106
+ threadId: z.string(),
107
+ turnId: z.string(),
108
+ itemId: z.string(),
109
+ timestamp: z.number()
110
+ });
111
+ /**
112
+ * Schema for turn.step_finished event
113
+ * Emitted when a step/item completes.
114
+ *
115
+ * Note: agentId is required for bus filtering to work correctly.
116
+ */
117
+ const TurnStepFinishedSchema = z.object({
118
+ agentId: z.string(),
119
+ threadId: z.string(),
120
+ turnId: z.string(),
121
+ itemId: z.string(),
122
+ timestamp: z.number()
123
+ });
124
+
125
+ //#endregion
126
+ //#region src/namespaces/schemas/item-lifecycle.ts
127
+ /**
128
+ * Schema for item.started event
129
+ * Emitted when an item begins execution
130
+ */
131
+ const ItemStartedSchema = z.object({
132
+ threadId: z.string(),
133
+ turnId: z.string(),
134
+ itemId: z.string(),
135
+ itemType: z.string(),
136
+ timestamp: z.number()
137
+ });
138
+ /**
139
+ * Schema for item.completed event
140
+ * Emitted when an item completes
141
+ */
142
+ const ItemCompletedSchema = z.object({
143
+ threadId: z.string(),
144
+ turnId: z.string(),
145
+ itemId: z.string(),
146
+ itemType: z.string(),
147
+ timestamp: z.number()
148
+ });
149
+
150
+ //#endregion
151
+ //#region src/namespaces/schemas/agent-message.ts
152
+ /**
153
+ * Schema for agent_message.delta event
154
+ * Emitted for incremental text updates from the agent
155
+ */
156
+ const AgentMessageDeltaSchema = z.object({
157
+ threadId: z.string(),
158
+ turnId: z.string(),
159
+ delta: z.string(),
160
+ timestamp: z.number()
161
+ });
162
+ /**
163
+ * Schema for agent_message event
164
+ * Emitted when a complete agent message is ready
165
+ */
166
+ const AgentMessageSchema = z.object({
167
+ threadId: z.string(),
168
+ turnId: z.string(),
169
+ message: z.string(),
170
+ timestamp: z.number()
171
+ });
172
+
173
+ //#endregion
174
+ //#region src/namespaces/schemas/shared.ts
175
+ /**
176
+ * Enrichment fields auto-injected by requestToolApproval.
177
+ * These are added by the connector's base class, not passed by the caller.
178
+ *
179
+ * Shared across command-execution and file-change approval schemas.
180
+ */
181
+ const EnrichmentFieldsSchema = z.object({
182
+ agentId: z.string().optional(),
183
+ adapterId: z.string().optional(),
184
+ adapterName: z.string().optional(),
185
+ adapterSessionId: z.string().optional()
186
+ });
187
+ /**
188
+ * Shared response schema for approval RPCs (exec and file-change).
189
+ *
190
+ * Both command execution and file change approval return
191
+ * the same accept/decline decision format.
192
+ */
193
+ const ApprovalResponseSchema = z.object({
194
+ decision: z.enum(["accept", "decline"]),
195
+ message: z.string().optional()
196
+ });
197
+
198
+ //#endregion
199
+ //#region src/namespaces/schemas/command-execution.ts
200
+ /**
201
+ * Schema for exec_command.begin event
202
+ * Emitted when a command execution starts
203
+ */
204
+ const ExecCommandBeginSchema = z.object({
205
+ threadId: z.string(),
206
+ turnId: z.string(),
207
+ callId: z.string(),
208
+ command: z.array(z.string()),
209
+ cwd: z.string(),
210
+ timestamp: z.number()
211
+ });
212
+ /**
213
+ * Schema for exec_approval_request RPC
214
+ * Request/response pair for command approval routing via scoped bus.
215
+ * Connector calls requestToolApproval → registerToolApprovalHandler routes to global bus → returns response.
216
+ *
217
+ * Note: Enrichment fields (agentId, adapterId, etc.) are auto-injected by requestToolApproval.
218
+ */
219
+ const ExecApprovalRequestSchema = {
220
+ request: z.object({
221
+ threadId: z.string(),
222
+ turnId: z.string(),
223
+ callId: z.string(),
224
+ command: z.array(z.string()),
225
+ cwd: z.string(),
226
+ reason: z.string().nullable(),
227
+ timestamp: z.number()
228
+ }).merge(EnrichmentFieldsSchema),
229
+ response: ApprovalResponseSchema
230
+ };
231
+ /**
232
+ * Schema for exec_command.output.delta event
233
+ * Emitted for incremental output from a running command
234
+ */
235
+ const ExecCommandOutputDeltaSchema = z.object({
236
+ threadId: z.string(),
237
+ turnId: z.string(),
238
+ callId: z.string(),
239
+ stream: z.enum(["stdout", "stderr"]),
240
+ chunk: z.string(),
241
+ timestamp: z.number()
242
+ });
243
+ /**
244
+ * Schema for exec_command.end event
245
+ * Emitted when a command execution completes
246
+ */
247
+ const ExecCommandEndSchema = z.object({
248
+ threadId: z.string(),
249
+ turnId: z.string(),
250
+ callId: z.string(),
251
+ exitCode: z.number(),
252
+ timestamp: z.number()
253
+ });
254
+
255
+ //#endregion
256
+ //#region src/namespaces/schemas/file-change.ts
257
+ /**
258
+ * Schema for file_change_approval_request RPC
259
+ * Request/response pair for file change approval routing via scoped bus.
260
+ * Connector calls requestToolApproval → registerToolApprovalHandler routes to global bus → returns response.
261
+ *
262
+ * Note: Enrichment fields (agentId, adapterId, etc.) are auto-injected by requestToolApproval.
263
+ */
264
+ const FileChangeApprovalRequestSchema = {
265
+ request: z.object({
266
+ threadId: z.string(),
267
+ turnId: z.string(),
268
+ itemId: z.string(),
269
+ reason: z.string().nullable(),
270
+ grantRoot: z.string().nullable(),
271
+ timestamp: z.number()
272
+ }).merge(EnrichmentFieldsSchema),
273
+ response: ApprovalResponseSchema
274
+ };
275
+ /**
276
+ * Schema for file_change.output.delta event
277
+ * Emitted for incremental file change updates
278
+ */
279
+ const FileChangeOutputDeltaSchema = z.object({
280
+ threadId: z.string(),
281
+ turnId: z.string(),
282
+ itemId: z.string(),
283
+ delta: z.string(),
284
+ timestamp: z.number()
285
+ });
286
+
287
+ //#endregion
288
+ //#region src/namespaces/schemas/reasoning.ts
289
+ /**
290
+ * Schema for reasoning.delta event
291
+ * Emitted for incremental reasoning content
292
+ */
293
+ const ReasoningDeltaSchema = z.object({
294
+ threadId: z.string(),
295
+ turnId: z.string(),
296
+ delta: z.string(),
297
+ timestamp: z.number()
298
+ });
299
+ /**
300
+ * Schema for reasoning event
301
+ * Emitted when complete reasoning is available
302
+ */
303
+ const ReasoningSchema = z.object({
304
+ threadId: z.string(),
305
+ turnId: z.string(),
306
+ reasoning: z.string(),
307
+ timestamp: z.number()
308
+ });
309
+
310
+ //#endregion
311
+ //#region src/namespaces/schemas/token-usage.ts
312
+ /**
313
+ * Schema for token_usage event
314
+ * Emitted when token usage is updated.
315
+ *
316
+ * Note: agentId is required for bus filtering to work correctly.
317
+ */
318
+ const TokenUsageSchema = z.object({
319
+ agentId: z.string(),
320
+ threadId: z.string(),
321
+ turnId: z.string().optional(),
322
+ promptTokens: z.number(),
323
+ inputCachedTokens: z.number().default(0),
324
+ completionTokens: z.number(),
325
+ reasoningTokens: z.number().default(0),
326
+ totalTokens: z.number(),
327
+ modelContextWindow: z.number().optional(),
328
+ timestamp: z.number()
329
+ });
330
+
331
+ //#endregion
332
+ //#region src/namespaces/schemas/dynamic-tool-call.ts
333
+ /**
334
+ * Schema for dynamic_tool_call_begin event.
335
+ *
336
+ * Emitted when an `item/started` notification arrives for a `dynamicToolCall` item.
337
+ * The `name` and `args` fields are populated from the cached `item/tool/call` server
338
+ * request that was handled before (or concurrent with) the `item/started` notification.
339
+ */
340
+ const DynamicToolCallBeginSchema = z.object({
341
+ threadId: z.string(),
342
+ turnId: z.string(),
343
+ /** Item ID — same as `toolCallId` used in `item/tool/call` */
344
+ itemId: z.string(),
345
+ /** Tool name as declared in `dynamicTools` at `thread/start` */
346
+ name: z.string(),
347
+ /** Tool input arguments */
348
+ args: z.record(z.string(), z.unknown()),
349
+ timestamp: z.number()
350
+ });
351
+ /**
352
+ * Schema for dynamic_tool_call_end event.
353
+ *
354
+ * Emitted when an `item/completed` notification arrives for a `dynamicToolCall` item.
355
+ * The `output` and `success` fields are populated from the cached tool execution result.
356
+ */
357
+ const DynamicToolCallEndSchema = z.object({
358
+ threadId: z.string(),
359
+ turnId: z.string(),
360
+ /** Item ID — same as `toolCallId` used in `item/tool/call` */
361
+ itemId: z.string(),
362
+ /** Tool name as declared in `dynamicTools` at `thread/start` */
363
+ name: z.string(),
364
+ /** Serialised tool output */
365
+ output: z.string(),
366
+ /** Whether execution succeeded (false if the result contained an `error` key) */
367
+ success: z.boolean(),
368
+ timestamp: z.number()
369
+ });
370
+ /**
371
+ * Schema for dynamic_tool_call_approval_request RPC.
372
+ *
373
+ * Request/response pair for dynamic tool call approval routing via scoped bus.
374
+ * Connector calls requestToolApproval → approval handler routes to global bus → returns response.
375
+ *
376
+ * Note: Enrichment fields (agentId, adapterId, etc.) are auto-injected by requestToolApproval.
377
+ */
378
+ const DynamicToolCallApprovalRequestSchema = {
379
+ request: z.object({
380
+ threadId: z.string(),
381
+ turnId: z.string(),
382
+ /** Item ID — same as `toolCallId` used in `item/tool/call` */
383
+ itemId: z.string(),
384
+ /** Tool name as declared in `dynamicTools` at `thread/start` */
385
+ name: z.string(),
386
+ /** Tool input arguments */
387
+ args: z.record(z.string(), z.unknown()),
388
+ timestamp: z.number()
389
+ }).merge(EnrichmentFieldsSchema),
390
+ response: ApprovalResponseSchema
391
+ };
392
+
393
+ //#endregion
394
+ //#region src/namespaces/index.ts
395
+ /**
396
+ * Codex App-Server Adapter Namespace
397
+ *
398
+ * Defines internal bus events for the codex app-server adapter.
399
+ * These events are emitted by the adapter and consumed by the core system.
400
+ */
401
+ const namespace = "adapter:codex-app-server";
402
+ /**
403
+ * Codex App-Server Adapter schemas.
404
+ * Extracted as const to enable FilterPayload type computation via typeof.
405
+ */
406
+ const codexAppServerSchemas = {
407
+ thread_started: ThreadStartedSchema,
408
+ thread_completed: ThreadCompletedSchema,
409
+ turn_state_changed: TurnStateChangedSchema,
410
+ turn_started: TurnStartedSchema,
411
+ turn_completed: TurnCompletedSchema,
412
+ turn_step_started: TurnStepStartedSchema,
413
+ turn_step_finished: TurnStepFinishedSchema,
414
+ item_started: ItemStartedSchema,
415
+ item_completed: ItemCompletedSchema,
416
+ agent_message_delta: AgentMessageDeltaSchema,
417
+ agent_message: AgentMessageSchema,
418
+ exec_command_begin: ExecCommandBeginSchema,
419
+ exec_approval_request: ExecApprovalRequestSchema,
420
+ exec_command_output_delta: ExecCommandOutputDeltaSchema,
421
+ exec_command_end: ExecCommandEndSchema,
422
+ file_change_approval_request: FileChangeApprovalRequestSchema,
423
+ file_change_output_delta: FileChangeOutputDeltaSchema,
424
+ reasoning_delta: ReasoningDeltaSchema,
425
+ reasoning: ReasoningSchema,
426
+ token_usage: TokenUsageSchema,
427
+ dynamic_tool_call_begin: DynamicToolCallBeginSchema,
428
+ dynamic_tool_call_end: DynamicToolCallEndSchema,
429
+ dynamic_tool_call_approval_request: DynamicToolCallApprovalRequestSchema
430
+ };
431
+ /**
432
+ * Codex App-Server Adapter Namespace
433
+ *
434
+ * Defines the event subjects and schemas for internal bus communication
435
+ * between the codex app-server adapter and the core system.
436
+ */
437
+ const CodexAppServerNamespace = createAdapterNamespace(namespace, codexAppServerSchemas);
438
+ /**
439
+ * Typed subject literals for Codex App-Server adapter.
440
+ * Use these constants to subscribe to specific message types with full type safety.
441
+ */
442
+ const CodexAppServerSubjects = CodexAppServerNamespace.subjects;
443
+
444
+ //#endregion
445
+ //#region src/tool-handling.ts
446
+ const TOOL_APPROVAL_DEBUG_ENABLED = process.env.MAKAIO_DEBUG_TOOL_APPROVAL === "1" || process.env.DEBUG?.includes("codex-tool-approval") === true;
447
+ /**
448
+ * Debug logger for tool-approval bridge flow.
449
+ * @param message - Message to log
450
+ * @param context - Optional structured context
451
+ */
452
+ function logToolApprovalDebug(message, context) {
453
+ if (!TOOL_APPROVAL_DEBUG_ENABLED) return;
454
+ console.debug("[registerToolApprovalHandler]", message, context ?? {});
455
+ }
456
+ /**
457
+ * Build the shared approval envelope fields from adapter context.
458
+ * @param context - Adapter context
459
+ * @returns Shared approval envelope fields
460
+ */
461
+ function createApprovalEnvelope(context) {
462
+ return {
463
+ adapterId: context.adapterId,
464
+ adapterName: context.adapterName,
465
+ agentId: context.agentId,
466
+ adapterSessionId: context.adapterSessionId,
467
+ sessionId: context.sessionId
468
+ };
469
+ }
470
+ /**
471
+ * Transform command execution approval request → AgentToolApproveRequest.
472
+ *
473
+ * Converts the app-server's command execution approval params into the global
474
+ * tool approval request format. The params are included as args for context.
475
+ * @param params - Raw app-server approval request parameters
476
+ * @param context - Adapter context for request enrichment
477
+ * @returns Global tool approval request
478
+ */
479
+ function toGlobalToolApproval(params, context) {
480
+ return {
481
+ ...createApprovalEnvelope(context),
482
+ toolCallId: params.itemId,
483
+ toolName: "bash",
484
+ reasoning: params.reason ?? void 0,
485
+ args: {
486
+ threadId: params.threadId,
487
+ turnId: params.turnId,
488
+ itemId: params.itemId,
489
+ reason: params.reason,
490
+ proposedExecpolicyAmendment: params.proposedExecpolicyAmendment
491
+ }
492
+ };
493
+ }
494
+ /**
495
+ * Transform file change approval request → AgentToolApproveRequest.
496
+ *
497
+ * Converts the app-server's file change approval params into the global
498
+ * tool approval request format. The params are included as args for context.
499
+ * @param params - Raw app-server approval request parameters
500
+ * @param context - Adapter context for request enrichment
501
+ * @returns Global tool approval request
502
+ */
503
+ function toGlobalFileApproval(params, context) {
504
+ return {
505
+ ...createApprovalEnvelope(context),
506
+ toolCallId: params.itemId,
507
+ toolName: "patch",
508
+ reasoning: params.reason ?? void 0,
509
+ args: {
510
+ threadId: params.threadId,
511
+ turnId: params.turnId,
512
+ itemId: params.itemId,
513
+ reason: params.reason,
514
+ grantRoot: params.grantRoot
515
+ }
516
+ };
517
+ }
518
+ /**
519
+ * Transform AgentToolApproveResponse → app-server decision format.
520
+ *
521
+ * Converts the global approval response into the simple accept/decline format
522
+ * expected by the codex app-server protocol.
523
+ * @param response - Global tool approval response
524
+ * @returns App-server approval decision with optional message
525
+ */
526
+ function fromGlobalToolApproval(response) {
527
+ if (response.action === "allow") return { decision: "accept" };
528
+ return {
529
+ decision: "decline",
530
+ message: response.message || void 0
531
+ };
532
+ }
533
+ /**
534
+ * Register tool approval handler that bridges connector's approval requests
535
+ * to the global AgentSubjects.toolApprove bus.
536
+ *
537
+ * Wires:
538
+ * - CodexAppServerSubjects.exec_approval_request → AgentSubjects.toolApprove
539
+ * - CodexAppServerSubjects.file_change_approval_request → AgentSubjects.toolApprove
540
+ *
541
+ * Used by both createTestConfig (test harness) and agent.ts (production).
542
+ * @param connector - Connector for Codex App-Server adapter (needs `on` and `getTimeoutMs` methods)
543
+ * @param context - Adapter context (can be lazy callback)
544
+ * @returns Unsubscribe function that removes both handlers
545
+ */
546
+ function registerToolApprovalHandler(connector, context) {
547
+ const timeout = connector.getTimeoutMs("toolApproval");
548
+ /**
549
+ * Resolve adapter context lazily (callback avoids race condition with adapterSessionId)
550
+ * @returns Resolved ToolApprovalContext
551
+ */
552
+ const resolveContext = async () => {
553
+ const providedContext = typeof context === "function" ? await context() : context;
554
+ if (providedContext == null || typeof providedContext !== "object" || typeof providedContext.sessionId !== "string") throw new Error("Tool approval requires a sessionId — ensure the agent was created within a session");
555
+ const sessionId = providedContext.sessionId.trim();
556
+ if (sessionId === "") throw new Error("Tool approval requires a sessionId — ensure the agent was created within a session");
557
+ return {
558
+ adapterId: providedContext?.adapterId ?? connector.adapterId,
559
+ adapterName: providedContext?.adapterName ?? connector.getAdapterName(),
560
+ agentId: providedContext?.agentId ?? connector.getAgentId(),
561
+ adapterSessionId: providedContext?.adapterSessionId ?? await connector.getAdapterSessionId(),
562
+ sessionId
563
+ };
564
+ };
565
+ /**
566
+ * Bridge a connector approval request to the global approval bus.
567
+ * @param ctx - Connector request context
568
+ * @param transform - Payload-to-global-request transformer
569
+ * @param logTag - Optional debug label
570
+ */
571
+ const handleApprovalRequest = async (ctx, transform, logTag) => {
572
+ if (logTag) logToolApprovalDebug(`${logTag} invoked`, { timestamp: Date.now() });
573
+ let globalResponse;
574
+ try {
575
+ const resolvedContext = await resolveContext();
576
+ if (logTag) logToolApprovalDebug("resolved context", {
577
+ agentId: resolvedContext.agentId,
578
+ timestamp: Date.now()
579
+ });
580
+ const request = transform(ctx.payload, resolvedContext);
581
+ if (logTag) logToolApprovalDebug("requesting global approval", {
582
+ agentId: request.agentId,
583
+ timestamp: Date.now()
584
+ });
585
+ globalResponse = await MakaioBus.request(AgentSubjects.toolApprove, request, { timeout });
586
+ } catch (error) {
587
+ console.error("[registerToolApprovalHandler] Tool approval request failed:", error);
588
+ const errorDetails = error instanceof Error ? `: ${error.message}` : "";
589
+ ctx.setResult({
590
+ decision: "decline",
591
+ message: `Tool approval request failed${errorDetails}`
592
+ });
593
+ return;
594
+ }
595
+ if (logTag) logToolApprovalDebug("received global approval response", {
596
+ action: globalResponse.action,
597
+ timestamp: Date.now()
598
+ });
599
+ if (globalResponse.action === "deny" && globalResponse.shouldAbort) throw new Error(`Tool use denied by approval handler: ${globalResponse.message ?? "access denied"}`);
600
+ ctx.setResult(fromGlobalToolApproval(globalResponse));
601
+ };
602
+ const unsubExec = connector.on(CodexAppServerSubjects.exec_approval_request, async (ctx) => {
603
+ await handleApprovalRequest(ctx, toGlobalToolApprovalFromInternal, "exec_approval_request");
604
+ });
605
+ const unsubFile = connector.on(CodexAppServerSubjects.file_change_approval_request, async (ctx) => {
606
+ await handleApprovalRequest(ctx, toGlobalFileApprovalFromInternal, "file_change_approval_request");
607
+ });
608
+ return () => {
609
+ unsubExec();
610
+ unsubFile();
611
+ };
612
+ }
613
+ /**
614
+ * Transform internal scoped bus exec approval payload → AgentToolApproveRequest.
615
+ *
616
+ * Similar to toGlobalToolApproval but works with the internal scoped bus payload
617
+ * which has a slightly different shape (callId vs itemId).
618
+ * @param payload - Internal scoped bus payload with threadId, turnId, callId, command, cwd
619
+ * @param context - Adapter context for request enrichment
620
+ * @returns Global tool approval request
621
+ */
622
+ function toGlobalToolApprovalFromInternal(payload, context) {
623
+ return {
624
+ ...createApprovalEnvelope(context),
625
+ toolCallId: payload.callId,
626
+ toolName: "bash",
627
+ reasoning: payload.reason ?? void 0,
628
+ args: {
629
+ threadId: payload.threadId,
630
+ turnId: payload.turnId,
631
+ callId: payload.callId,
632
+ command: payload.command,
633
+ cwd: payload.cwd,
634
+ reason: payload.reason
635
+ }
636
+ };
637
+ }
638
+ /**
639
+ * Transform internal scoped bus file approval payload → AgentToolApproveRequest.
640
+ *
641
+ * Similar to toGlobalFileApproval but works with the internal scoped bus payload
642
+ * which has a slightly different shape (itemId vs callId).
643
+ * @param payload - Internal scoped bus payload with threadId, turnId, itemId, reason, grantRoot
644
+ * @param context - Adapter context for request enrichment
645
+ * @returns Global tool approval request
646
+ */
647
+ function toGlobalFileApprovalFromInternal(payload, context) {
648
+ return {
649
+ ...createApprovalEnvelope(context),
650
+ toolCallId: payload.itemId,
651
+ toolName: "patch",
652
+ reasoning: payload.reason ?? void 0,
653
+ args: {
654
+ threadId: payload.threadId,
655
+ turnId: payload.turnId,
656
+ itemId: payload.itemId,
657
+ reason: payload.reason,
658
+ grantRoot: payload.grantRoot
659
+ }
660
+ };
661
+ }
662
+
663
+ //#endregion
664
+ //#region src/agent.ts
665
+ /**
666
+ * Codex App-Server Agent - Event routing layer.
667
+ *
668
+ * Wires connector events to global agent.* subjects.
669
+ * Auto-enriches payloads with AgentContext via emitGlobal().
670
+ *
671
+ * Responsibilities:
672
+ * 1. Wire connector's scoped bus events to global agent.* subjects
673
+ * 2. Auto-enrich payloads with AgentContext
674
+ * 3. Track usage metrics
675
+ * 4. Route tool approval to global bus
676
+ * 5. Emit normalized client.session.* observed-semantics events
677
+ * 6. Emit client.runtime.observe (best-effort) when a thread starts with a confirmed adapter session ID
678
+ *
679
+ * Event Flow:
680
+ * - CodexAppServerConnector emits to adapter:codex-app-server:* subjects
681
+ * - CodexAppServerAgent routes to semantic subjects via wireConnectorEvents()
682
+ * - CodexAppServerAgent subscribes to semantic subjects and emits to global bus (agent.*)
683
+ * - Downstream consumers subscribe to normalized agent.* subjects
684
+ * @packageDocumentation
685
+ */
686
+ /**
687
+ * Maximum number of characters retained in `toolOutputCache` per tool call.
688
+ *
689
+ * Streaming consumers already receive every chunk via
690
+ * `AgentSubjects.tool.output`, so the cache is only needed to populate the
691
+ * final `tool_output` content block in `emitToolStepFinished`. Capping it
692
+ * prevents unbounded memory growth for long-running commands (builds, test
693
+ * suites, etc.) whose output can otherwise reach tens of megabytes.
694
+ */
695
+ const MAX_TOOL_OUTPUT_CACHE_CHARS = 256 * 1024;
696
+ /**
697
+ * Keep the newest tool output in the bounded per-tool cache.
698
+ *
699
+ * Streaming consumers receive every chunk via `AgentSubjects.tool.output`; the
700
+ * cache is only the final content-block snapshot, so retaining the tail keeps
701
+ * the diagnostic context where command failures usually appear.
702
+ * @param output - Full accumulated output candidate
703
+ * @returns Output capped to the configured character count
704
+ */
705
+ function capToolOutputCache(output) {
706
+ return output.length > MAX_TOOL_OUTPUT_CACHE_CHARS ? output.slice(-MAX_TOOL_OUTPUT_CACHE_CHARS) : output;
707
+ }
708
+ /**
709
+ * Codex App-Server Agent - Middle layer between AIAdapter and CodexAppServerConnector.
710
+ *
711
+ * Responsibilities:
712
+ * 1. Wire connector's scoped bus events to global agent.* subjects
713
+ * 2. Auto-enrich payloads with AgentContext via emitGlobal()
714
+ * 3. Emit normalized client.session.* observed-semantics events (best-effort)
715
+ * 4. Emit client.runtime.observe (best-effort) when a thread starts with a confirmed adapter session ID
716
+ *
717
+ * Event Flow:
718
+ * - CodexAppServerConnector emits to adapter:codex-app-server:* subjects
719
+ * - CodexAppServerAgent routes to semantic subjects via wireConnectorEvents()
720
+ * - CodexAppServerAgent subscribes to semantic subjects and emits to global bus (agent.*)
721
+ * - Downstream consumers subscribe to normalized agent.* subjects
722
+ */
723
+ var CodexAppServerAgent = class extends AIAgent {
724
+ /** Cache of tool call arguments indexed by callId for content blocks */
725
+ toolCallCache = /* @__PURE__ */ new Map();
726
+ /** Accumulated tool output indexed by callId during streaming */
727
+ toolOutputCache = /* @__PURE__ */ new Map();
728
+ /** Track toolCallId to blockIndex for correlation between step.started and step.finished */
729
+ toolBlockIndexMap = /* @__PURE__ */ new Map();
730
+ /**
731
+ * Guard: client.session.* global bus observations are wired once per agent
732
+ * lifetime (not per connector swap). Connector-level wiring uses
733
+ * addConnectorWiringCleanup(); these stable global subscriptions use
734
+ * addBusHandlerCleanup() and must not accumulate across swaps.
735
+ */
736
+ clientSessionObservationsWired = false;
737
+ wireEvents(connector) {
738
+ this.wireConnectorEvents(connector);
739
+ this.wireToolApprovalRpc(connector);
740
+ this.wireClientSessionTurnObservations();
741
+ }
742
+ /**
743
+ * Wire connector's bus events to global agent.* subjects.
744
+ *
745
+ * Maps CodexAppServerSubjects.* to AgentSubjects.*
746
+ *
747
+ * Note: Turn lifecycle events (turn.started, turn.completed) are NOT wired here
748
+ * because the base AIAgent's message-lifecycle-tracker already handles emitting
749
+ * these global events when messages are acknowledged and turns complete.
750
+ * @param connector - The CodexAppServerConnector to wire events from
751
+ */
752
+ wireConnectorEvents(connector) {
753
+ this.wireThreadLifecycleEvents(connector);
754
+ this.wireMessageEvents(connector);
755
+ this.wireToolEvents(connector);
756
+ this.wireUsageTracking(connector);
757
+ }
758
+ /**
759
+ * Build the shared base payload for all `client.session.*` observed-semantics
760
+ * events by forwarding this agent's current identity fields to the shared
761
+ * {@link buildClientSessionBase} helper.
762
+ *
763
+ * Captures the adapter session ID best-effort: if the connector has not yet
764
+ * confirmed a session ID (e.g., very early in initialization), the field is
765
+ * omitted rather than blocking emission.
766
+ * @returns Base payload with clientId, source, observedAt, and optional session IDs
767
+ */
768
+ getClientSessionBase() {
769
+ return buildClientSessionBase({
770
+ clientId: this.config.clientId ?? "codex",
771
+ sessionId: this.sessionId,
772
+ adapterSessionId: this.connector?.adapterSessionId
773
+ });
774
+ }
775
+ /**
776
+ * Wire stable global bus subscriptions for client.session.turn.* events.
777
+ *
778
+ * Uses addBusHandlerCleanup() so these subscriptions survive connector swaps.
779
+ * Guarded by clientSessionObservationsWired so they are registered exactly once
780
+ * per agent lifetime regardless of how many connector swaps occur.
781
+ *
782
+ * Subscribes to:
783
+ * - AgentSubjects.turn.started → client.session.turn.started
784
+ * - AgentSubjects.turn.completed → client.session.turn.completed
785
+ * - AgentSubjects.user_message.sent → client.session.userPrompt.submitted
786
+ */
787
+ wireClientSessionTurnObservations() {
788
+ if (this.clientSessionObservationsWired) return;
789
+ this.clientSessionObservationsWired = true;
790
+ const filteredBus = this.globalBus.withFilter({ agentId: this.agentId });
791
+ this.addBusHandlerCleanup(filteredBus.on(AgentSubjects.turn.started, () => {
792
+ emitBestEffort(async () => {
793
+ await this.globalBus.emit(ClientSubjects.session.turn.started, this.getClientSessionBase());
794
+ });
795
+ }));
796
+ this.addBusHandlerCleanup(filteredBus.on(AgentSubjects.turn.completed, () => {
797
+ emitBestEffort(async () => {
798
+ await this.globalBus.emit(ClientSubjects.session.turn.completed, this.getClientSessionBase());
799
+ });
800
+ }));
801
+ this.addBusHandlerCleanup(filteredBus.on(AgentSubjects.user_message.sent, (ctx) => {
802
+ const prompt = ctx.payload.content.message;
803
+ emitBestEffort(async () => {
804
+ await this.globalBus.emit(ClientSubjects.session.userPrompt.submitted, {
805
+ ...this.getClientSessionBase(),
806
+ ...prompt !== void 0 && { prompt }
807
+ });
808
+ });
809
+ }));
810
+ }
811
+ /**
812
+ * Wire thread lifecycle events.
813
+ * @param connector - The CodexAppServerConnector to wire events from
814
+ */
815
+ wireThreadLifecycleEvents(connector) {
816
+ this.subscribeConnector(connector, CodexAppServerSubjects.thread_started, async () => {
817
+ this.toolCallCache.clear();
818
+ this.toolOutputCache.clear();
819
+ this.toolBlockIndexMap.clear();
820
+ await this.emitStart();
821
+ emitBestEffort(async () => {
822
+ await this.globalBus.emit(ClientSubjects.session.started, this.getClientSessionBase());
823
+ });
824
+ const adapterSessionId = this.connector?.adapterSessionId;
825
+ if (adapterSessionId) this.globalBus.requestOptional(ClientSubjects.runtime.observe, {
826
+ clientId: this.config.clientId ?? "codex",
827
+ source: {
828
+ layer: "adapter",
829
+ producer: "codex-app-server"
830
+ },
831
+ observedAt: Date.now(),
832
+ adapterSessionId,
833
+ sessionId: this.sessionId
834
+ }).catch(() => {});
835
+ });
836
+ this.subscribeConnector(connector, CodexAppServerSubjects.thread_completed, async () => {
837
+ const messageId = crypto.randomUUID();
838
+ return this.emitCompletion({ messageId });
839
+ });
840
+ }
841
+ /**
842
+ * Wire assistant message events.
843
+ *
844
+ * Note: Turn lifecycle events (agent.turn.started, agent.turn.completed) are emitted by
845
+ * MessageLifecycleTracker in ai-adapters-core based on message handle states,
846
+ * not by adapter-specific events.
847
+ * @param connector - The CodexAppServerConnector to wire events from
848
+ */
849
+ wireMessageEvents(connector) {
850
+ this.subscribeConnector(connector, CodexAppServerSubjects.agent_message_delta, async (ctx) => {
851
+ await this.emitGlobal(AgentSubjects.message_delta, { text: ctx.payload.delta });
852
+ });
853
+ this.subscribeConnector(connector, CodexAppServerSubjects.agent_message, async (ctx) => {
854
+ await this.emitGlobal(AgentSubjects.message, { content: ctx.payload.message });
855
+ await this.emitStepStarted("text");
856
+ await this.emitStepFinished("text", {
857
+ type: "text",
858
+ content: ctx.payload.message
859
+ });
860
+ });
861
+ this.subscribeConnector(connector, CodexAppServerSubjects.reasoning_delta, async (ctx) => {
862
+ await this.emitGlobal(AgentSubjects.reasoning_delta, { content: ctx.payload.delta });
863
+ });
864
+ this.subscribeConnector(connector, CodexAppServerSubjects.reasoning, async (ctx) => {
865
+ await this.emitGlobal(AgentSubjects.reasoning, { content: ctx.payload.reasoning });
866
+ await this.emitStepStarted("reasoning");
867
+ await this.emitStepFinished("reasoning", {
868
+ type: "reasoning",
869
+ content: ctx.payload.reasoning
870
+ });
871
+ });
872
+ }
873
+ /**
874
+ * Wire tool-related events.
875
+ * @param connector - The CodexAppServerConnector to wire events from
876
+ */
877
+ wireToolEvents(connector) {
878
+ this.wireCommandExecutionEvents(connector);
879
+ this.wireFileChangeEvents(connector);
880
+ this.wireDynamicToolCallEvents(connector);
881
+ }
882
+ /**
883
+ * Wire command execution events (bash tool).
884
+ * @param connector - The CodexAppServerConnector to wire events from
885
+ */
886
+ wireCommandExecutionEvents(connector) {
887
+ this.subscribeConnector(connector, CodexAppServerSubjects.exec_approval_request, async (ctx) => {
888
+ const payload = ctx.payload;
889
+ this.toolCallCache.set(payload.callId, {
890
+ command: payload.command,
891
+ cwd: payload.cwd,
892
+ toolCallId: payload.callId,
893
+ name: "bash",
894
+ args: {
895
+ command: payload.command,
896
+ cwd: payload.cwd
897
+ }
898
+ });
899
+ await this.emitGlobal(AgentSubjects.tool.use, {
900
+ toolName: "bash",
901
+ args: {
902
+ command: payload.command,
903
+ cwd: payload.cwd
904
+ },
905
+ toolCallId: payload.callId
906
+ });
907
+ });
908
+ this.subscribeConnector(connector, CodexAppServerSubjects.exec_command_begin, async (ctx) => {
909
+ await this.handleExecCommandBegin(ctx.payload);
910
+ });
911
+ this.subscribeConnector(connector, CodexAppServerSubjects.exec_command_output_delta, async (ctx) => {
912
+ const payload = ctx.payload;
913
+ const appended = (this.toolOutputCache.get(payload.callId) ?? "") + payload.chunk;
914
+ this.toolOutputCache.set(payload.callId, capToolOutputCache(appended));
915
+ await this.emitGlobal(AgentSubjects.tool.output, {
916
+ output: payload.chunk,
917
+ toolCallId: payload.callId
918
+ });
919
+ });
920
+ this.subscribeConnector(connector, CodexAppServerSubjects.exec_command_end, async (ctx) => {
921
+ const payload = ctx.payload;
922
+ await this.emitGlobal(AgentSubjects.tool.completed, {
923
+ toolName: "bash",
924
+ args: this.toolCallCache.get(payload.callId)?.args,
925
+ result: { exitCode: payload.exitCode },
926
+ success: payload.exitCode === 0,
927
+ toolCallId: payload.callId
928
+ });
929
+ await this.emitToolStepFinished(payload.callId, this.toolOutputCache.get(payload.callId) ?? `Command completed with exit code ${payload.exitCode}`, payload.exitCode !== 0);
930
+ emitBestEffort(async () => {
931
+ await this.globalBus.emit(ClientSubjects.session.tool.post, {
932
+ ...this.getClientSessionBase(),
933
+ toolName: "bash",
934
+ toolCallId: payload.callId,
935
+ success: payload.exitCode === 0
936
+ });
937
+ });
938
+ });
939
+ }
940
+ /**
941
+ * Handle exec_command_begin: build step.started content, emit step.started,
942
+ * tool.started, and client.session.tool.pre.
943
+ * @param payload - The exec_command_begin event payload
944
+ */
945
+ async handleExecCommandBegin(payload) {
946
+ const cachedTool = this.toolCallCache.get(payload.callId);
947
+ const toolCallContent = cachedTool ? {
948
+ type: "tool_call",
949
+ toolCallId: cachedTool.toolCallId,
950
+ name: cachedTool.name,
951
+ args: cachedTool.args
952
+ } : {
953
+ type: "tool_call",
954
+ toolCallId: payload.callId,
955
+ name: "bash",
956
+ args: {
957
+ command: payload.command,
958
+ cwd: payload.cwd
959
+ }
960
+ };
961
+ const blockIndex = this.getBlockIndex();
962
+ this.toolBlockIndexMap.set(payload.callId, blockIndex);
963
+ await this.emitStepStarted("tool_use", {
964
+ type: "tool_use",
965
+ toolName: "bash",
966
+ toolCallId: payload.callId
967
+ }, toolCallContent);
968
+ this.incrementBlockIndex();
969
+ await this.emitGlobal(AgentSubjects.tool.started, {
970
+ toolName: "bash",
971
+ toolCallId: payload.callId
972
+ });
973
+ emitBestEffort(async () => {
974
+ await this.globalBus.emit(ClientSubjects.session.tool.pre, {
975
+ ...this.getClientSessionBase(),
976
+ toolName: "bash",
977
+ toolCallId: payload.callId
978
+ });
979
+ });
980
+ }
981
+ /**
982
+ * Wire file change events (patch tool).
983
+ * @param connector - The CodexAppServerConnector to wire events from
984
+ */
985
+ wireFileChangeEvents(connector) {
986
+ this.subscribeConnector(connector, CodexAppServerSubjects.file_change_approval_request, async (ctx) => {
987
+ const { itemId, reason, grantRoot } = ctx.payload;
988
+ const args = {
989
+ reason,
990
+ grantRoot
991
+ };
992
+ this.toolCallCache.set(itemId, {
993
+ command: [],
994
+ cwd: "",
995
+ toolCallId: itemId,
996
+ name: "patch",
997
+ args
998
+ });
999
+ await this.emitGlobal(AgentSubjects.tool.use, {
1000
+ toolName: "patch",
1001
+ args,
1002
+ toolCallId: itemId
1003
+ });
1004
+ const blockIndex = this.getBlockIndex();
1005
+ this.toolBlockIndexMap.set(itemId, blockIndex);
1006
+ await this.emitStepStarted("tool_use", {
1007
+ type: "tool_use",
1008
+ toolName: "patch",
1009
+ toolCallId: itemId
1010
+ }, {
1011
+ type: "tool_call",
1012
+ toolCallId: itemId,
1013
+ name: "patch",
1014
+ args
1015
+ });
1016
+ this.incrementBlockIndex();
1017
+ emitBestEffort(async () => {
1018
+ await this.globalBus.emit(ClientSubjects.session.tool.pre, {
1019
+ ...this.getClientSessionBase(),
1020
+ toolName: "patch",
1021
+ toolCallId: itemId
1022
+ });
1023
+ });
1024
+ });
1025
+ this.subscribeConnector(connector, CodexAppServerSubjects.file_change_output_delta, async (ctx) => {
1026
+ const { itemId, delta } = ctx.payload;
1027
+ const appended = (this.toolOutputCache.get(itemId) ?? "") + delta;
1028
+ this.toolOutputCache.set(itemId, capToolOutputCache(appended));
1029
+ await this.emitGlobal(AgentSubjects.tool.output, {
1030
+ output: delta,
1031
+ toolCallId: itemId
1032
+ });
1033
+ });
1034
+ this.subscribeConnector(connector, CodexAppServerSubjects.item_completed, async (ctx) => {
1035
+ const cached = this.toolCallCache.get(ctx.payload.itemId);
1036
+ if (cached?.name === "patch") {
1037
+ const output = this.toolOutputCache.get(ctx.payload.itemId) ?? "File change applied";
1038
+ await this.emitGlobal(AgentSubjects.tool.completed, {
1039
+ toolName: "patch",
1040
+ args: cached.args,
1041
+ result: { output },
1042
+ success: true,
1043
+ toolCallId: ctx.payload.itemId
1044
+ });
1045
+ await this.emitToolStepFinished(ctx.payload.itemId, output, false);
1046
+ emitBestEffort(async () => {
1047
+ await this.globalBus.emit(ClientSubjects.session.tool.post, {
1048
+ ...this.getClientSessionBase(),
1049
+ toolName: "patch",
1050
+ toolCallId: ctx.payload.itemId,
1051
+ success: true
1052
+ });
1053
+ });
1054
+ }
1055
+ });
1056
+ }
1057
+ /**
1058
+ * Wire dynamic tool call events (experimental API).
1059
+ *
1060
+ * Handles registry tools declared in `dynamicTools` at `thread/start`.
1061
+ * The execution happens in the `item/tool/call` server request handler; these
1062
+ * events carry the cached result into the agent's step lifecycle.
1063
+ * @param connector - The CodexAppServerConnector to wire events from
1064
+ */
1065
+ wireDynamicToolCallEvents(connector) {
1066
+ this.subscribeConnector(connector, CodexAppServerSubjects.dynamic_tool_call_begin, async (ctx) => {
1067
+ const { itemId, name, args } = ctx.payload;
1068
+ this.toolCallCache.set(itemId, {
1069
+ command: [],
1070
+ cwd: "",
1071
+ toolCallId: itemId,
1072
+ name,
1073
+ args
1074
+ });
1075
+ const blockIndex = this.getBlockIndex();
1076
+ this.toolBlockIndexMap.set(itemId, blockIndex);
1077
+ await this.emitGlobal(AgentSubjects.tool.use, {
1078
+ toolName: name,
1079
+ args,
1080
+ toolCallId: itemId
1081
+ });
1082
+ await this.emitGlobal(AgentSubjects.tool.started, {
1083
+ toolName: name,
1084
+ toolCallId: itemId
1085
+ });
1086
+ await this.emitStepStarted("tool_use", {
1087
+ type: "tool_use",
1088
+ toolName: name,
1089
+ toolCallId: itemId
1090
+ }, {
1091
+ type: "tool_call",
1092
+ toolCallId: itemId,
1093
+ name,
1094
+ args
1095
+ });
1096
+ this.incrementBlockIndex();
1097
+ emitBestEffort(async () => {
1098
+ await this.globalBus.emit(ClientSubjects.session.tool.pre, {
1099
+ ...this.getClientSessionBase(),
1100
+ toolName: name,
1101
+ toolCallId: itemId
1102
+ });
1103
+ });
1104
+ });
1105
+ this.subscribeConnector(connector, CodexAppServerSubjects.dynamic_tool_call_end, async (ctx) => {
1106
+ const { itemId, name, output, success } = ctx.payload;
1107
+ const cached = this.toolCallCache.get(itemId);
1108
+ await this.emitGlobal(AgentSubjects.tool.output, {
1109
+ output,
1110
+ toolCallId: itemId
1111
+ });
1112
+ await this.emitGlobal(AgentSubjects.tool.completed, {
1113
+ toolName: name,
1114
+ args: cached?.args,
1115
+ result: { output },
1116
+ success,
1117
+ toolCallId: itemId
1118
+ });
1119
+ await this.emitToolStepFinished(itemId, output, !success);
1120
+ emitBestEffort(async () => {
1121
+ await this.globalBus.emit(ClientSubjects.session.tool.post, {
1122
+ ...this.getClientSessionBase(),
1123
+ toolName: name,
1124
+ toolCallId: itemId,
1125
+ success
1126
+ });
1127
+ });
1128
+ });
1129
+ }
1130
+ /**
1131
+ * Wire usage tracking events.
1132
+ * The base AIAgent.trackUsage() handles dual emission to both
1133
+ * AgentSubjects.usage and adapter session usage subjects.
1134
+ * @param connector - The CodexAppServerConnector to wire events from
1135
+ */
1136
+ wireUsageTracking(connector) {
1137
+ this.subscribeConnector(connector, CodexAppServerSubjects.token_usage, async (ctx) => {
1138
+ const payload = ctx.payload;
1139
+ const normalized = {
1140
+ provider: "openai",
1141
+ inputTokens: payload.promptTokens,
1142
+ inputCachedTokens: payload.inputCachedTokens,
1143
+ outputTokens: payload.completionTokens,
1144
+ reasoningTokens: payload.reasoningTokens,
1145
+ totalTokens: payload.totalTokens,
1146
+ costUnits: payload.totalTokens,
1147
+ costUnitType: "tokens",
1148
+ contextWindow: payload.modelContextWindow
1149
+ };
1150
+ await this.trackUsage(normalized);
1151
+ if (payload.modelContextWindow) await this.emitContextWindowUpdate({
1152
+ currentTokens: payload.totalTokens,
1153
+ maxTokens: payload.modelContextWindow,
1154
+ cachedTokens: payload.inputCachedTokens
1155
+ });
1156
+ });
1157
+ }
1158
+ /**
1159
+ * Wire tool approval RPC from connector's scoped bus to global AgentSubjects.toolApprove.
1160
+ *
1161
+ * Uses centralized tool-handling helper for consistent approval flow.
1162
+ * Note: adapterSessionId may be undefined at wire time — the lazy callback resolves it
1163
+ * at request time via connector.getAdapterSessionId() to avoid the race condition.
1164
+ * sessionId is always set for agents running within a session; asserted here as the
1165
+ * Zod schema on AgentSubjects.toolApprove enforces it at runtime.
1166
+ * @param connector - The CodexAppServerConnector to wire RPC from
1167
+ */
1168
+ wireToolApprovalRpc(connector) {
1169
+ this.addConnectorWiringCleanup(registerToolApprovalHandler(connector, async () => {
1170
+ if (this.sessionId == null) throw new Error("Agent sessionId is required for tool approval");
1171
+ return {
1172
+ adapterId: this.adapterId,
1173
+ adapterName: this.adapterName,
1174
+ agentId: this.agentId,
1175
+ adapterSessionId: await this.getAdapterSessionId(),
1176
+ sessionId: this.sessionId
1177
+ };
1178
+ }));
1179
+ }
1180
+ /**
1181
+ * Emit tool step.finished and cleanup cached data.
1182
+ * Shared helper for bash and patch tool completion.
1183
+ * @param toolCallId - Tool call identifier
1184
+ * @param output - Tool output content
1185
+ * @param isError - Whether the tool execution failed
1186
+ */
1187
+ async emitToolStepFinished(toolCallId, output, isError) {
1188
+ const blockIndex = this.toolBlockIndexMap.get(toolCallId);
1189
+ if (blockIndex === void 0) console.warn(`[CodexAdapter] toolCallId ${toolCallId} not found in toolBlockIndexMap - possible state mismatch`);
1190
+ const resolvedBlockIndex = blockIndex ?? -1;
1191
+ this.toolBlockIndexMap.delete(toolCallId);
1192
+ const content = {
1193
+ type: "tool_output",
1194
+ toolCallId,
1195
+ output,
1196
+ isError
1197
+ };
1198
+ await this.emitGlobal(AgentSubjects.step.finished, {
1199
+ stepType: "tool_use",
1200
+ blockIndex: resolvedBlockIndex,
1201
+ content
1202
+ });
1203
+ this.toolCallCache.delete(toolCallId);
1204
+ this.toolOutputCache.delete(toolCallId);
1205
+ }
1206
+ };
1207
+
1208
+ //#endregion
1209
+ //#region src/dynamic-tool-handling.ts
1210
+ /**
1211
+ * Dynamic tool integration for Codex App Server.
1212
+ *
1213
+ * Converts Makaio registry tools to Codex's dynamicTools format for
1214
+ * `thread/start`, and handles `item/tool/call` server requests by routing
1215
+ * execution through the bus.
1216
+ * @packageDocumentation
1217
+ */
1218
+ /**
1219
+ * Type guard for `item/tool/call` server requests.
1220
+ *
1221
+ * Used to narrow the generic server-request union (which does not yet include
1222
+ * `item/tool/call` in the generated types) to the experimental shape.
1223
+ * @param request - The raw server request object
1224
+ * @returns `true` if the request is an `item/tool/call` request
1225
+ */
1226
+ function isDynamicToolCallRequest(request) {
1227
+ return request.method === "item/tool/call";
1228
+ }
1229
+ /**
1230
+ * Convert registry `ToolListItem[]` to Codex dynamic tool format.
1231
+ *
1232
+ * Filters out tools without an `inputSchema` because the codex protocol
1233
+ * requires a JSON Schema for each dynamic tool declaration.
1234
+ * @param tools - Tools from `loadToolsFromRegistry`
1235
+ * @returns Codex-compatible dynamic tool declarations
1236
+ */
1237
+ function toCodexDynamicToolFormat(tools) {
1238
+ return filterToolsWithSchema(tools).map((tool) => ({
1239
+ name: tool.name,
1240
+ description: tool.description,
1241
+ inputSchema: tool.inputSchema
1242
+ }));
1243
+ }
1244
+ /**
1245
+ * Fetch registry tools and convert to Codex dynamic tool format.
1246
+ *
1247
+ * `loadToolsFromRegistry` catches fetch failures internally and returns `[]`,
1248
+ * so this function never throws. Callers always receive a (possibly empty) array.
1249
+ * @param adapterId - Adapter instance ID for policy filtering
1250
+ * @param adapterName - Adapter type name for policy filtering
1251
+ * @returns Codex-formatted dynamic tool declarations
1252
+ */
1253
+ async function fetchToolsForCodex(adapterId, adapterName) {
1254
+ return toCodexDynamicToolFormat(await loadToolsFromRegistry(adapterId, adapterName));
1255
+ }
1256
+ /**
1257
+ * Determine whether a serialised tool output string represents a success.
1258
+ *
1259
+ * Returns `false` when the output is valid JSON that contains a top-level `error`
1260
+ * key (the shape emitted by {@link handleDynamicToolCall} on bus failure), and
1261
+ * `true` in all other cases (including non-JSON output, which is treated as success).
1262
+ *
1263
+ * Known limitation: a tool that legitimately returns `{ "error": "..." }` as data
1264
+ * will be misclassified as failed. This is acceptable because (a) the Codex dynamic
1265
+ * tool protocol has no out-of-band success channel, (b) tools should not use
1266
+ * "error" as a top-level key for successful results, and (c) the misclassification
1267
+ * only affects lifecycle telemetry, not the tool output returned to the agent.
1268
+ *
1269
+ * **Error format contract:** {@link handleDynamicToolCall} serialises bus errors as
1270
+ * `JSON.stringify({ error: message })` (a single top-level `error` string key).
1271
+ * This function is coupled to that exact shape — any change to the error format in
1272
+ * `handleDynamicToolCall` must be reflected here.
1273
+ * @param outputText - Serialised tool output from a `DynamicToolCallResponse`
1274
+ * @returns `true` if the output represents a successful execution
1275
+ */
1276
+ function isDynamicToolCallSuccess(outputText) {
1277
+ try {
1278
+ return !("error" in JSON.parse(outputText));
1279
+ } catch {
1280
+ return true;
1281
+ }
1282
+ }
1283
+ /**
1284
+ * Handle an `item/tool/call` server request from codex.
1285
+ *
1286
+ * Routes execution through `ToolSubjects.execute` on the global bus and
1287
+ * converts the result to the content-item format codex expects in the response.
1288
+ * Both success and failure paths return a valid response — bus errors are
1289
+ * serialised as error content rather than rejecting the promise, so codex
1290
+ * always receives a well-formed reply.
1291
+ * @param params - Tool call parameters from the server request
1292
+ * @param context - Execution context for bus routing and attribution
1293
+ * @returns Content items to send back to codex
1294
+ */
1295
+ async function handleDynamicToolCall(params, context) {
1296
+ try {
1297
+ const result = await MakaioBus.request(ToolSubjects.execute, {
1298
+ toolName: params.name,
1299
+ input: params.arguments,
1300
+ adapterId: context.adapterId,
1301
+ adapterName: context.adapterName,
1302
+ contextOverrides: {
1303
+ sessionId: context.sessionId,
1304
+ agentId: context.agentId,
1305
+ toolCallId: params.itemId
1306
+ }
1307
+ });
1308
+ const text = result.success ? typeof result.data === "string" ? result.data : JSON.stringify(result.data ?? null) : JSON.stringify({
1309
+ error: result.error.message,
1310
+ code: result.error.code
1311
+ });
1312
+ if (result.success && context.toolLedger && isMcpCallTool(params.name)) {
1313
+ const targetTool = extractMcpCallTarget(params.arguments);
1314
+ if (targetTool !== void 0 && context.currentTurnNumber > 0) context.toolLedger.recordCall(targetTool, context.currentTurnNumber);
1315
+ }
1316
+ return { content: [{
1317
+ type: "text",
1318
+ text
1319
+ }] };
1320
+ } catch (error) {
1321
+ const message = error instanceof Error ? error.message : String(error);
1322
+ return { content: [{
1323
+ type: "text",
1324
+ text: JSON.stringify({ error: message })
1325
+ }] };
1326
+ }
1327
+ }
1328
+
1329
+ //#endregion
1330
+ //#region src/connector/approval-handlers.ts
1331
+ /**
1332
+ * Handle command approval request.
1333
+ *
1334
+ * Returns an immediate `decline` when the `bash` native tool is disabled by the
1335
+ * active harness, bypassing the normal approval flow.
1336
+ * Otherwise routes the JSON-RPC server request to the scoped bus via requestToolApproval.
1337
+ * @param request - Server request for command approval
1338
+ * @param ctx - Approval context
1339
+ * @returns Approval response with decision and optional message
1340
+ */
1341
+ async function handleCommandApprovalRequest(request, ctx) {
1342
+ if (ctx.getDisabledNativeTools().has("bash")) return {
1343
+ decision: "decline",
1344
+ message: "bash tool is disabled by the active harness"
1345
+ };
1346
+ const params = request.params;
1347
+ try {
1348
+ const commandInfo = ctx.commandExecutionByItemId.get(params.itemId) ?? await ctx.waitForCommandInfo(params.itemId);
1349
+ return await ctx.requestToolApproval(CodexAppServerSubjects.exec_approval_request, {
1350
+ threadId: params.threadId,
1351
+ turnId: params.turnId,
1352
+ callId: params.itemId,
1353
+ command: commandInfo ? [commandInfo.command] : ["<unknown>"],
1354
+ cwd: commandInfo?.cwd ?? ctx.cwd ?? "",
1355
+ timestamp: Date.now(),
1356
+ reason: params.reason ?? null
1357
+ });
1358
+ } catch (error) {
1359
+ ctx.handleError(/* @__PURE__ */ new Error(`Tool approval request failed, make sure that there's a handler registered: ${String(error)}`), false);
1360
+ return {
1361
+ decision: "decline",
1362
+ message: String(error)
1363
+ };
1364
+ }
1365
+ }
1366
+ /**
1367
+ * Handle file change approval request.
1368
+ *
1369
+ * Returns an immediate `decline` when the `patch` native tool is disabled by the
1370
+ * active harness, bypassing the normal approval flow.
1371
+ * Otherwise routes the JSON-RPC server request to the scoped bus via requestToolApproval.
1372
+ * @param request - Server request for file change approval
1373
+ * @param ctx - Approval context
1374
+ * @returns Approval response with decision and optional message
1375
+ */
1376
+ async function handleFileChangeApprovalRequest(request, ctx) {
1377
+ if (ctx.getDisabledNativeTools().has("patch")) return {
1378
+ decision: "decline",
1379
+ message: "patch tool is disabled by the active harness"
1380
+ };
1381
+ const params = request.params;
1382
+ try {
1383
+ return await ctx.requestToolApproval(CodexAppServerSubjects.file_change_approval_request, {
1384
+ threadId: params.threadId,
1385
+ turnId: params.turnId,
1386
+ itemId: params.itemId,
1387
+ reason: params.reason ?? null,
1388
+ grantRoot: params.grantRoot ?? null,
1389
+ timestamp: Date.now()
1390
+ });
1391
+ } catch (error) {
1392
+ ctx.handleError(/* @__PURE__ */ new Error(`Tool approval request failed, make sure that there's a handler registered: ${String(error)}`), false);
1393
+ return {
1394
+ decision: "decline",
1395
+ message: String(error)
1396
+ };
1397
+ }
1398
+ }
1399
+ /**
1400
+ * Emit a synthetic begin+end lifecycle pair for a denied or errored dynamic tool call.
1401
+ *
1402
+ * Denied tool calls never produce `item/started`/`item/completed` notifications from
1403
+ * codex, so the agent's `dynamic_tool_call_begin`/`end` handlers never fire. Emitting
1404
+ * the pair here ensures downstream consumers (e.g. AgentSubjects.tool.use) observe
1405
+ * every tool call regardless of its approval outcome.
1406
+ * @param params - Tool call parameters from the server request
1407
+ * @param output - Serialised error output to surface as the tool result
1408
+ * @param emit - Scoped bus emit function
1409
+ */
1410
+ async function emitDeniedDynamicToolCallLifecycle(params, output, emit) {
1411
+ const now = Date.now();
1412
+ await emit(CodexAppServerSubjects.dynamic_tool_call_begin, {
1413
+ threadId: params.threadId,
1414
+ turnId: params.turnId,
1415
+ itemId: params.itemId,
1416
+ name: params.name,
1417
+ args: params.arguments,
1418
+ timestamp: now
1419
+ });
1420
+ await emit(CodexAppServerSubjects.dynamic_tool_call_end, {
1421
+ threadId: params.threadId,
1422
+ turnId: params.turnId,
1423
+ itemId: params.itemId,
1424
+ name: params.name,
1425
+ output,
1426
+ success: false,
1427
+ timestamp: now
1428
+ });
1429
+ }
1430
+ /**
1431
+ * Handle an `item/tool/call` server request (experimental API).
1432
+ *
1433
+ * Requests tool approval via the scoped bus before executing. If denied, returns error
1434
+ * content without executing. If approved, executes via bus and caches result under
1435
+ * `itemId` for lifecycle handler correlation.
1436
+ *
1437
+ * Denied calls emit a synthetic `dynamic_tool_call_begin`/`end` pair so that
1438
+ * downstream lifecycle consumers (e.g. `AgentSubjects.tool.use`) observe every
1439
+ * tool invocation regardless of approval outcome.
1440
+ * @param params - Parsed `item/tool/call` request parameters
1441
+ * @param ctx - Dynamic tool approval and execution context
1442
+ * @returns Content items to send back to codex
1443
+ */
1444
+ async function handleDynamicToolCallApprovalRequest(params, ctx) {
1445
+ let approval;
1446
+ try {
1447
+ approval = await ctx.requestToolApproval(CodexAppServerSubjects.dynamic_tool_call_approval_request, {
1448
+ threadId: params.threadId,
1449
+ turnId: params.turnId,
1450
+ itemId: params.itemId,
1451
+ name: params.name,
1452
+ args: params.arguments,
1453
+ timestamp: Date.now()
1454
+ });
1455
+ } catch (error) {
1456
+ const errorText = JSON.stringify({ error: String(error) });
1457
+ ctx.dynamicToolCallByItemId.set(params.itemId, {
1458
+ name: params.name,
1459
+ args: params.arguments,
1460
+ output: errorText,
1461
+ success: false
1462
+ });
1463
+ try {
1464
+ await emitDeniedDynamicToolCallLifecycle(params, errorText, ctx.emit);
1465
+ } catch {} finally {
1466
+ ctx.dynamicToolCallByItemId.delete(params.itemId);
1467
+ }
1468
+ return { content: [{
1469
+ type: "text",
1470
+ text: errorText
1471
+ }] };
1472
+ }
1473
+ if (approval?.decision !== "accept") {
1474
+ const message = approval?.decision === "decline" ? approval.message ?? "Tool call denied by approval handler" : "Invalid approval response from approval handler";
1475
+ const errorText = JSON.stringify({ error: message });
1476
+ ctx.dynamicToolCallByItemId.set(params.itemId, {
1477
+ name: params.name,
1478
+ args: params.arguments,
1479
+ output: errorText,
1480
+ success: false
1481
+ });
1482
+ try {
1483
+ await emitDeniedDynamicToolCallLifecycle(params, errorText, ctx.emit);
1484
+ } catch {} finally {
1485
+ ctx.dynamicToolCallByItemId.delete(params.itemId);
1486
+ }
1487
+ return { content: [{
1488
+ type: "text",
1489
+ text: errorText
1490
+ }] };
1491
+ }
1492
+ const response = await handleDynamicToolCall(params, {
1493
+ sessionId: ctx.sessionId,
1494
+ agentId: ctx.agentId,
1495
+ adapterId: ctx.adapterId,
1496
+ adapterName: ctx.adapterName,
1497
+ toolLedger: ctx.toolLedger,
1498
+ currentTurnNumber: ctx.currentTurnNumber
1499
+ });
1500
+ const outputText = response.content[0]?.text ?? "";
1501
+ const success = response.content.length > 0 && isDynamicToolCallSuccess(outputText);
1502
+ ctx.dynamicToolCallByItemId.set(params.itemId, {
1503
+ name: params.name,
1504
+ args: params.arguments,
1505
+ output: outputText,
1506
+ success
1507
+ });
1508
+ return response;
1509
+ }
1510
+
1511
+ //#endregion
1512
+ //#region src/connector/delta-handlers.ts
1513
+ /**
1514
+ * Delta event handlers for Codex App-Server notifications.
1515
+ *
1516
+ * These handle streaming content updates (agent messages, command output, reasoning, file changes).
1517
+ * @packageDocumentation
1518
+ */
1519
+ /**
1520
+ * Create a type guard validator for a Zod schema.
1521
+ * Logs validation errors when parsing fails.
1522
+ * @param schema - The Zod schema to validate against
1523
+ * @param name - Human-readable name for error logging
1524
+ * @returns A type guard function that validates and logs errors
1525
+ */
1526
+ function createValidator(schema, name) {
1527
+ return (params) => {
1528
+ const result = schema.safeParse(params);
1529
+ if (!result.success) console.warn(`[delta-handlers] Invalid ${name} notification:`, result.error.format());
1530
+ return result.success;
1531
+ };
1532
+ }
1533
+ const isValidAgentMessageDeltaNotification = createValidator(z.union([z.object({
1534
+ threadId: z.string(),
1535
+ turnId: z.string(),
1536
+ delta: z.string()
1537
+ }), z.object({ delta: z.string() })]), "agent message delta");
1538
+ const isValidCommandOutputDeltaNotification = createValidator(z.object({
1539
+ threadId: z.string(),
1540
+ turnId: z.string(),
1541
+ itemId: z.string(),
1542
+ delta: z.string()
1543
+ }), "command output delta");
1544
+ const isValidReasoningDeltaNotification = createValidator(z.union([z.object({
1545
+ threadId: z.string(),
1546
+ turnId: z.string(),
1547
+ delta: z.string()
1548
+ }), z.object({ delta: z.string() })]), "reasoning delta");
1549
+ const isValidFileChangeDeltaNotification = createValidator(z.object({
1550
+ threadId: z.string(),
1551
+ turnId: z.string(),
1552
+ itemId: z.string(),
1553
+ delta: z.string()
1554
+ }), "file change delta");
1555
+ /**
1556
+ * Handle agent message delta notification.
1557
+ * @param emit - Bus emit function
1558
+ * @param params - Delta notification params
1559
+ * @param onAccumulate - Callback to accumulate content
1560
+ */
1561
+ async function handleAgentMessageDelta(emit, params, onAccumulate) {
1562
+ if (!isValidAgentMessageDeltaNotification(params)) {
1563
+ console.warn("[delta-handlers] Skipping invalid agent message delta notification");
1564
+ return;
1565
+ }
1566
+ const delta = params.delta;
1567
+ onAccumulate(delta);
1568
+ if ("threadId" in params) await emit(CodexAppServerSubjects.agent_message_delta, {
1569
+ threadId: params.threadId,
1570
+ turnId: params.turnId,
1571
+ delta: params.delta,
1572
+ timestamp: Date.now()
1573
+ });
1574
+ else console.warn("[CodexAppServerConnector] Agent message delta received without threadId, accumulating but not emitting");
1575
+ }
1576
+ /**
1577
+ * Handle command output delta notification.
1578
+ * @param emit - Bus emit function
1579
+ * @param params - Delta notification params
1580
+ */
1581
+ async function handleCommandOutputDelta(emit, params) {
1582
+ if (!isValidCommandOutputDeltaNotification(params)) {
1583
+ console.warn("[delta-handlers] Skipping invalid command output delta notification");
1584
+ return;
1585
+ }
1586
+ await emit(CodexAppServerSubjects.exec_command_output_delta, {
1587
+ threadId: params.threadId,
1588
+ turnId: params.turnId,
1589
+ callId: params.itemId,
1590
+ stream: "stdout",
1591
+ chunk: params.delta,
1592
+ timestamp: Date.now()
1593
+ });
1594
+ }
1595
+ /**
1596
+ * Handle reasoning delta notification.
1597
+ * @param emit - Bus emit function
1598
+ * @param params - Delta notification params
1599
+ */
1600
+ async function handleReasoningDelta(emit, params) {
1601
+ if (!isValidReasoningDeltaNotification(params)) {
1602
+ console.warn("[delta-handlers] Skipping invalid reasoning delta notification");
1603
+ return;
1604
+ }
1605
+ if ("threadId" in params) await emit(CodexAppServerSubjects.reasoning_delta, {
1606
+ threadId: params.threadId,
1607
+ turnId: params.turnId,
1608
+ delta: params.delta,
1609
+ timestamp: Date.now()
1610
+ });
1611
+ }
1612
+ /**
1613
+ * Handle file change delta notification.
1614
+ * @param emit - Bus emit function
1615
+ * @param params - Delta notification params
1616
+ */
1617
+ async function handleFileChangeDelta(emit, params) {
1618
+ if (!isValidFileChangeDeltaNotification(params)) {
1619
+ console.warn("[delta-handlers] Skipping invalid file change delta notification");
1620
+ return;
1621
+ }
1622
+ await emit(CodexAppServerSubjects.file_change_output_delta, {
1623
+ threadId: params.threadId,
1624
+ turnId: params.turnId,
1625
+ itemId: params.itemId,
1626
+ delta: params.delta,
1627
+ timestamp: Date.now()
1628
+ });
1629
+ }
1630
+
1631
+ //#endregion
1632
+ //#region src/connector/lifecycle-handlers.ts
1633
+ /**
1634
+ * Extract thread ID from thread/started notification.
1635
+ * @param notification - Thread started notification
1636
+ * @returns Thread ID string
1637
+ */
1638
+ function extractThreadId(notification) {
1639
+ return notification.thread.id;
1640
+ }
1641
+ /**
1642
+ * Handle turn/started notification.
1643
+ * Acknowledges the message when the turn starts (server echoed it).
1644
+ * @param notification - Turn started notification
1645
+ * @param turn - Current turn instance
1646
+ * @param updateProcessingState - State update callback
1647
+ */
1648
+ async function handleTurnStarted(notification, turn, updateProcessingState) {
1649
+ if (!turn) {
1650
+ console.warn("[CodexAppServerConnector] Received turn/started but no current turn");
1651
+ return;
1652
+ }
1653
+ turn.markAcknowledged();
1654
+ await turn.handleTurnStarted(notification.turn.id);
1655
+ await updateProcessingState("processing_started");
1656
+ await updateProcessingState("turn_started");
1657
+ }
1658
+ /**
1659
+ * Handle item/started notification.
1660
+ * @param notification - Item started notification
1661
+ * @param turn - Current turn instance
1662
+ * @param emit - Bus emit function
1663
+ * @param commandCache - Cache for command execution metadata
1664
+ * @param dynamicToolCallCache - Cache for dynamic tool call metadata and results
1665
+ * @param updateProcessingState - State update callback
1666
+ * @param onCommandInfoReady - Optional callback invoked after a commandExecution entry
1667
+ * is written to `commandCache`. Used to unblock approval requests that arrived before
1668
+ * this `item/started` notification.
1669
+ */
1670
+ async function handleItemStarted(notification, turn, emit, commandCache, dynamicToolCallCache, updateProcessingState, onCommandInfoReady) {
1671
+ if (!turn) return;
1672
+ const item = notification.item;
1673
+ const itemId = "id" in item ? item.id : "";
1674
+ const itemType = item.type;
1675
+ await turn.handleItemStarted(itemId, itemType);
1676
+ await updateProcessingState("step_started");
1677
+ if (item.type === "commandExecution") {
1678
+ const info = {
1679
+ command: item.command,
1680
+ cwd: item.cwd
1681
+ };
1682
+ commandCache.set(item.id, info);
1683
+ onCommandInfoReady?.(item.id, info);
1684
+ await emit(CodexAppServerSubjects.exec_command_begin, {
1685
+ threadId: notification.threadId,
1686
+ turnId: notification.turnId,
1687
+ callId: item.id,
1688
+ command: [item.command],
1689
+ cwd: item.cwd,
1690
+ timestamp: Date.now()
1691
+ });
1692
+ }
1693
+ if (itemType === "dynamicToolCall") {
1694
+ const cached = dynamicToolCallCache.get(itemId);
1695
+ if (cached) await emit(CodexAppServerSubjects.dynamic_tool_call_begin, {
1696
+ threadId: notification.threadId,
1697
+ turnId: notification.turnId,
1698
+ itemId,
1699
+ name: cached.name,
1700
+ args: cached.args,
1701
+ timestamp: Date.now()
1702
+ });
1703
+ else console.warn(`[lifecycle-handlers] dynamicToolCall item ${itemId} started with no cached tool call data`);
1704
+ }
1705
+ }
1706
+ /**
1707
+ * Handle item/completed notification.
1708
+ * @param notification - Item completed notification
1709
+ * @param turn - Current turn instance
1710
+ * @param emit - Bus emit function
1711
+ * @param commandCache - Cache for command execution metadata
1712
+ * @param dynamicToolCallCache - Cache for dynamic tool call metadata and results
1713
+ * @param updateProcessingState - State update callback
1714
+ */
1715
+ async function handleItemCompleted(notification, turn, emit, commandCache, dynamicToolCallCache, updateProcessingState) {
1716
+ if (!turn) return;
1717
+ const item = notification.item;
1718
+ const itemId = "id" in item ? item.id : "";
1719
+ const itemType = item.type;
1720
+ await turn.handleItemCompleted(itemId);
1721
+ await updateProcessingState("step_finished");
1722
+ if (item.type === "commandExecution") {
1723
+ await emit(CodexAppServerSubjects.exec_command_end, {
1724
+ threadId: notification.threadId,
1725
+ turnId: notification.turnId,
1726
+ callId: item.id,
1727
+ exitCode: item.exitCode ?? 0,
1728
+ timestamp: Date.now()
1729
+ });
1730
+ commandCache.delete(item.id);
1731
+ return;
1732
+ }
1733
+ if (itemType === "dynamicToolCall") {
1734
+ const cached = dynamicToolCallCache.get(itemId);
1735
+ if (cached) {
1736
+ await emit(CodexAppServerSubjects.dynamic_tool_call_end, {
1737
+ threadId: notification.threadId,
1738
+ turnId: notification.turnId,
1739
+ itemId,
1740
+ name: cached.name,
1741
+ output: cached.output,
1742
+ success: cached.success,
1743
+ timestamp: Date.now()
1744
+ });
1745
+ dynamicToolCallCache.delete(itemId);
1746
+ } else console.warn(`[lifecycle-handlers] dynamicToolCall item ${itemId} completed with no cached tool call data`);
1747
+ return;
1748
+ }
1749
+ await emit(CodexAppServerSubjects.item_completed, {
1750
+ threadId: notification.threadId,
1751
+ turnId: notification.turnId,
1752
+ itemId,
1753
+ itemType,
1754
+ timestamp: Date.now()
1755
+ });
1756
+ }
1757
+ /**
1758
+ * Handle token usage updated notification.
1759
+ * @param notification - Token usage notification
1760
+ * @param thread - Current thread instance
1761
+ */
1762
+ async function handleTokenUsageUpdated(notification, thread) {
1763
+ if (!thread) return;
1764
+ await thread.handleTokenUsageUpdated(notification.tokenUsage.last.inputTokens, notification.tokenUsage.last.cachedInputTokens, notification.tokenUsage.last.outputTokens, notification.tokenUsage.last.reasoningOutputTokens, notification.tokenUsage.last.totalTokens, notification.tokenUsage.modelContextWindow ?? void 0);
1765
+ }
1766
+
1767
+ //#endregion
1768
+ //#region src/connector/client-handlers.ts
1769
+ /**
1770
+ * Register JSON-RPC notification handlers for the connector client instance.
1771
+ * @param options - Callbacks and state accessors used to bridge notifications into connector state
1772
+ */
1773
+ function registerNotificationHandlers(options) {
1774
+ const { client, enqueueNotification, onThreadStarted, consumeTurnNumber, getCurrentTurn, emit, commandExecutionByItemId, dynamicToolCallByItemId, updateProcessingState, appendAgentMessageDelta, onTurnCompleted, getThread, handleAsyncError, onCommandInfoReady } = options;
1775
+ client.onNotification("thread/started", (_method, params) => {
1776
+ enqueueNotification(() => onThreadStarted(params));
1777
+ });
1778
+ client.onNotification("turn/started", (_method, params) => {
1779
+ consumeTurnNumber();
1780
+ enqueueNotification(() => handleTurnStarted(params, getCurrentTurn(), updateProcessingState));
1781
+ });
1782
+ client.onNotification("item/started", (_method, params) => {
1783
+ enqueueNotification(() => handleItemStarted(params, getCurrentTurn(), emit, commandExecutionByItemId, dynamicToolCallByItemId, updateProcessingState, onCommandInfoReady));
1784
+ });
1785
+ client.onNotification("item/completed", (_method, params) => {
1786
+ enqueueNotification(() => handleItemCompleted(params, getCurrentTurn(), emit, commandExecutionByItemId, dynamicToolCallByItemId, updateProcessingState));
1787
+ });
1788
+ client.onNotification("item/agentMessage/delta", (_method, params) => {
1789
+ handleAgentMessageDelta(emit, params, appendAgentMessageDelta).catch(handleAsyncError);
1790
+ });
1791
+ client.onNotification("item/commandExecution/outputDelta", (_method, params) => {
1792
+ handleCommandOutputDelta(emit, params).catch(handleAsyncError);
1793
+ });
1794
+ client.onNotification("item/reasoning/textDelta", (_method, params) => {
1795
+ handleReasoningDelta(emit, params).catch(handleAsyncError);
1796
+ });
1797
+ client.onNotification("item/fileChange/outputDelta", (_method, params) => {
1798
+ handleFileChangeDelta(emit, params).catch(handleAsyncError);
1799
+ });
1800
+ client.onNotification("turn/completed", (_method, params) => {
1801
+ enqueueNotification(() => onTurnCompleted(params));
1802
+ });
1803
+ client.onNotification("thread/tokenUsage/updated", (_method, params) => {
1804
+ Promise.resolve(handleTokenUsageUpdated(params, getThread())).catch(handleAsyncError);
1805
+ });
1806
+ }
1807
+ /**
1808
+ * Register the JSON-RPC server-request handler for approval and dynamic tool call flows.
1809
+ * @param options - Callbacks and state accessors needed to answer server requests
1810
+ */
1811
+ function registerServerRequestHandler(options) {
1812
+ const ctx = {
1813
+ agentId: options.agentId,
1814
+ cwd: options.cwd,
1815
+ commandExecutionByItemId: options.commandExecutionByItemId,
1816
+ requestToolApproval: options.requestToolApproval,
1817
+ handleError: options.handleError,
1818
+ getDisabledNativeTools: options.getDisabledNativeTools,
1819
+ waitForCommandInfo: options.waitForCommandInfo
1820
+ };
1821
+ options.client.onServerRequest(async (request) => {
1822
+ const asMethodRequest = request;
1823
+ if (isDynamicToolCallRequest(asMethodRequest)) return options.handleDynamicToolCallRequest(asMethodRequest.params);
1824
+ if (request.method === "item/commandExecution/requestApproval") return handleCommandApprovalRequest(request, ctx);
1825
+ if (request.method === "item/fileChange/requestApproval") return handleFileChangeApprovalRequest(request, ctx);
1826
+ throw new Error(`Unknown server request: ${request.method}`);
1827
+ });
1828
+ }
1829
+
1830
+ //#endregion
1831
+ //#region ../../../packages/subprocess/src/json-rpc-client.ts
1832
+ /** Default request timeout for JSON-RPC calls. */
1833
+ const DEFAULT_JSON_RPC_REQUEST_TIMEOUT_MS = 6e4;
1834
+ /**
1835
+ * Create a JSON-RPC 2.0 client on top of a JSONL transport.
1836
+ *
1837
+ * Message dispatch rules (JSON-RPC 2.0):
1838
+ * - `id` + `result` → correlated response (resolves pending promise)
1839
+ * - `id` + `error` → correlated error response (rejects pending promise)
1840
+ * - `id` + `method` → server-initiated request (call handlers, send response)
1841
+ * - `method`, no `id` → notification (call method-specific handler)
1842
+ * @param transport - JSONL transport to send/receive messages on.
1843
+ * @returns JSON-RPC 2.0 client interface.
1844
+ */
1845
+ function createJsonRpcClient$1(transport) {
1846
+ let nextId = 1;
1847
+ let closed = false;
1848
+ const pendingRequests = /* @__PURE__ */ new Map();
1849
+ const notificationHandlers = /* @__PURE__ */ new Map();
1850
+ const serverRequestHandlers = /* @__PURE__ */ new Set();
1851
+ /**
1852
+ * Generate a unique, monotonically increasing request ID.
1853
+ * @returns Next available request ID.
1854
+ */
1855
+ function generateRequestId() {
1856
+ return nextId++;
1857
+ }
1858
+ /**
1859
+ * Reject all pending requests with the given error and clear the map.
1860
+ * @param error - Error to reject all pending requests with.
1861
+ */
1862
+ function rejectAllPending(error) {
1863
+ for (const [, pending] of pendingRequests) {
1864
+ if (pending.timeoutId !== null) clearTimeout(pending.timeoutId);
1865
+ pending.reject(error);
1866
+ }
1867
+ pendingRequests.clear();
1868
+ }
1869
+ /**
1870
+ * Resolve or reject a pending request exactly once and clear its timer.
1871
+ * @param id - Request ID to settle.
1872
+ * @param settle - Callback receiving the pending request callbacks.
1873
+ */
1874
+ function settlePending(id, settle) {
1875
+ const pending = pendingRequests.get(id);
1876
+ if (!pending) return;
1877
+ if (pending.timeoutId !== null) clearTimeout(pending.timeoutId);
1878
+ pendingRequests.delete(id);
1879
+ settle(pending);
1880
+ }
1881
+ /**
1882
+ * Dispatch an incoming message according to JSON-RPC 2.0 semantics.
1883
+ * @param message - Raw parsed message from the transport.
1884
+ */
1885
+ function handleMessage(message) {
1886
+ if (typeof message !== "object" || message === null) return;
1887
+ const msg = message;
1888
+ const id = "id" in msg ? msg["id"] : void 0;
1889
+ if (id !== void 0 && "result" in msg && !("method" in msg)) {
1890
+ settlePending(id, (pending) => {
1891
+ pending.resolve(msg["result"]);
1892
+ });
1893
+ return;
1894
+ }
1895
+ if (id !== void 0 && "error" in msg && !("method" in msg)) {
1896
+ settlePending(id, (pending) => {
1897
+ const rpcError = msg["error"];
1898
+ pending.reject(/* @__PURE__ */ new Error(`JSON-RPC error ${rpcError.code}: ${rpcError.message}`));
1899
+ });
1900
+ return;
1901
+ }
1902
+ if (id !== void 0 && "method" in msg) {
1903
+ handleServerRequest(message, id, msg["method"]);
1904
+ return;
1905
+ }
1906
+ if ("method" in msg && !("id" in msg)) {
1907
+ const method = msg["method"];
1908
+ const handlers = notificationHandlers.get(method);
1909
+ if (handlers) for (const handler of handlers) try {
1910
+ handler(method, msg["params"]);
1911
+ } catch {}
1912
+ return;
1913
+ }
1914
+ }
1915
+ /**
1916
+ * Dispatch a server-initiated request to registered handlers.
1917
+ * @param message - Raw JSON-RPC request message.
1918
+ * @param requestId - JSON-RPC request ID to echo in the response.
1919
+ * @param method - JSON-RPC method name.
1920
+ */
1921
+ async function handleServerRequest(message, requestId, method) {
1922
+ const handlers = [...serverRequestHandlers];
1923
+ if (handlers.length === 0) {
1924
+ transport.send({
1925
+ jsonrpc: "2.0",
1926
+ id: requestId,
1927
+ error: {
1928
+ code: -32601,
1929
+ message: `No handler registered for server request: ${method}`
1930
+ }
1931
+ });
1932
+ return;
1933
+ }
1934
+ const results = await Promise.allSettled(handlers.map((handler) => Promise.resolve().then(() => handler(message))));
1935
+ if (closed) return;
1936
+ const success = results.find((result) => result.status === "fulfilled");
1937
+ if (success) {
1938
+ transport.send({
1939
+ jsonrpc: "2.0",
1940
+ id: requestId,
1941
+ result: success.value
1942
+ });
1943
+ return;
1944
+ }
1945
+ const reason = results[0]?.reason;
1946
+ const messageText = reason instanceof Error ? reason.message : String(reason ?? "handler failed");
1947
+ transport.send({
1948
+ jsonrpc: "2.0",
1949
+ id: requestId,
1950
+ error: {
1951
+ code: -32603,
1952
+ message: messageText
1953
+ }
1954
+ });
1955
+ }
1956
+ const unsubMessage = transport.onMessage(handleMessage);
1957
+ const unsubError = transport.onError((error) => {
1958
+ rejectAllPending(error);
1959
+ });
1960
+ return {
1961
+ request(method, params, timeoutMs = DEFAULT_JSON_RPC_REQUEST_TIMEOUT_MS) {
1962
+ if (closed) return Promise.reject(/* @__PURE__ */ new Error("JSON-RPC client is closed"));
1963
+ return new Promise((resolve, reject) => {
1964
+ const id = generateRequestId();
1965
+ const requestMessage = {
1966
+ jsonrpc: "2.0",
1967
+ id,
1968
+ method,
1969
+ params
1970
+ };
1971
+ const timeoutId = timeoutMs === 0 ? null : setTimeout(() => {
1972
+ settlePending(id, (pending) => {
1973
+ pending.reject(/* @__PURE__ */ new Error(`JSON-RPC request timed out after ${timeoutMs}ms: ${method}`));
1974
+ });
1975
+ }, timeoutMs);
1976
+ pendingRequests.set(id, {
1977
+ resolve,
1978
+ reject,
1979
+ timeoutId
1980
+ });
1981
+ try {
1982
+ transport.send(requestMessage);
1983
+ } catch (error) {
1984
+ settlePending(id, (pending) => {
1985
+ pending.reject(error instanceof Error ? error : new Error(String(error)));
1986
+ });
1987
+ }
1988
+ });
1989
+ },
1990
+ notification(method, params) {
1991
+ if (closed) return;
1992
+ const msg = {
1993
+ jsonrpc: "2.0",
1994
+ method,
1995
+ params
1996
+ };
1997
+ transport.send(msg);
1998
+ },
1999
+ onNotification(method, handler) {
2000
+ let handlers = notificationHandlers.get(method);
2001
+ if (!handlers) {
2002
+ handlers = /* @__PURE__ */ new Set();
2003
+ notificationHandlers.set(method, handlers);
2004
+ }
2005
+ handlers.add(handler);
2006
+ return () => {
2007
+ handlers.delete(handler);
2008
+ if (handlers.size === 0) notificationHandlers.delete(method);
2009
+ };
2010
+ },
2011
+ onServerRequest(handler) {
2012
+ serverRequestHandlers.add(handler);
2013
+ return () => {
2014
+ serverRequestHandlers.delete(handler);
2015
+ };
2016
+ },
2017
+ close() {
2018
+ if (closed) return;
2019
+ closed = true;
2020
+ unsubMessage();
2021
+ unsubError();
2022
+ rejectAllPending(/* @__PURE__ */ new Error("JSON-RPC client closed"));
2023
+ notificationHandlers.clear();
2024
+ serverRequestHandlers.clear();
2025
+ transport.close();
2026
+ }
2027
+ };
2028
+ }
2029
+
2030
+ //#endregion
2031
+ //#region src/utils/jsonRpcClient.ts
2032
+ /**
2033
+ * Creates a JSON-RPC 2.0 client wrapping a stdio transport
2034
+ * @param transport - Stdio transport for sending/receiving messages
2035
+ * @returns JSON-RPC client interface
2036
+ * @example
2037
+ * ```ts
2038
+ * const transport = createStdioTransport(cwd, env);
2039
+ * const client = createJsonRpcClient(transport);
2040
+ *
2041
+ * // Send request
2042
+ * const result = await client.request('thread/start', { ... });
2043
+ *
2044
+ * // Send notification
2045
+ * client.notification('initialized', {});
2046
+ *
2047
+ * // Handle notifications
2048
+ * client.onNotification('turn/started', (method, params) => {
2049
+ * console.log('Turn started:', params);
2050
+ * });
2051
+ *
2052
+ * // Handle server requests (approvals)
2053
+ * client.onServerRequest(async (request) => {
2054
+ * return { decision: 'accept' };
2055
+ * });
2056
+ * ```
2057
+ */
2058
+ function createJsonRpcClient(transport) {
2059
+ const generic = createJsonRpcClient$1({
2060
+ send: (msg) => transport.send(msg),
2061
+ close: () => transport.close(),
2062
+ onMessage: (listener) => {
2063
+ transport.onMessage((msg) => listener(msg));
2064
+ return () => {};
2065
+ },
2066
+ onError: (listener) => {
2067
+ transport.onError((err) => listener(err));
2068
+ return () => {};
2069
+ },
2070
+ get process() {}
2071
+ });
2072
+ let serverRequestUnsubscribe;
2073
+ return {
2074
+ /**
2075
+ * Send a JSON-RPC request and wait for response
2076
+ * @param method - Method name to call
2077
+ * @param params - Method parameters
2078
+ * @returns Promise resolving to response result
2079
+ */
2080
+ request(method, params) {
2081
+ return generic.request(method, params);
2082
+ },
2083
+ /**
2084
+ * Send a JSON-RPC notification (no response expected)
2085
+ * @param method - Notification method name
2086
+ * @param params - Notification parameters
2087
+ */
2088
+ notification(method, params) {
2089
+ generic.notification(method, params);
2090
+ },
2091
+ /**
2092
+ * Register a handler for specific notification type
2093
+ * @param method - Notification method name to handle
2094
+ * @param handler - Function to handle notifications
2095
+ */
2096
+ onNotification(method, handler) {
2097
+ generic.onNotification(method, handler);
2098
+ },
2099
+ /**
2100
+ * Register a handler for server requests (approvals)
2101
+ * @param handler - Function to handle server requests
2102
+ */
2103
+ onServerRequest(handler) {
2104
+ serverRequestUnsubscribe?.();
2105
+ serverRequestUnsubscribe = generic.onServerRequest((req) => handler(req));
2106
+ },
2107
+ /**
2108
+ * Close the client and cleanup resources
2109
+ */
2110
+ close() {
2111
+ serverRequestUnsubscribe?.();
2112
+ serverRequestUnsubscribe = void 0;
2113
+ generic.close();
2114
+ }
2115
+ };
2116
+ }
2117
+
2118
+ //#endregion
2119
+ //#region src/utils/createStdioTransport.ts
2120
+ /**
2121
+ * Resolve the command used to start the Codex app server.
2122
+ * @param binaryPath - Optional resolved managed binary path.
2123
+ * @returns Absolute managed binary path, or the PATH-resolved `codex` command.
2124
+ */
2125
+ function resolveSpawnCommand(binaryPath) {
2126
+ if (binaryPath === void 0) return "codex";
2127
+ if (binaryPath.trim() === "" || !path.isAbsolute(binaryPath)) throw new Error("binaryPath must be a non-empty absolute path when provided");
2128
+ return binaryPath;
2129
+ }
2130
+ /**
2131
+ * Creates a stdio transport for communicating with codex app-server subprocess
2132
+ * @param cwd - Working directory for the subprocess
2133
+ * @param env - Environment variables to pass to the subprocess (undefined values are filtered out)
2134
+ * @param binaryPath - Absolute path to the codex binary; when omitted, `'codex'` is resolved from PATH
2135
+ * @returns Transport interface for sending/receiving messages
2136
+ * @throws Error if subprocess fails to spawn
2137
+ * @example
2138
+ * ```ts
2139
+ * const transport = createStdioTransport('/path/to/project', { PATH: process.env.PATH });
2140
+ * transport.onMessage((message) => console.log('Received:', message));
2141
+ * transport.send({ jsonrpc: '2.0', id: 1, method: 'initialize', params: {} });
2142
+ * ```
2143
+ */
2144
+ function createStdioTransport(cwd, env, binaryPath) {
2145
+ const subprocess = spawn(resolveSpawnCommand(binaryPath), ["app-server"], {
2146
+ cwd,
2147
+ env: {
2148
+ ...Object.fromEntries(Object.entries(env).filter(([, value]) => value !== void 0)),
2149
+ RUST_LOG: "debug"
2150
+ },
2151
+ stdio: [
2152
+ "pipe",
2153
+ "pipe",
2154
+ "pipe"
2155
+ ]
2156
+ });
2157
+ let messageCallback = null;
2158
+ let errorCallback = null;
2159
+ let buffer = "";
2160
+ /**
2161
+ * Parse JSONL from stdout and dispatch messages
2162
+ */
2163
+ subprocess.stdout.on("data", (chunk) => {
2164
+ buffer += chunk.toString("utf-8");
2165
+ const lines = buffer.split("\n");
2166
+ buffer = lines.pop() || "";
2167
+ for (const line of lines) if (line.trim()) try {
2168
+ const message = JSON.parse(line);
2169
+ messageCallback?.(message);
2170
+ } catch (err) {
2171
+ const error = err instanceof Error ? err : /* @__PURE__ */ new Error(`Failed to parse JSONL: ${line}`);
2172
+ errorCallback?.(error);
2173
+ }
2174
+ });
2175
+ subprocess.stderr.on("data", (chunk) => {
2176
+ console.warn("[codex app-server]", chunk.toString("utf-8"));
2177
+ });
2178
+ /**
2179
+ * Handle subprocess errors (e.g., codex not found)
2180
+ */
2181
+ subprocess.on("error", (error) => {
2182
+ errorCallback?.(error);
2183
+ });
2184
+ /**
2185
+ * Handle subprocess exit
2186
+ */
2187
+ subprocess.on("exit", (code) => {
2188
+ if (code !== 0) {
2189
+ const error = /* @__PURE__ */ new Error(`codex app-server exited with code ${code ?? "unknown"}`);
2190
+ errorCallback?.(error);
2191
+ }
2192
+ });
2193
+ return {
2194
+ /**
2195
+ * Send a JSON-RPC message to the subprocess via stdin
2196
+ * @param message - JSON-RPC message to send (request, response, or notification)
2197
+ */
2198
+ send(message) {
2199
+ subprocess.stdin.write(JSON.stringify(message) + "\n");
2200
+ },
2201
+ /**
2202
+ * Close the subprocess and cleanup resources
2203
+ */
2204
+ close() {
2205
+ subprocess.kill();
2206
+ },
2207
+ /**
2208
+ * Register a callback for incoming messages
2209
+ * @param callback - Function to handle incoming messages
2210
+ */
2211
+ onMessage(callback) {
2212
+ messageCallback = callback;
2213
+ },
2214
+ /**
2215
+ * Register a callback for errors
2216
+ * @param callback - Function to handle errors
2217
+ */
2218
+ onError(callback) {
2219
+ errorCallback = callback;
2220
+ }
2221
+ };
2222
+ }
2223
+
2224
+ //#endregion
2225
+ //#region src/connector/types.ts
2226
+ /**
2227
+ * Client info for JSON-RPC initialize handshake.
2228
+ */
2229
+ const CLIENT_INFO = {
2230
+ name: "makaio-codex-app-server",
2231
+ title: "Makaio Codex App-Server",
2232
+ version: "0.1.0"
2233
+ };
2234
+
2235
+ //#endregion
2236
+ //#region src/connector/connection-manager.ts
2237
+ /**
2238
+ * Connection lifecycle management for the Codex App-Server connector.
2239
+ *
2240
+ * Handles JSON-RPC client creation, subprocess spawning, credential resolution,
2241
+ * the ACP `initialize` handshake, and error-path teardown. The connector holds all
2242
+ * mutable state; this module mutates it only through the typed accessors in
2243
+ * {@link ConnectionManagerContext}.
2244
+ * @packageDocumentation
2245
+ */
2246
+ /**
2247
+ * Create and attach the JSON-RPC client to the connector.
2248
+ *
2249
+ * If the context already has a client (from a test injection or a prior call),
2250
+ * only registers handlers and returns immediately. Otherwise, resolves credentials
2251
+ * and the managed binary path, spawns the subprocess via stdio transport, and
2252
+ * registers the error callback.
2253
+ * @param ctx - Connection manager context
2254
+ */
2255
+ async function createClient(ctx) {
2256
+ if (ctx.getJsonRpcClient()) {
2257
+ ctx.registerClientHandlers();
2258
+ return;
2259
+ }
2260
+ const injected = ctx.getInjectedJsonRpcClient();
2261
+ if (injected) {
2262
+ ctx.setJsonRpcClient(injected);
2263
+ ctx.registerClientHandlers();
2264
+ return;
2265
+ }
2266
+ const { resolvedBinary, spawnEnv } = await resolveSessionEnvironment({
2267
+ bus: ctx.bus,
2268
+ providerContext: ctx.providerContext,
2269
+ clientId: ctx.clientId ?? "codex",
2270
+ baseEnv: ctx.env
2271
+ });
2272
+ const transport = ctx.getInjectedTransport() ?? createStdioTransport(ctx.cwd, spawnEnv, resolvedBinary?.binaryPath ?? void 0);
2273
+ if (!ctx.getInjectedTransport()) ctx.setOwnedTransport(transport);
2274
+ ctx.setJsonRpcClient(createJsonRpcClient(transport));
2275
+ transport.onError((error) => ctx.handleError(error, true));
2276
+ ctx.registerClientHandlers();
2277
+ }
2278
+ /**
2279
+ * Tear down the active JSON-RPC client and owned transport after a connection failure.
2280
+ *
2281
+ * Resets connection and handler-registration flags so the next `initializeConnection`
2282
+ * call starts fresh. If an injected test client is present it is restored (not closed)
2283
+ * so the test can continue using the same mock instance.
2284
+ * @param ctx - Connection manager context
2285
+ */
2286
+ function resetClient(ctx) {
2287
+ ctx.setIsConnected(false);
2288
+ ctx.setClientHandlersRegistered(false);
2289
+ ctx.setDisabledNativeTools(/* @__PURE__ */ new Set());
2290
+ const transport = ctx.getOwnedTransport();
2291
+ ctx.setOwnedTransport(void 0);
2292
+ const injected = ctx.getInjectedJsonRpcClient();
2293
+ if (!injected) {
2294
+ ctx.getJsonRpcClient()?.close();
2295
+ if (!ctx.getJsonRpcClient() && transport) transport.close();
2296
+ ctx.setJsonRpcClient(void 0);
2297
+ return;
2298
+ }
2299
+ ctx.setJsonRpcClient(injected);
2300
+ }
2301
+ /**
2302
+ * Perform the full ACP `initialize` handshake in a single flight.
2303
+ *
2304
+ * Spawns the subprocess (or reuses an injected client), resolves disabled native
2305
+ * tools via the global harness bus, sends `initialize`, and fires `initialized`.
2306
+ * On any error the client is torn down via {@link resetClient} before re-throwing
2307
+ * so the next call starts from a clean state.
2308
+ * @param ctx - Connection manager context
2309
+ */
2310
+ async function performConnectionInit(ctx) {
2311
+ try {
2312
+ await createClient(ctx);
2313
+ ctx.setDisabledNativeTools(new Set(await resolveDisabledNativeTools(MakaioBus, ctx.adapterName, ctx.harnessId, ctx.clientId)));
2314
+ const initParams = {
2315
+ clientInfo: CLIENT_INFO,
2316
+ capabilities: { experimentalApi: true }
2317
+ };
2318
+ const client = ctx.getJsonRpcClient();
2319
+ if (!client) throw new Error("JSON-RPC client not initialized");
2320
+ await client.request("initialize", initParams);
2321
+ client.notification("initialized", {});
2322
+ ctx.setIsConnected(true);
2323
+ } catch (error) {
2324
+ resetClient(ctx);
2325
+ throw error;
2326
+ }
2327
+ }
2328
+ /**
2329
+ * Ensure the connector is connected, using single-flight deduplication.
2330
+ *
2331
+ * Concurrent callers share the same in-flight promise so the subprocess is spawned
2332
+ * exactly once. Resolves immediately when already connected.
2333
+ * @param ctx - Connection manager context
2334
+ * @param inflight - Mutable holder for the in-flight promise; updated by this function
2335
+ * @returns Promise that resolves when the connection is established
2336
+ */
2337
+ function initializeConnection(ctx, inflight) {
2338
+ if (ctx.getIsConnected()) return Promise.resolve();
2339
+ inflight.promise ??= performConnectionInit(ctx).finally(() => {
2340
+ inflight.promise = void 0;
2341
+ });
2342
+ return inflight.promise;
2343
+ }
2344
+
2345
+ //#endregion
2346
+ //#region src/thread.ts
2347
+ /**
2348
+ * Thread lifecycle management for Codex App-Server.
2349
+ *
2350
+ * Tracks thread state and emits namespace bus events:
2351
+ * - thread_started when thread/started notification received
2352
+ * - thread_completed when thread is archived or completed
2353
+ * - token_usage when thread/tokenUsage/updated received
2354
+ *
2355
+ * Thread is created by connector when thread/start response is received,
2356
+ * and updated throughout the conversation via notifications.
2357
+ */
2358
+ var CodexAppServerThread = class {
2359
+ bus;
2360
+ adapterId;
2361
+ agentId;
2362
+ _threadId;
2363
+ _state = "active";
2364
+ _config = {};
2365
+ _tokenUsage;
2366
+ constructor(config) {
2367
+ this.bus = config.bus;
2368
+ this.adapterId = config.adapterId;
2369
+ this.agentId = config.agentId;
2370
+ }
2371
+ /**
2372
+ * Get the thread ID.
2373
+ * @returns Thread ID or undefined if not yet started
2374
+ */
2375
+ get threadId() {
2376
+ return this._threadId;
2377
+ }
2378
+ /**
2379
+ * Get the current thread state.
2380
+ * @returns Current thread state
2381
+ */
2382
+ get state() {
2383
+ return this._state;
2384
+ }
2385
+ /**
2386
+ * Get the thread configuration.
2387
+ * @returns Thread configuration overrides
2388
+ */
2389
+ get config() {
2390
+ return { ...this._config };
2391
+ }
2392
+ /**
2393
+ * Get the current token usage.
2394
+ * @returns Token usage or undefined if not yet received
2395
+ */
2396
+ get tokenUsage() {
2397
+ return this._tokenUsage;
2398
+ }
2399
+ /**
2400
+ * Check if thread is active.
2401
+ * @returns True if thread is active
2402
+ */
2403
+ isActive() {
2404
+ return this._state === "active";
2405
+ }
2406
+ /**
2407
+ * Check if thread is completed.
2408
+ * @returns True if thread is completed
2409
+ */
2410
+ isCompleted() {
2411
+ return this._state === "completed";
2412
+ }
2413
+ /**
2414
+ * Handle thread/started notification.
2415
+ * Sets threadId and transitions to active state.
2416
+ * @param threadId - Thread ID from thread/started notification
2417
+ */
2418
+ async handleThreadStarted(threadId) {
2419
+ this._threadId = threadId;
2420
+ this._state = "active";
2421
+ await this.bus.emit(CodexAppServerSubjects.thread_started, {
2422
+ agentId: this.agentId,
2423
+ threadId: this._threadId,
2424
+ timestamp: Date.now()
2425
+ });
2426
+ }
2427
+ /**
2428
+ * Handle thread completion or archival.
2429
+ * Transitions state and emits completion event.
2430
+ * @param newState - New thread state (completed or archived)
2431
+ */
2432
+ async handleThreadCompleted(newState = "completed") {
2433
+ if (!this._threadId) throw new Error("Cannot complete thread: threadId not set");
2434
+ this._state = newState;
2435
+ await this.bus.emit(CodexAppServerSubjects.thread_completed, {
2436
+ agentId: this.agentId,
2437
+ threadId: this._threadId,
2438
+ timestamp: Date.now()
2439
+ });
2440
+ }
2441
+ /**
2442
+ * Handle thread/tokenUsage/updated notification.
2443
+ * Updates token usage tracking.
2444
+ * @param promptTokens - Prompt/input tokens used
2445
+ * @param inputCachedTokens - Cached input tokens reused for this update
2446
+ * @param completionTokens - Completion/output tokens used
2447
+ * @param reasoningTokens - Reasoning output tokens used (subset of completionTokens)
2448
+ * @param totalTokens - Full protocol-reported total for this update
2449
+ * @param modelContextWindow - Optional model context window size
2450
+ */
2451
+ async handleTokenUsageUpdated(promptTokens, inputCachedTokens, completionTokens, reasoningTokens, totalTokens, modelContextWindow) {
2452
+ this._tokenUsage = {
2453
+ promptTokens,
2454
+ inputCachedTokens,
2455
+ completionTokens,
2456
+ reasoningTokens,
2457
+ totalTokens,
2458
+ modelContextWindow
2459
+ };
2460
+ await this.bus.emit(CodexAppServerSubjects.token_usage, {
2461
+ agentId: this.agentId,
2462
+ threadId: this._threadId ?? "",
2463
+ promptTokens,
2464
+ inputCachedTokens,
2465
+ completionTokens,
2466
+ reasoningTokens,
2467
+ totalTokens,
2468
+ modelContextWindow,
2469
+ timestamp: Date.now()
2470
+ });
2471
+ }
2472
+ /**
2473
+ * Update thread configuration.
2474
+ * Used for per-turn overrides.
2475
+ * @param config - Configuration updates
2476
+ */
2477
+ updateConfig(config) {
2478
+ this._config = {
2479
+ ...this._config,
2480
+ ...config
2481
+ };
2482
+ }
2483
+ };
2484
+
2485
+ //#endregion
2486
+ //#region src/turn.ts
2487
+ /**
2488
+ * Codex App-Server Turn State Machine
2489
+ *
2490
+ * Manages turn lifecycle for the codex app-server protocol.
2491
+ * A turn is a single user input → agent response cycle within a thread.
2492
+ *
2493
+ * State machine follows the app-server protocol:
2494
+ * - idle → active (turn/start request sent)
2495
+ * - active → processing_started (turn/started notification received)
2496
+ * - processing_started → turn_started (first item/started notification)
2497
+ * - turn_started → step_started (command/file item starts)
2498
+ * - step_started → step_finished (item completes)
2499
+ * - step_finished → turn_finished (turn/completed received)
2500
+ * - turn_finished → idle (cleanup, ready for next turn)
2501
+ *
2502
+ * Immediate mode: active → interrupted → idle (cancelled, new turn with merged content)
2503
+ */
2504
+ /**
2505
+ * Turn state machine for Codex App-Server.
2506
+ *
2507
+ * Tracks state transitions based on app-server notifications:
2508
+ * - turn/started → processing_started state
2509
+ * - item/started → step_started state
2510
+ * - item/completed → step_finished state
2511
+ * - turn/completed → turn_finished state
2512
+ *
2513
+ * Similar to codex-mcp but adapted for app-server protocol:
2514
+ * - Uses turnId instead of taskId
2515
+ * - Item-based events instead of MCP event types
2516
+ * - Supports interrupt via turn/interrupt method
2517
+ */
2518
+ var CodexAppServerTurn = class extends BaseConnectorTurn {
2519
+ connectorBus;
2520
+ agentId;
2521
+ threadId;
2522
+ activeMessageHandle;
2523
+ turnId;
2524
+ currentItemId;
2525
+ currentItemType;
2526
+ interrupted = false;
2527
+ constructor(bus, adapterId, adapterName, agentId, threadId, messageHandle) {
2528
+ super(bus, adapterId, adapterName, "idle");
2529
+ this.connectorBus = bus;
2530
+ this.agentId = agentId;
2531
+ this.threadId = threadId;
2532
+ this.activeMessageHandle = messageHandle;
2533
+ }
2534
+ /**
2535
+ * Get the turn ID.
2536
+ * @returns Turn ID or undefined if not yet received from turn/started
2537
+ */
2538
+ getTurnId() {
2539
+ return this.turnId;
2540
+ }
2541
+ /**
2542
+ * Set the turn ID from turn/started notification.
2543
+ * @param turnId - Turn ID from the app-server
2544
+ */
2545
+ setTurnId(turnId) {
2546
+ this.turnId = turnId;
2547
+ }
2548
+ /**
2549
+ * Get the current item ID being processed.
2550
+ * @returns Current item ID or undefined
2551
+ */
2552
+ getCurrentItemId() {
2553
+ return this.currentItemId;
2554
+ }
2555
+ /**
2556
+ * Emit state change using typed namespace subjects.
2557
+ * @param oldState - The previous turn state
2558
+ * @param newState - The new turn state
2559
+ */
2560
+ async emitStateChange(oldState, newState) {
2561
+ const payload = {
2562
+ adapterId: this.adapterId,
2563
+ agentId: this.agentId,
2564
+ oldState,
2565
+ newState,
2566
+ timestamp: Date.now()
2567
+ };
2568
+ await this.connectorBus.emit(CodexAppServerSubjects.turn_state_changed, payload);
2569
+ switch (newState) {
2570
+ case "turn_started":
2571
+ await this.connectorBus.emit(CodexAppServerSubjects.turn_started, {
2572
+ agentId: this.agentId,
2573
+ threadId: this.threadId,
2574
+ turnId: this.turnId ?? "",
2575
+ timestamp: Date.now()
2576
+ });
2577
+ break;
2578
+ case "step_started":
2579
+ await this.connectorBus.emit(CodexAppServerSubjects.turn_step_started, {
2580
+ agentId: this.agentId,
2581
+ threadId: this.threadId,
2582
+ turnId: this.turnId ?? "",
2583
+ itemId: this.currentItemId ?? "",
2584
+ timestamp: Date.now()
2585
+ });
2586
+ break;
2587
+ case "step_finished":
2588
+ await this.connectorBus.emit(CodexAppServerSubjects.turn_step_finished, {
2589
+ agentId: this.agentId,
2590
+ threadId: this.threadId,
2591
+ turnId: this.turnId ?? "",
2592
+ itemId: this.currentItemId ?? "",
2593
+ timestamp: Date.now()
2594
+ });
2595
+ break;
2596
+ case "turn_finished":
2597
+ await this.connectorBus.emit(CodexAppServerSubjects.turn_completed, {
2598
+ agentId: this.agentId,
2599
+ threadId: this.threadId,
2600
+ turnId: this.turnId ?? "",
2601
+ timestamp: Date.now()
2602
+ });
2603
+ break;
2604
+ }
2605
+ }
2606
+ /**
2607
+ * Start the turn - transition to active state.
2608
+ * Called when turn/start request is sent.
2609
+ */
2610
+ async start() {
2611
+ await this.transitionTo("active");
2612
+ }
2613
+ /**
2614
+ * Handle turn/started notification.
2615
+ * Transitions from active to processing_started.
2616
+ * @param turnId - Turn ID from the notification
2617
+ */
2618
+ async handleTurnStarted(turnId) {
2619
+ this.turnId = turnId;
2620
+ await this.transitionTo("processing_started");
2621
+ }
2622
+ /**
2623
+ * Handle item/started notification.
2624
+ * Transitions from processing_started to turn_started (first item)
2625
+ * or from step_finished to step_started (subsequent items).
2626
+ * @param itemId - Item ID from the notification
2627
+ * @param itemType - Type of item (agentMessage, commandExecution, etc.)
2628
+ */
2629
+ async handleItemStarted(itemId, itemType) {
2630
+ this.currentItemId = itemId;
2631
+ this.currentItemType = itemType;
2632
+ if (this.state === "processing_started") {
2633
+ await this.transitionTo("turn_started");
2634
+ await this.transitionTo("step_started");
2635
+ } else if (this.state === "step_finished") await this.transitionTo("step_started");
2636
+ }
2637
+ /**
2638
+ * Handle item/completed notification.
2639
+ * Transitions from step_started to step_finished.
2640
+ * @param itemId - Item ID from the notification
2641
+ */
2642
+ async handleItemCompleted(itemId) {
2643
+ if (this.currentItemId !== itemId) return;
2644
+ if (this.state === "step_started") await this.transitionTo("step_finished");
2645
+ }
2646
+ /**
2647
+ * Handle turn/completed notification.
2648
+ * Transitions from step_finished to turn_finished.
2649
+ */
2650
+ async handleTurnCompleted() {
2651
+ if (this.state === "step_finished" || this.state === "turn_started") await this.transitionTo("turn_finished");
2652
+ }
2653
+ /**
2654
+ * Mark turn as finished.
2655
+ * Called by connector when turn completes.
2656
+ */
2657
+ async markTurnFinished() {
2658
+ await this.transitionTo("turn_finished");
2659
+ }
2660
+ /**
2661
+ * Interrupt (pause) the turn.
2662
+ * Used for immediate mode - sends turn/interrupt and restarts with merged content.
2663
+ * @returns Pause result indicating whether turn had already ended
2664
+ */
2665
+ async pause() {
2666
+ if (this.state === "turn_finished") return {
2667
+ stateBeforePause: this.state,
2668
+ turnEnded: true
2669
+ };
2670
+ const stateBeforePause = this.state;
2671
+ this.interrupted = true;
2672
+ return {
2673
+ stateBeforePause,
2674
+ turnEnded: false
2675
+ };
2676
+ }
2677
+ /**
2678
+ * Resume is a no-op for app-server - caller creates new turn instead.
2679
+ * App-server doesn't support true resume - use interrupt + restart with merged content.
2680
+ * @param _message - Optional message (unused - app-server doesn't support resume)
2681
+ * @throws Error always - app-server requires creating a new turn with merged content
2682
+ */
2683
+ async resume(_message) {
2684
+ throw new Error("Codex app-server does not support resume - create new turn with merged content");
2685
+ }
2686
+ /**
2687
+ * Check if turn was interrupted.
2688
+ * @returns True if turn was interrupted
2689
+ */
2690
+ isPaused() {
2691
+ return this.interrupted;
2692
+ }
2693
+ /**
2694
+ * Check if turn is completed.
2695
+ * @returns True if turn has finished
2696
+ */
2697
+ isCompleted() {
2698
+ return this.state === "turn_finished";
2699
+ }
2700
+ /**
2701
+ * Check if turn can accept immediate message.
2702
+ * True if turn is active (not finished, not interrupted).
2703
+ * @returns True if turn can accept an immediate message
2704
+ */
2705
+ canAcceptImmediate() {
2706
+ return this.state !== "turn_finished" && !this.interrupted;
2707
+ }
2708
+ };
2709
+
2710
+ //#endregion
2711
+ //#region src/utils/attachmentHelpers.ts
2712
+ /**
2713
+ * Helpers for converting message blocks to Codex `UserInput` items.
2714
+ *
2715
+ * Kept separate from the connector to respect its max-lines budget and to
2716
+ * allow independent testing of the conversion logic.
2717
+ */
2718
+ /**
2719
+ * Decode a base64-encoded string to UTF-8 text.
2720
+ *
2721
+ * Uses Web-standard `TextDecoder`/`atob` so this works in both browser and
2722
+ * Node.js (16+) environments. Returns an empty string on malformed input
2723
+ * rather than propagating the `DOMException` thrown by `atob`.
2724
+ * @param data - Base64-encoded string.
2725
+ * @returns Decoded UTF-8 string, or `''` if `data` is not valid base64.
2726
+ */
2727
+ function decodeBase64Text(data) {
2728
+ try {
2729
+ return new TextDecoder().decode(Uint8Array.from(atob(data), (c) => c.charCodeAt(0)));
2730
+ } catch {
2731
+ return "";
2732
+ }
2733
+ }
2734
+ /**
2735
+ * Return true when a MIME type represents plain-text or text-like content
2736
+ * that Codex can consume as a text input rather than a binary blob.
2737
+ * @param mimeType - MIME type string to test, or `undefined`.
2738
+ * @returns `true` when the MIME type is considered text-based.
2739
+ */
2740
+ function isTextMimeType(mimeType) {
2741
+ if (!mimeType) return false;
2742
+ if (mimeType.startsWith("text/")) return true;
2743
+ return [
2744
+ "application/json",
2745
+ "application/xml",
2746
+ "application/sql",
2747
+ "application/graphql",
2748
+ "application/javascript",
2749
+ "application/typescript",
2750
+ "application/x-yaml",
2751
+ "application/toml"
2752
+ ].includes(mimeType);
2753
+ }
2754
+ /**
2755
+ * Convert a single {@link MessageBlock} to a Codex {@link UserInput}, or
2756
+ * `null` when the block type has no Codex representation (e.g. tool_call).
2757
+ *
2758
+ * Dispatch rules:
2759
+ * - `text` → `{ type: 'text' }`
2760
+ * - `image` with URL source → `{ type: 'image' }`
2761
+ * - `image` with base64 source → `null` (unsupported; Codex only accepts local paths via attachment blocks)
2762
+ * - `attachment` with image MIME → `{ type: 'localImage' }` (uses `filePath`)
2763
+ * - `attachment` with text-like MIME → decoded `{ type: 'text' }`
2764
+ * - `attachment` otherwise → `{ type: 'text' }` placeholder via `serializeBlockToText`
2765
+ * - all other block types → `null` (skipped)
2766
+ * @param block - The normalized message block to convert.
2767
+ * @returns A Codex `UserInput` or `null` if the block should be skipped.
2768
+ */
2769
+ function convertBlockToUserInput(block) {
2770
+ if (block.type === "text") return {
2771
+ type: "text",
2772
+ text: block.content
2773
+ };
2774
+ if (block.type === "image" && block.source.type === "url") return {
2775
+ type: "image",
2776
+ url: block.source.url
2777
+ };
2778
+ if (block.type === "image" && block.source.type === "base64") return null;
2779
+ if (block.type === "attachment") {
2780
+ const mimeType = block.source.type === "base64" ? block.source.mimeType : void 0;
2781
+ if (mimeType?.startsWith("image/")) return {
2782
+ type: "localImage",
2783
+ path: block.filePath
2784
+ };
2785
+ if (isTextMimeType(mimeType) && block.source.type === "base64") return {
2786
+ type: "text",
2787
+ text: decodeBase64Text(block.source.data)
2788
+ };
2789
+ return {
2790
+ type: "text",
2791
+ text: serializeBlockToText(block)
2792
+ };
2793
+ }
2794
+ return null;
2795
+ }
2796
+
2797
+ //#endregion
2798
+ //#region src/utils/formatMessageHistory.ts
2799
+ /**
2800
+ * Format message history as a prompt prefix for codex app-server.
2801
+ *
2802
+ * Since the codex app-server protocol's turn/start only accepts user input,
2803
+ * we serialize the message history into a human-readable format that
2804
+ * the AI can understand as conversation context.
2805
+ */
2806
+ /**
2807
+ * Format curated message history as a text prefix.
2808
+ *
2809
+ * Serializes the history into a format the AI can understand as prior context.
2810
+ * Uses role labels to distinguish different message types.
2811
+ * @param history - Curated messages from sessionContext.messageHistory
2812
+ * @returns Formatted string to prepend to the prompt, or empty string if no history
2813
+ */
2814
+ function formatMessageHistory(history) {
2815
+ if (!history || history.length === 0) return "";
2816
+ return `${formatMessageHistoryAsTranscript(history)}\n\n`;
2817
+ }
2818
+
2819
+ //#endregion
2820
+ //#region src/utils/buildUserInputs.ts
2821
+ /**
2822
+ * User input assembly utilities for Codex App-Server turn/start.
2823
+ *
2824
+ * Builds the `input` array expected by `turn/start` from a normalized
2825
+ * `MessageHandle`, inserting history, merged context, turn context, and
2826
+ * content blocks in the correct order.
2827
+ */
2828
+ /**
2829
+ * Assemble the `input` array for a `turn/start` request from a message handle.
2830
+ *
2831
+ * Ordering: outermost → innermost is
2832
+ * `message_history` → `merged_context` → `turn_context` → user text and blocks.
2833
+ * @param messageHandle - The message handle carrying user input and context
2834
+ * @param mergedContent - Optional array of prior message text merged into this turn
2835
+ * @returns Ordered array of `UserInput` items for `turn/start`
2836
+ */
2837
+ function buildUserInputs(messageHandle, mergedContent) {
2838
+ const messageContent = messageHandle.message;
2839
+ const userInputs = [];
2840
+ const historyPrefix = formatMessageHistory(messageHandle.messageHistory);
2841
+ if (historyPrefix) userInputs.push({
2842
+ type: "text",
2843
+ text: `[CONVERSATION HISTORY]\n${historyPrefix}[END CONVERSATION HISTORY]\n\n`
2844
+ });
2845
+ if (mergedContent && mergedContent.length > 0) {
2846
+ const mergedText = `[MERGED CONTEXT FROM PREVIOUS MESSAGES]\n${mergedContent.join("\n")}\n[END MERGED CONTEXT]\n\n`;
2847
+ userInputs.push({
2848
+ type: "text",
2849
+ text: mergedText
2850
+ });
2851
+ }
2852
+ const contextText = formatContextBlocksAsText(serializeTurnContext(messageHandle.turnContext));
2853
+ if (contextText) userInputs.push({
2854
+ type: "text",
2855
+ text: contextText
2856
+ });
2857
+ for (const block of messageContent.blocks) {
2858
+ const input = convertBlockToUserInput(block);
2859
+ if (input) userInputs.push(input);
2860
+ }
2861
+ if (userInputs.length === 0 && messageContent.message) userInputs.push({
2862
+ type: "text",
2863
+ text: messageContent.message
2864
+ });
2865
+ return userInputs;
2866
+ }
2867
+
2868
+ //#endregion
2869
+ //#region src/connector/turn-flow-handlers.ts
2870
+ /**
2871
+ * Maps canonical {@link AIReasoningLevel} values to Codex protocol {@link CodexReasoningEffort} strings.
2872
+ * @param level - Canonical reasoning level (must not be `'none'`)
2873
+ * @returns Codex-native effort string
2874
+ */
2875
+ function mapToCodexEffort(level) {
2876
+ return level === "extra-high" ? "xhigh" : level;
2877
+ }
2878
+ /**
2879
+ * Launch the ACP `thread/start` request, await the corresponding `thread/started`
2880
+ * notification, and populate the connector's `adapterSessionId`.
2881
+ *
2882
+ * A deferred promise is created before the request so the notification handler
2883
+ * can resolve it even if it fires before `await threadStartedPromise` is reached.
2884
+ * On error the deferred is cleared to prevent `getAdapterSessionId()` from hanging.
2885
+ * @param ctx - Turn flow context
2886
+ */
2887
+ async function startThread(ctx) {
2888
+ let resolve;
2889
+ const threadStartedPromise = new Promise((res) => {
2890
+ resolve = res;
2891
+ });
2892
+ ctx.setThreadStartedDeferred({
2893
+ promise: threadStartedPromise,
2894
+ resolve
2895
+ });
2896
+ try {
2897
+ const dynamicTools = await fetchToolsForCodex(ctx.adapterId, ctx.adapterName);
2898
+ const threadStartParams = {
2899
+ model: ctx.getModel() ?? null,
2900
+ modelProvider: null,
2901
+ cwd: ctx.cwd ?? null,
2902
+ approvalPolicy: ctx.getApprovalPolicy() ?? null,
2903
+ sandbox: ctx.getSandboxMode() ?? null,
2904
+ config: null,
2905
+ baseInstructions: ctx.resolveSystemPrompt(),
2906
+ developerInstructions: null,
2907
+ experimentalRawEvents: false,
2908
+ dynamicTools: dynamicTools.length > 0 ? dynamicTools : void 0
2909
+ };
2910
+ await ctx.getJsonRpcClient().request("thread/start", threadStartParams);
2911
+ const threadId = await threadStartedPromise;
2912
+ ctx.setAdapterSessionId(threadId);
2913
+ ctx.setThreadStartedDeferred(void 0);
2914
+ } catch (error) {
2915
+ ctx.setThreadStartedDeferred(void 0);
2916
+ throw error;
2917
+ }
2918
+ }
2919
+ /**
2920
+ * Send a single `turn/start` request and wire the active turn.
2921
+ *
2922
+ * Creates a new {@link CodexAppServerTurn}, assigns it as the active turn, resets
2923
+ * the message accumulator, and fires the ACP `turn/start` request with the
2924
+ * current model and reasoning effort.
2925
+ * @param ctx - Turn flow context
2926
+ * @param messageHandle - Handle for the message being dispatched
2927
+ * @param mergedContent - Optional content lines merged from superseded handles
2928
+ */
2929
+ async function startTurn(ctx, messageHandle, mergedContent) {
2930
+ const thread = ctx.getThread();
2931
+ if (!thread?.threadId) throw new Error("Cannot start turn: thread not started");
2932
+ ctx.setCurrentTurn(new CodexAppServerTurn(ctx.bus, ctx.adapterId, ctx.adapterName, ctx.agentId, thread.threadId, messageHandle));
2933
+ ctx.setPendingMessageHandle(messageHandle);
2934
+ ctx.setAgentMessageContent("");
2935
+ await ctx.getCurrentTurn().start();
2936
+ const userInputs = buildUserInputs(messageHandle, mergedContent);
2937
+ const effort = ctx.getReasoningEffort();
2938
+ await ctx.getJsonRpcClient().request("turn/start", {
2939
+ threadId: thread.threadId,
2940
+ input: userInputs,
2941
+ cwd: null,
2942
+ approvalPolicy: null,
2943
+ sandboxPolicy: null,
2944
+ model: ctx.getModel() ?? null,
2945
+ effort: effort !== void 0 && effort !== "none" ? mapToCodexEffort(effort) : null,
2946
+ summary: null,
2947
+ outputSchema: null
2948
+ });
2949
+ }
2950
+ /**
2951
+ * Drain the message queue, dispatching the next eligible message as a new turn.
2952
+ *
2953
+ * Late-arriving `immediate` messages (arriving after a turn has already completed)
2954
+ * are rejected and the queue is re-drained to unblock any remaining entries.
2955
+ * @param ctx - Turn flow context
2956
+ */
2957
+ async function processQueue(ctx) {
2958
+ if (ctx.getCurrentTurn() && !ctx.getCurrentTurn().isCompleted()) return;
2959
+ const nextMessage = ctx.messageQueue.peek();
2960
+ if (!nextMessage) return;
2961
+ if (nextMessage.deliveryMode === "immediate" && ctx.getLastResult() !== null) {
2962
+ ctx.messageQueue.dequeue();
2963
+ nextMessage.markCompleted({ outcome: "rejected" });
2964
+ await processQueue(ctx);
2965
+ if (!ctx.getCurrentTurn() && ctx.messageQueue.isEmpty()) await ctx.updateProcessingState("idle");
2966
+ return;
2967
+ }
2968
+ const message = ctx.messageQueue.dequeue();
2969
+ if (!message) return;
2970
+ await startTurn(ctx, message);
2971
+ if (!ctx.messageQueue.isEmpty()) await processQueue(ctx);
2972
+ }
2973
+ /**
2974
+ * Handle the `thread/started` notification from the Codex server.
2975
+ *
2976
+ * Resolves the deferred thread-started promise, sets the adapter session ID,
2977
+ * creates and registers the {@link CodexAppServerThread}, and fires the thread
2978
+ * lifecycle handler.
2979
+ * @param ctx - Turn flow context
2980
+ * @param notification - Parsed notification payload
2981
+ */
2982
+ async function onThreadStarted(ctx, notification) {
2983
+ const threadId = extractThreadId(notification);
2984
+ ctx.setAdapterSessionId(threadId);
2985
+ ctx.getThreadStartedDeferred()?.resolve(threadId);
2986
+ ctx.setThread(new CodexAppServerThread({
2987
+ bus: ctx.bus,
2988
+ adapterId: ctx.adapterId,
2989
+ agentId: ctx.agentId
2990
+ }));
2991
+ await ctx.getThread().handleThreadStarted(threadId);
2992
+ }
2993
+ /**
2994
+ * Handle the `turn/completed` notification from the Codex server.
2995
+ *
2996
+ * Manages the superseded-message / merged-message fast path (when an `immediate`
2997
+ * message is waiting), then settles the pending handle with its outcome. Drains
2998
+ * the queue or transitions to idle when no further messages are queued.
2999
+ * @param ctx - Turn flow context
3000
+ * @param _notification - Parsed notification payload (currently unused)
3001
+ */
3002
+ async function onTurnCompleted(ctx, _notification) {
3003
+ if (!ctx.getCurrentTurn()) return;
3004
+ await ctx.getCurrentTurn().handleTurnCompleted();
3005
+ const immediateMsg = ctx.messageQueue.findImmediate();
3006
+ if (immediateMsg && ctx.getPendingMessageHandle()) {
3007
+ ctx.messageQueue.removeImmediate(immediateMsg);
3008
+ const mergedContent = [];
3009
+ const currentHandle = ctx.getPendingMessageHandle();
3010
+ if (currentHandle.message.message) mergedContent.push(currentHandle.message.message);
3011
+ currentHandle.supersededBy = immediateMsg.messageId;
3012
+ currentHandle.markCompleted({
3013
+ outcome: "superseded",
3014
+ supersededBy: immediateMsg.messageId
3015
+ });
3016
+ const enqueuedHandles = ctx.messageQueue.drainEnqueued();
3017
+ for (const handle of enqueuedHandles) {
3018
+ if (handle.message.message) mergedContent.push(handle.message.message);
3019
+ handle.supersededBy = immediateMsg.messageId;
3020
+ handle.markCompleted({
3021
+ outcome: "merged",
3022
+ mergedInto: immediateMsg.messageId
3023
+ });
3024
+ }
3025
+ ctx.setLastResult({
3026
+ outcome: "superseded",
3027
+ supersededBy: immediateMsg.messageId
3028
+ });
3029
+ ctx.setPendingMessageHandle(void 0);
3030
+ ctx.setCurrentTurn(void 0);
3031
+ await ctx.updateProcessingState("turn_finished");
3032
+ await ctx.updateProcessingState("processing_finished");
3033
+ await startTurn(ctx, immediateMsg, mergedContent);
3034
+ return;
3035
+ }
3036
+ const pendingHandle = ctx.getPendingMessageHandle();
3037
+ if (pendingHandle) {
3038
+ const turnId = ctx.getCurrentTurn()?.getTurnId();
3039
+ const threadId = ctx.getThread()?.threadId;
3040
+ const agentMessageContent = ctx.getAgentMessageContent();
3041
+ if (agentMessageContent && threadId && turnId) await ctx.emit(CodexAppServerSubjects.agent_message, {
3042
+ threadId,
3043
+ turnId,
3044
+ message: agentMessageContent,
3045
+ timestamp: Date.now()
3046
+ });
3047
+ const result = {
3048
+ outcome: "completed",
3049
+ result: { message: agentMessageContent || "(Empty response)" }
3050
+ };
3051
+ pendingHandle.markCompleted(result);
3052
+ ctx.setLastResult(result);
3053
+ ctx.setPendingMessageHandle(void 0);
3054
+ }
3055
+ ctx.setCurrentTurn(void 0);
3056
+ await ctx.updateProcessingState("turn_finished");
3057
+ await ctx.updateProcessingState("processing_finished");
3058
+ if (!ctx.messageQueue.isEmpty()) await processQueue(ctx);
3059
+ else await ctx.updateProcessingState("idle");
3060
+ }
3061
+
3062
+ //#endregion
3063
+ //#region src/connector/connector.ts
3064
+ var CodexAppServerConnector = class extends AIAgentConnector {
3065
+ /** Lazy: initialised in initializeConnection(); public methods access via getJsonRpcClient(). */
3066
+ jsonRpcClient;
3067
+ /** Injected transport for tests; null means "create from subprocess on first connect". */
3068
+ _injectedTransport;
3069
+ /** Injected JSON-RPC client for tests; bypasses subprocess creation entirely. */
3070
+ _injectedJsonRpcClient;
3071
+ /** Transport owned by this connector and safe to tear down after partial startup failures. */
3072
+ ownedTransport;
3073
+ messageQueue = new UserMessageQueue();
3074
+ thread;
3075
+ currentTurn;
3076
+ isConnected = false;
3077
+ /** In-flight connection initialization promise for single-flight deduplication. */
3078
+ initConnectionInflight = { promise: void 0 };
3079
+ /** Handlers are registered once per client instance to avoid duplicate listeners on retries. */
3080
+ clientHandlersRegistered = false;
3081
+ isTerminated = false;
3082
+ agentMessageContent = "";
3083
+ notificationQueue = Promise.resolve();
3084
+ threadStartedDeferred;
3085
+ _approvalPolicy;
3086
+ _sandboxMode;
3087
+ _reasoningEffort;
3088
+ commandExecutionByItemId = /* @__PURE__ */ new Map();
3089
+ dynamicToolCallByItemId = /* @__PURE__ */ new Map();
3090
+ /** Pending resolvers for {@link waitForCommandInfo}, keyed by itemId. */
3091
+ commandInfoWaiters = /* @__PURE__ */ new Map();
3092
+ disabledNativeTools = /* @__PURE__ */ new Set();
3093
+ /**
3094
+ * Stable context object passed to connection-manager module functions.
3095
+ * All state access is via closures over `this` so the object is never stale.
3096
+ */
3097
+ connCtx;
3098
+ /**
3099
+ * Stable context object passed to turn-flow-handlers module functions.
3100
+ * All state access is via closures over `this` so the object is never stale.
3101
+ */
3102
+ turnCtx;
3103
+ constructor(config) {
3104
+ super({
3105
+ bus: config.bus,
3106
+ adapterId: config.adapterId,
3107
+ adapterName: config.adapterName ?? "codex-app-server",
3108
+ agentId: config.agentId,
3109
+ model: config.model,
3110
+ cwd: config.cwd,
3111
+ env: config.env,
3112
+ onMessageSent: config.onMessageSent,
3113
+ toolLedger: config.toolLedger,
3114
+ reasoningEffort: config.reasoningEffort,
3115
+ clientId: config.clientId,
3116
+ harnessId: config.harnessId,
3117
+ providerContext: config.providerContext
3118
+ });
3119
+ const fullConfig = config;
3120
+ this._approvalPolicy = fullConfig.providerConfig?.approvalPolicy ?? fullConfig.approvalPolicy;
3121
+ this._sandboxMode = fullConfig.providerConfig?.sandboxMode ?? fullConfig.sandboxMode;
3122
+ this._reasoningEffort = fullConfig.providerConfig?.reasoningEffort ?? fullConfig.reasoningEffort;
3123
+ this.currentReasoningEffort = this._reasoningEffort;
3124
+ this._injectedJsonRpcClient = config.jsonRpcClient;
3125
+ this._injectedTransport = config.transport;
3126
+ if (config.jsonRpcClient) this.jsonRpcClient = config.jsonRpcClient;
3127
+ this.connCtx = this.buildConnectionContext();
3128
+ this.turnCtx = this.buildTurnFlowContext();
3129
+ }
3130
+ buildConnectionContext() {
3131
+ return {
3132
+ getJsonRpcClient: () => this.jsonRpcClient,
3133
+ setJsonRpcClient: (client) => {
3134
+ this.jsonRpcClient = client;
3135
+ },
3136
+ getInjectedJsonRpcClient: () => this._injectedJsonRpcClient,
3137
+ getInjectedTransport: () => this._injectedTransport,
3138
+ getOwnedTransport: () => this.ownedTransport,
3139
+ setOwnedTransport: (transport) => {
3140
+ this.ownedTransport = transport;
3141
+ },
3142
+ getIsConnected: () => this.isConnected,
3143
+ setIsConnected: (value) => {
3144
+ this.isConnected = value;
3145
+ },
3146
+ setClientHandlersRegistered: (value) => {
3147
+ this.clientHandlersRegistered = value;
3148
+ },
3149
+ setDisabledNativeTools: (tools) => {
3150
+ this.disabledNativeTools = tools;
3151
+ },
3152
+ cwd: this.cwd,
3153
+ env: this.env,
3154
+ adapterName: this.adapterName,
3155
+ providerContext: this.config.providerContext,
3156
+ clientId: this.config.clientId,
3157
+ harnessId: this.config.harnessId,
3158
+ bus: this.config.bus,
3159
+ registerClientHandlers: () => this.registerClientHandlers(),
3160
+ handleError: (error, terminate) => this.handleError(error, terminate)
3161
+ };
3162
+ }
3163
+ buildTurnFlowContext() {
3164
+ return {
3165
+ getCurrentTurn: () => this.currentTurn,
3166
+ setCurrentTurn: (turn) => {
3167
+ this.currentTurn = turn;
3168
+ },
3169
+ getThread: () => this.thread,
3170
+ setThread: (thread) => {
3171
+ this.thread = thread;
3172
+ },
3173
+ getAgentMessageContent: () => this.agentMessageContent,
3174
+ setAgentMessageContent: (content) => {
3175
+ this.agentMessageContent = content;
3176
+ },
3177
+ getPendingMessageHandle: () => this.pendingMessageHandle,
3178
+ setPendingMessageHandle: (handle) => {
3179
+ this.pendingMessageHandle = handle;
3180
+ },
3181
+ setLastResult: (result) => {
3182
+ this.lastResult = result;
3183
+ },
3184
+ getLastResult: () => this.lastResult,
3185
+ setAdapterSessionId: (id) => {
3186
+ this.adapterSessionId = id;
3187
+ },
3188
+ getThreadStartedDeferred: () => this.threadStartedDeferred,
3189
+ setThreadStartedDeferred: (deferred) => {
3190
+ this.threadStartedDeferred = deferred;
3191
+ },
3192
+ messageQueue: this.messageQueue,
3193
+ getJsonRpcClient: () => this.getJsonRpcClient(),
3194
+ emit: this.emit.bind(this),
3195
+ updateProcessingState: this.updateProcessingState.bind(this),
3196
+ agentId: this.agentId,
3197
+ adapterId: this.adapterId,
3198
+ adapterName: this.adapterName,
3199
+ bus: this.config.bus,
3200
+ getModel: () => this.model,
3201
+ getReasoningEffort: () => this.currentReasoningEffort,
3202
+ getApprovalPolicy: () => this._approvalPolicy,
3203
+ getSandboxMode: () => this._sandboxMode,
3204
+ resolveSystemPrompt: () => this.resolveSystemPrompt(),
3205
+ cwd: this.cwd
3206
+ };
3207
+ }
3208
+ get approvalPolicy() {
3209
+ return this._approvalPolicy;
3210
+ }
3211
+ get sandboxMode() {
3212
+ return this._sandboxMode;
3213
+ }
3214
+ get reasoningEffort() {
3215
+ return this._reasoningEffort;
3216
+ }
3217
+ /**
3218
+ * Resolve any pending {@link waitForCommandInfo} promise for `itemId`.
3219
+ * Called by the `item/started` lifecycle path after populating `commandExecutionByItemId`.
3220
+ * @param itemId - Item now available in commandExecutionByItemId
3221
+ * @param info - Command execution metadata just written to the cache
3222
+ */
3223
+ notifyCommandInfoReady(itemId, info) {
3224
+ const resolve = this.commandInfoWaiters.get(itemId);
3225
+ if (resolve) {
3226
+ this.commandInfoWaiters.delete(itemId);
3227
+ resolve(info);
3228
+ }
3229
+ }
3230
+ /**
3231
+ * Return `commandExecutionByItemId` entry for `itemId` immediately if present,
3232
+ * otherwise wait up to 5 seconds for `item/started` to populate it.
3233
+ * Returns `undefined` on timeout so callers can degrade gracefully.
3234
+ * @param itemId - Item ID to wait for
3235
+ * @returns Command execution metadata, or `undefined` on timeout
3236
+ */
3237
+ waitForCommandInfo(itemId) {
3238
+ const existing = this.commandExecutionByItemId.get(itemId);
3239
+ if (existing) return Promise.resolve(existing);
3240
+ return new Promise((resolve) => {
3241
+ const timeout = setTimeout(() => {
3242
+ this.commandInfoWaiters.delete(itemId);
3243
+ resolve(void 0);
3244
+ }, 5e3);
3245
+ this.commandInfoWaiters.set(itemId, (info) => {
3246
+ clearTimeout(timeout);
3247
+ resolve(info);
3248
+ });
3249
+ });
3250
+ }
3251
+ resolveSystemPrompt() {
3252
+ if (this.systemPrompt === void 0) return null;
3253
+ return typeof this.systemPrompt === "string" ? this.systemPrompt : this.systemPrompt.content;
3254
+ }
3255
+ enqueueNotification(handler) {
3256
+ this.notificationQueue = this.notificationQueue.then(handler).catch((error) => {
3257
+ console.error("[CodexAppServerConnector] Notification handler error:", error);
3258
+ });
3259
+ }
3260
+ getJsonRpcClient() {
3261
+ if (!this.jsonRpcClient) throw new Error("JSON-RPC client not initialized");
3262
+ return this.jsonRpcClient;
3263
+ }
3264
+ registerClientHandlers() {
3265
+ if (this.clientHandlersRegistered) return;
3266
+ const client = this.getJsonRpcClient();
3267
+ const tfCtx = this.turnCtx;
3268
+ registerNotificationHandlers({
3269
+ client,
3270
+ enqueueNotification: this.enqueueNotification.bind(this),
3271
+ onThreadStarted: (n) => onThreadStarted(tfCtx, n),
3272
+ consumeTurnNumber: this.consumeTurnNumber.bind(this),
3273
+ getCurrentTurn: () => this.currentTurn,
3274
+ emit: this.emit.bind(this),
3275
+ commandExecutionByItemId: this.commandExecutionByItemId,
3276
+ dynamicToolCallByItemId: this.dynamicToolCallByItemId,
3277
+ updateProcessingState: this.updateProcessingState.bind(this),
3278
+ appendAgentMessageDelta: (delta) => {
3279
+ this.agentMessageContent += delta;
3280
+ },
3281
+ onTurnCompleted: (n) => onTurnCompleted(tfCtx, n),
3282
+ getThread: () => this.thread,
3283
+ handleAsyncError: (error) => this.handleError(error),
3284
+ onCommandInfoReady: this.notifyCommandInfoReady.bind(this)
3285
+ });
3286
+ registerServerRequestHandler({
3287
+ client,
3288
+ agentId: this.agentId,
3289
+ cwd: this.cwd ?? "",
3290
+ commandExecutionByItemId: this.commandExecutionByItemId,
3291
+ requestToolApproval: this.requestToolApproval.bind(this),
3292
+ handleError: this.handleError.bind(this),
3293
+ getDisabledNativeTools: () => this.disabledNativeTools,
3294
+ handleDynamicToolCallRequest: this.handleDynamicToolCallRequest.bind(this),
3295
+ waitForCommandInfo: this.waitForCommandInfo.bind(this)
3296
+ });
3297
+ this.clientHandlersRegistered = true;
3298
+ }
3299
+ async handleDynamicToolCallRequest(params) {
3300
+ return handleDynamicToolCallApprovalRequest(params, {
3301
+ requestToolApproval: this.requestToolApproval.bind(this),
3302
+ emit: this.emit.bind(this),
3303
+ sessionId: this.sessionId,
3304
+ agentId: this.agentId,
3305
+ adapterId: this.adapterId,
3306
+ adapterName: this.adapterName,
3307
+ dynamicToolCallByItemId: this.dynamicToolCallByItemId,
3308
+ toolLedger: this.config.toolLedger,
3309
+ currentTurnNumber: this.currentTurnNumber
3310
+ });
3311
+ }
3312
+ async initialize(options) {
3313
+ this.captureSystemPrompt(options?.systemPrompt);
3314
+ if (!this.isConnected) await initializeConnection(this.connCtx, this.initConnectionInflight);
3315
+ if (!this.thread) await startThread(this.turnCtx);
3316
+ }
3317
+ async start(message, options) {
3318
+ this.captureSystemPrompt(options?.systemPrompt);
3319
+ const messageHandle = await this.sendMessage(message, options);
3320
+ return {
3321
+ adapterSessionId: await this.getAdapterSessionId(),
3322
+ messageHandle,
3323
+ agentId: this.agentId
3324
+ };
3325
+ }
3326
+ async sendMessage(message, options) {
3327
+ if (!this.isConnected) await initializeConnection(this.connCtx, this.initConnectionInflight);
3328
+ if (!this.thread) await startThread(this.turnCtx);
3329
+ const handle = this.createMessageHandle(message, options);
3330
+ if (this.getProcessingState() === "idle") await this.updateProcessingState("active");
3331
+ this.messageQueue.enqueue(handle);
3332
+ if (!this.currentTurn || this.currentTurn.isCompleted()) await processQueue(this.turnCtx);
3333
+ return handle;
3334
+ }
3335
+ /**
3336
+ * Codex supports per-turn model switching via `turn/start`.
3337
+ * The caller updates `this.model` after this returns true.
3338
+ * @param _newModel - The model identifier (unused — read from `this.model` at turn start)
3339
+ * @returns Always `true`
3340
+ */
3341
+ async changeModelInPlace(_newModel) {
3342
+ return true;
3343
+ }
3344
+ /**
3345
+ * Codex passes reasoning effort per-turn via `turn/start`; `startTurn` reads
3346
+ * `this.currentReasoningEffort` at send time, so no SDK reconfiguration is needed.
3347
+ * @param _newLevel - Unused — read from `this.currentReasoningEffort` at turn start
3348
+ * @returns Always `true`
3349
+ */
3350
+ async changeReasoningInPlace(_newLevel) {
3351
+ return true;
3352
+ }
3353
+ async interrupt() {
3354
+ if (!this.currentTurn?.getTurnId()) return;
3355
+ await this.getJsonRpcClient().request("turn/interrupt", { turnId: this.currentTurn.getTurnId() });
3356
+ }
3357
+ async getAdapterSessionId() {
3358
+ if (this.adapterSessionId) return this.adapterSessionId;
3359
+ if (this.threadStartedDeferred) return this.threadStartedDeferred.promise;
3360
+ throw new Error("Thread not started");
3361
+ }
3362
+ async complete() {
3363
+ while (this.getProcessingState() !== "idle") await this.onceProcessingStateChanged();
3364
+ return this.lastResult;
3365
+ }
3366
+ abort() {
3367
+ if (this.isTerminated) return;
3368
+ this.isTerminated = true;
3369
+ this.jsonRpcClient?.close();
3370
+ }
3371
+ async close() {
3372
+ if (this.isTerminated) return;
3373
+ this.isTerminated = true;
3374
+ await this.archiveThread();
3375
+ this.jsonRpcClient?.close();
3376
+ }
3377
+ async archiveThread() {
3378
+ const threadId = this.thread?.threadId;
3379
+ if (!threadId) return;
3380
+ const archiveRequest = this.jsonRpcClient?.request("thread/archive", { threadId });
3381
+ if (!archiveRequest) return;
3382
+ try {
3383
+ await Promise.race([archiveRequest, new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("archive timeout")), 2e3))]);
3384
+ } catch {}
3385
+ }
3386
+ acceptsImmediate() {
3387
+ if (!this.currentTurn) return true;
3388
+ return this.currentTurn.canAcceptImmediate();
3389
+ }
3390
+ handleError(error, terminate = false) {
3391
+ const err = error instanceof Error ? error : new Error(String(error));
3392
+ if (this.pendingMessageHandle && !this.pendingMessageHandle.isProcessed) this.lastResult = {
3393
+ outcome: "error",
3394
+ error: err
3395
+ };
3396
+ super.handleError(err, terminate);
3397
+ }
3398
+ };
3399
+
3400
+ //#endregion
3401
+ //#region src/constants.ts
3402
+ /** Adapter name constant for consistent identification */
3403
+ const CodexAppServerAdapterName = "codex-app-server";
3404
+ /** Default model for Codex App-Server adapter */
3405
+ const DefaultModel = "gpt-5.1-codex-mini";
3406
+ /** Default provider for Codex App-Server adapter */
3407
+ const DefaultProvider = "openai";
3408
+ /** Default timeout configuration for Codex App-Server adapter */
3409
+ const DEFAULT_TIMEOUTS = {
3410
+ initialization: 3e4,
3411
+ acknowledgement: 3e4,
3412
+ completion: 6e4,
3413
+ toolApproval: 5e3,
3414
+ eventWait: 3e3
3415
+ };
3416
+
3417
+ //#endregion
3418
+ //#region src/schemas.ts
3419
+ /**
3420
+ * Approval policy enum values for Codex app-server.
3421
+ * Maps to AskForApproval in app-server protocol.
3422
+ * - 'untrusted': Always ask for approval (most restrictive)
3423
+ * - 'on-failure': Ask for approval only if command fails
3424
+ * - 'on-request': Ask for approval when command requests escalated permissions
3425
+ * - 'never': Never ask for approval (least restrictive)
3426
+ */
3427
+ const ApprovalPolicyValues = [
3428
+ "untrusted",
3429
+ "on-failure",
3430
+ "on-request",
3431
+ "never"
3432
+ ];
3433
+ /**
3434
+ * Sandbox mode enum values for Codex app-server.
3435
+ * Maps to SandboxMode in app-server protocol.
3436
+ * - 'read-only': Commands cannot write to filesystem
3437
+ * - 'workspace-write': Commands can write to workspace directories
3438
+ * - 'danger-full-access': Commands have full filesystem access
3439
+ */
3440
+ const SandboxModeValues = [
3441
+ "read-only",
3442
+ "workspace-write",
3443
+ "danger-full-access"
3444
+ ];
3445
+ /**
3446
+ * Reasoning effort enum values for Codex app-server.
3447
+ * Maps to ReasoningEffort in app-server protocol.
3448
+ */
3449
+ const ReasoningEffortValues = [
3450
+ "low",
3451
+ "medium",
3452
+ "high"
3453
+ ];
3454
+ /**
3455
+ * Zod schema for Codex app-server provider-specific configuration.
3456
+ *
3457
+ * Used for:
3458
+ * 1. Type-safe config resolution
3459
+ * 2. Serialization to JSON Schema for web-ui form generation
3460
+ * 3. Runtime validation
3461
+ *
3462
+ * Note: `cwd` and `model` come from the adapter options (BaseAgentConnectorConfig),
3463
+ * not from this provider config. This schema only contains provider-specific settings.
3464
+ */
3465
+ const CodexAppServerProviderConfigSchema = z.object({
3466
+ /**
3467
+ * Approval policy for tool execution.
3468
+ */
3469
+ approvalPolicy: z.enum(ApprovalPolicyValues).default("on-request").meta({
3470
+ title: "Approval Policy",
3471
+ description: "How to handle tool approval requests"
3472
+ }),
3473
+ /**
3474
+ * Sandbox mode for command execution.
3475
+ */
3476
+ sandboxMode: z.enum(SandboxModeValues).default("read-only").meta({
3477
+ title: "Sandbox Mode",
3478
+ description: "Sandbox execution environment"
3479
+ }),
3480
+ /**
3481
+ * Reasoning effort level.
3482
+ */
3483
+ reasoningEffort: z.enum(ReasoningEffortValues).optional().meta({
3484
+ title: "Reasoning Effort",
3485
+ description: "Level of reasoning effort for the model"
3486
+ })
3487
+ });
3488
+
3489
+ //#endregion
3490
+ //#region src/config.ts
3491
+ const CodexAppServerConfig = createAdapterConfigFactory(() => ({
3492
+ adapterName: CodexAppServerAdapterName,
3493
+ adapterDefaults: {
3494
+ reasoningEffort: "low",
3495
+ providerConfig: {
3496
+ approvalPolicy: "untrusted",
3497
+ sandboxMode: "workspace-write"
3498
+ }
3499
+ },
3500
+ schema: CodexAppServerProviderConfigSchema,
3501
+ adapterDefinition: { defaultTimeouts: DEFAULT_TIMEOUTS },
3502
+ protocol: "openai"
3503
+ }));
3504
+
3505
+ //#endregion
3506
+ //#region src/adapter.ts
3507
+ /**
3508
+ * Codex App-Server Adapter - Domain-level adapter using the three-layer architecture.
3509
+ *
3510
+ * Architecture:
3511
+ * ```
3512
+ * CodexAppServerAdapter extends AIAdapter
3513
+ * -> creates via agentFactory()
3514
+ * CodexAppServerAgent extends AIAgent
3515
+ * -> receives connector via connectorFactory()
3516
+ * CodexAppServerConnector extends AIAgentConnector
3517
+ * ```
3518
+ *
3519
+ * Responsibilities:
3520
+ * - Handle adapter.startAgent RPC (inherited from AIAdapter)
3521
+ * - Provide agent and connector factories for instance creation
3522
+ * - Emit adapter.initialized and adapter.session.created events
3523
+ * - Manage agent lifecycle (tracking, disposal)
3524
+ * @example
3525
+ * ```typescript
3526
+ * // Using the class directly
3527
+ * const adapter = new CodexAppServerAdapter();
3528
+ * await adapter.init();
3529
+ *
3530
+ * // Using the convenience factory
3531
+ * const adapter = await createCodexAppServerAdapter();
3532
+ * ```
3533
+ */
3534
+ var CodexAppServerAdapter = class extends AIAdapter {
3535
+ constructor(config) {
3536
+ super({
3537
+ name: CodexAppServerAdapterName,
3538
+ capabilities: [
3539
+ "tools",
3540
+ "streaming",
3541
+ "systemPrompt:override",
3542
+ "systemPrompt:append"
3543
+ ],
3544
+ nativeTools: ["bash", "patch"],
3545
+ ...config,
3546
+ namespace: CodexAppServerNamespace,
3547
+ agentFactory: (agentConfig) => {
3548
+ return new CodexAppServerAgent(agentConfig);
3549
+ },
3550
+ configFactory: CodexAppServerConfig.getConfig,
3551
+ connectorFactory: (fullConfig) => new CodexAppServerConnector(fullConfig),
3552
+ definitionProviders: config?.definitionProviders
3553
+ });
3554
+ }
3555
+ };
3556
+ /**
3557
+ * Factory function to create and initialize a Codex App-Server adapter.
3558
+ *
3559
+ * Convenience wrapper that creates the adapter and calls init() for you.
3560
+ * @param config - Optional adapter configuration
3561
+ * @returns Initialized CodexAppServerAdapter instance
3562
+ * @example
3563
+ * ```typescript
3564
+ * const adapter = await createCodexAppServerAdapter();
3565
+ *
3566
+ * // Adapter is ready to handle requests via bus
3567
+ * // e.g., MakaioBus.request(AdapterSubjects.startAgent, { adapterId: adapter.adapterId, ... })
3568
+ * ```
3569
+ */
3570
+ async function createCodexAppServerAdapter(config) {
3571
+ const adapter = new CodexAppServerAdapter(config);
3572
+ await adapter.init();
3573
+ return adapter;
3574
+ }
3575
+
3576
+ //#endregion
3577
+ //#region src/event-normalizers.ts
3578
+ /**
3579
+ * Normalize app-server event to canonical AgentSubjects.
3580
+ *
3581
+ * Transforms app-server protocol notifications to global agent events.
3582
+ * Returns array of normalized events (some notifications may map to multiple).
3583
+ * @param notification - The server notification from app-server
3584
+ * @param context - Normalization context with session/adapter info
3585
+ * @returns Array of normalized events (may be empty for unhandled types)
3586
+ */
3587
+ function normalizeAppServerEvent(notification, context) {
3588
+ const { method, params } = notification;
3589
+ if (method === "item/agentMessage/delta") return [normalizeAgentMessageDelta(params, context)];
3590
+ if (method === "item/started") return normalizeItemStarted(params, context);
3591
+ if (method === "item/commandExecution/outputDelta") return [normalizeCommandOutputDelta(params, context)];
3592
+ if (method === "item/reasoning/textDelta") return [normalizeReasoningDelta(params, context)];
3593
+ if (method === "thread/tokenUsage/updated") return [normalizeTokenUsage(params, context)];
3594
+ if (method === "turn/completed") return [normalizeTurnCompleted(params, context)];
3595
+ return [];
3596
+ }
3597
+ /**
3598
+ * Normalize agent message delta to agent.message_delta subject.
3599
+ * @param params - The agent message delta notification
3600
+ * @param ctx - Normalization context
3601
+ * @returns Normalized event for message_delta
3602
+ */
3603
+ function normalizeAgentMessageDelta(params, ctx) {
3604
+ return {
3605
+ subject: AgentSubjects.message_delta,
3606
+ payload: {
3607
+ ...ctx,
3608
+ text: params.delta
3609
+ }
3610
+ };
3611
+ }
3612
+ /**
3613
+ * Normalize item started to appropriate subject based on item type.
3614
+ * @param params - The item started notification
3615
+ * @param ctx - Normalization context
3616
+ * @returns Array of normalized events (may be empty for unhandled types)
3617
+ */
3618
+ function normalizeItemStarted(params, ctx) {
3619
+ const { item } = params;
3620
+ if (item.type === "agentMessage") return [{
3621
+ subject: AgentSubjects.message_delta,
3622
+ payload: {
3623
+ ...ctx,
3624
+ text: ""
3625
+ }
3626
+ }];
3627
+ if (item.type === "commandExecution") return [{
3628
+ subject: AgentSubjects.tool.started,
3629
+ payload: {
3630
+ ...ctx,
3631
+ toolName: "bash",
3632
+ toolCallId: item.id
3633
+ }
3634
+ }];
3635
+ if (item.type === "fileChange") return [{
3636
+ subject: AgentSubjects.tool.started,
3637
+ payload: {
3638
+ ...ctx,
3639
+ toolName: "patch",
3640
+ toolCallId: item.id
3641
+ }
3642
+ }];
3643
+ if (item.type === "mcpToolCall") return [{
3644
+ subject: AgentSubjects.tool.started,
3645
+ payload: {
3646
+ ...ctx,
3647
+ toolName: `${item.server}:${item.tool}`,
3648
+ toolCallId: item.id
3649
+ }
3650
+ }];
3651
+ return [];
3652
+ }
3653
+ /**
3654
+ * Normalize command output delta to agent.tool.output subject.
3655
+ * @param params - The command output delta notification
3656
+ * @param ctx - Normalization context
3657
+ * @returns Normalized event for tool output
3658
+ */
3659
+ function normalizeCommandOutputDelta(params, ctx) {
3660
+ return {
3661
+ subject: AgentSubjects.tool.output,
3662
+ payload: {
3663
+ ...ctx,
3664
+ output: params.delta,
3665
+ toolCallId: params.itemId
3666
+ }
3667
+ };
3668
+ }
3669
+ /**
3670
+ * Normalize reasoning delta to agent.reasoning_delta subject.
3671
+ * @param params - The reasoning text delta notification
3672
+ * @param ctx - Normalization context
3673
+ * @returns Normalized event for reasoning delta
3674
+ */
3675
+ function normalizeReasoningDelta(params, ctx) {
3676
+ return {
3677
+ subject: AgentSubjects.reasoning_delta,
3678
+ payload: {
3679
+ ...ctx,
3680
+ content: params.delta
3681
+ }
3682
+ };
3683
+ }
3684
+ /**
3685
+ * Derive provider from model name.
3686
+ * @param model - The model name
3687
+ * @returns The provider identifier
3688
+ */
3689
+ function deriveProvider(model) {
3690
+ if (model.startsWith("claude")) return "anthropic";
3691
+ if (model.startsWith("gpt") || model.startsWith("o1") || model.startsWith("o3")) return "openai";
3692
+ return DefaultProvider;
3693
+ }
3694
+ /**
3695
+ * Normalize token usage to agent.usage subject.
3696
+ * @param params - The token usage updated notification
3697
+ * @param ctx - Normalization context
3698
+ * @returns Normalized event for usage
3699
+ */
3700
+ function normalizeTokenUsage(params, ctx) {
3701
+ const { last } = params.tokenUsage;
3702
+ const model = ctx.model ?? "gpt-5.1-codex-mini";
3703
+ const provider = deriveProvider(model);
3704
+ return {
3705
+ subject: AgentSubjects.usage,
3706
+ payload: {
3707
+ ...ctx,
3708
+ provider,
3709
+ model,
3710
+ inputTokens: last.inputTokens,
3711
+ inputCachedTokens: last.cachedInputTokens,
3712
+ outputTokens: last.outputTokens,
3713
+ reasoningTokens: last.reasoningOutputTokens,
3714
+ totalTokens: last.totalTokens,
3715
+ costUnits: last.totalTokens,
3716
+ costUnitType: "tokens"
3717
+ }
3718
+ };
3719
+ }
3720
+ /**
3721
+ * Normalize turn completed to agent.complete subject.
3722
+ * @param params - The turn completed notification
3723
+ * @param ctx - Normalization context
3724
+ * @returns Normalized event for completion
3725
+ */
3726
+ function normalizeTurnCompleted(params, ctx) {
3727
+ return {
3728
+ subject: AgentSubjects.complete,
3729
+ payload: { ...ctx }
3730
+ };
3731
+ }
3732
+
3733
+ //#endregion
3734
+ //#region src/protocol/generated/v2/index.ts
3735
+ var v2_exports = /* @__PURE__ */ __exportAll({});
3736
+
3737
+ //#endregion
3738
+ //#region src/provider.ts
3739
+ /**
3740
+ * Provider IDs and preset configuration for the Codex App-Server adapter.
3741
+ *
3742
+ * Provider compatibility is declared by stable definition ID — the adapter
3743
+ * subsystem resolves each ID to a full ProviderDefinitionInput from the
3744
+ * provider registry at boot time.
3745
+ */
3746
+ const providerIds = ["openai-codex"];
3747
+ /**
3748
+ * Default provider id to use when no provider is explicitly configured.
3749
+ */
3750
+ const defaultPresetId = "openai-codex";
3751
+ /** Provider id used for conformance tests (same as host default for this adapter). */
3752
+ const testPresetId = defaultPresetId;
3753
+
3754
+ //#endregion
3755
+ //#region src/index.ts
3756
+ const createTestConfig = async (options) => {
3757
+ const { scopedBus } = CodexAppServerNamespace;
3758
+ const bus = await scopedBus();
3759
+ const testPreset = resolveConformanceTestPreset({
3760
+ adapterName: CodexAppServerAdapterName,
3761
+ defaultProviderId: testPresetId,
3762
+ providerIds,
3763
+ providerDefinitions: options?.providerDefinitions,
3764
+ reasoningEffort: "low"
3765
+ });
3766
+ return {
3767
+ createConnector: async (connectorOptions) => new CodexAppServerConnector(await CodexAppServerConfig.getConfig(resolveTestConfig(connectorOptions, bus, testPreset.provider, testPreset.providers))),
3768
+ bus,
3769
+ registerToolApprovalHandler,
3770
+ capabilities: {
3771
+ supportsReplace: true,
3772
+ supportsInterrupt: true,
3773
+ supportsUsageMetrics: true
3774
+ },
3775
+ options: {
3776
+ defaultTimeout: 45e3,
3777
+ primaryModel: testPreset.primaryModel,
3778
+ secondaryModel: testPreset.secondaryModel
3779
+ },
3780
+ createAdapter: async (adapterOptions) => createCodexAppServerAdapter(adapterOptions),
3781
+ adapterName: CodexAppServerAdapterName,
3782
+ testProviderContext: testPreset.providerContext
3783
+ };
3784
+ };
3785
+
3786
+ //#endregion
3787
+ export { AgentMessageDeltaSchema, AgentMessageSchema, CodexAppServerAdapter, CodexAppServerAdapterName, CodexAppServerAgent, CodexAppServerConfig, CodexAppServerConnector, CodexAppServerNamespace, CodexAppServerProviderConfigSchema, CodexAppServerSubjects, CodexAppServerThread, CodexAppServerTurn, CodexAppServerTurnStateSchema, DynamicToolCallApprovalRequestSchema, DynamicToolCallBeginSchema, DynamicToolCallEndSchema, ExecApprovalRequestSchema, ExecCommandBeginSchema, ExecCommandEndSchema, ExecCommandOutputDeltaSchema, FileChangeApprovalRequestSchema, FileChangeOutputDeltaSchema, ItemCompletedSchema, ItemStartedSchema, ReasoningDeltaSchema, ReasoningSchema, ThreadCompletedSchema, ThreadStartedSchema, TokenUsageSchema, TurnCompletedSchema, TurnStartedSchema, TurnStateChangedSchema, TurnStepFinishedSchema, TurnStepStartedSchema, createCodexAppServerAdapter, createJsonRpcClient, createStdioTransport, createTestConfig, formatMessageHistory, fromGlobalToolApproval, normalizeAppServerEvent, registerToolApprovalHandler, toGlobalFileApproval, toGlobalToolApproval, v2_exports as v2 };