@wingman-ai/gateway 0.2.2 → 0.2.3

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 (65) hide show
  1. package/.wingman/agents/coding/agent.md +174 -169
  2. package/.wingman/agents/coding/implementor.md +25 -1
  3. package/.wingman/agents/main/agent.md +4 -0
  4. package/README.md +1 -0
  5. package/dist/agent/config/agentConfig.cjs +1 -1
  6. package/dist/agent/config/agentConfig.js +1 -1
  7. package/dist/agent/config/modelFactory.cjs +22 -2
  8. package/dist/agent/config/modelFactory.d.ts +2 -0
  9. package/dist/agent/config/modelFactory.js +22 -2
  10. package/dist/agent/tests/modelFactory.test.cjs +12 -5
  11. package/dist/agent/tests/modelFactory.test.js +12 -5
  12. package/dist/cli/commands/init.cjs +7 -6
  13. package/dist/cli/commands/init.js +7 -6
  14. package/dist/cli/commands/provider.cjs +17 -3
  15. package/dist/cli/commands/provider.js +17 -3
  16. package/dist/cli/config/loader.cjs +27 -0
  17. package/dist/cli/config/loader.js +27 -0
  18. package/dist/cli/config/schema.cjs +80 -2
  19. package/dist/cli/config/schema.d.ts +88 -0
  20. package/dist/cli/config/schema.js +67 -1
  21. package/dist/cli/core/agentInvoker.cjs +191 -13
  22. package/dist/cli/core/agentInvoker.d.ts +42 -3
  23. package/dist/cli/core/agentInvoker.js +163 -9
  24. package/dist/cli/core/sessionManager.cjs +32 -5
  25. package/dist/cli/core/sessionManager.js +32 -5
  26. package/dist/cli/index.cjs +6 -5
  27. package/dist/cli/index.js +6 -5
  28. package/dist/cli/types.d.ts +32 -0
  29. package/dist/gateway/http/sessions.cjs +7 -7
  30. package/dist/gateway/http/sessions.js +7 -7
  31. package/dist/gateway/server.cjs +191 -41
  32. package/dist/gateway/server.d.ts +8 -1
  33. package/dist/gateway/server.js +191 -41
  34. package/dist/gateway/types.d.ts +1 -0
  35. package/dist/providers/codex.cjs +167 -0
  36. package/dist/providers/codex.d.ts +15 -0
  37. package/dist/providers/codex.js +127 -0
  38. package/dist/providers/credentials.cjs +8 -0
  39. package/dist/providers/credentials.js +8 -0
  40. package/dist/providers/registry.cjs +11 -0
  41. package/dist/providers/registry.d.ts +1 -1
  42. package/dist/providers/registry.js +11 -0
  43. package/dist/tests/agentInvokerSummarization.test.cjs +296 -0
  44. package/dist/tests/agentInvokerSummarization.test.d.ts +1 -0
  45. package/dist/tests/agentInvokerSummarization.test.js +290 -0
  46. package/dist/tests/cli-config-loader.test.cjs +88 -0
  47. package/dist/tests/cli-config-loader.test.js +88 -0
  48. package/dist/tests/codex-credentials-precedence.test.cjs +94 -0
  49. package/dist/tests/codex-credentials-precedence.test.d.ts +1 -0
  50. package/dist/tests/codex-credentials-precedence.test.js +88 -0
  51. package/dist/tests/codex-provider.test.cjs +186 -0
  52. package/dist/tests/codex-provider.test.d.ts +1 -0
  53. package/dist/tests/codex-provider.test.js +180 -0
  54. package/dist/tests/gateway.test.cjs +108 -1
  55. package/dist/tests/gateway.test.js +108 -1
  56. package/dist/tests/provider-command-codex.test.cjs +57 -0
  57. package/dist/tests/provider-command-codex.test.d.ts +1 -0
  58. package/dist/tests/provider-command-codex.test.js +51 -0
  59. package/dist/tests/sessionStateMessages.test.cjs +38 -0
  60. package/dist/tests/sessionStateMessages.test.js +38 -0
  61. package/dist/webui/assets/{index-DDsMIOTX.css → index-BVMavpud.css} +1 -1
  62. package/dist/webui/assets/index-DCB2aVVf.js +182 -0
  63. package/dist/webui/index.html +2 -2
  64. package/package.json +1 -1
  65. package/dist/webui/assets/index-CPhfGPHc.js +0 -182
