@dexto/core 1.6.12 → 1.6.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/events/index.cjs +1 -0
  2. package/dist/events/index.d.ts +16 -2
  3. package/dist/events/index.d.ts.map +1 -1
  4. package/dist/events/index.js +1 -0
  5. package/dist/llm/executor/turn-executor.cjs +11 -1
  6. package/dist/llm/executor/turn-executor.d.ts +3 -1
  7. package/dist/llm/executor/turn-executor.d.ts.map +1 -1
  8. package/dist/llm/executor/turn-executor.js +11 -1
  9. package/dist/llm/index.cjs +16 -0
  10. package/dist/llm/index.d.ts +2 -0
  11. package/dist/llm/index.d.ts.map +1 -1
  12. package/dist/llm/index.js +18 -0
  13. package/dist/llm/providers/codex-app-server.cjs +1391 -0
  14. package/dist/llm/providers/codex-app-server.d.ts +150 -0
  15. package/dist/llm/providers/codex-app-server.d.ts.map +1 -0
  16. package/dist/llm/providers/codex-app-server.js +1367 -0
  17. package/dist/llm/providers/codex-base-url.cjs +97 -0
  18. package/dist/llm/providers/codex-base-url.d.ts +9 -0
  19. package/dist/llm/providers/codex-base-url.d.ts.map +1 -0
  20. package/dist/llm/providers/codex-base-url.js +70 -0
  21. package/dist/llm/services/factory.cjs +19 -1
  22. package/dist/llm/services/factory.d.ts +3 -0
  23. package/dist/llm/services/factory.d.ts.map +1 -1
  24. package/dist/llm/services/factory.js +21 -1
  25. package/dist/session/chat-session.cjs +17 -0
  26. package/dist/session/chat-session.d.ts +1 -0
  27. package/dist/session/chat-session.d.ts.map +1 -1
  28. package/dist/session/chat-session.js +17 -0
  29. package/dist/session/session-manager.cjs +27 -1
  30. package/dist/session/session-manager.d.ts +6 -0
  31. package/dist/session/session-manager.d.ts.map +1 -1
  32. package/dist/session/session-manager.js +27 -1
  33. package/dist/utils/result.cjs +3 -2
  34. package/dist/utils/result.d.ts.map +1 -1
  35. package/dist/utils/result.js +3 -2
  36. package/package.json +1 -1
