@wingman-ai/gateway 0.2.1 → 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 (72) hide show
  1. package/.wingman/agents/README.md +1 -0
  2. package/.wingman/agents/coding/agent.md +179 -112
  3. package/.wingman/agents/coding/implementor.md +50 -3
  4. package/.wingman/agents/main/agent.md +4 -0
  5. package/README.md +1 -0
  6. package/dist/agent/config/agentConfig.cjs +30 -1
  7. package/dist/agent/config/agentConfig.js +30 -1
  8. package/dist/agent/config/modelFactory.cjs +22 -2
  9. package/dist/agent/config/modelFactory.d.ts +2 -0
  10. package/dist/agent/config/modelFactory.js +22 -2
  11. package/dist/agent/tests/agentConfig.test.cjs +39 -0
  12. package/dist/agent/tests/agentConfig.test.js +39 -0
  13. package/dist/agent/tests/modelFactory.test.cjs +12 -5
  14. package/dist/agent/tests/modelFactory.test.js +12 -5
  15. package/dist/cli/commands/init.cjs +7 -6
  16. package/dist/cli/commands/init.js +7 -6
  17. package/dist/cli/commands/provider.cjs +17 -3
  18. package/dist/cli/commands/provider.js +17 -3
  19. package/dist/cli/config/loader.cjs +27 -0
  20. package/dist/cli/config/loader.js +27 -0
  21. package/dist/cli/config/schema.cjs +80 -2
  22. package/dist/cli/config/schema.d.ts +88 -0
  23. package/dist/cli/config/schema.js +67 -1
  24. package/dist/cli/core/agentInvoker.cjs +242 -17
  25. package/dist/cli/core/agentInvoker.d.ts +46 -4
  26. package/dist/cli/core/agentInvoker.js +214 -13
  27. package/dist/cli/core/sessionManager.cjs +32 -5
  28. package/dist/cli/core/sessionManager.js +32 -5
  29. package/dist/cli/index.cjs +6 -5
  30. package/dist/cli/index.js +6 -5
  31. package/dist/cli/types.d.ts +32 -0
  32. package/dist/gateway/http/sessions.cjs +7 -7
  33. package/dist/gateway/http/sessions.js +7 -7
  34. package/dist/gateway/server.cjs +230 -28
  35. package/dist/gateway/server.d.ts +11 -1
  36. package/dist/gateway/server.js +230 -28
  37. package/dist/gateway/types.d.ts +5 -1
  38. package/dist/gateway/validation.cjs +1 -0
  39. package/dist/gateway/validation.d.ts +2 -0
  40. package/dist/gateway/validation.js +1 -0
  41. package/dist/providers/codex.cjs +167 -0
  42. package/dist/providers/codex.d.ts +15 -0
  43. package/dist/providers/codex.js +127 -0
  44. package/dist/providers/credentials.cjs +8 -0
  45. package/dist/providers/credentials.js +8 -0
  46. package/dist/providers/registry.cjs +11 -0
  47. package/dist/providers/registry.d.ts +1 -1
  48. package/dist/providers/registry.js +11 -0
  49. package/dist/tests/agentInvokerSummarization.test.cjs +296 -0
  50. package/dist/tests/agentInvokerSummarization.test.d.ts +1 -0
  51. package/dist/tests/agentInvokerSummarization.test.js +290 -0
  52. package/dist/tests/cli-config-loader.test.cjs +88 -0
  53. package/dist/tests/cli-config-loader.test.js +88 -0
  54. package/dist/tests/codex-credentials-precedence.test.cjs +94 -0
  55. package/dist/tests/codex-credentials-precedence.test.d.ts +1 -0
  56. package/dist/tests/codex-credentials-precedence.test.js +88 -0
  57. package/dist/tests/codex-provider.test.cjs +186 -0
  58. package/dist/tests/codex-provider.test.d.ts +1 -0
  59. package/dist/tests/codex-provider.test.js +180 -0
  60. package/dist/tests/gateway.test.cjs +173 -1
  61. package/dist/tests/gateway.test.js +173 -1
  62. package/dist/tests/provider-command-codex.test.cjs +57 -0
  63. package/dist/tests/provider-command-codex.test.d.ts +1 -0
  64. package/dist/tests/provider-command-codex.test.js +51 -0
  65. package/dist/tests/sessionStateMessages.test.cjs +38 -0
  66. package/dist/tests/sessionStateMessages.test.js +38 -0
  67. package/dist/webui/assets/index-BVMavpud.css +11 -0
  68. package/dist/webui/assets/index-DCB2aVVf.js +182 -0
  69. package/dist/webui/index.html +2 -2
  70. package/package.json +3 -1
  71. package/dist/webui/assets/index-BytPznA_.css +0 -1
  72. package/dist/webui/assets/index-u_5qlVip.js +0 -176