@@ -1,28 +1,28 @@
1
- import { NodeManager } from "./node.js";
2
- import { BroadcastGroupManager } from "./broadcast.js";
3
- import { GatewayAuth } from "./auth.js";
4
- import { validateGatewayMessage } from "./validation.js";
5
- import { getGatewayTokenFromEnv } from "./env.js";
6
- import { MDNSDiscoveryService, TailscaleDiscoveryService } from "./discovery/index.js";
7
- import { createLogger } from "../logger.js";
1
+ import { existsSync, mkdirSync, statSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { dirname, isAbsolute, join, normalize, sep } from "node:path";
4
+ import { fileURLToPath } from "node:url";
8
5
  import { WingmanConfigLoader } from "../cli/config/loader.js";
9
- import { GatewayRouter } from "./router.js";
10
- import { OutputManager } from "../cli/core/outputManager.js";
11
6
  import { AgentInvoker } from "../cli/core/agentInvoker.js";
7
+ import { OutputManager } from "../cli/core/outputManager.js";
12
8
  import { SessionManager } from "../cli/core/sessionManager.js";
9
+ import { createLogger } from "../logger.js";
10
+ import { DiscordGatewayAdapter } from "./adapters/discord.js";
11
+ import { GatewayAuth } from "./auth.js";
12
+ import { BroadcastGroupManager } from "./broadcast.js";
13
+ import { MDNSDiscoveryService, TailscaleDiscoveryService } from "./discovery/index.js";
14
+ import { getGatewayTokenFromEnv } from "./env.js";
15
+ import { InternalHookRegistry } from "./hooks/registry.js";
13
16
  import { handleAgentsApi } from "./http/agents.js";
14
17
  import { handleFsApi } from "./http/fs.js";
15
18
  import { handleProvidersApi } from "./http/providers.js";
16
- import { handleVoiceApi } from "./http/voice.js";
19
+ import { createRoutineStore, handleRoutinesApi } from "./http/routines.js";
17
20
  import { handleSessionsApi } from "./http/sessions.js";
21
+ import { handleVoiceApi } from "./http/voice.js";
18
22
  import { createWebhookStore, handleWebhookInvoke, handleWebhooksApi } from "./http/webhooks.js";
19
- import { createRoutineStore, handleRoutinesApi } from "./http/routines.js";
20
- import { DiscordGatewayAdapter } from "./adapters/discord.js";
21
- import { InternalHookRegistry } from "./hooks/registry.js";
22
- import { homedir } from "node:os";
23
- import { dirname, isAbsolute, join, normalize, sep } from "node:path";
24
- import { existsSync, mkdirSync, statSync, writeFileSync } from "node:fs";
25
- import { fileURLToPath } from "node:url";
23
+ import { NodeManager } from "./node.js";
24
+ import { GatewayRouter } from "./router.js";
25
+ import { validateGatewayMessage } from "./validation.js";
26
26
  function _define_property(obj, key, value) {
27
27
  if (key in obj) Object.defineProperty(obj, key, {
28
28
  value: value,
@@ -319,6 +319,7 @@ class GatewayServer {
319
319
  existing?.abortController.abort();
320
320
  this.activeAgentRequests.delete(msg.id);
321
321
  }
322
+ this.removeQueuedRequestById(msg.id);
322
323
  const payload = msg.payload;
323
324
  const content = "string" == typeof payload?.content ? payload.content : "";
324
325
  const attachments = Array.isArray(payload?.attachments) ? payload.attachments : [];
@@ -328,6 +329,7 @@ class GatewayServer {
328
329
  const agentId = this.router.selectAgent(payload.agentId, payload.routing);
329
330
  if (!agentId) return void this.sendAgentError(ws, msg.id, "No agent matched the request");
330
331
  const sessionKey = payload.sessionKey || this.router.buildSessionKey(agentId, payload.routing);
332
+ const sessionQueueKey = this.buildSessionQueueKey(agentId, sessionKey);
331
333
  const sessionManager = await this.getSessionManager(agentId);
332
334
  const existingSession = sessionManager.getSession(sessionKey);
333
335
  const session = existingSession || sessionManager.getOrCreateSession(sessionKey, agentId);
@@ -378,9 +380,70 @@ class GatewayServer {
378
380
  ],
379
381
  skipSessionId: sessionKey
380
382
  });
383
+ const request = {
384
+ ws,
385
+ msg,
386
+ payload,
387
+ agentId,
388
+ sessionKey,
389
+ sessionQueueKey,
390
+ content,
391
+ attachments,
392
+ sessionManager,
393
+ workdir,
394
+ defaultOutputDir
395
+ };
396
+ this.requestSessionKeys.set(msg.id, sessionQueueKey);
397
+ const queueIfBusy = false !== payload.queueIfBusy;
398
+ const activeRequestId = this.activeSessionRequests.get(sessionQueueKey);
399
+ if (activeRequestId && queueIfBusy) {
400
+ const queued = this.queuedSessionRequests.get(sessionQueueKey) || [];
401
+ queued.push(request);
402
+ this.queuedSessionRequests.set(sessionQueueKey, queued);
403
+ const position = queued.length;
404
+ this.sendMessage(ws, {
405
+ type: "ack",
406
+ id: msg.id,
407
+ payload: {
408
+ action: "req:agent",
409
+ status: "queued",
410
+ requestId: msg.id,
411
+ sessionId: sessionKey,
412
+ agentId,
413
+ position
414
+ },
415
+ timestamp: Date.now()
416
+ });
417
+ this.sendMessage(ws, {
418
+ type: "event:agent",
419
+ id: msg.id,
420
+ clientId: ws.data.clientId,
421
+ payload: this.attachSessionContext({
422
+ type: "request-queued",
423
+ position
424
+ }, sessionKey, agentId),
425
+ timestamp: Date.now()
426
+ });
427
+ return;
428
+ }
429
+ if (activeRequestId && !queueIfBusy) {
430
+ this.requestSessionKeys.delete(msg.id);
431
+ this.sendAgentError(ws, msg.id, "Session already has an in-flight request. Set queueIfBusy=true to enqueue.", {
432
+ sessionId: sessionKey,
433
+ agentId
434
+ });
435
+ return;
436
+ }
437
+ this.executeAgentRequest(request);
438
+ }
439
+ async executeAgentRequest(request) {
440
+ const { ws, msg, agentId, sessionKey, sessionQueueKey, content, attachments, sessionManager, workdir, defaultOutputDir } = request;
441
+ this.activeSessionRequests.set(sessionQueueKey, msg.id);
381
442
  const outputManager = new OutputManager("interactive");
443
+ let emittedAgentError = false;
382
444
  const outputHandler = (event)=>{
383
445
  const payloadWithSession = this.attachSessionContext(event, sessionKey, agentId);
446
+ if (payloadWithSession && "object" == typeof payloadWithSession && !Array.isArray(payloadWithSession) && "agent-error" === payloadWithSession.type) emittedAgentError = true;
384
447
  const baseMessage = {
385
448
  type: "event:agent",
386
449
  id: msg.id,
@@ -419,41 +482,111 @@ class GatewayServer {
419
482
  });
420
483
  } catch (error) {
421
484
  this.logger.error("Agent invocation failed", error);
485
+ if (!emittedAgentError) {
486
+ const message = error instanceof Error ? error.message : String(error);
487
+ const stack = error instanceof Error ? error.stack : void 0;
488
+ this.sendAgentError(ws, msg.id, message, {
489
+ sessionId: sessionKey,
490
+ agentId,
491
+ stack,
492
+ broadcastToSession: true,
493
+ exclude: ws
494
+ });
495
+ }
422
496
  } finally{
423
497
  this.activeAgentRequests.delete(msg.id);
498
+ this.activeSessionRequests.delete(sessionQueueKey);
499
+ this.requestSessionKeys.delete(msg.id);
424
500
  outputManager.off("output-event", outputHandler);
501
+ this.processNextQueuedAgentRequest(sessionQueueKey);
425
502
  }
426
503
  }
504
+ processNextQueuedAgentRequest(sessionQueueKey) {
505
+ if (this.activeSessionRequests.has(sessionQueueKey)) return;
506
+ const queue = this.queuedSessionRequests.get(sessionQueueKey);
507
+ if (!queue || 0 === queue.length) return void this.queuedSessionRequests.delete(sessionQueueKey);
508
+ const next = queue.shift();
509
+ if (!next) return;
510
+ if (0 === queue.length) this.queuedSessionRequests.delete(sessionQueueKey);
511
+ else this.queuedSessionRequests.set(sessionQueueKey, queue);
512
+ this.sendMessage(next.ws, {
513
+ type: "ack",
514
+ id: next.msg.id,
515
+ payload: {
516
+ action: "req:agent",
517
+ status: "dequeued",
518
+ requestId: next.msg.id,
519
+ sessionId: next.sessionKey,
520
+ agentId: next.agentId,
521
+ remaining: queue.length
522
+ },
523
+ timestamp: Date.now()
524
+ });
525
+ this.executeAgentRequest(next);
526
+ }
427
527
  handleAgentCancel(ws, msg) {
428
528
  if (!ws.data.authenticated) return void this.sendError(ws, "AUTH_FAILED", "Client is not authenticated");
429
529
  const payload = msg.payload;
430
530
  const requestId = "string" == typeof payload?.requestId && payload.requestId || void 0;
431
531
  if (!requestId) return void this.sendError(ws, "INVALID_REQUEST", "Missing requestId for cancellation");
432
532
  const active = this.activeAgentRequests.get(requestId);
433
- if (!active) return void this.sendMessage(ws, {
434
- type: "ack",
435
- id: msg.id,
436
- payload: {
437
- action: "req:agent:cancel",
438
- requestId,
439
- status: "not_found"
440
- },
441
- timestamp: Date.now()
442
- });
443
- if (active.socket !== ws) return void this.sendError(ws, "FORBIDDEN", "Cannot cancel a request started by another client");
444
- active.abortController.abort();
445
- this.activeAgentRequests.delete(requestId);
533
+ if (active) {
534
+ if (active.socket !== ws) return void this.sendError(ws, "FORBIDDEN", "Cannot cancel a request started by another client");
535
+ active.abortController.abort();
536
+ this.activeAgentRequests.delete(requestId);
537
+ this.sendMessage(ws, {
538
+ type: "ack",
539
+ id: msg.id,
540
+ payload: {
541
+ action: "req:agent:cancel",
542
+ requestId,
543
+ status: "cancelled"
544
+ },
545
+ timestamp: Date.now()
546
+ });
547
+ return;
548
+ }
549
+ const queued = this.removeQueuedRequestById(requestId);
550
+ if (queued) {
551
+ if (queued.ws !== ws) return void this.sendError(ws, "FORBIDDEN", "Cannot cancel a request started by another client");
552
+ this.sendMessage(ws, {
553
+ type: "ack",
554
+ id: msg.id,
555
+ payload: {
556
+ action: "req:agent:cancel",
557
+ requestId,
558
+ status: "cancelled_queued"
559
+ },
560
+ timestamp: Date.now()
561
+ });
562
+ return;
563
+ }
446
564
  this.sendMessage(ws, {
447
565
  type: "ack",
448
566
  id: msg.id,
449
567
  payload: {
450
568
  action: "req:agent:cancel",
451
569
  requestId,
452
- status: "cancelled"
570
+ status: "not_found"
453
571
  },
454
572
  timestamp: Date.now()
455
573
  });
456
574
  }
575
+ buildSessionQueueKey(agentId, sessionKey) {
576
+ return `${agentId}:${sessionKey}`;
577
+ }
578
+ removeQueuedRequestById(requestId) {
579
+ for (const [queueKey, queue] of this.queuedSessionRequests){
580
+ const index = queue.findIndex((item)=>item.msg.id === requestId);
581
+ if (-1 === index) continue;
582
+ const [removed] = queue.splice(index, 1);
583
+ if (0 === queue.length) this.queuedSessionRequests.delete(queueKey);
584
+ else this.queuedSessionRequests.set(queueKey, queue);
585
+ this.requestSessionKeys.delete(requestId);
586
+ return removed || null;
587
+ }
588
+ return null;
589
+ }
457
590
  handleRegister(ws, msg) {
458
591
  const payload = msg.payload;
459
592
  if (!this.auth.validate({
@@ -616,23 +749,39 @@ class GatewayServer {
616
749
  timestamp: Date.now()
617
750
  });
618
751
  }
619
- sendAgentError(ws, requestId, message) {
620
- this.sendMessage(ws, {
752
+ sendAgentError(ws, requestId, message, options) {
753
+ let payload = {
754
+ type: "agent-error",
755
+ error: message,
756
+ timestamp: new Date().toISOString()
757
+ };
758
+ if (options?.stack) payload.stack = options.stack;
759
+ if (options?.sessionId && options?.agentId) payload = this.attachSessionContext(payload, options.sessionId, options.agentId);
760
+ const baseMessage = {
621
761
  type: "event:agent",
622
762
  id: requestId,
623
- payload: {
624
- type: "agent-error",
625
- error: message,
626
- timestamp: new Date().toISOString()
627
- },
763
+ payload,
628
764
  timestamp: Date.now()
765
+ };
766
+ this.sendMessage(ws, {
767
+ ...baseMessage,
768
+ clientId: ws.data.clientId
629
769
  });
770
+ if (options?.broadcastToSession && options.sessionId) this.broadcastSessionEvent(options.sessionId, baseMessage, options.exclude);
630
771
  }
631
772
  cancelSocketAgentRequests(ws) {
632
773
  for (const [requestId, active] of this.activeAgentRequests)if (active.socket === ws) {
633
774
  active.abortController.abort();
634
775
  this.activeAgentRequests.delete(requestId);
635
776
  }
777
+ for (const [queueKey, queue] of this.queuedSessionRequests){
778
+ const nextQueue = queue.filter((request)=>request.ws !== ws);
779
+ if (nextQueue.length !== queue.length) {
780
+ for (const request of queue)if (request.ws === ws && request.msg.id) this.requestSessionKeys.delete(request.msg.id);
781
+ if (0 === nextQueue.length) this.queuedSessionRequests.delete(queueKey);
782
+ else this.queuedSessionRequests.set(queueKey, nextQueue);
783
+ }
784
+ }
636
785
  }
637
786
  attachSessionContext(event, sessionId, agentId) {
638
787
  if (event && "object" == typeof event && !Array.isArray(event)) return {
@@ -824,9 +973,7 @@ class GatewayServer {
824
973
  ];
825
974
  for (const candidate of candidates)try {
826
975
  if (existsSync(candidate) && statSync(candidate).isDirectory() && existsSync(join(candidate, "index.html"))) return candidate;
827
- } catch {
828
- continue;
829
- }
976
+ } catch {}
830
977
  return null;
831
978
  }
832
979
  async getSessionManager(agentId) {
@@ -1150,6 +1297,9 @@ class GatewayServer {
1150
1297
  _define_property(this, "socketSubscriptions", new Map());
1151
1298
  _define_property(this, "connectedClients", new Set());
1152
1299
  _define_property(this, "activeAgentRequests", new Map());
1300
+ _define_property(this, "activeSessionRequests", new Map());
1301
+ _define_property(this, "queuedSessionRequests", new Map());
1302
+ _define_property(this, "requestSessionKeys", new Map());
1153
1303
  _define_property(this, "bridgeQueues", new Map());
1154
1304
  _define_property(this, "bridgePollWaiters", new Map());
1155
1305
  this.workspace = config.workspace || process.cwd();
@@ -60,6 +60,7 @@ export interface AgentRequestPayload {
60
60
  attachments?: MediaAttachment[];
61
61
  routing?: RoutingInfo;
62
62
  sessionKey?: string;
63
+ queueIfBusy?: boolean;
63
64
  }
64
65
  export interface AgentCancelPayload {
65
66
  requestId: string;
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.d = (exports1, definition)=>{
5
+ for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
6
+ enumerable: true,
7
+ get: definition[key]
8
+ });
9
+ };
10
+ })();
11
+ (()=>{
12
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
13
+ })();
14
+ (()=>{
15
+ __webpack_require__.r = (exports1)=>{
16
+ if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
17
+ value: 'Module'
18
+ });
19
+ Object.defineProperty(exports1, '__esModule', {
20
+ value: true
21
+ });
22
+ };
23
+ })();
24
+ var __webpack_exports__ = {};
25
+ __webpack_require__.r(__webpack_exports__);
26
+ __webpack_require__.d(__webpack_exports__, {
27
+ createCodexFetch: ()=>createCodexFetch,
28
+ getCodexAuthPath: ()=>getCodexAuthPath,
29
+ resolveCodexAuthFromFile: ()=>resolveCodexAuthFromFile
30
+ });
31
+ const external_node_fs_namespaceObject = require("node:fs");
32
+ const external_node_os_namespaceObject = require("node:os");
33
+ const external_node_path_namespaceObject = require("node:path");
34
+ const external_logger_cjs_namespaceObject = require("../logger.cjs");
35
+ const CODEX_HOME_ENV = "CODEX_HOME";
36
+ const CODEX_AUTH_FILE = "auth.json";
37
+ const DEFAULT_CODEX_INSTRUCTIONS = "You are Wingman, a coding assistant. Follow the user's request exactly and keep tool usage focused.";
38
+ const logger = (0, external_logger_cjs_namespaceObject.createLogger)();
39
+ function getCodexAuthPath() {
40
+ const codexHome = process.env[CODEX_HOME_ENV]?.trim();
41
+ if (codexHome) return (0, external_node_path_namespaceObject.join)(codexHome, CODEX_AUTH_FILE);
42
+ return (0, external_node_path_namespaceObject.join)((0, external_node_os_namespaceObject.homedir)(), ".codex", CODEX_AUTH_FILE);
43
+ }
44
+ function resolveCodexAuthFromFile() {
45
+ const authPath = getCodexAuthPath();
46
+ if (!(0, external_node_fs_namespaceObject.existsSync)(authPath)) return {
47
+ authPath
48
+ };
49
+ try {
50
+ const parsed = JSON.parse((0, external_node_fs_namespaceObject.readFileSync)(authPath, "utf-8"));
51
+ if (!parsed || "object" != typeof parsed) return {
52
+ authPath
53
+ };
54
+ const root = parsed;
55
+ const tokens = root.tokens && "object" == typeof root.tokens ? root.tokens : void 0;
56
+ const accessToken = firstNonEmptyString([
57
+ tokens?.access_token,
58
+ root.access_token
59
+ ]);
60
+ const accountId = firstNonEmptyString([
61
+ tokens?.account_id,
62
+ root.account_id
63
+ ]);
64
+ return {
65
+ accessToken,
66
+ accountId,
67
+ authPath
68
+ };
69
+ } catch {
70
+ return {
71
+ authPath
72
+ };
73
+ }
74
+ }
75
+ function createCodexFetch(options = {}) {
76
+ const baseFetch = options.baseFetch || globalThis.fetch.bind(globalThis);
77
+ return async (input, init)=>{
78
+ const codexAuth = resolveCodexAuthFromFile();
79
+ const accessToken = codexAuth.accessToken || options.fallbackToken;
80
+ const accountId = codexAuth.accountId || options.fallbackAccountId;
81
+ if (!accessToken) throw new Error("Codex credentials missing. Run `codex login` or set CODEX_ACCESS_TOKEN.");
82
+ const headers = new Headers(init?.headers || {});
83
+ headers.delete("authorization");
84
+ headers.delete("x-api-key");
85
+ headers.set("Authorization", `Bearer ${accessToken}`);
86
+ if (accountId) headers.set("ChatGPT-Account-ID", accountId);
87
+ const body = withCodexRequestDefaults(init?.body);
88
+ const response = await baseFetch(input, {
89
+ ...init,
90
+ headers,
91
+ body
92
+ });
93
+ if (!response.ok) {
94
+ let responseBody = "";
95
+ try {
96
+ responseBody = await response.clone().text();
97
+ } catch {}
98
+ const preview = responseBody.trim().slice(0, 1200);
99
+ logger.warn(`Codex request failed (${response.status} ${response.statusText || ""})`, {
100
+ url: "string" == typeof input ? input : input instanceof URL ? input.toString() : input.url,
101
+ bodyPresent: preview.length > 0,
102
+ bodyPreview: preview || null
103
+ });
104
+ }
105
+ return response;
106
+ };
107
+ }
108
+ function withCodexRequestDefaults(body) {
109
+ if ("string" != typeof body || !body.trim()) return body;
110
+ try {
111
+ const parsed = JSON.parse(body);
112
+ if (!parsed || "object" != typeof parsed || Array.isArray(parsed)) return body;
113
+ const root = stripUnsupportedCodexParams(parsed);
114
+ const instructions = "string" == typeof root.instructions && root.instructions.trim() ? root.instructions.trim() : extractInstructionsFromInput(root.input) || DEFAULT_CODEX_INSTRUCTIONS;
115
+ return JSON.stringify({
116
+ ...root,
117
+ store: false,
118
+ instructions
119
+ });
120
+ } catch {
121
+ return body;
122
+ }
123
+ }
124
+ function stripUnsupportedCodexParams(payload) {
125
+ const next = {
126
+ ...payload
127
+ };
128
+ delete next.temperature;
129
+ return next;
130
+ }
131
+ function extractInstructionsFromInput(input) {
132
+ if (!Array.isArray(input)) return;
133
+ for (const item of input){
134
+ if (!item || "object" != typeof item || Array.isArray(item)) continue;
135
+ const message = item;
136
+ const role = "string" == typeof message.role ? message.role : "";
137
+ if ("system" !== role && "developer" !== role) continue;
138
+ const text = extractTextContent(message.content);
139
+ if (text) return text;
140
+ }
141
+ }
142
+ function extractTextContent(content) {
143
+ if ("string" == typeof content && content.trim()) return content.trim();
144
+ if (!Array.isArray(content)) return;
145
+ for (const part of content){
146
+ if (!part || "object" != typeof part || Array.isArray(part)) continue;
147
+ const block = part;
148
+ const text = "string" == typeof block.text ? block.text : "string" == typeof block.content ? block.content : void 0;
149
+ if (!text || !text.trim()) continue;
150
+ const type = "string" == typeof block.type ? block.type : "";
151
+ if (!type || type.includes("text")) return text.trim();
152
+ }
153
+ }
154
+ function firstNonEmptyString(values) {
155
+ for (const value of values)if ("string" == typeof value && value.trim()) return value.trim();
156
+ }
157
+ exports.createCodexFetch = __webpack_exports__.createCodexFetch;
158
+ exports.getCodexAuthPath = __webpack_exports__.getCodexAuthPath;
159
+ exports.resolveCodexAuthFromFile = __webpack_exports__.resolveCodexAuthFromFile;
160
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
161
+ "createCodexFetch",
162
+ "getCodexAuthPath",
163
+ "resolveCodexAuthFromFile"
164
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
165
+ Object.defineProperty(exports, '__esModule', {
166
+ value: true
167
+ });
@@ -0,0 +1,15 @@
1
+ type FetchLike = (input: Parameters<typeof fetch>[0], init?: Parameters<typeof fetch>[1]) => ReturnType<typeof fetch>;
2
+ export interface CodexAuthState {
3
+ accessToken?: string;
4
+ accountId?: string;
5
+ authPath: string;
6
+ }
7
+ export interface CodexFetchOptions {
8
+ baseFetch?: FetchLike;
9
+ fallbackToken?: string;
10
+ fallbackAccountId?: string;
11
+ }
12
+ export declare function getCodexAuthPath(): string;
13
+ export declare function resolveCodexAuthFromFile(): CodexAuthState;
14
+ export declare function createCodexFetch(options?: CodexFetchOptions): FetchLike;
15
+ export {};
@@ -0,0 +1,127 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { createLogger } from "../logger.js";
5
+ const CODEX_HOME_ENV = "CODEX_HOME";
6
+ const CODEX_AUTH_FILE = "auth.json";
7
+ const DEFAULT_CODEX_INSTRUCTIONS = "You are Wingman, a coding assistant. Follow the user's request exactly and keep tool usage focused.";
8
+ const logger = createLogger();
9
+ function getCodexAuthPath() {
10
+ const codexHome = process.env[CODEX_HOME_ENV]?.trim();
11
+ if (codexHome) return join(codexHome, CODEX_AUTH_FILE);
12
+ return join(homedir(), ".codex", CODEX_AUTH_FILE);
13
+ }
14
+ function resolveCodexAuthFromFile() {
15
+ const authPath = getCodexAuthPath();
16
+ if (!existsSync(authPath)) return {
17
+ authPath
18
+ };
19
+ try {
20
+ const parsed = JSON.parse(readFileSync(authPath, "utf-8"));
21
+ if (!parsed || "object" != typeof parsed) return {
22
+ authPath
23
+ };
24
+ const root = parsed;
25
+ const tokens = root.tokens && "object" == typeof root.tokens ? root.tokens : void 0;
26
+ const accessToken = firstNonEmptyString([
27
+ tokens?.access_token,
28
+ root.access_token
29
+ ]);
30
+ const accountId = firstNonEmptyString([
31
+ tokens?.account_id,
32
+ root.account_id
33
+ ]);
34
+ return {
35
+ accessToken,
36
+ accountId,
37
+ authPath
38
+ };
39
+ } catch {
40
+ return {
41
+ authPath
42
+ };
43
+ }
44
+ }
45
+ function createCodexFetch(options = {}) {
46
+ const baseFetch = options.baseFetch || globalThis.fetch.bind(globalThis);
47
+ return async (input, init)=>{
48
+ const codexAuth = resolveCodexAuthFromFile();
49
+ const accessToken = codexAuth.accessToken || options.fallbackToken;
50
+ const accountId = codexAuth.accountId || options.fallbackAccountId;
51
+ if (!accessToken) throw new Error("Codex credentials missing. Run `codex login` or set CODEX_ACCESS_TOKEN.");
52
+ const headers = new Headers(init?.headers || {});
53
+ headers.delete("authorization");
54
+ headers.delete("x-api-key");
55
+ headers.set("Authorization", `Bearer ${accessToken}`);
56
+ if (accountId) headers.set("ChatGPT-Account-ID", accountId);
57
+ const body = withCodexRequestDefaults(init?.body);
58
+ const response = await baseFetch(input, {
59
+ ...init,
60
+ headers,
61
+ body
62
+ });
63
+ if (!response.ok) {
64
+ let responseBody = "";
65
+ try {
66
+ responseBody = await response.clone().text();
67
+ } catch {}
68
+ const preview = responseBody.trim().slice(0, 1200);
69
+ logger.warn(`Codex request failed (${response.status} ${response.statusText || ""})`, {
70
+ url: "string" == typeof input ? input : input instanceof URL ? input.toString() : input.url,
71
+ bodyPresent: preview.length > 0,
72
+ bodyPreview: preview || null
73
+ });
74
+ }
75
+ return response;
76
+ };
77
+ }
78
+ function withCodexRequestDefaults(body) {
79
+ if ("string" != typeof body || !body.trim()) return body;
80
+ try {
81
+ const parsed = JSON.parse(body);
82
+ if (!parsed || "object" != typeof parsed || Array.isArray(parsed)) return body;
83
+ const root = stripUnsupportedCodexParams(parsed);
84
+ const instructions = "string" == typeof root.instructions && root.instructions.trim() ? root.instructions.trim() : extractInstructionsFromInput(root.input) || DEFAULT_CODEX_INSTRUCTIONS;
85
+ return JSON.stringify({
86
+ ...root,
87
+ store: false,
88
+ instructions
89
+ });
90
+ } catch {
91
+ return body;
92
+ }
93
+ }
94
+ function stripUnsupportedCodexParams(payload) {
95
+ const next = {
96
+ ...payload
97
+ };
98
+ delete next.temperature;
99
+ return next;
100
+ }
101
+ function extractInstructionsFromInput(input) {
102
+ if (!Array.isArray(input)) return;
103
+ for (const item of input){
104
+ if (!item || "object" != typeof item || Array.isArray(item)) continue;
105
+ const message = item;
106
+ const role = "string" == typeof message.role ? message.role : "";
107
+ if ("system" !== role && "developer" !== role) continue;
108
+ const text = extractTextContent(message.content);
109
+ if (text) return text;
110
+ }
111
+ }
112
+ function extractTextContent(content) {
113
+ if ("string" == typeof content && content.trim()) return content.trim();
114
+ if (!Array.isArray(content)) return;
115
+ for (const part of content){
116
+ if (!part || "object" != typeof part || Array.isArray(part)) continue;
117
+ const block = part;
118
+ const text = "string" == typeof block.text ? block.text : "string" == typeof block.content ? block.content : void 0;
119
+ if (!text || !text.trim()) continue;
120
+ const type = "string" == typeof block.type ? block.type : "";
121
+ if (!type || type.includes("text")) return text.trim();
122
+ }
123
+ }
124
+ function firstNonEmptyString(values) {
125
+ for (const value of values)if ("string" == typeof value && value.trim()) return value.trim();
126
+ }
127
+ export { createCodexFetch, getCodexAuthPath, resolveCodexAuthFromFile };
@@ -36,6 +36,7 @@ __webpack_require__.d(__webpack_exports__, {
36
36
  const external_node_fs_namespaceObject = require("node:fs");
37
37
  const external_node_os_namespaceObject = require("node:os");
38
38
  const external_node_path_namespaceObject = require("node:path");
39
+ const external_codex_cjs_namespaceObject = require("./codex.cjs");
39
40
  const external_registry_cjs_namespaceObject = require("./registry.cjs");
40
41
  const CREDENTIALS_VERSION = 1;
41
42
  const CREDENTIALS_DIR = (0, external_node_path_namespaceObject.join)((0, external_node_os_namespaceObject.homedir)(), ".wingman");
@@ -121,6 +122,13 @@ function resolveProviderToken(providerName) {
121
122
  envVar
122
123
  };
123
124
  }
125
+ if ("codex" === provider.name) {
126
+ const codexAuth = (0, external_codex_cjs_namespaceObject.resolveCodexAuthFromFile)();
127
+ if (codexAuth.accessToken) return {
128
+ token: codexAuth.accessToken,
129
+ source: "credentials"
130
+ };
131
+ }
124
132
  const credentials = getProviderCredentials(provider.name);
125
133
  const token = credentials?.accessToken ?? credentials?.apiKey ?? credentials?.refreshToken;
126
134
  if (token && token.trim()) return {