@wingman-ai/gateway 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.wingman/agents/README.md +7 -1
- package/.wingman/agents/coding/agent.md +299 -201
- package/.wingman/agents/coding-v2/agent.md +127 -0
- package/.wingman/agents/coding-v2/implementor.md +89 -0
- package/.wingman/agents/main/agent.md +4 -0
- package/README.md +1 -0
- package/dist/agent/config/agentConfig.cjs +31 -17
- package/dist/agent/config/agentConfig.d.ts +23 -1
- package/dist/agent/config/agentConfig.js +30 -19
- package/dist/agent/config/agentLoader.cjs +26 -8
- package/dist/agent/config/agentLoader.d.ts +4 -2
- package/dist/agent/config/agentLoader.js +26 -8
- package/dist/agent/config/modelFactory.cjs +95 -25
- package/dist/agent/config/modelFactory.d.ts +13 -1
- package/dist/agent/config/modelFactory.js +95 -25
- package/dist/agent/config/toolRegistry.cjs +19 -6
- package/dist/agent/config/toolRegistry.d.ts +5 -2
- package/dist/agent/config/toolRegistry.js +19 -6
- package/dist/agent/middleware/hooks/types.cjs +13 -13
- package/dist/agent/middleware/hooks/types.d.ts +1 -1
- package/dist/agent/middleware/hooks/types.js +14 -14
- package/dist/agent/tests/agentConfig.test.cjs +22 -2
- package/dist/agent/tests/agentConfig.test.js +22 -2
- package/dist/agent/tests/agentLoader.test.cjs +38 -1
- package/dist/agent/tests/agentLoader.test.js +38 -1
- package/dist/agent/tests/backgroundTerminal.test.cjs +70 -0
- package/dist/agent/tests/backgroundTerminal.test.d.ts +1 -0
- package/dist/agent/tests/backgroundTerminal.test.js +64 -0
- package/dist/agent/tests/commandExecuteTool.test.cjs +29 -0
- package/dist/agent/tests/commandExecuteTool.test.d.ts +1 -0
- package/dist/agent/tests/commandExecuteTool.test.js +23 -0
- package/dist/agent/tests/modelFactory.test.cjs +47 -5
- package/dist/agent/tests/modelFactory.test.js +47 -5
- package/dist/agent/tests/terminalSessionManager.test.cjs +121 -0
- package/dist/agent/tests/terminalSessionManager.test.d.ts +1 -0
- package/dist/agent/tests/terminalSessionManager.test.js +115 -0
- package/dist/agent/tests/toolRegistry.test.cjs +14 -2
- package/dist/agent/tests/toolRegistry.test.js +14 -2
- package/dist/agent/tools/background_terminal.cjs +128 -0
- package/dist/agent/tools/background_terminal.d.ts +41 -0
- package/dist/agent/tools/background_terminal.js +94 -0
- package/dist/agent/tools/code_search.cjs +6 -6
- package/dist/agent/tools/code_search.d.ts +1 -1
- package/dist/agent/tools/code_search.js +7 -7
- package/dist/agent/tools/command_execute.cjs +22 -7
- package/dist/agent/tools/command_execute.d.ts +3 -2
- package/dist/agent/tools/command_execute.js +23 -8
- package/dist/agent/tools/git_status.cjs +3 -3
- package/dist/agent/tools/git_status.d.ts +1 -1
- package/dist/agent/tools/git_status.js +4 -4
- package/dist/agent/tools/internet_search.cjs +6 -6
- package/dist/agent/tools/internet_search.d.ts +1 -1
- package/dist/agent/tools/internet_search.js +7 -7
- package/dist/agent/tools/terminal_session_manager.cjs +321 -0
- package/dist/agent/tools/terminal_session_manager.d.ts +77 -0
- package/dist/agent/tools/terminal_session_manager.js +284 -0
- package/dist/agent/tools/think.cjs +4 -4
- package/dist/agent/tools/think.d.ts +1 -1
- package/dist/agent/tools/think.js +5 -5
- package/dist/agent/tools/ui_registry.cjs +13 -13
- package/dist/agent/tools/ui_registry.d.ts +4 -4
- package/dist/agent/tools/ui_registry.js +14 -14
- package/dist/agent/tools/web_crawler.cjs +4 -4
- package/dist/agent/tools/web_crawler.d.ts +1 -1
- package/dist/agent/tools/web_crawler.js +5 -5
- package/dist/agent/utils.cjs +2 -1
- package/dist/agent/utils.js +2 -1
- package/dist/cli/commands/init.cjs +7 -6
- package/dist/cli/commands/init.js +7 -6
- package/dist/cli/commands/provider.cjs +17 -3
- package/dist/cli/commands/provider.js +17 -3
- package/dist/cli/config/loader.cjs +27 -0
- package/dist/cli/config/loader.js +27 -0
- package/dist/cli/config/schema.cjs +146 -68
- package/dist/cli/config/schema.d.ts +89 -1
- package/dist/cli/config/schema.js +134 -68
- package/dist/cli/core/agentInvoker.cjs +344 -17
- package/dist/cli/core/agentInvoker.d.ts +63 -3
- package/dist/cli/core/agentInvoker.js +303 -12
- package/dist/cli/core/sessionManager.cjs +32 -5
- package/dist/cli/core/sessionManager.js +32 -5
- package/dist/cli/core/streamParser.cjs +15 -0
- package/dist/cli/core/streamParser.js +15 -0
- package/dist/cli/index.cjs +6 -5
- package/dist/cli/index.js +6 -5
- package/dist/cli/types.d.ts +32 -0
- package/dist/cli/ui/toolDisplayHelpers.cjs +2 -0
- package/dist/cli/ui/toolDisplayHelpers.js +2 -0
- package/dist/gateway/hooks/registry.cjs +2 -1
- package/dist/gateway/hooks/registry.d.ts +1 -1
- package/dist/gateway/hooks/registry.js +2 -1
- package/dist/gateway/hooks/types.cjs +11 -11
- package/dist/gateway/hooks/types.d.ts +1 -1
- package/dist/gateway/hooks/types.js +12 -12
- package/dist/gateway/http/agents.cjs +67 -4
- package/dist/gateway/http/agents.js +67 -4
- package/dist/gateway/http/sessions.cjs +7 -7
- package/dist/gateway/http/sessions.js +7 -7
- package/dist/gateway/http/types.d.ts +5 -3
- package/dist/gateway/http/webhooks.cjs +6 -5
- package/dist/gateway/http/webhooks.js +6 -5
- package/dist/gateway/server.cjs +198 -41
- package/dist/gateway/server.d.ts +9 -1
- package/dist/gateway/server.js +198 -41
- package/dist/gateway/types.d.ts +1 -0
- package/dist/gateway/validation.cjs +39 -39
- package/dist/gateway/validation.d.ts +1 -1
- package/dist/gateway/validation.js +40 -40
- package/dist/providers/codex.cjs +167 -0
- package/dist/providers/codex.d.ts +15 -0
- package/dist/providers/codex.js +127 -0
- package/dist/providers/credentials.cjs +8 -0
- package/dist/providers/credentials.js +8 -0
- package/dist/providers/registry.cjs +11 -0
- package/dist/providers/registry.d.ts +1 -1
- package/dist/providers/registry.js +11 -0
- package/dist/tests/additionalMessageMiddleware.test.cjs +3 -0
- package/dist/tests/additionalMessageMiddleware.test.js +3 -0
- package/dist/tests/agentInvokerSummarization.test.cjs +455 -0
- package/dist/tests/agentInvokerSummarization.test.d.ts +1 -0
- package/dist/tests/agentInvokerSummarization.test.js +449 -0
- package/dist/tests/agents-api.test.cjs +45 -5
- package/dist/tests/agents-api.test.js +45 -5
- package/dist/tests/cli-config-loader.test.cjs +88 -0
- package/dist/tests/cli-config-loader.test.js +88 -0
- package/dist/tests/cli-init.test.cjs +27 -3
- package/dist/tests/cli-init.test.js +27 -3
- package/dist/tests/codex-credentials-precedence.test.cjs +94 -0
- package/dist/tests/codex-credentials-precedence.test.d.ts +1 -0
- package/dist/tests/codex-credentials-precedence.test.js +88 -0
- package/dist/tests/codex-provider.test.cjs +210 -0
- package/dist/tests/codex-provider.test.d.ts +1 -0
- package/dist/tests/codex-provider.test.js +204 -0
- package/dist/tests/gateway.test.cjs +115 -8
- package/dist/tests/gateway.test.js +115 -8
- package/dist/tests/provider-command-codex.test.cjs +57 -0
- package/dist/tests/provider-command-codex.test.d.ts +1 -0
- package/dist/tests/provider-command-codex.test.js +51 -0
- package/dist/tests/sessionStateMessages.test.cjs +38 -0
- package/dist/tests/sessionStateMessages.test.js +38 -0
- package/dist/tests/toolDisplayHelpers.test.cjs +3 -0
- package/dist/tests/toolDisplayHelpers.test.js +3 -0
- package/dist/tools/mcp-finance.cjs +48 -48
- package/dist/tools/mcp-finance.js +48 -48
- package/dist/types/mcp.cjs +15 -15
- package/dist/types/mcp.d.ts +1 -1
- package/dist/types/mcp.js +16 -16
- package/dist/types/voice.cjs +21 -21
- package/dist/types/voice.d.ts +1 -1
- package/dist/types/voice.js +22 -22
- package/dist/webui/assets/index-DVWQluit.css +11 -0
- package/dist/webui/assets/index-Dlyzwalc.js +270 -0
- package/dist/webui/favicon-32x32.png +0 -0
- package/dist/webui/favicon-64x64.png +0 -0
- package/dist/webui/favicon.webp +0 -0
- package/dist/webui/index.html +4 -2
- package/package.json +13 -12
- package/.wingman/agents/coding/implementor.md +0 -79
- package/dist/webui/assets/index-CPhfGPHc.js +0 -182
- package/dist/webui/assets/index-DDsMIOTX.css +0 -11
|
@@ -0,0 +1,210 @@
|
|
|
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
|
+
const codex_cjs_namespaceObject = require("../providers/codex.cjs");
|
|
8
|
+
(0, external_vitest_namespaceObject.describe)("codex provider", ()=>{
|
|
9
|
+
let codexHome;
|
|
10
|
+
const originalCodexHome = process.env.CODEX_HOME;
|
|
11
|
+
(0, external_vitest_namespaceObject.beforeEach)(()=>{
|
|
12
|
+
codexHome = (0, external_node_fs_namespaceObject.mkdtempSync)((0, external_node_path_namespaceObject.join)((0, external_node_os_namespaceObject.tmpdir)(), "wingman-codex-"));
|
|
13
|
+
process.env.CODEX_HOME = codexHome;
|
|
14
|
+
delete process.env.CODEX_ACCESS_TOKEN;
|
|
15
|
+
delete process.env.CHATGPT_ACCESS_TOKEN;
|
|
16
|
+
});
|
|
17
|
+
(0, external_vitest_namespaceObject.afterEach)(()=>{
|
|
18
|
+
if (void 0 === originalCodexHome) delete process.env.CODEX_HOME;
|
|
19
|
+
else process.env.CODEX_HOME = originalCodexHome;
|
|
20
|
+
if ((0, external_node_fs_namespaceObject.existsSync)(codexHome)) (0, external_node_fs_namespaceObject.rmSync)(codexHome, {
|
|
21
|
+
recursive: true,
|
|
22
|
+
force: true
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
(0, external_vitest_namespaceObject.it)("reads access token and account id from codex auth file", ()=>{
|
|
26
|
+
writeCodexAuth({
|
|
27
|
+
tokens: {
|
|
28
|
+
access_token: "codex-access-token",
|
|
29
|
+
account_id: "acct_123"
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
const resolved = (0, codex_cjs_namespaceObject.resolveCodexAuthFromFile)();
|
|
33
|
+
(0, external_vitest_namespaceObject.expect)(resolved.accessToken).toBe("codex-access-token");
|
|
34
|
+
(0, external_vitest_namespaceObject.expect)(resolved.accountId).toBe("acct_123");
|
|
35
|
+
(0, external_vitest_namespaceObject.expect)(resolved.authPath).toBe((0, external_node_path_namespaceObject.join)(codexHome, "auth.json"));
|
|
36
|
+
});
|
|
37
|
+
(0, external_vitest_namespaceObject.it)("applies codex auth headers and forces store=false", async ()=>{
|
|
38
|
+
writeCodexAuth({
|
|
39
|
+
tokens: {
|
|
40
|
+
access_token: "file-token",
|
|
41
|
+
account_id: "acct_file"
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
const baseFetch = external_vitest_namespaceObject.vi.fn(async (_input, _init)=>new Response("{}", {
|
|
45
|
+
status: 200
|
|
46
|
+
}));
|
|
47
|
+
const codexFetch = (0, codex_cjs_namespaceObject.createCodexFetch)({
|
|
48
|
+
baseFetch
|
|
49
|
+
});
|
|
50
|
+
await codexFetch("https://chatgpt.com/backend-api/codex/responses", {
|
|
51
|
+
method: "POST",
|
|
52
|
+
headers: {
|
|
53
|
+
"x-api-key": "placeholder"
|
|
54
|
+
},
|
|
55
|
+
body: JSON.stringify({
|
|
56
|
+
model: "codex-mini-latest",
|
|
57
|
+
input: "hello",
|
|
58
|
+
temperature: 1
|
|
59
|
+
})
|
|
60
|
+
});
|
|
61
|
+
(0, external_vitest_namespaceObject.expect)(baseFetch).toHaveBeenCalledTimes(1);
|
|
62
|
+
const requestInit = baseFetch.mock.calls[0]?.[1];
|
|
63
|
+
(0, external_vitest_namespaceObject.expect)(requestInit).toBeDefined();
|
|
64
|
+
const headers = new Headers(requestInit?.headers);
|
|
65
|
+
const payload = JSON.parse(String(requestInit?.body));
|
|
66
|
+
(0, external_vitest_namespaceObject.expect)(headers.get("authorization")).toBe("Bearer file-token");
|
|
67
|
+
(0, external_vitest_namespaceObject.expect)(headers.get("chatgpt-account-id")).toBe("acct_file");
|
|
68
|
+
(0, external_vitest_namespaceObject.expect)(headers.get("x-api-key")).toBeNull();
|
|
69
|
+
(0, external_vitest_namespaceObject.expect)(payload.store).toBe(false);
|
|
70
|
+
(0, external_vitest_namespaceObject.expect)(payload.temperature).toBeUndefined();
|
|
71
|
+
(0, external_vitest_namespaceObject.expect)(typeof payload.instructions).toBe("string");
|
|
72
|
+
(0, external_vitest_namespaceObject.expect)(payload.instructions.length).toBeGreaterThan(0);
|
|
73
|
+
});
|
|
74
|
+
(0, external_vitest_namespaceObject.it)("derives instructions from system/developer input when missing", async ()=>{
|
|
75
|
+
writeCodexAuth({
|
|
76
|
+
tokens: {
|
|
77
|
+
access_token: "file-token"
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
const baseFetch = external_vitest_namespaceObject.vi.fn(async (_input, _init)=>new Response("{}", {
|
|
81
|
+
status: 200
|
|
82
|
+
}));
|
|
83
|
+
const codexFetch = (0, codex_cjs_namespaceObject.createCodexFetch)({
|
|
84
|
+
baseFetch
|
|
85
|
+
});
|
|
86
|
+
await codexFetch("https://chatgpt.com/backend-api/codex/responses", {
|
|
87
|
+
method: "POST",
|
|
88
|
+
body: JSON.stringify({
|
|
89
|
+
model: "gpt-5.3-codex",
|
|
90
|
+
input: [
|
|
91
|
+
{
|
|
92
|
+
role: "developer",
|
|
93
|
+
content: [
|
|
94
|
+
{
|
|
95
|
+
type: "input_text",
|
|
96
|
+
text: "Always run tests first."
|
|
97
|
+
}
|
|
98
|
+
]
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
role: "user",
|
|
102
|
+
content: [
|
|
103
|
+
{
|
|
104
|
+
type: "input_text",
|
|
105
|
+
text: "Fix the bug."
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
}
|
|
109
|
+
]
|
|
110
|
+
})
|
|
111
|
+
});
|
|
112
|
+
const requestInit = baseFetch.mock.calls[0]?.[1];
|
|
113
|
+
const payload = JSON.parse(String(requestInit?.body));
|
|
114
|
+
(0, external_vitest_namespaceObject.expect)(payload.instructions).toBe("Always run tests first.");
|
|
115
|
+
});
|
|
116
|
+
(0, external_vitest_namespaceObject.it)("preserves explicit instructions when provided", async ()=>{
|
|
117
|
+
writeCodexAuth({
|
|
118
|
+
tokens: {
|
|
119
|
+
access_token: "file-token"
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
const baseFetch = external_vitest_namespaceObject.vi.fn(async (_input, _init)=>new Response("{}", {
|
|
123
|
+
status: 200
|
|
124
|
+
}));
|
|
125
|
+
const codexFetch = (0, codex_cjs_namespaceObject.createCodexFetch)({
|
|
126
|
+
baseFetch
|
|
127
|
+
});
|
|
128
|
+
await codexFetch("https://chatgpt.com/backend-api/codex/responses", {
|
|
129
|
+
method: "POST",
|
|
130
|
+
body: JSON.stringify({
|
|
131
|
+
model: "gpt-5.3-codex",
|
|
132
|
+
instructions: "Use concise answers.",
|
|
133
|
+
input: "hello"
|
|
134
|
+
})
|
|
135
|
+
});
|
|
136
|
+
const requestInit = baseFetch.mock.calls[0]?.[1];
|
|
137
|
+
const payload = JSON.parse(String(requestInit?.body));
|
|
138
|
+
(0, external_vitest_namespaceObject.expect)(payload.instructions).toBe("Use concise answers.");
|
|
139
|
+
});
|
|
140
|
+
(0, external_vitest_namespaceObject.it)("overrides explicit store values to false when provided", async ()=>{
|
|
141
|
+
writeCodexAuth({
|
|
142
|
+
tokens: {
|
|
143
|
+
access_token: "file-token"
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
const baseFetch = external_vitest_namespaceObject.vi.fn(async (_input, _init)=>new Response("{}", {
|
|
147
|
+
status: 200
|
|
148
|
+
}));
|
|
149
|
+
const codexFetch = (0, codex_cjs_namespaceObject.createCodexFetch)({
|
|
150
|
+
baseFetch
|
|
151
|
+
});
|
|
152
|
+
await codexFetch("https://chatgpt.com/backend-api/codex/responses", {
|
|
153
|
+
method: "POST",
|
|
154
|
+
body: JSON.stringify({
|
|
155
|
+
model: "gpt-5.3-codex",
|
|
156
|
+
store: true,
|
|
157
|
+
input: "hello"
|
|
158
|
+
})
|
|
159
|
+
});
|
|
160
|
+
const requestInit = baseFetch.mock.calls[0]?.[1];
|
|
161
|
+
const payload = JSON.parse(String(requestInit?.body));
|
|
162
|
+
(0, external_vitest_namespaceObject.expect)(payload.store).toBe(false);
|
|
163
|
+
});
|
|
164
|
+
(0, external_vitest_namespaceObject.it)("uses fallback token when codex auth file is unavailable", async ()=>{
|
|
165
|
+
const baseFetch = external_vitest_namespaceObject.vi.fn(async (_input, _init)=>new Response("{}", {
|
|
166
|
+
status: 200
|
|
167
|
+
}));
|
|
168
|
+
const codexFetch = (0, codex_cjs_namespaceObject.createCodexFetch)({
|
|
169
|
+
baseFetch,
|
|
170
|
+
fallbackToken: "fallback-token"
|
|
171
|
+
});
|
|
172
|
+
await codexFetch("https://chatgpt.com/backend-api/codex/responses", {
|
|
173
|
+
method: "POST",
|
|
174
|
+
body: JSON.stringify({
|
|
175
|
+
model: "codex-mini-latest",
|
|
176
|
+
input: "hello"
|
|
177
|
+
})
|
|
178
|
+
});
|
|
179
|
+
const requestInit = baseFetch.mock.calls[0]?.[1];
|
|
180
|
+
(0, external_vitest_namespaceObject.expect)(requestInit).toBeDefined();
|
|
181
|
+
const headers = new Headers(requestInit?.headers);
|
|
182
|
+
(0, external_vitest_namespaceObject.expect)(headers.get("authorization")).toBe("Bearer fallback-token");
|
|
183
|
+
});
|
|
184
|
+
(0, external_vitest_namespaceObject.it)("throws when no codex token is available", async ()=>{
|
|
185
|
+
const baseFetch = external_vitest_namespaceObject.vi.fn(async (_input, _init)=>new Response("{}", {
|
|
186
|
+
status: 200
|
|
187
|
+
}));
|
|
188
|
+
const codexFetch = (0, codex_cjs_namespaceObject.createCodexFetch)({
|
|
189
|
+
baseFetch
|
|
190
|
+
});
|
|
191
|
+
await (0, external_vitest_namespaceObject.expect)(codexFetch("https://chatgpt.com/backend-api/codex/responses", {
|
|
192
|
+
method: "POST",
|
|
193
|
+
body: JSON.stringify({
|
|
194
|
+
model: "codex-mini-latest",
|
|
195
|
+
input: "hello"
|
|
196
|
+
})
|
|
197
|
+
})).rejects.toThrow(/Codex credentials missing/);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
function writeCodexAuth(payload) {
|
|
201
|
+
const authPath = (0, codex_cjs_namespaceObject.getCodexAuthPath)();
|
|
202
|
+
(0, external_node_fs_namespaceObject.mkdirSync)((0, external_node_path_namespaceObject.dirname)(authPath), {
|
|
203
|
+
recursive: true
|
|
204
|
+
});
|
|
205
|
+
(0, external_node_fs_namespaceObject.writeFileSync)(authPath, JSON.stringify(payload, null, 2));
|
|
206
|
+
}
|
|
207
|
+
for(var __rspack_i in __webpack_exports__)exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
208
|
+
Object.defineProperty(exports, '__esModule', {
|
|
209
|
+
value: true
|
|
210
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, mkdtempSync, 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
|
+
import { createCodexFetch, getCodexAuthPath, resolveCodexAuthFromFile } from "../providers/codex.js";
|
|
6
|
+
describe("codex provider", ()=>{
|
|
7
|
+
let codexHome;
|
|
8
|
+
const originalCodexHome = process.env.CODEX_HOME;
|
|
9
|
+
beforeEach(()=>{
|
|
10
|
+
codexHome = mkdtempSync(join(tmpdir(), "wingman-codex-"));
|
|
11
|
+
process.env.CODEX_HOME = codexHome;
|
|
12
|
+
delete process.env.CODEX_ACCESS_TOKEN;
|
|
13
|
+
delete process.env.CHATGPT_ACCESS_TOKEN;
|
|
14
|
+
});
|
|
15
|
+
afterEach(()=>{
|
|
16
|
+
if (void 0 === originalCodexHome) delete process.env.CODEX_HOME;
|
|
17
|
+
else process.env.CODEX_HOME = originalCodexHome;
|
|
18
|
+
if (existsSync(codexHome)) rmSync(codexHome, {
|
|
19
|
+
recursive: true,
|
|
20
|
+
force: true
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
it("reads access token and account id from codex auth file", ()=>{
|
|
24
|
+
writeCodexAuth({
|
|
25
|
+
tokens: {
|
|
26
|
+
access_token: "codex-access-token",
|
|
27
|
+
account_id: "acct_123"
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
const resolved = resolveCodexAuthFromFile();
|
|
31
|
+
expect(resolved.accessToken).toBe("codex-access-token");
|
|
32
|
+
expect(resolved.accountId).toBe("acct_123");
|
|
33
|
+
expect(resolved.authPath).toBe(join(codexHome, "auth.json"));
|
|
34
|
+
});
|
|
35
|
+
it("applies codex auth headers and forces store=false", async ()=>{
|
|
36
|
+
writeCodexAuth({
|
|
37
|
+
tokens: {
|
|
38
|
+
access_token: "file-token",
|
|
39
|
+
account_id: "acct_file"
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
const baseFetch = vi.fn(async (_input, _init)=>new Response("{}", {
|
|
43
|
+
status: 200
|
|
44
|
+
}));
|
|
45
|
+
const codexFetch = createCodexFetch({
|
|
46
|
+
baseFetch
|
|
47
|
+
});
|
|
48
|
+
await codexFetch("https://chatgpt.com/backend-api/codex/responses", {
|
|
49
|
+
method: "POST",
|
|
50
|
+
headers: {
|
|
51
|
+
"x-api-key": "placeholder"
|
|
52
|
+
},
|
|
53
|
+
body: JSON.stringify({
|
|
54
|
+
model: "codex-mini-latest",
|
|
55
|
+
input: "hello",
|
|
56
|
+
temperature: 1
|
|
57
|
+
})
|
|
58
|
+
});
|
|
59
|
+
expect(baseFetch).toHaveBeenCalledTimes(1);
|
|
60
|
+
const requestInit = baseFetch.mock.calls[0]?.[1];
|
|
61
|
+
expect(requestInit).toBeDefined();
|
|
62
|
+
const headers = new Headers(requestInit?.headers);
|
|
63
|
+
const payload = JSON.parse(String(requestInit?.body));
|
|
64
|
+
expect(headers.get("authorization")).toBe("Bearer file-token");
|
|
65
|
+
expect(headers.get("chatgpt-account-id")).toBe("acct_file");
|
|
66
|
+
expect(headers.get("x-api-key")).toBeNull();
|
|
67
|
+
expect(payload.store).toBe(false);
|
|
68
|
+
expect(payload.temperature).toBeUndefined();
|
|
69
|
+
expect(typeof payload.instructions).toBe("string");
|
|
70
|
+
expect(payload.instructions.length).toBeGreaterThan(0);
|
|
71
|
+
});
|
|
72
|
+
it("derives instructions from system/developer input when missing", async ()=>{
|
|
73
|
+
writeCodexAuth({
|
|
74
|
+
tokens: {
|
|
75
|
+
access_token: "file-token"
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
const baseFetch = vi.fn(async (_input, _init)=>new Response("{}", {
|
|
79
|
+
status: 200
|
|
80
|
+
}));
|
|
81
|
+
const codexFetch = createCodexFetch({
|
|
82
|
+
baseFetch
|
|
83
|
+
});
|
|
84
|
+
await codexFetch("https://chatgpt.com/backend-api/codex/responses", {
|
|
85
|
+
method: "POST",
|
|
86
|
+
body: JSON.stringify({
|
|
87
|
+
model: "gpt-5.3-codex",
|
|
88
|
+
input: [
|
|
89
|
+
{
|
|
90
|
+
role: "developer",
|
|
91
|
+
content: [
|
|
92
|
+
{
|
|
93
|
+
type: "input_text",
|
|
94
|
+
text: "Always run tests first."
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
role: "user",
|
|
100
|
+
content: [
|
|
101
|
+
{
|
|
102
|
+
type: "input_text",
|
|
103
|
+
text: "Fix the bug."
|
|
104
|
+
}
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
})
|
|
109
|
+
});
|
|
110
|
+
const requestInit = baseFetch.mock.calls[0]?.[1];
|
|
111
|
+
const payload = JSON.parse(String(requestInit?.body));
|
|
112
|
+
expect(payload.instructions).toBe("Always run tests first.");
|
|
113
|
+
});
|
|
114
|
+
it("preserves explicit instructions when provided", async ()=>{
|
|
115
|
+
writeCodexAuth({
|
|
116
|
+
tokens: {
|
|
117
|
+
access_token: "file-token"
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
const baseFetch = vi.fn(async (_input, _init)=>new Response("{}", {
|
|
121
|
+
status: 200
|
|
122
|
+
}));
|
|
123
|
+
const codexFetch = createCodexFetch({
|
|
124
|
+
baseFetch
|
|
125
|
+
});
|
|
126
|
+
await codexFetch("https://chatgpt.com/backend-api/codex/responses", {
|
|
127
|
+
method: "POST",
|
|
128
|
+
body: JSON.stringify({
|
|
129
|
+
model: "gpt-5.3-codex",
|
|
130
|
+
instructions: "Use concise answers.",
|
|
131
|
+
input: "hello"
|
|
132
|
+
})
|
|
133
|
+
});
|
|
134
|
+
const requestInit = baseFetch.mock.calls[0]?.[1];
|
|
135
|
+
const payload = JSON.parse(String(requestInit?.body));
|
|
136
|
+
expect(payload.instructions).toBe("Use concise answers.");
|
|
137
|
+
});
|
|
138
|
+
it("overrides explicit store values to false when provided", async ()=>{
|
|
139
|
+
writeCodexAuth({
|
|
140
|
+
tokens: {
|
|
141
|
+
access_token: "file-token"
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
const baseFetch = vi.fn(async (_input, _init)=>new Response("{}", {
|
|
145
|
+
status: 200
|
|
146
|
+
}));
|
|
147
|
+
const codexFetch = createCodexFetch({
|
|
148
|
+
baseFetch
|
|
149
|
+
});
|
|
150
|
+
await codexFetch("https://chatgpt.com/backend-api/codex/responses", {
|
|
151
|
+
method: "POST",
|
|
152
|
+
body: JSON.stringify({
|
|
153
|
+
model: "gpt-5.3-codex",
|
|
154
|
+
store: true,
|
|
155
|
+
input: "hello"
|
|
156
|
+
})
|
|
157
|
+
});
|
|
158
|
+
const requestInit = baseFetch.mock.calls[0]?.[1];
|
|
159
|
+
const payload = JSON.parse(String(requestInit?.body));
|
|
160
|
+
expect(payload.store).toBe(false);
|
|
161
|
+
});
|
|
162
|
+
it("uses fallback token when codex auth file is unavailable", async ()=>{
|
|
163
|
+
const baseFetch = vi.fn(async (_input, _init)=>new Response("{}", {
|
|
164
|
+
status: 200
|
|
165
|
+
}));
|
|
166
|
+
const codexFetch = createCodexFetch({
|
|
167
|
+
baseFetch,
|
|
168
|
+
fallbackToken: "fallback-token"
|
|
169
|
+
});
|
|
170
|
+
await codexFetch("https://chatgpt.com/backend-api/codex/responses", {
|
|
171
|
+
method: "POST",
|
|
172
|
+
body: JSON.stringify({
|
|
173
|
+
model: "codex-mini-latest",
|
|
174
|
+
input: "hello"
|
|
175
|
+
})
|
|
176
|
+
});
|
|
177
|
+
const requestInit = baseFetch.mock.calls[0]?.[1];
|
|
178
|
+
expect(requestInit).toBeDefined();
|
|
179
|
+
const headers = new Headers(requestInit?.headers);
|
|
180
|
+
expect(headers.get("authorization")).toBe("Bearer fallback-token");
|
|
181
|
+
});
|
|
182
|
+
it("throws when no codex token is available", async ()=>{
|
|
183
|
+
const baseFetch = vi.fn(async (_input, _init)=>new Response("{}", {
|
|
184
|
+
status: 200
|
|
185
|
+
}));
|
|
186
|
+
const codexFetch = createCodexFetch({
|
|
187
|
+
baseFetch
|
|
188
|
+
});
|
|
189
|
+
await expect(codexFetch("https://chatgpt.com/backend-api/codex/responses", {
|
|
190
|
+
method: "POST",
|
|
191
|
+
body: JSON.stringify({
|
|
192
|
+
model: "codex-mini-latest",
|
|
193
|
+
input: "hello"
|
|
194
|
+
})
|
|
195
|
+
})).rejects.toThrow(/Codex credentials missing/);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
function writeCodexAuth(payload) {
|
|
199
|
+
const authPath = getCodexAuthPath();
|
|
200
|
+
mkdirSync(dirname(authPath), {
|
|
201
|
+
recursive: true
|
|
202
|
+
});
|
|
203
|
+
writeFileSync(authPath, JSON.stringify(payload, null, 2));
|
|
204
|
+
}
|
|
@@ -16,7 +16,8 @@ const isBun = void 0 !== globalThis.Bun;
|
|
|
16
16
|
const describeIfBun = isBun ? external_vitest_namespaceObject.describe : external_vitest_namespaceObject.describe.skip;
|
|
17
17
|
external_vitest_namespaceObject.vi.mock("@/cli/core/agentInvoker.js", ()=>({
|
|
18
18
|
AgentInvoker: class {
|
|
19
|
-
async invokeAgent(_agentId,
|
|
19
|
+
async invokeAgent(_agentId, content, _sessionId, _attachments, options) {
|
|
20
|
+
if ("throw-no-event" === content) throw new Error("Synthetic invocation failure");
|
|
20
21
|
const signal = options?.signal;
|
|
21
22
|
await new Promise((resolve)=>{
|
|
22
23
|
const timer = setTimeout(resolve, 75);
|
|
@@ -175,14 +176,15 @@ describeIfBun("Gateway", ()=>{
|
|
|
175
176
|
}));
|
|
176
177
|
(0, external_vitest_namespaceObject.it)("should broadcast messages to group members", async ()=>new Promise((resolve, reject)=>{
|
|
177
178
|
let client1NodeId = null;
|
|
178
|
-
let
|
|
179
|
+
let broadcastGroupId = null;
|
|
179
180
|
const client1 = new index_cjs_namespaceObject.GatewayClient(`ws://localhost:${port}/ws`, "broadcaster", {
|
|
180
181
|
events: {
|
|
181
182
|
registered: async (nodeId)=>{
|
|
182
183
|
client1NodeId = nodeId;
|
|
183
184
|
await client1.joinGroup("broadcast-test");
|
|
184
185
|
},
|
|
185
|
-
joinedGroup: ()=>{
|
|
186
|
+
joinedGroup: (groupId)=>{
|
|
187
|
+
broadcastGroupId = groupId;
|
|
186
188
|
client2.connect().catch(reject);
|
|
187
189
|
}
|
|
188
190
|
}
|
|
@@ -193,14 +195,13 @@ describeIfBun("Gateway", ()=>{
|
|
|
193
195
|
await client2.joinGroup("broadcast-test");
|
|
194
196
|
},
|
|
195
197
|
joinedGroup: ()=>{
|
|
196
|
-
client1.broadcast("broadcast-test", {
|
|
198
|
+
client1.broadcast(broadcastGroupId || "broadcast-test", {
|
|
197
199
|
message: "Hello from client 1"
|
|
198
200
|
});
|
|
199
201
|
},
|
|
200
202
|
broadcast: (message, fromNodeId)=>{
|
|
201
203
|
(0, external_vitest_namespaceObject.expect)(fromNodeId).toBe(client1NodeId);
|
|
202
204
|
(0, external_vitest_namespaceObject.expect)(message.message).toBe("Hello from client 1");
|
|
203
|
-
messagesReceived++;
|
|
204
205
|
client1.disconnect();
|
|
205
206
|
client2.disconnect();
|
|
206
207
|
resolve();
|
|
@@ -333,6 +334,26 @@ describeIfBun("Gateway", ()=>{
|
|
|
333
334
|
desktopClient.close();
|
|
334
335
|
requester.close();
|
|
335
336
|
});
|
|
337
|
+
(0, external_vitest_namespaceObject.it)("should emit agent-error to requester when invocation throws without emitting", async ()=>{
|
|
338
|
+
const requester = await connectClient("session-error-requester");
|
|
339
|
+
const requestId = "req-invocation-error";
|
|
340
|
+
const sessionId = "session-error-test";
|
|
341
|
+
requester.send(JSON.stringify({
|
|
342
|
+
type: "req:agent",
|
|
343
|
+
id: requestId,
|
|
344
|
+
payload: {
|
|
345
|
+
agentId: "main",
|
|
346
|
+
sessionKey: sessionId,
|
|
347
|
+
content: "throw-no-event"
|
|
348
|
+
},
|
|
349
|
+
timestamp: Date.now()
|
|
350
|
+
}));
|
|
351
|
+
const errorMsg = await waitForMessage(requester, (msg)=>"event:agent" === msg.type && msg.id === requestId && msg.payload?.type === "agent-error");
|
|
352
|
+
(0, external_vitest_namespaceObject.expect)(errorMsg.payload?.error).toContain("Synthetic invocation failure");
|
|
353
|
+
(0, external_vitest_namespaceObject.expect)(errorMsg.payload?.sessionId).toBe(sessionId);
|
|
354
|
+
(0, external_vitest_namespaceObject.expect)(errorMsg.payload?.agentId).toBe("main");
|
|
355
|
+
requester.close();
|
|
356
|
+
});
|
|
336
357
|
(0, external_vitest_namespaceObject.it)("should cancel an in-flight agent request", async ()=>{
|
|
337
358
|
const requester = await connectClient("session-cancel-requester");
|
|
338
359
|
const requestId = "req-cancel-test";
|
|
@@ -361,6 +382,92 @@ describeIfBun("Gateway", ()=>{
|
|
|
361
382
|
]).toContain(ack.payload?.status);
|
|
362
383
|
requester.close();
|
|
363
384
|
});
|
|
385
|
+
(0, external_vitest_namespaceObject.it)("should queue and dequeue requests for the same session", async ()=>{
|
|
386
|
+
const requester = await connectClient("session-queue-requester");
|
|
387
|
+
const sessionId = `session-queue-${Date.now()}`;
|
|
388
|
+
const firstRequestId = `req-queue-first-${Date.now()}`;
|
|
389
|
+
const secondRequestId = `req-queue-second-${Date.now()}`;
|
|
390
|
+
const firstCompletePromise = waitForMessage(requester, (msg)=>"event:agent" === msg.type && msg.id === firstRequestId && msg.payload?.type === "agent-complete", 10000);
|
|
391
|
+
const queuedAckPromise = waitForMessage(requester, (msg)=>"ack" === msg.type && msg.id === secondRequestId && msg.payload?.action === "req:agent" && msg.payload?.status === "queued", 10000);
|
|
392
|
+
const queuedEventPromise = waitForMessage(requester, (msg)=>"event:agent" === msg.type && msg.id === secondRequestId && msg.payload?.type === "request-queued", 10000);
|
|
393
|
+
const dequeuedAckPromise = waitForMessage(requester, (msg)=>"ack" === msg.type && msg.id === secondRequestId && msg.payload?.action === "req:agent" && msg.payload?.status === "dequeued", 10000);
|
|
394
|
+
const secondCompletePromise = waitForMessage(requester, (msg)=>"event:agent" === msg.type && msg.id === secondRequestId && msg.payload?.type === "agent-complete", 10000);
|
|
395
|
+
requester.send(JSON.stringify({
|
|
396
|
+
type: "req:agent",
|
|
397
|
+
id: firstRequestId,
|
|
398
|
+
payload: {
|
|
399
|
+
agentId: "main",
|
|
400
|
+
sessionKey: sessionId,
|
|
401
|
+
content: "First queued request"
|
|
402
|
+
},
|
|
403
|
+
timestamp: Date.now()
|
|
404
|
+
}));
|
|
405
|
+
requester.send(JSON.stringify({
|
|
406
|
+
type: "req:agent",
|
|
407
|
+
id: secondRequestId,
|
|
408
|
+
payload: {
|
|
409
|
+
agentId: "main",
|
|
410
|
+
sessionKey: sessionId,
|
|
411
|
+
content: "Second queued request"
|
|
412
|
+
},
|
|
413
|
+
timestamp: Date.now()
|
|
414
|
+
}));
|
|
415
|
+
const queuedAck = await queuedAckPromise;
|
|
416
|
+
(0, external_vitest_namespaceObject.expect)(queuedAck.payload?.position).toBe(1);
|
|
417
|
+
const queuedEvent = await queuedEventPromise;
|
|
418
|
+
(0, external_vitest_namespaceObject.expect)(queuedEvent.payload?.position).toBe(1);
|
|
419
|
+
(0, external_vitest_namespaceObject.expect)(queuedEvent.payload?.sessionId).toBe(sessionId);
|
|
420
|
+
await firstCompletePromise;
|
|
421
|
+
const dequeuedAck = await dequeuedAckPromise;
|
|
422
|
+
(0, external_vitest_namespaceObject.expect)(dequeuedAck.payload?.remaining).toBe(0);
|
|
423
|
+
await secondCompletePromise;
|
|
424
|
+
requester.close();
|
|
425
|
+
});
|
|
426
|
+
(0, external_vitest_namespaceObject.it)("should cancel a queued request", async ()=>{
|
|
427
|
+
const requester = await connectClient("session-cancel-queued-requester");
|
|
428
|
+
const sessionId = `session-cancel-queued-${Date.now()}`;
|
|
429
|
+
const firstRequestId = `req-cancel-queued-first-${Date.now()}`;
|
|
430
|
+
const secondRequestId = `req-cancel-queued-second-${Date.now()}`;
|
|
431
|
+
const queuedAckPromise = waitForMessage(requester, (msg)=>"ack" === msg.type && msg.id === secondRequestId && msg.payload?.action === "req:agent" && msg.payload?.status === "queued", 10000);
|
|
432
|
+
requester.send(JSON.stringify({
|
|
433
|
+
type: "req:agent",
|
|
434
|
+
id: firstRequestId,
|
|
435
|
+
payload: {
|
|
436
|
+
agentId: "main",
|
|
437
|
+
sessionKey: sessionId,
|
|
438
|
+
content: "First request"
|
|
439
|
+
},
|
|
440
|
+
timestamp: Date.now()
|
|
441
|
+
}));
|
|
442
|
+
requester.send(JSON.stringify({
|
|
443
|
+
type: "req:agent",
|
|
444
|
+
id: secondRequestId,
|
|
445
|
+
payload: {
|
|
446
|
+
agentId: "main",
|
|
447
|
+
sessionKey: sessionId,
|
|
448
|
+
content: "Second request"
|
|
449
|
+
},
|
|
450
|
+
timestamp: Date.now()
|
|
451
|
+
}));
|
|
452
|
+
await queuedAckPromise;
|
|
453
|
+
requester.send(JSON.stringify({
|
|
454
|
+
type: "req:agent:cancel",
|
|
455
|
+
id: `cancel-${secondRequestId}`,
|
|
456
|
+
payload: {
|
|
457
|
+
requestId: secondRequestId
|
|
458
|
+
},
|
|
459
|
+
timestamp: Date.now()
|
|
460
|
+
}));
|
|
461
|
+
const cancelAck = await waitForMessage(requester, (msg)=>"ack" === msg.type && msg.payload?.action === "req:agent:cancel" && msg.payload?.requestId === secondRequestId && msg.payload?.status === "cancelled_queued", 10000);
|
|
462
|
+
(0, external_vitest_namespaceObject.expect)(cancelAck.payload?.status).toBe("cancelled_queued");
|
|
463
|
+
await waitForMessage(requester, (msg)=>"event:agent" === msg.type && msg.id === firstRequestId && msg.payload?.type === "agent-complete", 10000);
|
|
464
|
+
const queuedRequests = server.queuedSessionRequests;
|
|
465
|
+
const isStillQueued = [
|
|
466
|
+
...queuedRequests.values()
|
|
467
|
+
].some((queue)=>queue.some((item)=>item.msg?.id === secondRequestId));
|
|
468
|
+
(0, external_vitest_namespaceObject.expect)(isStillQueued).toBe(false);
|
|
469
|
+
requester.close();
|
|
470
|
+
});
|
|
364
471
|
(0, external_vitest_namespaceObject.it)("should clear session messages via API", async ()=>{
|
|
365
472
|
const createRes = await fetch(`http://localhost:${port}/api/sessions`, {
|
|
366
473
|
method: "POST",
|
|
@@ -368,18 +475,18 @@ describeIfBun("Gateway", ()=>{
|
|
|
368
475
|
"Content-Type": "application/json"
|
|
369
476
|
},
|
|
370
477
|
body: JSON.stringify({
|
|
371
|
-
agentId: "main",
|
|
372
478
|
name: "Clear Test"
|
|
373
479
|
})
|
|
374
480
|
});
|
|
375
481
|
(0, external_vitest_namespaceObject.expect)(createRes.ok).toBe(true);
|
|
376
482
|
const session = await createRes.json();
|
|
377
|
-
const
|
|
483
|
+
const sessionAgentId = session.agentId || "main";
|
|
484
|
+
const manager = await server.getSessionManager(sessionAgentId);
|
|
378
485
|
manager.updateSession(session.id, {
|
|
379
486
|
messageCount: 3,
|
|
380
487
|
lastMessagePreview: "Hello"
|
|
381
488
|
});
|
|
382
|
-
const clearRes = await fetch(`http://localhost:${port}/api/sessions/${encodeURIComponent(session.id)}/messages?agentId
|
|
489
|
+
const clearRes = await fetch(`http://localhost:${port}/api/sessions/${encodeURIComponent(session.id)}/messages?agentId=${encodeURIComponent(sessionAgentId)}`, {
|
|
383
490
|
method: "DELETE"
|
|
384
491
|
});
|
|
385
492
|
(0, external_vitest_namespaceObject.expect)(clearRes.ok).toBe(true);
|