@@ -0,0 +1,1391 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var codex_app_server_exports = {};
20
+ __export(codex_app_server_exports, {
21
+ CodexAppServerClient: () => CodexAppServerClient,
22
+ createCodexLanguageModel: () => createCodexLanguageModel
23
+ });
24
+ module.exports = __toCommonJS(codex_app_server_exports);
25
+ var import_node_child_process = require("node:child_process");
26
+ var import_node_readline = require("node:readline");
27
+ var import_DextoRuntimeError = require("../../errors/DextoRuntimeError.js");
28
+ var import_types = require("../../errors/types.js");
29
+ var import_safe_stringify = require("../../utils/safe-stringify.js");
30
+ var import_error_codes = require("../error-codes.js");
31
+ var import_errors = require("../errors.js");
32
+ var import_codex_base_url = require("./codex-base-url.js");
33
+ const CODEX_PROTOCOL_ERROR_CODE = "llm_codex_protocol_invalid";
34
+ const CODEX_CLIENT_RUNTIME_ERROR_CODE = "llm_codex_client_runtime";
35
+ function createCodexProtocolError(message, context) {
36
+ return new import_DextoRuntimeError.DextoRuntimeError(
37
+ CODEX_PROTOCOL_ERROR_CODE,
38
+ import_types.ErrorScope.LLM,
39
+ import_types.ErrorType.THIRD_PARTY,
40
+ message,
41
+ context
42
+ );
43
+ }
44
+ function createCodexClientRuntimeError(message, context, type = import_types.ErrorType.SYSTEM) {
45
+ return new import_DextoRuntimeError.DextoRuntimeError(
46
+ CODEX_CLIENT_RUNTIME_ERROR_CODE,
47
+ import_types.ErrorScope.LLM,
48
+ type,
49
+ message,
50
+ context
51
+ );
52
+ }
53
+ function createCodexClientExitedError(input) {
54
+ return createCodexClientRuntimeError(
55
+ `Codex app-server exited unexpectedly (${input.signal ?? `code ${input.code ?? "unknown"}`})`,
56
+ {
57
+ ...input.code !== void 0 ? { code: input.code } : {},
58
+ ...input.signal !== void 0 ? { signal: input.signal } : {}
59
+ },
60
+ import_types.ErrorType.THIRD_PARTY
61
+ );
62
+ }
63
+ const DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
64
+ const DEFAULT_CLIENT_INFO = {
65
+ name: "dexto",
66
+ title: "Dexto",
67
+ version: "1.0.0"
68
+ };
69
+ const CODEX_DEVELOPER_INSTRUCTIONS = [
70
+ "You are providing model responses for a host application.",
71
+ "Treat the provided input as the full conversation transcript.",
72
+ "Use the host-provided dynamic tools when tool use is needed.",
73
+ "Do not use Codex built-in tools, shell commands, file edits, approvals, MCP tools, or ask-user flows.",
74
+ "When you are not calling a tool, answer with the assistant response only."
75
+ ].join(" ");
76
+ function isRecord(value) {
77
+ return typeof value === "object" && value !== null;
78
+ }
79
+ function getString(value) {
80
+ return typeof value === "string" && value.trim().length > 0 ? value : null;
81
+ }
82
+ function getBoolean(value) {
83
+ return typeof value === "boolean" ? value : null;
84
+ }
85
+ function getNumber(value) {
86
+ if (typeof value === "number" && Number.isFinite(value)) {
87
+ return value;
88
+ }
89
+ if (typeof value === "string") {
90
+ const parsed = Number(value);
91
+ return Number.isFinite(parsed) ? parsed : null;
92
+ }
93
+ return null;
94
+ }
95
+ function getArray(value) {
96
+ return Array.isArray(value) ? value : null;
97
+ }
98
+ function normalizeError(error) {
99
+ return error instanceof Error ? error : createCodexClientRuntimeError(String(error));
100
+ }
101
+ function parseCodexAccount(value) {
102
+ if (!isRecord(value)) {
103
+ return null;
104
+ }
105
+ const type = getString(value["type"]);
106
+ if (type === "apiKey") {
107
+ return { type: "apiKey" };
108
+ }
109
+ if (type !== "chatgpt") {
110
+ return null;
111
+ }
112
+ const email = getString(value["email"]);
113
+ const planType = getString(value["planType"]);
114
+ if (!email || !planType) {
115
+ return null;
116
+ }
117
+ return {
118
+ type: "chatgpt",
119
+ email,
120
+ planType
121
+ };
122
+ }
123
+ function parseReadAccountResponse(value) {
124
+ if (!isRecord(value)) {
125
+ throw createCodexProtocolError("Invalid account/read response from Codex", {
126
+ method: "account/read"
127
+ });
128
+ }
129
+ const requiresOpenaiAuth = getBoolean(value["requiresOpenaiAuth"]);
130
+ if (requiresOpenaiAuth === null) {
131
+ throw createCodexProtocolError(
132
+ "Codex account/read response is missing requiresOpenaiAuth",
133
+ {
134
+ method: "account/read"
135
+ }
136
+ );
137
+ }
138
+ return {
139
+ account: parseCodexAccount(value["account"]),
140
+ requiresOpenaiAuth
141
+ };
142
+ }
143
+ function parseLoginResponse(value) {
144
+ if (!isRecord(value)) {
145
+ throw createCodexProtocolError("Invalid account/login/start response from Codex", {
146
+ method: "account/login/start"
147
+ });
148
+ }
149
+ const type = getString(value["type"]);
150
+ if (!type) {
151
+ throw createCodexProtocolError("Codex login response is missing type", {
152
+ method: "account/login/start"
153
+ });
154
+ }
155
+ if (type === "apiKey") {
156
+ return { type };
157
+ }
158
+ if (type === "chatgptAuthTokens") {
159
+ return { type };
160
+ }
161
+ if (type !== "chatgpt") {
162
+ throw createCodexProtocolError(`Unsupported Codex login response type: ${type}`, {
163
+ method: "account/login/start",
164
+ type
165
+ });
166
+ }
167
+ const loginId = getString(value["loginId"]);
168
+ const authUrl = getString(value["authUrl"]);
169
+ if (!loginId || !authUrl) {
170
+ throw createCodexProtocolError("Codex ChatGPT login response is missing login details", {
171
+ method: "account/login/start"
172
+ });
173
+ }
174
+ return {
175
+ type,
176
+ loginId,
177
+ authUrl
178
+ };
179
+ }
180
+ function parseModelListResponse(value) {
181
+ if (!isRecord(value)) {
182
+ throw createCodexProtocolError("Invalid model/list response from Codex", {
183
+ method: "model/list"
184
+ });
185
+ }
186
+ const data = getArray(value["data"]);
187
+ if (!data) {
188
+ throw createCodexProtocolError("Codex model/list response is missing data", {
189
+ method: "model/list"
190
+ });
191
+ }
192
+ const models = [];
193
+ for (const entry of data) {
194
+ if (!isRecord(entry)) {
195
+ continue;
196
+ }
197
+ const id = getString(entry["id"]);
198
+ const model = getString(entry["model"]);
199
+ const displayName = getString(entry["displayName"]);
200
+ const description = getString(entry["description"]);
201
+ const hidden = getBoolean(entry["hidden"]);
202
+ const isDefault = getBoolean(entry["isDefault"]);
203
+ const defaultReasoningEffort = getString(entry["defaultReasoningEffort"]);
204
+ const supportedReasoningEffortsRaw = getArray(entry["supportedReasoningEfforts"]);
205
+ if (!id || !model || !displayName || description === null || hidden === null || isDefault === null || !defaultReasoningEffort || !supportedReasoningEffortsRaw) {
206
+ continue;
207
+ }
208
+ const supportedReasoningEfforts = supportedReasoningEffortsRaw.map((candidate) => getString(candidate)).filter((candidate) => candidate !== null);
209
+ models.push({
210
+ id,
211
+ model,
212
+ displayName,
213
+ description,
214
+ hidden,
215
+ isDefault,
216
+ supportedReasoningEfforts,
217
+ defaultReasoningEffort
218
+ });
219
+ }
220
+ return {
221
+ data: models,
222
+ nextCursor: getString(value["nextCursor"])
223
+ };
224
+ }
225
+ function parseThreadStartResponse(value) {
226
+ if (!isRecord(value) || !isRecord(value["thread"])) {
227
+ throw createCodexProtocolError("Invalid thread/start response from Codex", {
228
+ method: "thread/start"
229
+ });
230
+ }
231
+ const threadId = getString(value["thread"]["id"]);
232
+ if (!threadId) {
233
+ throw createCodexProtocolError("Codex thread/start response is missing a thread ID", {
234
+ method: "thread/start"
235
+ });
236
+ }
237
+ return {
238
+ thread: {
239
+ id: threadId
240
+ }
241
+ };
242
+ }
243
+ function parseTurnStartResponse(value) {
244
+ if (!isRecord(value) || !isRecord(value["turn"])) {
245
+ throw createCodexProtocolError("Invalid turn/start response from Codex", {
246
+ method: "turn/start"
247
+ });
248
+ }
249
+ const turnId = getString(value["turn"]["id"]);
250
+ if (!turnId) {
251
+ throw createCodexProtocolError("Codex turn/start response is missing a turn ID", {
252
+ method: "turn/start"
253
+ });
254
+ }
255
+ return {
256
+ turn: {
257
+ id: turnId
258
+ }
259
+ };
260
+ }
261
+ function parseRateLimitEntry(value) {
262
+ if (!isRecord(value)) {
263
+ return null;
264
+ }
265
+ const usedPercent = getNumber(value["usedPercent"]);
266
+ if (usedPercent === null) {
267
+ return null;
268
+ }
269
+ const normalizedUsedPercent = Math.max(0, Math.min(100, usedPercent));
270
+ const windowMinutes = getNumber(value["windowDurationMins"]) ?? getNumber(value["windowMinutes"]);
271
+ const limitId = getString(value["limitId"]);
272
+ const limitName = getString(value["limitName"]);
273
+ const resetsAt = getString(value["resetsAt"]);
274
+ return {
275
+ source: "chatgpt-login",
276
+ usedPercent: normalizedUsedPercent,
277
+ exceeded: normalizedUsedPercent >= 100,
278
+ ...limitId ? { limitId } : {},
279
+ ...limitName ? { limitName } : {},
280
+ ...resetsAt ? { resetsAt } : {},
281
+ ...windowMinutes !== null ? { windowMinutes } : {}
282
+ };
283
+ }
284
+ function collectRateLimitEntries(value) {
285
+ const entries = [];
286
+ const directEntry = parseRateLimitEntry(value);
287
+ if (directEntry) {
288
+ entries.push(directEntry);
289
+ }
290
+ if (!isRecord(value)) {
291
+ return entries;
292
+ }
293
+ const rateLimits = getArray(value["rateLimits"]);
294
+ if (rateLimits) {
295
+ for (const candidate of rateLimits) {
296
+ const entry = parseRateLimitEntry(candidate);
297
+ if (entry) {
298
+ entries.push(entry);
299
+ }
300
+ }
301
+ }
302
+ const rateLimitsByLimitId = isRecord(value["rateLimitsByLimitId"]) ? value["rateLimitsByLimitId"] : null;
303
+ if (rateLimitsByLimitId) {
304
+ for (const candidate of Object.values(rateLimitsByLimitId)) {
305
+ if (Array.isArray(candidate)) {
306
+ for (const item of candidate) {
307
+ const entry2 = parseRateLimitEntry(item);
308
+ if (entry2) {
309
+ entries.push(entry2);
310
+ }
311
+ }
312
+ continue;
313
+ }
314
+ const entry = parseRateLimitEntry(candidate);
315
+ if (entry) {
316
+ entries.push(entry);
317
+ }
318
+ }
319
+ }
320
+ return entries;
321
+ }
322
+ function pickPrimaryRateLimitSnapshot(value) {
323
+ const entries = collectRateLimitEntries(value);
324
+ if (entries.length === 0) {
325
+ return null;
326
+ }
327
+ entries.sort((left, right) => {
328
+ if (left.exceeded !== right.exceeded) {
329
+ return left.exceeded ? -1 : 1;
330
+ }
331
+ if (left.usedPercent !== right.usedPercent) {
332
+ return right.usedPercent - left.usedPercent;
333
+ }
334
+ const leftReset = left.resetsAt ? Date.parse(left.resetsAt) : Number.POSITIVE_INFINITY;
335
+ const rightReset = right.resetsAt ? Date.parse(right.resetsAt) : Number.POSITIVE_INFINITY;
336
+ return leftReset - rightReset;
337
+ });
338
+ return entries[0] ?? null;
339
+ }
340
+ function parseCodexErrorDetails(value) {
341
+ if (!isRecord(value)) {
342
+ return null;
343
+ }
344
+ const codexErrorInfo = isRecord(value["codexErrorInfo"]) ? value["codexErrorInfo"] : isRecord(value["codex_error_info"]) ? value["codex_error_info"] : null;
345
+ return {
346
+ message: getString(value["message"]),
347
+ additionalDetails: getString(value["additionalDetails"]) ?? getString(value["additional_details"]),
348
+ errorInfoKeys: codexErrorInfo ? Object.keys(codexErrorInfo) : []
349
+ };
350
+ }
351
+ function isUsageLimitError(details) {
352
+ if (!details) {
353
+ return false;
354
+ }
355
+ const normalizedInfoKeys = details.errorInfoKeys.map((key) => key.toLowerCase());
356
+ if (normalizedInfoKeys.includes("usagelimitexceeded")) {
357
+ return true;
358
+ }
359
+ const combinedText = `${details.message ?? ""} ${details.additionalDetails ?? ""}`.toLowerCase();
360
+ return combinedText.includes("usage limit") || combinedText.includes("rate limit") || combinedText.includes("quota exceeded") || combinedText.includes("purchase more credits");
361
+ }
362
+ function buildUsageLimitSnapshot(existing) {
363
+ if (!existing) {
364
+ return {
365
+ source: "chatgpt-login",
366
+ usedPercent: 100,
367
+ exceeded: true
368
+ };
369
+ }
370
+ return {
371
+ ...existing,
372
+ usedPercent: Math.max(100, existing.usedPercent),
373
+ exceeded: true
374
+ };
375
+ }
376
+ function toChatGPTUsageLimitError(details, modelId, snapshot) {
377
+ const message = details.message ?? "You have reached your ChatGPT usage limit.";
378
+ return new import_DextoRuntimeError.DextoRuntimeError(
379
+ import_error_codes.LLMErrorCode.RATE_LIMIT_EXCEEDED,
380
+ import_types.ErrorScope.LLM,
381
+ import_types.ErrorType.RATE_LIMIT,
382
+ message,
383
+ {
384
+ provider: "openai-compatible",
385
+ model: modelId,
386
+ source: "chatgpt-login",
387
+ ...snapshot?.limitId ? { limitId: snapshot.limitId } : {},
388
+ ...snapshot?.limitName ? { limitName: snapshot.limitName } : {},
389
+ ...snapshot?.resetsAt ? { resetsAt: snapshot.resetsAt } : {},
390
+ ...snapshot?.windowMinutes !== void 0 ? { windowMinutes: snapshot.windowMinutes } : {},
391
+ usedPercent: snapshot?.usedPercent ?? 100,
392
+ additionalDetails: details.additionalDetails ?? void 0,
393
+ errorInfoKeys: details.errorInfoKeys
394
+ },
395
+ [
396
+ "Wait for your ChatGPT usage window to reset, or switch this session to an OpenAI API key.",
397
+ "Use `/model` to move this session onto your API key-backed OpenAI provider."
398
+ ]
399
+ );
400
+ }
401
+ function createUsage() {
402
+ return {
403
+ inputTokens: void 0,
404
+ outputTokens: void 0,
405
+ totalTokens: void 0
406
+ };
407
+ }
408
+ function promptMessageToTranscript(message) {
409
+ if (message.role === "system") {
410
+ return `System:
411
+ ${message.content}`;
412
+ }
413
+ const parts = message.role === "tool" ? message.content.map((part) => {
414
+ const output = (0, import_safe_stringify.safeStringify)(part.output);
415
+ return `[Tool Result: ${part.toolName}#${part.toolCallId}]
416
+ ${output}`;
417
+ }) : message.content.map((part) => {
418
+ switch (part.type) {
419
+ case "text":
420
+ return part.text;
421
+ case "reasoning":
422
+ return `[Reasoning]
423
+ ${part.text}`;
424
+ case "tool-call":
425
+ return `[Tool Call: ${part.toolName}#${part.toolCallId}]
426
+ ${(0, import_safe_stringify.safeStringify)(part.input)}`;
427
+ case "tool-result":
428
+ return `[Tool Result: ${part.toolName}#${part.toolCallId}]
429
+ ${(0, import_safe_stringify.safeStringify)(part.output)}`;
430
+ case "file": {
431
+ const url = part.data instanceof URL ? part.data.toString() : typeof part.data === "string" && /^(https?:)?\/\//i.test(part.data) ? part.data : null;
432
+ const fileLabel = part.filename ?? part.mediaType;
433
+ if (url) {
434
+ return `[File: ${fileLabel}] ${url}`;
435
+ }
436
+ return `[File: ${fileLabel}] data omitted`;
437
+ }
438
+ default: {
439
+ const unknownType = isRecord(part) && typeof part["type"] === "string" ? part["type"] : "unknown";
440
+ return `[Unknown Prompt Part: ${unknownType}]`;
441
+ }
442
+ }
443
+ });
444
+ const roleLabel = message.role === "user" ? "User" : message.role === "assistant" ? "Assistant" : "Tool";
445
+ return `${roleLabel}:
446
+ ${parts.join("\n")}`;
447
+ }
448
+ function buildTranscript(prompt) {
449
+ return prompt.map((message) => promptMessageToTranscript(message)).join("\n\n");
450
+ }
451
+ function isFunctionTool(tool) {
452
+ return tool.type === "function";
453
+ }
454
+ function selectCodexFunctionTools(options) {
455
+ const functionTools = (options.tools ?? []).filter(isFunctionTool);
456
+ const toolChoice = options.toolChoice;
457
+ if (!toolChoice || toolChoice.type === "auto" || toolChoice.type === "required") {
458
+ return functionTools;
459
+ }
460
+ if (toolChoice.type === "none") {
461
+ return [];
462
+ }
463
+ return functionTools.filter((tool) => tool.name === toolChoice.toolName);
464
+ }
465
+ function buildDynamicTools(options) {
466
+ return selectCodexFunctionTools(options).map((tool) => ({
467
+ name: tool.name,
468
+ ...tool.description ? { description: tool.description } : {},
469
+ inputSchema: tool.inputSchema
470
+ }));
471
+ }
472
+ function buildWarnings(options) {
473
+ const warnings = [];
474
+ const unsupportedSettings = [
475
+ "maxOutputTokens",
476
+ "temperature",
477
+ "stopSequences",
478
+ "topP",
479
+ "topK",
480
+ "presencePenalty",
481
+ "frequencyPenalty",
482
+ "seed"
483
+ ];
484
+ for (const setting of unsupportedSettings) {
485
+ if (options[setting] !== void 0) {
486
+ warnings.push({
487
+ type: "unsupported-setting",
488
+ setting,
489
+ details: "Codex app-server manages this setting internally."
490
+ });
491
+ }
492
+ }
493
+ if (options.tools) {
494
+ for (const tool of options.tools) {
495
+ if (!isFunctionTool(tool)) {
496
+ warnings.push({
497
+ type: "unsupported-tool",
498
+ tool,
499
+ details: "Codex app-server integration only supports host function tools."
500
+ });
501
+ }
502
+ }
503
+ }
504
+ return warnings;
505
+ }
506
+ function getRequestedReasoningEffort(options) {
507
+ if (!isRecord(options.providerOptions)) {
508
+ return null;
509
+ }
510
+ const openAiCompatibleOptions = options.providerOptions["openaiCompatible"];
511
+ if (!isRecord(openAiCompatibleOptions)) {
512
+ return null;
513
+ }
514
+ const reasoningEffort = getString(openAiCompatibleOptions["reasoningEffort"]);
515
+ return reasoningEffort ?? null;
516
+ }
517
+ function buildDeveloperInstructions(options, dynamicTools) {
518
+ const instructions = [CODEX_DEVELOPER_INSTRUCTIONS];
519
+ if (dynamicTools.length > 0) {
520
+ instructions.push(
521
+ "Host-provided dynamic tools are available for this turn. Prefer them over guessing."
522
+ );
523
+ if (options.toolChoice?.type === "required") {
524
+ instructions.push(
525
+ "You must call at least one host-provided dynamic tool before your final answer."
526
+ );
527
+ }
528
+ } else {
529
+ instructions.push("No host-provided dynamic tools are available for this turn.");
530
+ }
531
+ if (options.toolChoice?.type === "tool") {
532
+ instructions.push(
533
+ `If you need a tool, use only the host tool named "${options.toolChoice.toolName}".`
534
+ );
535
+ }
536
+ if (options.responseFormat?.type === "json" && !options.responseFormat.schema) {
537
+ instructions.push("Return valid JSON only. Do not wrap it in Markdown fences.");
538
+ }
539
+ return instructions.join(" ");
540
+ }
541
+ function toCodexFailureMessage(error, modelId) {
542
+ if (error instanceof import_DextoRuntimeError.DextoRuntimeError) {
543
+ return error;
544
+ }
545
+ const normalized = normalizeError(error);
546
+ if (normalized.message.includes("spawn codex ENOENT")) {
547
+ return import_errors.LLMError.missingConfig(
548
+ "openai-compatible",
549
+ "the Codex CLI on PATH (install Codex to use ChatGPT Login in Dexto)"
550
+ );
551
+ }
552
+ const fallbackDetails = {
553
+ message: normalized.message,
554
+ additionalDetails: null,
555
+ errorInfoKeys: []
556
+ };
557
+ if (isUsageLimitError(fallbackDetails)) {
558
+ return toChatGPTUsageLimitError(fallbackDetails, modelId, null);
559
+ }
560
+ return import_errors.LLMError.generationFailed(normalized.message, "openai-compatible", modelId);
561
+ }
562
+ function enforceAuthMode(authMode, accountState) {
563
+ if (!accountState.account) {
564
+ if (accountState.requiresOpenaiAuth) {
565
+ throw import_errors.LLMError.missingConfig(
566
+ "openai-compatible",
567
+ "Codex authentication (run `codex login` or re-run `dexto setup` and choose ChatGPT Login)"
568
+ );
569
+ }
570
+ return;
571
+ }
572
+ if (authMode === "auto") {
573
+ return;
574
+ }
575
+ if (authMode === "chatgpt" && accountState.account.type !== "chatgpt") {
576
+ throw import_errors.LLMError.missingConfig(
577
+ "openai-compatible",
578
+ "a ChatGPT-backed Codex login (run `codex logout` and sign in with ChatGPT, or re-run `dexto setup`)"
579
+ );
580
+ }
581
+ if (authMode === "apikey" && accountState.account.type !== "apiKey") {
582
+ throw import_errors.LLMError.missingConfig(
583
+ "openai-compatible",
584
+ "an API key-backed Codex login (run `codex login --with-api-key` or re-run `dexto setup`)"
585
+ );
586
+ }
587
+ }
588
+ function extractErrorMessage(params) {
589
+ if (!isRecord(params) || !isRecord(params["error"])) {
590
+ return null;
591
+ }
592
+ return getString(params["error"]["message"]);
593
+ }
594
+ function stringifyToolInput(input) {
595
+ try {
596
+ const serialized = JSON.stringify(input ?? {});
597
+ return serialized === void 0 ? "{}" : serialized;
598
+ } catch {
599
+ return "{}";
600
+ }
601
+ }
602
+ function parseDynamicToolCallRequest(params) {
603
+ if (!isRecord(params)) {
604
+ return null;
605
+ }
606
+ const threadId = getString(params["threadId"]);
607
+ const turnId = getString(params["turnId"]);
608
+ const callId = getString(params["callId"]);
609
+ const toolName = getString(params["tool"]);
610
+ if (!threadId || !turnId || !callId || !toolName) {
611
+ return null;
612
+ }
613
+ return {
614
+ threadId,
615
+ turnId,
616
+ callId,
617
+ toolName,
618
+ input: stringifyToolInput(params["arguments"])
619
+ };
620
+ }
621
+ class CodexAppServerClient {
622
+ command;
623
+ cwd;
624
+ requestTimeoutMs;
625
+ clientInfo;
626
+ child = null;
627
+ reader = null;
628
+ nextId = 1;
629
+ pending = /* @__PURE__ */ new Map();
630
+ listeners = /* @__PURE__ */ new Set();
631
+ requestListeners = /* @__PURE__ */ new Set();
632
+ started = false;
633
+ closed = false;
634
+ constructor(options = {}) {
635
+ this.command = options.command ?? "codex";
636
+ this.cwd = options.cwd;
637
+ this.requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
638
+ this.clientInfo = options.clientInfo ?? DEFAULT_CLIENT_INFO;
639
+ }
640
+ static async create(options = {}) {
641
+ const client = new CodexAppServerClient(options);
642
+ try {
643
+ await client.start();
644
+ return client;
645
+ } catch (error) {
646
+ await client.close().catch(() => void 0);
647
+ throw error;
648
+ }
649
+ }
650
+ async close() {
651
+ if (this.closed) {
652
+ return;
653
+ }
654
+ this.closed = true;
655
+ this.started = false;
656
+ this.rejectPending(createCodexClientRuntimeError("Codex app-server client closed"));
657
+ this.listeners.clear();
658
+ this.requestListeners.clear();
659
+ if (this.reader) {
660
+ this.reader.close();
661
+ this.reader = null;
662
+ }
663
+ const child = this.child;
664
+ this.child = null;
665
+ if (!child || child.killed || child.exitCode !== null || child.signalCode !== null) {
666
+ return;
667
+ }
668
+ await new Promise((resolve) => {
669
+ child.once("exit", () => resolve());
670
+ child.kill();
671
+ });
672
+ }
673
+ onNotification(listener) {
674
+ this.listeners.add(listener);
675
+ return () => {
676
+ this.listeners.delete(listener);
677
+ };
678
+ }
679
+ onServerRequest(listener) {
680
+ this.requestListeners.add(listener);
681
+ return () => {
682
+ this.requestListeners.delete(listener);
683
+ };
684
+ }
685
+ async readAccount(refreshToken = false) {
686
+ const result = await this.request("account/read", { refreshToken });
687
+ return parseReadAccountResponse(result);
688
+ }
689
+ async logout() {
690
+ await this.request("account/logout", void 0);
691
+ }
692
+ async startLogin(params) {
693
+ const result = await this.request("account/login/start", params);
694
+ return parseLoginResponse(result);
695
+ }
696
+ async waitForLoginCompleted(loginId, options = {}) {
697
+ const params = await this.waitForNotification(
698
+ "account/login/completed",
699
+ (candidate) => {
700
+ if (!isRecord(candidate)) {
701
+ return false;
702
+ }
703
+ const candidateLoginId = getString(candidate["loginId"]);
704
+ return candidateLoginId === loginId;
705
+ },
706
+ options
707
+ );
708
+ if (!isRecord(params)) {
709
+ throw createCodexProtocolError(
710
+ "Invalid account/login/completed notification from Codex",
711
+ {
712
+ method: "account/login/completed"
713
+ }
714
+ );
715
+ }
716
+ const success = getBoolean(params["success"]);
717
+ if (success === null) {
718
+ throw createCodexProtocolError("Codex login completion is missing success", {
719
+ method: "account/login/completed"
720
+ });
721
+ }
722
+ return {
723
+ loginId: getString(params["loginId"]),
724
+ success,
725
+ error: getString(params["error"])
726
+ };
727
+ }
728
+ async listModels() {
729
+ const models = [];
730
+ let cursor = null;
731
+ while (true) {
732
+ const result = await this.request("model/list", {
733
+ includeHidden: false,
734
+ ...cursor ? { cursor } : {}
735
+ });
736
+ const parsed = parseModelListResponse(result);
737
+ models.push(...parsed.data);
738
+ if (!parsed.nextCursor) {
739
+ return models;
740
+ }
741
+ cursor = parsed.nextCursor;
742
+ }
743
+ }
744
+ async readRateLimits() {
745
+ const result = await this.request("account/rateLimits/read", void 0);
746
+ return pickPrimaryRateLimitSnapshot(result);
747
+ }
748
+ async startEphemeralThread(params) {
749
+ const result = await this.request("thread/start", {
750
+ model: params.model,
751
+ ...params.cwd ? { cwd: params.cwd } : {},
752
+ approvalPolicy: "untrusted",
753
+ sandbox: "read-only",
754
+ developerInstructions: params.developerInstructions ?? CODEX_DEVELOPER_INSTRUCTIONS,
755
+ ...params.dynamicTools && params.dynamicTools.length > 0 ? { dynamicTools: params.dynamicTools } : {},
756
+ ephemeral: true,
757
+ experimentalRawEvents: false,
758
+ persistExtendedHistory: false
759
+ });
760
+ return parseThreadStartResponse(result);
761
+ }
762
+ async startTurn(params) {
763
+ const result = await this.request("turn/start", {
764
+ threadId: params.threadId,
765
+ model: params.model,
766
+ input: [
767
+ {
768
+ type: "text",
769
+ text: params.transcript,
770
+ text_elements: []
771
+ }
772
+ ],
773
+ ...params.reasoningEffort ? { effort: params.reasoningEffort } : {},
774
+ ...params.outputSchema ? { outputSchema: params.outputSchema } : {}
775
+ });
776
+ return parseTurnStartResponse(result);
777
+ }
778
+ async waitForNotification(method, predicate, options = {}) {
779
+ if (this.closed) {
780
+ throw createCodexClientRuntimeError("Codex app-server client is closed");
781
+ }
782
+ const timeoutMs = options.timeoutMs ?? this.requestTimeoutMs;
783
+ return await new Promise((resolve, reject) => {
784
+ let timeout = null;
785
+ let offAbort = null;
786
+ const cleanup = (unsubscribe2) => {
787
+ unsubscribe2();
788
+ if (timeout) {
789
+ clearTimeout(timeout);
790
+ }
791
+ if (offAbort) {
792
+ offAbort();
793
+ }
794
+ };
795
+ const unsubscribe = this.onNotification((message) => {
796
+ if (message.method !== method) {
797
+ return;
798
+ }
799
+ if (predicate && !predicate(message.params)) {
800
+ return;
801
+ }
802
+ cleanup(unsubscribe);
803
+ resolve(message.params);
804
+ });
805
+ timeout = setTimeout(() => {
806
+ cleanup(unsubscribe);
807
+ reject(
808
+ createCodexClientRuntimeError(
809
+ `Timed out waiting for Codex notification: ${method}`,
810
+ { method },
811
+ import_types.ErrorType.TIMEOUT
812
+ )
813
+ );
814
+ }, timeoutMs);
815
+ if (options.signal) {
816
+ const onAbort = () => {
817
+ cleanup(unsubscribe);
818
+ reject(
819
+ options.signal?.reason instanceof Error ? options.signal.reason : createCodexClientRuntimeError(
820
+ "Codex operation aborted",
821
+ { method },
822
+ import_types.ErrorType.USER
823
+ )
824
+ );
825
+ };
826
+ if (options.signal.aborted) {
827
+ onAbort();
828
+ return;
829
+ }
830
+ options.signal.addEventListener("abort", onAbort, { once: true });
831
+ offAbort = () => options.signal?.removeEventListener("abort", onAbort);
832
+ }
833
+ });
834
+ }
835
+ async start() {
836
+ if (this.started) {
837
+ return;
838
+ }
839
+ const child = (0, import_node_child_process.spawn)(this.command, ["app-server"], {
840
+ cwd: this.cwd,
841
+ stdio: ["pipe", "pipe", "pipe"]
842
+ });
843
+ this.child = child;
844
+ this.reader = (0, import_node_readline.createInterface)({
845
+ input: child.stdout,
846
+ crlfDelay: Infinity
847
+ });
848
+ const drainStderr = () => void 0;
849
+ child.stderr.on("data", drainStderr);
850
+ child.on("error", (error) => {
851
+ this.rejectPending(error);
852
+ });
853
+ child.on("exit", (code, signal) => {
854
+ const wasClosed = this.closed;
855
+ const error = createCodexClientExitedError({
856
+ ...code !== null ? { code } : {},
857
+ ...signal !== null ? { signal } : {}
858
+ });
859
+ child.stderr.off("data", drainStderr);
860
+ if (!wasClosed) {
861
+ this.publishNotification({
862
+ method: "codex/client-exited",
863
+ params: {
864
+ ...code !== null ? { code } : {},
865
+ ...signal !== null ? { signal } : {}
866
+ }
867
+ });
868
+ this.rejectPending(error);
869
+ }
870
+ });
871
+ this.reader.on("line", (line) => {
872
+ this.handleLine(line);
873
+ });
874
+ await this.request("initialize", {
875
+ clientInfo: this.clientInfo,
876
+ capabilities: {
877
+ experimentalApi: true
878
+ }
879
+ });
880
+ this.notify("initialized", {});
881
+ this.started = true;
882
+ }
883
+ handleLine(line) {
884
+ if (!line.trim()) {
885
+ return;
886
+ }
887
+ let payload;
888
+ try {
889
+ payload = JSON.parse(line);
890
+ } catch {
891
+ return;
892
+ }
893
+ if (!isRecord(payload)) {
894
+ return;
895
+ }
896
+ const id = payload["id"];
897
+ const method = getString(payload["method"]);
898
+ if ((typeof id === "number" || typeof id === "string") && method) {
899
+ const request = {
900
+ id,
901
+ method,
902
+ ...payload["params"] !== void 0 ? { params: payload["params"] } : {}
903
+ };
904
+ for (const listener of this.requestListeners) {
905
+ listener(request);
906
+ }
907
+ return;
908
+ }
909
+ if (typeof id === "number") {
910
+ const pending = this.pending.get(id);
911
+ if (!pending) {
912
+ return;
913
+ }
914
+ this.pending.delete(id);
915
+ clearTimeout(pending.timeout);
916
+ if (isRecord(payload["error"])) {
917
+ const message = getString(payload["error"]["message"]) ?? "Codex JSON-RPC request failed";
918
+ pending.reject(
919
+ createCodexClientRuntimeError(message, { id }, import_types.ErrorType.THIRD_PARTY)
920
+ );
921
+ return;
922
+ }
923
+ pending.resolve(payload["result"]);
924
+ return;
925
+ }
926
+ if (!method) {
927
+ return;
928
+ }
929
+ const notification = {
930
+ method,
931
+ ...payload["params"] !== void 0 ? { params: payload["params"] } : {}
932
+ };
933
+ this.publishNotification(notification);
934
+ }
935
+ publishNotification(notification) {
936
+ for (const listener of this.listeners) {
937
+ listener(notification);
938
+ }
939
+ }
940
+ notify(method, params) {
941
+ this.write({ method, params });
942
+ }
943
+ async request(method, params) {
944
+ if (this.closed) {
945
+ throw createCodexClientRuntimeError("Codex app-server client is closed");
946
+ }
947
+ const id = this.nextId++;
948
+ return await new Promise((resolve, reject) => {
949
+ const timeout = setTimeout(() => {
950
+ this.pending.delete(id);
951
+ reject(
952
+ createCodexClientRuntimeError(
953
+ `Codex request timed out: ${method}`,
954
+ { method, id },
955
+ import_types.ErrorType.TIMEOUT
956
+ )
957
+ );
958
+ }, this.requestTimeoutMs);
959
+ this.pending.set(id, {
960
+ resolve,
961
+ reject,
962
+ timeout
963
+ });
964
+ try {
965
+ this.write({
966
+ method,
967
+ id,
968
+ ...params !== void 0 ? { params } : {}
969
+ });
970
+ } catch (error) {
971
+ clearTimeout(timeout);
972
+ this.pending.delete(id);
973
+ reject(error);
974
+ }
975
+ });
976
+ }
977
+ respondToServerRequest(id, result) {
978
+ this.write({
979
+ id,
980
+ result
981
+ });
982
+ }
983
+ rejectServerRequest(id, message, code = -32601) {
984
+ this.write({
985
+ id,
986
+ error: {
987
+ code,
988
+ message
989
+ }
990
+ });
991
+ }
992
+ write(payload) {
993
+ if (!this.child?.stdin.writable) {
994
+ throw createCodexClientRuntimeError("Codex app-server stdin is not writable");
995
+ }
996
+ this.child.stdin.write(`${JSON.stringify(payload)}
997
+ `);
998
+ }
999
+ rejectPending(error) {
1000
+ const normalized = normalizeError(error);
1001
+ for (const [id, pending] of this.pending.entries()) {
1002
+ clearTimeout(pending.timeout);
1003
+ pending.reject(normalized);
1004
+ this.pending.delete(id);
1005
+ }
1006
+ }
1007
+ }
1008
+ function createCodexLanguageModel(options) {
1009
+ const parsedBaseURL = (0, import_codex_base_url.parseCodexBaseURL)(options.baseURL);
1010
+ const authMode = parsedBaseURL?.authMode ?? "auto";
1011
+ async function executeTurn(callOptions) {
1012
+ const warnings = buildWarnings(callOptions);
1013
+ const transcript = buildTranscript(callOptions.prompt);
1014
+ const reasoningEffort = getRequestedReasoningEffort(callOptions);
1015
+ const dynamicTools = buildDynamicTools(callOptions);
1016
+ const developerInstructions = buildDeveloperInstructions(callOptions, dynamicTools);
1017
+ const outputSchema = callOptions.responseFormat?.type === "json" ? callOptions.responseFormat.schema : void 0;
1018
+ let latestRateLimitSnapshot = null;
1019
+ const emitRateLimitStatus = (snapshot) => {
1020
+ if (!snapshot) {
1021
+ return;
1022
+ }
1023
+ latestRateLimitSnapshot = snapshot;
1024
+ options.onRateLimitStatus?.(snapshot);
1025
+ };
1026
+ let client = null;
1027
+ try {
1028
+ client = await CodexAppServerClient.create({
1029
+ ...options.cwd ? { cwd: options.cwd } : {}
1030
+ });
1031
+ const activeClient = client;
1032
+ const account = await activeClient.readAccount(false);
1033
+ enforceAuthMode(authMode, account);
1034
+ const shouldTrackRateLimits = account.account?.type === "chatgpt" || authMode === "chatgpt";
1035
+ if (shouldTrackRateLimits) {
1036
+ void activeClient.readRateLimits().then((snapshot) => emitRateLimitStatus(snapshot)).catch(() => void 0);
1037
+ }
1038
+ const thread = await activeClient.startEphemeralThread({
1039
+ model: options.modelId,
1040
+ ...options.cwd ? { cwd: options.cwd } : {},
1041
+ developerInstructions,
1042
+ dynamicTools
1043
+ });
1044
+ const turn = await activeClient.startTurn({
1045
+ threadId: thread.thread.id,
1046
+ model: options.modelId,
1047
+ transcript,
1048
+ ...reasoningEffort ? { reasoningEffort } : {},
1049
+ ...outputSchema ? { outputSchema } : {}
1050
+ });
1051
+ const stream = new ReadableStream({
1052
+ start(controller) {
1053
+ let closed = false;
1054
+ let streamStarted = false;
1055
+ let textStarted = false;
1056
+ let textEnded = false;
1057
+ let emittedText = "";
1058
+ let offAbort = null;
1059
+ const textPartId = `codex-text-${turn.turn.id}`;
1060
+ const cleanup = () => {
1061
+ if (closed) {
1062
+ return;
1063
+ }
1064
+ closed = true;
1065
+ unsubscribeNotifications();
1066
+ unsubscribeRequests();
1067
+ if (offAbort) {
1068
+ offAbort();
1069
+ offAbort = null;
1070
+ }
1071
+ void activeClient.close();
1072
+ };
1073
+ const ensureStreamStarted = () => {
1074
+ if (streamStarted) {
1075
+ return;
1076
+ }
1077
+ streamStarted = true;
1078
+ controller.enqueue({
1079
+ type: "stream-start",
1080
+ warnings
1081
+ });
1082
+ };
1083
+ const endText = () => {
1084
+ if (textStarted && !textEnded) {
1085
+ controller.enqueue({
1086
+ type: "text-end",
1087
+ id: textPartId
1088
+ });
1089
+ textEnded = true;
1090
+ }
1091
+ };
1092
+ const finishStream = (finishReason) => {
1093
+ ensureStreamStarted();
1094
+ endText();
1095
+ controller.enqueue({
1096
+ type: "finish",
1097
+ finishReason,
1098
+ usage: createUsage()
1099
+ });
1100
+ controller.close();
1101
+ cleanup();
1102
+ };
1103
+ const failStream = (error) => {
1104
+ if (closed) {
1105
+ return;
1106
+ }
1107
+ controller.enqueue({
1108
+ type: "error",
1109
+ error
1110
+ });
1111
+ cleanup();
1112
+ controller.close();
1113
+ };
1114
+ const failWithTurnError = (details, fallbackMessage) => {
1115
+ if (details && isUsageLimitError(details)) {
1116
+ const snapshot = buildUsageLimitSnapshot(latestRateLimitSnapshot);
1117
+ emitRateLimitStatus(snapshot);
1118
+ failStream(
1119
+ toChatGPTUsageLimitError(details, options.modelId, snapshot)
1120
+ );
1121
+ return;
1122
+ }
1123
+ failStream(
1124
+ import_errors.LLMError.generationFailed(
1125
+ details?.message ?? fallbackMessage,
1126
+ "openai-compatible",
1127
+ options.modelId
1128
+ )
1129
+ );
1130
+ };
1131
+ const unsubscribeNotifications = activeClient.onNotification((message) => {
1132
+ if (message.method === "codex/client-exited") {
1133
+ const params = isRecord(message.params) ? message.params : {};
1134
+ failStream(
1135
+ createCodexClientExitedError({
1136
+ ...getNumber(params["code"]) !== null ? { code: getNumber(params["code"]) ?? void 0 } : {},
1137
+ ...getString(params["signal"]) !== null ? { signal: getString(params["signal"]) ?? void 0 } : {}
1138
+ })
1139
+ );
1140
+ return;
1141
+ }
1142
+ if (message.method === "account/rateLimits/updated") {
1143
+ emitRateLimitStatus(pickPrimaryRateLimitSnapshot(message.params));
1144
+ return;
1145
+ }
1146
+ if (message.method === "item/agentMessage/delta") {
1147
+ if (!isRecord(message.params)) {
1148
+ return;
1149
+ }
1150
+ const threadId = getString(message.params["threadId"]);
1151
+ const turnId = getString(message.params["turnId"]);
1152
+ const delta = getString(message.params["delta"]);
1153
+ if (threadId !== thread.thread.id || turnId !== turn.turn.id || delta === null) {
1154
+ return;
1155
+ }
1156
+ if (!textStarted) {
1157
+ ensureStreamStarted();
1158
+ controller.enqueue({
1159
+ type: "text-start",
1160
+ id: textPartId
1161
+ });
1162
+ textStarted = true;
1163
+ }
1164
+ emittedText += delta;
1165
+ controller.enqueue({
1166
+ type: "text-delta",
1167
+ id: textPartId,
1168
+ delta
1169
+ });
1170
+ return;
1171
+ }
1172
+ if (message.method === "item/completed") {
1173
+ if (!isRecord(message.params)) {
1174
+ return;
1175
+ }
1176
+ const threadId = getString(message.params["threadId"]);
1177
+ const turnId = getString(message.params["turnId"]);
1178
+ const item = isRecord(message.params["item"]) ? message.params["item"] : null;
1179
+ if (threadId !== thread.thread.id || turnId !== turn.turn.id || !item) {
1180
+ return;
1181
+ }
1182
+ if (item["type"] !== "agentMessage") {
1183
+ return;
1184
+ }
1185
+ const text = getString(item["text"]);
1186
+ if (!text) {
1187
+ return;
1188
+ }
1189
+ const missingText = text.startsWith(emittedText) ? text.slice(emittedText.length) : text;
1190
+ if (!textStarted) {
1191
+ ensureStreamStarted();
1192
+ controller.enqueue({
1193
+ type: "text-start",
1194
+ id: textPartId
1195
+ });
1196
+ textStarted = true;
1197
+ }
1198
+ if (missingText) {
1199
+ emittedText += missingText;
1200
+ controller.enqueue({
1201
+ type: "text-delta",
1202
+ id: textPartId,
1203
+ delta: missingText
1204
+ });
1205
+ }
1206
+ return;
1207
+ }
1208
+ if (message.method === "error") {
1209
+ if (!isRecord(message.params)) {
1210
+ return;
1211
+ }
1212
+ const willRetry = getBoolean(message.params["willRetry"]);
1213
+ if (willRetry === true) {
1214
+ return;
1215
+ }
1216
+ const threadId = getString(message.params["threadId"]);
1217
+ const turnId = getString(message.params["turnId"]);
1218
+ if (threadId !== thread.thread.id || turnId !== turn.turn.id) {
1219
+ return;
1220
+ }
1221
+ const details = parseCodexErrorDetails(
1222
+ isRecord(message.params["error"]) ? message.params["error"] : message.params
1223
+ );
1224
+ failWithTurnError(
1225
+ details,
1226
+ extractErrorMessage(message.params) ?? "Codex turn failed"
1227
+ );
1228
+ return;
1229
+ }
1230
+ if (message.method === "turn/completed") {
1231
+ if (!isRecord(message.params)) {
1232
+ return;
1233
+ }
1234
+ const threadId = getString(message.params["threadId"]);
1235
+ const turnInfo = isRecord(message.params["turn"]) ? message.params["turn"] : null;
1236
+ const turnId = turnInfo ? getString(turnInfo["id"]) : null;
1237
+ const status = turnInfo ? getString(turnInfo["status"]) : null;
1238
+ if (threadId !== thread.thread.id || turnId !== turn.turn.id || status === null) {
1239
+ return;
1240
+ }
1241
+ if (status === "failed") {
1242
+ const details = turnInfo && isRecord(turnInfo["error"]) ? parseCodexErrorDetails(turnInfo["error"]) : null;
1243
+ failWithTurnError(details, "Codex turn failed");
1244
+ return;
1245
+ }
1246
+ finishStream(status === "completed" ? "stop" : "other");
1247
+ }
1248
+ });
1249
+ const unsubscribeRequests = activeClient.onServerRequest((request) => {
1250
+ if (request.method !== "item/tool/call") {
1251
+ activeClient.rejectServerRequest(
1252
+ request.id,
1253
+ `Codex request "${request.method}" is not supported because Dexto executes tools and approvals itself.`
1254
+ );
1255
+ return;
1256
+ }
1257
+ const toolCall = parseDynamicToolCallRequest(request.params);
1258
+ if (!toolCall || toolCall.threadId !== thread.thread.id || toolCall.turnId !== turn.turn.id) {
1259
+ activeClient.rejectServerRequest(
1260
+ request.id,
1261
+ "Received an invalid Codex dynamic tool call payload."
1262
+ );
1263
+ return;
1264
+ }
1265
+ ensureStreamStarted();
1266
+ endText();
1267
+ controller.enqueue({
1268
+ type: "tool-input-start",
1269
+ id: toolCall.callId,
1270
+ toolName: toolCall.toolName
1271
+ });
1272
+ controller.enqueue({
1273
+ type: "tool-input-delta",
1274
+ id: toolCall.callId,
1275
+ delta: toolCall.input
1276
+ });
1277
+ controller.enqueue({
1278
+ type: "tool-input-end",
1279
+ id: toolCall.callId
1280
+ });
1281
+ controller.enqueue({
1282
+ type: "tool-call",
1283
+ toolCallId: toolCall.callId,
1284
+ toolName: toolCall.toolName,
1285
+ input: toolCall.input
1286
+ });
1287
+ finishStream("tool-calls");
1288
+ });
1289
+ if (callOptions.abortSignal) {
1290
+ const onAbort = () => {
1291
+ failStream(
1292
+ callOptions.abortSignal?.reason instanceof Error ? callOptions.abortSignal.reason : createCodexClientRuntimeError(
1293
+ "Codex generation aborted",
1294
+ { modelId: options.modelId },
1295
+ import_types.ErrorType.USER
1296
+ )
1297
+ );
1298
+ };
1299
+ if (callOptions.abortSignal.aborted) {
1300
+ onAbort();
1301
+ return;
1302
+ }
1303
+ callOptions.abortSignal.addEventListener("abort", onAbort, { once: true });
1304
+ offAbort = () => callOptions.abortSignal?.removeEventListener("abort", onAbort);
1305
+ }
1306
+ },
1307
+ cancel() {
1308
+ void activeClient.close();
1309
+ }
1310
+ });
1311
+ return {
1312
+ stream,
1313
+ request: {
1314
+ body: {
1315
+ provider: "codex-app-server",
1316
+ model: options.modelId,
1317
+ transcript,
1318
+ dynamicTools: dynamicTools.map((tool) => tool.name)
1319
+ }
1320
+ }
1321
+ };
1322
+ } catch (error) {
1323
+ await client?.close().catch(() => void 0);
1324
+ const mappedError = toCodexFailureMessage(error, options.modelId);
1325
+ if (mappedError instanceof import_DextoRuntimeError.DextoRuntimeError && mappedError.code === import_error_codes.LLMErrorCode.RATE_LIMIT_EXCEEDED) {
1326
+ emitRateLimitStatus(buildUsageLimitSnapshot(latestRateLimitSnapshot));
1327
+ }
1328
+ throw mappedError;
1329
+ }
1330
+ }
1331
+ return {
1332
+ specificationVersion: "v2",
1333
+ provider: "codex-app-server",
1334
+ modelId: options.modelId,
1335
+ supportedUrls: {},
1336
+ async doGenerate(callOptions) {
1337
+ const execution = await executeTurn(callOptions);
1338
+ const reader = execution.stream.getReader();
1339
+ const content = [];
1340
+ let text = "";
1341
+ let finishReason = "other";
1342
+ const flushText = () => {
1343
+ if (!text) {
1344
+ return;
1345
+ }
1346
+ content.push({
1347
+ type: "text",
1348
+ text
1349
+ });
1350
+ text = "";
1351
+ };
1352
+ while (true) {
1353
+ const { done, value } = await reader.read();
1354
+ if (done) {
1355
+ break;
1356
+ }
1357
+ if (value.type === "text-delta") {
1358
+ text += value.delta;
1359
+ } else if (value.type === "tool-call") {
1360
+ flushText();
1361
+ content.push({
1362
+ type: "tool-call",
1363
+ toolCallId: value.toolCallId,
1364
+ toolName: value.toolName,
1365
+ input: value.input
1366
+ });
1367
+ } else if (value.type === "error") {
1368
+ throw toCodexFailureMessage(value.error, options.modelId);
1369
+ } else if (value.type === "finish") {
1370
+ finishReason = value.finishReason;
1371
+ }
1372
+ }
1373
+ flushText();
1374
+ return {
1375
+ content,
1376
+ finishReason,
1377
+ usage: createUsage(),
1378
+ warnings: buildWarnings(callOptions),
1379
+ request: execution.request
1380
+ };
1381
+ },
1382
+ async doStream(callOptions) {
1383
+ return await executeTurn(callOptions);
1384
+ }
1385
+ };
1386
+ }
1387
+ // Annotate the CommonJS export names for ESM import in node:
1388
+ 0 && (module.exports = {
1389
+ CodexAppServerClient,
1390
+ createCodexLanguageModel
1391
+ });