@@ -0,0 +1,290 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { validateConfig } from "../cli/config/schema.js";
3
+ import { chunkHasAssistantText, configureDeepAgentSummarizationMiddleware, detectToolEventContext, resolveHumanInTheLoopSettings, resolveModelRetryMiddlewareSettings, resolveSummarizationMiddlewareSettings, resolveToolRetryMiddlewareSettings, selectStreamingFallbackText } from "../cli/core/agentInvoker.js";
4
+ const parseConfig = (input)=>{
5
+ const result = validateConfig(input);
6
+ if (!result.success || !result.data) throw new Error(result.error || "Expected config validation to succeed");
7
+ return result.data;
8
+ };
9
+ describe("resolveSummarizationMiddlewareSettings", ()=>{
10
+ it("returns default summarization settings from config defaults", ()=>{
11
+ const config = parseConfig({});
12
+ expect(resolveSummarizationMiddlewareSettings(config)).toEqual({
13
+ maxTokensBeforeSummary: 12000,
14
+ messagesToKeep: 8
15
+ });
16
+ });
17
+ it("returns null when summarization is disabled", ()=>{
18
+ const config = parseConfig({
19
+ summarization: {
20
+ enabled: false
21
+ }
22
+ });
23
+ expect(resolveSummarizationMiddlewareSettings(config)).toBeNull();
24
+ });
25
+ it("returns custom summarization settings when configured", ()=>{
26
+ const config = parseConfig({
27
+ summarization: {
28
+ enabled: true,
29
+ maxTokensBeforeSummary: 20000,
30
+ messagesToKeep: 10
31
+ }
32
+ });
33
+ expect(resolveSummarizationMiddlewareSettings(config)).toEqual({
34
+ maxTokensBeforeSummary: 20000,
35
+ messagesToKeep: 10
36
+ });
37
+ });
38
+ });
39
+ describe("configureDeepAgentSummarizationMiddleware", ()=>{
40
+ it("replaces built-in summarization middleware with configured settings", ()=>{
41
+ const agent = {
42
+ options: {
43
+ middleware: [
44
+ {
45
+ name: "todoListMiddleware"
46
+ },
47
+ {
48
+ name: "SummarizationMiddleware",
49
+ marker: "old"
50
+ }
51
+ ]
52
+ }
53
+ };
54
+ configureDeepAgentSummarizationMiddleware(agent, {
55
+ maxTokensBeforeSummary: 9000,
56
+ messagesToKeep: 5
57
+ }, "openai:gpt-4o-mini");
58
+ expect(agent.options.middleware).toHaveLength(2);
59
+ expect(agent.options.middleware[1]?.name).toBe("SummarizationMiddleware");
60
+ expect(agent.options.middleware[1]?.marker).toBeUndefined();
61
+ });
62
+ it("removes built-in summarization middleware when disabled", ()=>{
63
+ const agent = {
64
+ options: {
65
+ middleware: [
66
+ {
67
+ name: "todoListMiddleware"
68
+ },
69
+ {
70
+ name: "SummarizationMiddleware"
71
+ },
72
+ {
73
+ name: "patchToolCallsMiddleware"
74
+ }
75
+ ]
76
+ }
77
+ };
78
+ configureDeepAgentSummarizationMiddleware(agent, null);
79
+ expect(agent.options.middleware.map((m)=>m.name)).toEqual([
80
+ "todoListMiddleware",
81
+ "patchToolCallsMiddleware"
82
+ ]);
83
+ });
84
+ });
85
+ describe("resolveModelRetryMiddlewareSettings", ()=>{
86
+ it("returns default retry settings when config defaults are used", ()=>{
87
+ const config = parseConfig({});
88
+ expect(resolveModelRetryMiddlewareSettings(config)).toEqual({
89
+ maxRetries: 2,
90
+ backoffFactor: 2,
91
+ initialDelayMs: 1000,
92
+ maxDelayMs: 60000,
93
+ jitter: true,
94
+ onFailure: "continue"
95
+ });
96
+ });
97
+ it("returns configured retry settings when enabled", ()=>{
98
+ const config = parseConfig({
99
+ modelRetry: {
100
+ enabled: true,
101
+ maxRetries: 3,
102
+ backoffFactor: 1.5,
103
+ initialDelayMs: 250,
104
+ maxDelayMs: 5000,
105
+ jitter: false,
106
+ onFailure: "error"
107
+ }
108
+ });
109
+ expect(resolveModelRetryMiddlewareSettings(config)).toEqual({
110
+ maxRetries: 3,
111
+ backoffFactor: 1.5,
112
+ initialDelayMs: 250,
113
+ maxDelayMs: 5000,
114
+ jitter: false,
115
+ onFailure: "error"
116
+ });
117
+ });
118
+ });
119
+ describe("resolveToolRetryMiddlewareSettings", ()=>{
120
+ it("returns null by default", ()=>{
121
+ const config = parseConfig({});
122
+ expect(resolveToolRetryMiddlewareSettings(config)).toBeNull();
123
+ });
124
+ it("returns configured retry settings and tool filter when enabled", ()=>{
125
+ const config = parseConfig({
126
+ toolRetry: {
127
+ enabled: true,
128
+ maxRetries: 4,
129
+ backoffFactor: 2,
130
+ initialDelayMs: 500,
131
+ maxDelayMs: 10000,
132
+ jitter: true,
133
+ onFailure: "continue",
134
+ tools: [
135
+ "internet_search",
136
+ "web_crawler"
137
+ ]
138
+ }
139
+ });
140
+ expect(resolveToolRetryMiddlewareSettings(config)).toEqual({
141
+ maxRetries: 4,
142
+ backoffFactor: 2,
143
+ initialDelayMs: 500,
144
+ maxDelayMs: 10000,
145
+ jitter: true,
146
+ onFailure: "continue",
147
+ tools: [
148
+ "internet_search",
149
+ "web_crawler"
150
+ ]
151
+ });
152
+ });
153
+ });
154
+ describe("resolveHumanInTheLoopSettings", ()=>{
155
+ it("returns null by default", ()=>{
156
+ const config = parseConfig({});
157
+ expect(resolveHumanInTheLoopSettings(config)).toBeNull();
158
+ });
159
+ it("returns null when enabled but no tool policy is defined", ()=>{
160
+ const config = parseConfig({
161
+ humanInTheLoop: {
162
+ enabled: true,
163
+ interruptOn: {}
164
+ }
165
+ });
166
+ expect(resolveHumanInTheLoopSettings(config)).toBeNull();
167
+ });
168
+ it("returns interrupt map when enabled with tool policies", ()=>{
169
+ const config = parseConfig({
170
+ humanInTheLoop: {
171
+ enabled: true,
172
+ interruptOn: {
173
+ command_execute: {
174
+ allowedDecisions: [
175
+ "approve",
176
+ "reject"
177
+ ],
178
+ description: "Command execution requires approval"
179
+ },
180
+ internet_search: false
181
+ }
182
+ }
183
+ });
184
+ expect(resolveHumanInTheLoopSettings(config)).toEqual({
185
+ interruptOn: {
186
+ command_execute: {
187
+ allowedDecisions: [
188
+ "approve",
189
+ "reject"
190
+ ],
191
+ description: "Command execution requires approval"
192
+ },
193
+ internet_search: false
194
+ }
195
+ });
196
+ });
197
+ });
198
+ describe("detectToolEventContext", ()=>{
199
+ it("extracts tool context for tool start events", ()=>{
200
+ const detected = detectToolEventContext({
201
+ event: "on_tool_start",
202
+ name: "glob"
203
+ });
204
+ expect(detected).toEqual({
205
+ event: "on_tool_start",
206
+ toolName: "glob"
207
+ });
208
+ });
209
+ it("returns null for non-tool events", ()=>{
210
+ const detected = detectToolEventContext({
211
+ event: "on_chat_model_stream",
212
+ data: {
213
+ chunk: {
214
+ text: "hello"
215
+ }
216
+ }
217
+ });
218
+ expect(detected).toBeNull();
219
+ });
220
+ });
221
+ describe("chunkHasAssistantText", ()=>{
222
+ it("detects text in on_chat_model_stream chunks", ()=>{
223
+ expect(chunkHasAssistantText({
224
+ event: "on_chat_model_stream",
225
+ data: {
226
+ chunk: {
227
+ content: [
228
+ {
229
+ type: "text",
230
+ text: "hello"
231
+ }
232
+ ]
233
+ }
234
+ }
235
+ })).toBe(true);
236
+ });
237
+ it("detects text in on_llm_stream chunks", ()=>{
238
+ expect(chunkHasAssistantText({
239
+ event: "on_llm_stream",
240
+ data: {
241
+ chunk: {
242
+ text: "delta"
243
+ }
244
+ }
245
+ })).toBe(true);
246
+ });
247
+ it("returns false when chunk has no assistant text", ()=>{
248
+ expect(chunkHasAssistantText({
249
+ event: "on_tool_start",
250
+ name: "glob"
251
+ })).toBe(false);
252
+ });
253
+ });
254
+ describe("selectStreamingFallbackText", ()=>{
255
+ it("returns the most recent assistant message in the same invocation window", ()=>{
256
+ const selected = selectStreamingFallbackText([
257
+ {
258
+ role: "assistant",
259
+ createdAt: 1000,
260
+ content: "stale message"
261
+ },
262
+ {
263
+ role: "assistant",
264
+ createdAt: 4500,
265
+ content: "fresh fallback"
266
+ }
267
+ ], 4000);
268
+ expect(selected).toBe("fresh fallback");
269
+ });
270
+ it("ignores stale, user-role, and empty-content messages", ()=>{
271
+ const selected = selectStreamingFallbackText([
272
+ {
273
+ role: "assistant",
274
+ createdAt: 1000,
275
+ content: "too old"
276
+ },
277
+ {
278
+ role: "user",
279
+ createdAt: 4900,
280
+ content: "not assistant"
281
+ },
282
+ {
283
+ role: "assistant",
284
+ createdAt: 5000,
285
+ content: " "
286
+ }
287
+ ], 5000);
288
+ expect(selected).toBeUndefined();
289
+ });
290
+ });
@@ -33,6 +33,33 @@ const external_os_namespaceObject = require("os");
33
33
  (0, external_vitest_namespaceObject.expect)(config).toEqual({
34
34
  logLevel: "info",
35
35
  recursionLimit: 5000,
36
+ summarization: {
37
+ enabled: true,
38
+ maxTokensBeforeSummary: 12000,
39
+ messagesToKeep: 8
40
+ },
41
+ modelRetry: {
42
+ enabled: true,
43
+ maxRetries: 2,
44
+ backoffFactor: 2,
45
+ initialDelayMs: 1000,
46
+ maxDelayMs: 60000,
47
+ jitter: true,
48
+ onFailure: "continue"
49
+ },
50
+ toolRetry: {
51
+ enabled: false,
52
+ maxRetries: 2,
53
+ backoffFactor: 2,
54
+ initialDelayMs: 1000,
55
+ maxDelayMs: 60000,
56
+ jitter: true,
57
+ onFailure: "continue"
58
+ },
59
+ humanInTheLoop: {
60
+ enabled: false,
61
+ interruptOn: {}
62
+ },
36
63
  search: {
37
64
  provider: "duckduckgo",
38
65
  maxResults: 5
@@ -111,6 +138,11 @@ const external_os_namespaceObject = require("os");
111
138
  logLevel: "debug",
112
139
  defaultAgent: "coder",
113
140
  recursionLimit: 5,
141
+ summarization: {
142
+ enabled: true,
143
+ maxTokensBeforeSummary: 18000,
144
+ messagesToKeep: 10
145
+ },
114
146
  search: {
115
147
  provider: "perplexity",
116
148
  maxResults: 10
@@ -130,6 +162,62 @@ const external_os_namespaceObject = require("os");
130
162
  const config = loader.loadConfig();
131
163
  (0, external_vitest_namespaceObject.expect)(config).toMatchObject(configData);
132
164
  });
165
+ (0, external_vitest_namespaceObject.it)("should allow disabling summarization middleware", ()=>{
166
+ const configData = {
167
+ summarization: {
168
+ enabled: false
169
+ }
170
+ };
171
+ (0, external_fs_namespaceObject.writeFileSync)((0, external_path_namespaceObject.join)(configDir, "wingman.config.json"), JSON.stringify(configData));
172
+ const loader = new loader_cjs_namespaceObject.WingmanConfigLoader(".wingman", testDir);
173
+ const config = loader.loadConfig();
174
+ (0, external_vitest_namespaceObject.expect)(config.summarization).toEqual({
175
+ enabled: false,
176
+ maxTokensBeforeSummary: 12000,
177
+ messagesToKeep: 8
178
+ });
179
+ });
180
+ (0, external_vitest_namespaceObject.it)("should load retry and HITL middleware config", ()=>{
181
+ const configData = {
182
+ modelRetry: {
183
+ enabled: true,
184
+ maxRetries: 3,
185
+ backoffFactor: 1.5,
186
+ initialDelayMs: 250,
187
+ maxDelayMs: 4000,
188
+ jitter: false,
189
+ onFailure: "error"
190
+ },
191
+ toolRetry: {
192
+ enabled: true,
193
+ maxRetries: 4,
194
+ backoffFactor: 2,
195
+ initialDelayMs: 500,
196
+ maxDelayMs: 8000,
197
+ jitter: true,
198
+ onFailure: "continue",
199
+ tools: [
200
+ "internet_search",
201
+ "web_crawler"
202
+ ]
203
+ },
204
+ humanInTheLoop: {
205
+ enabled: true,
206
+ interruptOn: {
207
+ command_execute: {
208
+ allowedDecisions: [
209
+ "approve",
210
+ "reject"
211
+ ]
212
+ }
213
+ }
214
+ }
215
+ };
216
+ (0, external_fs_namespaceObject.writeFileSync)((0, external_path_namespaceObject.join)(configDir, "wingman.config.json"), JSON.stringify(configData));
217
+ const loader = new loader_cjs_namespaceObject.WingmanConfigLoader(".wingman", testDir);
218
+ const config = loader.loadConfig();
219
+ (0, external_vitest_namespaceObject.expect)(config).toMatchObject(configData);
220
+ });
133
221
  (0, external_vitest_namespaceObject.it)("should handle all valid log levels", ()=>{
134
222
  const levels = [
135
223
  "debug",
@@ -31,6 +31,33 @@ describe("CLI Config Loader", ()=>{
31
31
  expect(config).toEqual({
32
32
  logLevel: "info",
33
33
  recursionLimit: 5000,
34
+ summarization: {
35
+ enabled: true,
36
+ maxTokensBeforeSummary: 12000,
37
+ messagesToKeep: 8
38
+ },
39
+ modelRetry: {
40
+ enabled: true,
41
+ maxRetries: 2,
42
+ backoffFactor: 2,
43
+ initialDelayMs: 1000,
44
+ maxDelayMs: 60000,
45
+ jitter: true,
46
+ onFailure: "continue"
47
+ },
48
+ toolRetry: {
49
+ enabled: false,
50
+ maxRetries: 2,
51
+ backoffFactor: 2,
52
+ initialDelayMs: 1000,
53
+ maxDelayMs: 60000,
54
+ jitter: true,
55
+ onFailure: "continue"
56
+ },
57
+ humanInTheLoop: {
58
+ enabled: false,
59
+ interruptOn: {}
60
+ },
34
61
  search: {
35
62
  provider: "duckduckgo",
36
63
  maxResults: 5
@@ -109,6 +136,11 @@ describe("CLI Config Loader", ()=>{
109
136
  logLevel: "debug",
110
137
  defaultAgent: "coder",
111
138
  recursionLimit: 5,
139
+ summarization: {
140
+ enabled: true,
141
+ maxTokensBeforeSummary: 18000,
142
+ messagesToKeep: 10
143
+ },
112
144
  search: {
113
145
  provider: "perplexity",
114
146
  maxResults: 10
@@ -128,6 +160,62 @@ describe("CLI Config Loader", ()=>{
128
160
  const config = loader.loadConfig();
129
161
  expect(config).toMatchObject(configData);
130
162
  });
163
+ it("should allow disabling summarization middleware", ()=>{
164
+ const configData = {
165
+ summarization: {
166
+ enabled: false
167
+ }
168
+ };
169
+ writeFileSync(join(configDir, "wingman.config.json"), JSON.stringify(configData));
170
+ const loader = new WingmanConfigLoader(".wingman", testDir);
171
+ const config = loader.loadConfig();
172
+ expect(config.summarization).toEqual({
173
+ enabled: false,
174
+ maxTokensBeforeSummary: 12000,
175
+ messagesToKeep: 8
176
+ });
177
+ });
178
+ it("should load retry and HITL middleware config", ()=>{
179
+ const configData = {
180
+ modelRetry: {
181
+ enabled: true,
182
+ maxRetries: 3,
183
+ backoffFactor: 1.5,
184
+ initialDelayMs: 250,
185
+ maxDelayMs: 4000,
186
+ jitter: false,
187
+ onFailure: "error"
188
+ },
189
+ toolRetry: {
190
+ enabled: true,
191
+ maxRetries: 4,
192
+ backoffFactor: 2,
193
+ initialDelayMs: 500,
194
+ maxDelayMs: 8000,
195
+ jitter: true,
196
+ onFailure: "continue",
197
+ tools: [
198
+ "internet_search",
199
+ "web_crawler"
200
+ ]
201
+ },
202
+ humanInTheLoop: {
203
+ enabled: true,
204
+ interruptOn: {
205
+ command_execute: {
206
+ allowedDecisions: [
207
+ "approve",
208
+ "reject"
209
+ ]
210
+ }
211
+ }
212
+ }
213
+ };
214
+ writeFileSync(join(configDir, "wingman.config.json"), JSON.stringify(configData));
215
+ const loader = new WingmanConfigLoader(".wingman", testDir);
216
+ const config = loader.loadConfig();
217
+ expect(config).toMatchObject(configData);
218
+ });
131
219
  it("should handle all valid log levels", ()=>{
132
220
  const levels = [
133
221
  "debug",
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ var __webpack_exports__ = {};
3
+ const external_node_fs_namespaceObject = require("node:fs");
4
+ const external_node_os_namespaceObject = require("node:os");
5
+ const external_node_path_namespaceObject = require("node:path");
6
+ const external_vitest_namespaceObject = require("vitest");
7
+ (0, external_vitest_namespaceObject.describe)("codex credential precedence", ()=>{
8
+ let homeDir;
9
+ let codexHome;
10
+ const originalHome = process.env.HOME;
11
+ const originalCodexHome = process.env.CODEX_HOME;
12
+ const originalCodexAccessToken = process.env.CODEX_ACCESS_TOKEN;
13
+ const originalChatGptAccessToken = process.env.CHATGPT_ACCESS_TOKEN;
14
+ (0, external_vitest_namespaceObject.beforeEach)(()=>{
15
+ homeDir = (0, external_node_fs_namespaceObject.mkdtempSync)((0, external_node_path_namespaceObject.join)((0, external_node_os_namespaceObject.tmpdir)(), "wingman-home-"));
16
+ codexHome = (0, external_node_fs_namespaceObject.mkdtempSync)((0, external_node_path_namespaceObject.join)((0, external_node_os_namespaceObject.tmpdir)(), "wingman-codex-home-"));
17
+ process.env.HOME = homeDir;
18
+ process.env.CODEX_HOME = codexHome;
19
+ delete process.env.CODEX_ACCESS_TOKEN;
20
+ delete process.env.CHATGPT_ACCESS_TOKEN;
21
+ });
22
+ (0, external_vitest_namespaceObject.afterEach)(()=>{
23
+ if (void 0 === originalHome) delete process.env.HOME;
24
+ else process.env.HOME = originalHome;
25
+ if (void 0 === originalCodexHome) delete process.env.CODEX_HOME;
26
+ else process.env.CODEX_HOME = originalCodexHome;
27
+ if (void 0 === originalCodexAccessToken) delete process.env.CODEX_ACCESS_TOKEN;
28
+ else process.env.CODEX_ACCESS_TOKEN = originalCodexAccessToken;
29
+ if (void 0 === originalChatGptAccessToken) delete process.env.CHATGPT_ACCESS_TOKEN;
30
+ else process.env.CHATGPT_ACCESS_TOKEN = originalChatGptAccessToken;
31
+ if ((0, external_node_fs_namespaceObject.existsSync)(homeDir)) (0, external_node_fs_namespaceObject.rmSync)(homeDir, {
32
+ recursive: true,
33
+ force: true
34
+ });
35
+ if ((0, external_node_fs_namespaceObject.existsSync)(codexHome)) (0, external_node_fs_namespaceObject.rmSync)(codexHome, {
36
+ recursive: true,
37
+ force: true
38
+ });
39
+ });
40
+ (0, external_vitest_namespaceObject.it)("prefers Codex auth file over stored Wingman credentials for codex", async ()=>{
41
+ external_vitest_namespaceObject.vi.resetModules();
42
+ const credentials = await import("../providers/credentials.cjs");
43
+ const codex = await import("../providers/codex.cjs");
44
+ const credsPath = credentials.getCredentialsPath();
45
+ (0, external_node_fs_namespaceObject.mkdirSync)((0, external_node_path_namespaceObject.dirname)(credsPath), {
46
+ recursive: true
47
+ });
48
+ (0, external_node_fs_namespaceObject.writeFileSync)(credsPath, JSON.stringify({
49
+ version: 1,
50
+ updatedAt: new Date().toISOString(),
51
+ providers: {
52
+ codex: {
53
+ apiKey: "stale-wingman-token"
54
+ }
55
+ }
56
+ }, null, 2));
57
+ const authPath = codex.getCodexAuthPath();
58
+ (0, external_node_fs_namespaceObject.mkdirSync)((0, external_node_path_namespaceObject.dirname)(authPath), {
59
+ recursive: true
60
+ });
61
+ (0, external_node_fs_namespaceObject.writeFileSync)(authPath, JSON.stringify({
62
+ tokens: {
63
+ access_token: "codex-file-token",
64
+ account_id: "acct_123"
65
+ }
66
+ }, null, 2));
67
+ const resolved = credentials.resolveProviderToken("codex");
68
+ (0, external_vitest_namespaceObject.expect)(resolved.token).toBe("codex-file-token");
69
+ (0, external_vitest_namespaceObject.expect)(resolved.source).toBe("credentials");
70
+ });
71
+ (0, external_vitest_namespaceObject.it)("still prefers env vars over Codex auth file", async ()=>{
72
+ process.env.CODEX_ACCESS_TOKEN = "env-token";
73
+ external_vitest_namespaceObject.vi.resetModules();
74
+ const credentials = await import("../providers/credentials.cjs");
75
+ const codex = await import("../providers/codex.cjs");
76
+ const authPath = codex.getCodexAuthPath();
77
+ (0, external_node_fs_namespaceObject.mkdirSync)((0, external_node_path_namespaceObject.dirname)(authPath), {
78
+ recursive: true
79
+ });
80
+ (0, external_node_fs_namespaceObject.writeFileSync)(authPath, JSON.stringify({
81
+ tokens: {
82
+ access_token: "codex-file-token"
83
+ }
84
+ }, null, 2));
85
+ const resolved = credentials.resolveProviderToken("codex");
86
+ (0, external_vitest_namespaceObject.expect)(resolved.token).toBe("env-token");
87
+ (0, external_vitest_namespaceObject.expect)(resolved.source).toBe("env");
88
+ (0, external_vitest_namespaceObject.expect)((0, external_node_fs_namespaceObject.readFileSync)(authPath, "utf-8")).toContain("codex-file-token");
89
+ });
90
+ });
91
+ for(var __rspack_i in __webpack_exports__)exports[__rspack_i] = __webpack_exports__[__rspack_i];
92
+ Object.defineProperty(exports, '__esModule', {
93
+ value: true
94
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,88 @@
1
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5
+ describe("codex credential precedence", ()=>{
6
+ let homeDir;
7
+ let codexHome;
8
+ const originalHome = process.env.HOME;
9
+ const originalCodexHome = process.env.CODEX_HOME;
10
+ const originalCodexAccessToken = process.env.CODEX_ACCESS_TOKEN;
11
+ const originalChatGptAccessToken = process.env.CHATGPT_ACCESS_TOKEN;
12
+ beforeEach(()=>{
13
+ homeDir = mkdtempSync(join(tmpdir(), "wingman-home-"));
14
+ codexHome = mkdtempSync(join(tmpdir(), "wingman-codex-home-"));
15
+ process.env.HOME = homeDir;
16
+ process.env.CODEX_HOME = codexHome;
17
+ delete process.env.CODEX_ACCESS_TOKEN;
18
+ delete process.env.CHATGPT_ACCESS_TOKEN;
19
+ });
20
+ afterEach(()=>{
21
+ if (void 0 === originalHome) delete process.env.HOME;
22
+ else process.env.HOME = originalHome;
23
+ if (void 0 === originalCodexHome) delete process.env.CODEX_HOME;
24
+ else process.env.CODEX_HOME = originalCodexHome;
25
+ if (void 0 === originalCodexAccessToken) delete process.env.CODEX_ACCESS_TOKEN;
26
+ else process.env.CODEX_ACCESS_TOKEN = originalCodexAccessToken;
27
+ if (void 0 === originalChatGptAccessToken) delete process.env.CHATGPT_ACCESS_TOKEN;
28
+ else process.env.CHATGPT_ACCESS_TOKEN = originalChatGptAccessToken;
29
+ if (existsSync(homeDir)) rmSync(homeDir, {
30
+ recursive: true,
31
+ force: true
32
+ });
33
+ if (existsSync(codexHome)) rmSync(codexHome, {
34
+ recursive: true,
35
+ force: true
36
+ });
37
+ });
38
+ it("prefers Codex auth file over stored Wingman credentials for codex", async ()=>{
39
+ vi.resetModules();
40
+ const credentials = await import("../providers/credentials.js");
41
+ const codex = await import("../providers/codex.js");
42
+ const credsPath = credentials.getCredentialsPath();
43
+ mkdirSync(dirname(credsPath), {
44
+ recursive: true
45
+ });
46
+ writeFileSync(credsPath, JSON.stringify({
47
+ version: 1,
48
+ updatedAt: new Date().toISOString(),
49
+ providers: {
50
+ codex: {
51
+ apiKey: "stale-wingman-token"
52
+ }
53
+ }
54
+ }, null, 2));
55
+ const authPath = codex.getCodexAuthPath();
56
+ mkdirSync(dirname(authPath), {
57
+ recursive: true
58
+ });
59
+ writeFileSync(authPath, JSON.stringify({
60
+ tokens: {
61
+ access_token: "codex-file-token",
62
+ account_id: "acct_123"
63
+ }
64
+ }, null, 2));
65
+ const resolved = credentials.resolveProviderToken("codex");
66
+ expect(resolved.token).toBe("codex-file-token");
67
+ expect(resolved.source).toBe("credentials");
68
+ });
69
+ it("still prefers env vars over Codex auth file", async ()=>{
70
+ process.env.CODEX_ACCESS_TOKEN = "env-token";
71
+ vi.resetModules();
72
+ const credentials = await import("../providers/credentials.js");
73
+ const codex = await import("../providers/codex.js");
74
+ const authPath = codex.getCodexAuthPath();
75
+ mkdirSync(dirname(authPath), {
76
+ recursive: true
77
+ });
78
+ writeFileSync(authPath, JSON.stringify({
79
+ tokens: {
80
+ access_token: "codex-file-token"
81
+ }
82
+ }, null, 2));
83
+ const resolved = credentials.resolveProviderToken("codex");
84
+ expect(resolved.token).toBe("env-token");
85
+ expect(resolved.source).toBe("env");
86
+ expect(readFileSync(authPath, "utf-8")).toContain("codex-file-token");
87
+ });
88
+ });