@northflare/runner 0.0.12 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -3
- package/coverage/base.css +0 -224
- package/coverage/block-navigation.js +0 -87
- package/coverage/coverage-final.json +0 -12
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +0 -176
- package/coverage/lib/index.html +0 -116
- package/coverage/lib/preload-script.js.html +0 -964
- package/coverage/prettify.css +0 -1
- package/coverage/prettify.js +0 -2
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +0 -196
- package/coverage/src/collections/index.html +0 -116
- package/coverage/src/collections/runner-messages.ts.html +0 -312
- package/coverage/src/components/claude-manager.ts.html +0 -1290
- package/coverage/src/components/index.html +0 -146
- package/coverage/src/components/message-handler.ts.html +0 -730
- package/coverage/src/components/repository-manager.ts.html +0 -841
- package/coverage/src/index.html +0 -131
- package/coverage/src/index.ts.html +0 -448
- package/coverage/src/runner.ts.html +0 -1239
- package/coverage/src/utils/config.ts.html +0 -780
- package/coverage/src/utils/console.ts.html +0 -121
- package/coverage/src/utils/index.html +0 -161
- package/coverage/src/utils/logger.ts.html +0 -475
- package/coverage/src/utils/status-line.ts.html +0 -445
- package/exceptions.log +0 -24
- package/lib/codex-sdk/src/codex.ts +0 -38
- package/lib/codex-sdk/src/codexOptions.ts +0 -10
- package/lib/codex-sdk/src/events.ts +0 -80
- package/lib/codex-sdk/src/exec.ts +0 -336
- package/lib/codex-sdk/src/index.ts +0 -39
- package/lib/codex-sdk/src/items.ts +0 -127
- package/lib/codex-sdk/src/outputSchemaFile.ts +0 -40
- package/lib/codex-sdk/src/thread.ts +0 -155
- package/lib/codex-sdk/src/threadOptions.ts +0 -18
- package/lib/codex-sdk/src/turnOptions.ts +0 -6
- package/lib/codex-sdk/tests/abort.test.ts +0 -165
- package/lib/codex-sdk/tests/codexExecSpy.ts +0 -37
- package/lib/codex-sdk/tests/responsesProxy.ts +0 -225
- package/lib/codex-sdk/tests/run.test.ts +0 -687
- package/lib/codex-sdk/tests/runStreamed.test.ts +0 -211
- package/lib/codex-sdk/tsconfig.json +0 -24
- package/rejections.log +0 -68
- package/runner.log +0 -488
- package/src/components/claude-sdk-manager.ts +0 -1425
- package/src/components/codex-sdk-manager.ts +0 -1358
- package/src/components/enhanced-repository-manager.ts +0 -823
- package/src/components/message-handler-sse.ts +0 -1097
- package/src/components/repository-manager.ts +0 -337
- package/src/index.ts +0 -168
- package/src/runner-sse.ts +0 -917
- package/src/services/RunnerAPIClient.ts +0 -175
- package/src/services/SSEClient.ts +0 -258
- package/src/types/claude.ts +0 -66
- package/src/types/computer-name.d.ts +0 -4
- package/src/types/index.ts +0 -64
- package/src/types/messages.ts +0 -39
- package/src/types/runner-interface.ts +0 -36
- package/src/utils/StateManager.ts +0 -187
- package/src/utils/config.ts +0 -327
- package/src/utils/console.ts +0 -15
- package/src/utils/debug.ts +0 -18
- package/src/utils/expand-env.ts +0 -22
- package/src/utils/logger.ts +0 -134
- package/src/utils/model.ts +0 -29
- package/src/utils/status-line.ts +0 -122
- package/src/utils/tool-response-sanitizer.ts +0 -160
- package/test-debug.sh +0 -26
- package/tests/retry-strategies.test.ts +0 -410
- package/tests/sdk-integration.test.ts +0 -329
- package/tests/sdk-streaming.test.ts +0 -1180
- package/tests/setup.ts +0 -5
- package/tests/test-claude-manager.ts +0 -120
- package/tests/tool-response-sanitizer.test.ts +0 -63
- package/tsconfig.json +0 -36
- package/vitest.config.ts +0 -27
|
@@ -1,687 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
|
|
5
|
-
import { codexExecSpy } from "./codexExecSpy";
|
|
6
|
-
import { describe, expect, it } from "@jest/globals";
|
|
7
|
-
|
|
8
|
-
import { Codex } from "../src/codex";
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
assistantMessage,
|
|
12
|
-
responseCompleted,
|
|
13
|
-
responseStarted,
|
|
14
|
-
sse,
|
|
15
|
-
responseFailed,
|
|
16
|
-
startResponsesTestProxy,
|
|
17
|
-
SseResponseBody,
|
|
18
|
-
} from "./responsesProxy";
|
|
19
|
-
|
|
20
|
-
const codexExecPath = path.join(process.cwd(), "..", "..", "codex-rs", "target", "debug", "codex");
|
|
21
|
-
|
|
22
|
-
describe("Codex", () => {
|
|
23
|
-
it("returns thread events", async () => {
|
|
24
|
-
const { url, close } = await startResponsesTestProxy({
|
|
25
|
-
statusCode: 200,
|
|
26
|
-
responseBodies: [sse(responseStarted(), assistantMessage("Hi!"), responseCompleted())],
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
try {
|
|
30
|
-
const client = new Codex({ codexPathOverride: codexExecPath, baseUrl: url, apiKey: "test" });
|
|
31
|
-
|
|
32
|
-
const thread = client.startThread();
|
|
33
|
-
const result = await thread.run("Hello, world!");
|
|
34
|
-
|
|
35
|
-
const expectedItems = [
|
|
36
|
-
{
|
|
37
|
-
id: expect.any(String),
|
|
38
|
-
type: "agent_message",
|
|
39
|
-
text: "Hi!",
|
|
40
|
-
},
|
|
41
|
-
];
|
|
42
|
-
expect(result.items).toEqual(expectedItems);
|
|
43
|
-
expect(result.usage).toEqual({
|
|
44
|
-
cached_input_tokens: 12,
|
|
45
|
-
input_tokens: 42,
|
|
46
|
-
output_tokens: 5,
|
|
47
|
-
});
|
|
48
|
-
expect(thread.id).toEqual(expect.any(String));
|
|
49
|
-
} finally {
|
|
50
|
-
await close();
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it("sends previous items when run is called twice", async () => {
|
|
55
|
-
const { url, close, requests } = await startResponsesTestProxy({
|
|
56
|
-
statusCode: 200,
|
|
57
|
-
responseBodies: [
|
|
58
|
-
sse(
|
|
59
|
-
responseStarted("response_1"),
|
|
60
|
-
assistantMessage("First response", "item_1"),
|
|
61
|
-
responseCompleted("response_1"),
|
|
62
|
-
),
|
|
63
|
-
sse(
|
|
64
|
-
responseStarted("response_2"),
|
|
65
|
-
assistantMessage("Second response", "item_2"),
|
|
66
|
-
responseCompleted("response_2"),
|
|
67
|
-
),
|
|
68
|
-
],
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
try {
|
|
72
|
-
const client = new Codex({ codexPathOverride: codexExecPath, baseUrl: url, apiKey: "test" });
|
|
73
|
-
|
|
74
|
-
const thread = client.startThread();
|
|
75
|
-
await thread.run("first input");
|
|
76
|
-
await thread.run("second input");
|
|
77
|
-
|
|
78
|
-
// Check second request continues the same thread
|
|
79
|
-
expect(requests.length).toBeGreaterThanOrEqual(2);
|
|
80
|
-
const secondRequest = requests[1];
|
|
81
|
-
expect(secondRequest).toBeDefined();
|
|
82
|
-
const payload = secondRequest!.json;
|
|
83
|
-
|
|
84
|
-
const assistantEntry = payload.input.find(
|
|
85
|
-
(entry: { role: string }) => entry.role === "assistant",
|
|
86
|
-
);
|
|
87
|
-
expect(assistantEntry).toBeDefined();
|
|
88
|
-
const assistantText = assistantEntry?.content?.find(
|
|
89
|
-
(item: { type: string; text: string }) => item.type === "output_text",
|
|
90
|
-
)?.text;
|
|
91
|
-
expect(assistantText).toBe("First response");
|
|
92
|
-
} finally {
|
|
93
|
-
await close();
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it("continues the thread when run is called twice with options", async () => {
|
|
98
|
-
const { url, close, requests } = await startResponsesTestProxy({
|
|
99
|
-
statusCode: 200,
|
|
100
|
-
responseBodies: [
|
|
101
|
-
sse(
|
|
102
|
-
responseStarted("response_1"),
|
|
103
|
-
assistantMessage("First response", "item_1"),
|
|
104
|
-
responseCompleted("response_1"),
|
|
105
|
-
),
|
|
106
|
-
sse(
|
|
107
|
-
responseStarted("response_2"),
|
|
108
|
-
assistantMessage("Second response", "item_2"),
|
|
109
|
-
responseCompleted("response_2"),
|
|
110
|
-
),
|
|
111
|
-
],
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
try {
|
|
115
|
-
const client = new Codex({ codexPathOverride: codexExecPath, baseUrl: url, apiKey: "test" });
|
|
116
|
-
|
|
117
|
-
const thread = client.startThread();
|
|
118
|
-
await thread.run("first input");
|
|
119
|
-
await thread.run("second input");
|
|
120
|
-
|
|
121
|
-
// Check second request continues the same thread
|
|
122
|
-
expect(requests.length).toBeGreaterThanOrEqual(2);
|
|
123
|
-
const secondRequest = requests[1];
|
|
124
|
-
expect(secondRequest).toBeDefined();
|
|
125
|
-
const payload = secondRequest!.json;
|
|
126
|
-
|
|
127
|
-
expect(payload.input.at(-1)!.content![0]!.text).toBe("second input");
|
|
128
|
-
const assistantEntry = payload.input.find(
|
|
129
|
-
(entry: { role: string }) => entry.role === "assistant",
|
|
130
|
-
);
|
|
131
|
-
expect(assistantEntry).toBeDefined();
|
|
132
|
-
const assistantText = assistantEntry?.content?.find(
|
|
133
|
-
(item: { type: string; text: string }) => item.type === "output_text",
|
|
134
|
-
)?.text;
|
|
135
|
-
expect(assistantText).toBe("First response");
|
|
136
|
-
} finally {
|
|
137
|
-
await close();
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it("resumes thread by id", async () => {
|
|
142
|
-
const { url, close, requests } = await startResponsesTestProxy({
|
|
143
|
-
statusCode: 200,
|
|
144
|
-
responseBodies: [
|
|
145
|
-
sse(
|
|
146
|
-
responseStarted("response_1"),
|
|
147
|
-
assistantMessage("First response", "item_1"),
|
|
148
|
-
responseCompleted("response_1"),
|
|
149
|
-
),
|
|
150
|
-
sse(
|
|
151
|
-
responseStarted("response_2"),
|
|
152
|
-
assistantMessage("Second response", "item_2"),
|
|
153
|
-
responseCompleted("response_2"),
|
|
154
|
-
),
|
|
155
|
-
],
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
try {
|
|
159
|
-
const client = new Codex({ codexPathOverride: codexExecPath, baseUrl: url, apiKey: "test" });
|
|
160
|
-
|
|
161
|
-
const originalThread = client.startThread();
|
|
162
|
-
await originalThread.run("first input");
|
|
163
|
-
|
|
164
|
-
const resumedThread = client.resumeThread(originalThread.id!);
|
|
165
|
-
const result = await resumedThread.run("second input");
|
|
166
|
-
|
|
167
|
-
expect(resumedThread.id).toBe(originalThread.id);
|
|
168
|
-
expect(result.finalResponse).toBe("Second response");
|
|
169
|
-
|
|
170
|
-
expect(requests.length).toBeGreaterThanOrEqual(2);
|
|
171
|
-
const secondRequest = requests[1];
|
|
172
|
-
expect(secondRequest).toBeDefined();
|
|
173
|
-
const payload = secondRequest!.json;
|
|
174
|
-
|
|
175
|
-
const assistantEntry = payload.input.find(
|
|
176
|
-
(entry: { role: string }) => entry.role === "assistant",
|
|
177
|
-
);
|
|
178
|
-
expect(assistantEntry).toBeDefined();
|
|
179
|
-
const assistantText = assistantEntry?.content?.find(
|
|
180
|
-
(item: { type: string; text: string }) => item.type === "output_text",
|
|
181
|
-
)?.text;
|
|
182
|
-
expect(assistantText).toBe("First response");
|
|
183
|
-
} finally {
|
|
184
|
-
await close();
|
|
185
|
-
}
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
it("passes turn options to exec", async () => {
|
|
189
|
-
const { url, close, requests } = await startResponsesTestProxy({
|
|
190
|
-
statusCode: 200,
|
|
191
|
-
responseBodies: [
|
|
192
|
-
sse(
|
|
193
|
-
responseStarted("response_1"),
|
|
194
|
-
assistantMessage("Turn options applied", "item_1"),
|
|
195
|
-
responseCompleted("response_1"),
|
|
196
|
-
),
|
|
197
|
-
],
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
const { args: spawnArgs, restore } = codexExecSpy();
|
|
201
|
-
|
|
202
|
-
try {
|
|
203
|
-
const client = new Codex({ codexPathOverride: codexExecPath, baseUrl: url, apiKey: "test" });
|
|
204
|
-
|
|
205
|
-
const thread = client.startThread({
|
|
206
|
-
model: "gpt-test-1",
|
|
207
|
-
sandboxMode: "workspace-write",
|
|
208
|
-
});
|
|
209
|
-
await thread.run("apply options");
|
|
210
|
-
|
|
211
|
-
const payload = requests[0];
|
|
212
|
-
expect(payload).toBeDefined();
|
|
213
|
-
const json = payload!.json as { model?: string } | undefined;
|
|
214
|
-
|
|
215
|
-
expect(json?.model).toBe("gpt-test-1");
|
|
216
|
-
expect(spawnArgs.length).toBeGreaterThan(0);
|
|
217
|
-
const commandArgs = spawnArgs[0];
|
|
218
|
-
|
|
219
|
-
expectPair(commandArgs, ["--sandbox", "workspace-write"]);
|
|
220
|
-
expectPair(commandArgs, ["--model", "gpt-test-1"]);
|
|
221
|
-
} finally {
|
|
222
|
-
restore();
|
|
223
|
-
await close();
|
|
224
|
-
}
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
it("passes modelReasoningEffort to exec", async () => {
|
|
228
|
-
const { url, close } = await startResponsesTestProxy({
|
|
229
|
-
statusCode: 200,
|
|
230
|
-
responseBodies: [
|
|
231
|
-
sse(
|
|
232
|
-
responseStarted("response_1"),
|
|
233
|
-
assistantMessage("Reasoning effort applied", "item_1"),
|
|
234
|
-
responseCompleted("response_1"),
|
|
235
|
-
),
|
|
236
|
-
],
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
const { args: spawnArgs, restore } = codexExecSpy();
|
|
240
|
-
|
|
241
|
-
try {
|
|
242
|
-
const client = new Codex({ codexPathOverride: codexExecPath, baseUrl: url, apiKey: "test" });
|
|
243
|
-
|
|
244
|
-
const thread = client.startThread({
|
|
245
|
-
modelReasoningEffort: "high",
|
|
246
|
-
});
|
|
247
|
-
await thread.run("apply reasoning effort");
|
|
248
|
-
|
|
249
|
-
const commandArgs = spawnArgs[0];
|
|
250
|
-
expect(commandArgs).toBeDefined();
|
|
251
|
-
expectPair(commandArgs, ["--config", 'model_reasoning_effort="high"']);
|
|
252
|
-
} finally {
|
|
253
|
-
restore();
|
|
254
|
-
await close();
|
|
255
|
-
}
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
it("passes networkAccessEnabled to exec", async () => {
|
|
259
|
-
const { url, close } = await startResponsesTestProxy({
|
|
260
|
-
statusCode: 200,
|
|
261
|
-
responseBodies: [
|
|
262
|
-
sse(
|
|
263
|
-
responseStarted("response_1"),
|
|
264
|
-
assistantMessage("Network access enabled", "item_1"),
|
|
265
|
-
responseCompleted("response_1"),
|
|
266
|
-
),
|
|
267
|
-
],
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
const { args: spawnArgs, restore } = codexExecSpy();
|
|
271
|
-
|
|
272
|
-
try {
|
|
273
|
-
const client = new Codex({ codexPathOverride: codexExecPath, baseUrl: url, apiKey: "test" });
|
|
274
|
-
|
|
275
|
-
const thread = client.startThread({
|
|
276
|
-
networkAccessEnabled: true,
|
|
277
|
-
});
|
|
278
|
-
await thread.run("test network access");
|
|
279
|
-
|
|
280
|
-
const commandArgs = spawnArgs[0];
|
|
281
|
-
expect(commandArgs).toBeDefined();
|
|
282
|
-
expectPair(commandArgs, ["--config", "sandbox_workspace_write.network_access=true"]);
|
|
283
|
-
} finally {
|
|
284
|
-
restore();
|
|
285
|
-
await close();
|
|
286
|
-
}
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
it("passes webSearchEnabled to exec", async () => {
|
|
290
|
-
const { url, close } = await startResponsesTestProxy({
|
|
291
|
-
statusCode: 200,
|
|
292
|
-
responseBodies: [
|
|
293
|
-
sse(
|
|
294
|
-
responseStarted("response_1"),
|
|
295
|
-
assistantMessage("Web search enabled", "item_1"),
|
|
296
|
-
responseCompleted("response_1"),
|
|
297
|
-
),
|
|
298
|
-
],
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
const { args: spawnArgs, restore } = codexExecSpy();
|
|
302
|
-
|
|
303
|
-
try {
|
|
304
|
-
const client = new Codex({ codexPathOverride: codexExecPath, baseUrl: url, apiKey: "test" });
|
|
305
|
-
|
|
306
|
-
const thread = client.startThread({
|
|
307
|
-
webSearchEnabled: true,
|
|
308
|
-
});
|
|
309
|
-
await thread.run("test web search");
|
|
310
|
-
|
|
311
|
-
const commandArgs = spawnArgs[0];
|
|
312
|
-
expect(commandArgs).toBeDefined();
|
|
313
|
-
expectPair(commandArgs, ["--config", "features.web_search_request=true"]);
|
|
314
|
-
} finally {
|
|
315
|
-
restore();
|
|
316
|
-
await close();
|
|
317
|
-
}
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
it("passes approvalPolicy to exec", async () => {
|
|
321
|
-
const { url, close } = await startResponsesTestProxy({
|
|
322
|
-
statusCode: 200,
|
|
323
|
-
responseBodies: [
|
|
324
|
-
sse(
|
|
325
|
-
responseStarted("response_1"),
|
|
326
|
-
assistantMessage("Approval policy set", "item_1"),
|
|
327
|
-
responseCompleted("response_1"),
|
|
328
|
-
),
|
|
329
|
-
],
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
const { args: spawnArgs, restore } = codexExecSpy();
|
|
333
|
-
|
|
334
|
-
try {
|
|
335
|
-
const client = new Codex({ codexPathOverride: codexExecPath, baseUrl: url, apiKey: "test" });
|
|
336
|
-
|
|
337
|
-
const thread = client.startThread({
|
|
338
|
-
approvalPolicy: "on-request",
|
|
339
|
-
});
|
|
340
|
-
await thread.run("test approval policy");
|
|
341
|
-
|
|
342
|
-
const commandArgs = spawnArgs[0];
|
|
343
|
-
expect(commandArgs).toBeDefined();
|
|
344
|
-
expectPair(commandArgs, ["--config", 'approval_policy="on-request"']);
|
|
345
|
-
} finally {
|
|
346
|
-
restore();
|
|
347
|
-
await close();
|
|
348
|
-
}
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
it("allows overriding the env passed to the Codex CLI", async () => {
|
|
352
|
-
const { url, close } = await startResponsesTestProxy({
|
|
353
|
-
statusCode: 200,
|
|
354
|
-
responseBodies: [
|
|
355
|
-
sse(
|
|
356
|
-
responseStarted("response_1"),
|
|
357
|
-
assistantMessage("Custom env", "item_1"),
|
|
358
|
-
responseCompleted("response_1"),
|
|
359
|
-
),
|
|
360
|
-
],
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
const { envs: spawnEnvs, restore } = codexExecSpy();
|
|
364
|
-
process.env.CODEX_ENV_SHOULD_NOT_LEAK = "leak";
|
|
365
|
-
|
|
366
|
-
try {
|
|
367
|
-
const client = new Codex({
|
|
368
|
-
codexPathOverride: codexExecPath,
|
|
369
|
-
baseUrl: url,
|
|
370
|
-
apiKey: "test",
|
|
371
|
-
env: { CUSTOM_ENV: "custom" },
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
const thread = client.startThread();
|
|
375
|
-
await thread.run("custom env");
|
|
376
|
-
|
|
377
|
-
const spawnEnv = spawnEnvs[0];
|
|
378
|
-
expect(spawnEnv).toBeDefined();
|
|
379
|
-
if (!spawnEnv) {
|
|
380
|
-
throw new Error("Spawn env missing");
|
|
381
|
-
}
|
|
382
|
-
expect(spawnEnv.CUSTOM_ENV).toBe("custom");
|
|
383
|
-
expect(spawnEnv.CODEX_ENV_SHOULD_NOT_LEAK).toBeUndefined();
|
|
384
|
-
expect(spawnEnv.OPENAI_BASE_URL).toBe(url);
|
|
385
|
-
expect(spawnEnv.CODEX_API_KEY).toBe("test");
|
|
386
|
-
expect(spawnEnv.CODEX_INTERNAL_ORIGINATOR_OVERRIDE).toBeDefined();
|
|
387
|
-
} finally {
|
|
388
|
-
delete process.env.CODEX_ENV_SHOULD_NOT_LEAK;
|
|
389
|
-
restore();
|
|
390
|
-
await close();
|
|
391
|
-
}
|
|
392
|
-
});
|
|
393
|
-
|
|
394
|
-
it("passes additionalDirectories as repeated flags", async () => {
|
|
395
|
-
const { url, close } = await startResponsesTestProxy({
|
|
396
|
-
statusCode: 200,
|
|
397
|
-
responseBodies: [
|
|
398
|
-
sse(
|
|
399
|
-
responseStarted("response_1"),
|
|
400
|
-
assistantMessage("Additional directories applied", "item_1"),
|
|
401
|
-
responseCompleted("response_1"),
|
|
402
|
-
),
|
|
403
|
-
],
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
const { args: spawnArgs, restore } = codexExecSpy();
|
|
407
|
-
|
|
408
|
-
try {
|
|
409
|
-
const client = new Codex({ codexPathOverride: codexExecPath, baseUrl: url, apiKey: "test" });
|
|
410
|
-
|
|
411
|
-
const thread = client.startThread({
|
|
412
|
-
additionalDirectories: ["../backend", "/tmp/shared"],
|
|
413
|
-
});
|
|
414
|
-
await thread.run("test additional dirs");
|
|
415
|
-
|
|
416
|
-
const commandArgs = spawnArgs[0];
|
|
417
|
-
expect(commandArgs).toBeDefined();
|
|
418
|
-
if (!commandArgs) {
|
|
419
|
-
throw new Error("Command args missing");
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
// Find the --add-dir flags
|
|
423
|
-
const addDirArgs: string[] = [];
|
|
424
|
-
for (let i = 0; i < commandArgs.length; i += 1) {
|
|
425
|
-
if (commandArgs[i] === "--add-dir") {
|
|
426
|
-
addDirArgs.push(commandArgs[i + 1] ?? "");
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
expect(addDirArgs).toEqual(["../backend", "/tmp/shared"]);
|
|
430
|
-
} finally {
|
|
431
|
-
restore();
|
|
432
|
-
await close();
|
|
433
|
-
}
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
it("writes output schema to a temporary file and forwards it", async () => {
|
|
437
|
-
const { url, close, requests } = await startResponsesTestProxy({
|
|
438
|
-
statusCode: 200,
|
|
439
|
-
responseBodies: [
|
|
440
|
-
sse(
|
|
441
|
-
responseStarted("response_1"),
|
|
442
|
-
assistantMessage("Structured response", "item_1"),
|
|
443
|
-
responseCompleted("response_1"),
|
|
444
|
-
),
|
|
445
|
-
],
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
const { args: spawnArgs, restore } = codexExecSpy();
|
|
449
|
-
|
|
450
|
-
const schema = {
|
|
451
|
-
type: "object",
|
|
452
|
-
properties: {
|
|
453
|
-
answer: { type: "string" },
|
|
454
|
-
},
|
|
455
|
-
required: ["answer"],
|
|
456
|
-
additionalProperties: false,
|
|
457
|
-
} as const;
|
|
458
|
-
|
|
459
|
-
try {
|
|
460
|
-
const client = new Codex({ codexPathOverride: codexExecPath, baseUrl: url, apiKey: "test" });
|
|
461
|
-
|
|
462
|
-
const thread = client.startThread();
|
|
463
|
-
await thread.run("structured", { outputSchema: schema });
|
|
464
|
-
|
|
465
|
-
expect(requests.length).toBeGreaterThanOrEqual(1);
|
|
466
|
-
const payload = requests[0];
|
|
467
|
-
expect(payload).toBeDefined();
|
|
468
|
-
const text = payload!.json.text;
|
|
469
|
-
expect(text).toBeDefined();
|
|
470
|
-
expect(text?.format).toEqual({
|
|
471
|
-
name: "codex_output_schema",
|
|
472
|
-
type: "json_schema",
|
|
473
|
-
strict: true,
|
|
474
|
-
schema,
|
|
475
|
-
});
|
|
476
|
-
|
|
477
|
-
const commandArgs = spawnArgs[0];
|
|
478
|
-
expect(commandArgs).toBeDefined();
|
|
479
|
-
const schemaFlagIndex = commandArgs!.indexOf("--output-schema");
|
|
480
|
-
expect(schemaFlagIndex).toBeGreaterThan(-1);
|
|
481
|
-
const schemaPath = commandArgs![schemaFlagIndex + 1];
|
|
482
|
-
expect(typeof schemaPath).toBe("string");
|
|
483
|
-
if (typeof schemaPath !== "string") {
|
|
484
|
-
throw new Error("--output-schema flag missing path argument");
|
|
485
|
-
}
|
|
486
|
-
expect(fs.existsSync(schemaPath)).toBe(false);
|
|
487
|
-
} finally {
|
|
488
|
-
restore();
|
|
489
|
-
await close();
|
|
490
|
-
}
|
|
491
|
-
});
|
|
492
|
-
it("combines structured text input segments", async () => {
|
|
493
|
-
const { url, close, requests } = await startResponsesTestProxy({
|
|
494
|
-
statusCode: 200,
|
|
495
|
-
responseBodies: [
|
|
496
|
-
sse(
|
|
497
|
-
responseStarted("response_1"),
|
|
498
|
-
assistantMessage("Combined input applied", "item_1"),
|
|
499
|
-
responseCompleted("response_1"),
|
|
500
|
-
),
|
|
501
|
-
],
|
|
502
|
-
});
|
|
503
|
-
|
|
504
|
-
try {
|
|
505
|
-
const client = new Codex({ codexPathOverride: codexExecPath, baseUrl: url, apiKey: "test" });
|
|
506
|
-
|
|
507
|
-
const thread = client.startThread();
|
|
508
|
-
await thread.run([
|
|
509
|
-
{ type: "text", text: "Describe file changes" },
|
|
510
|
-
{ type: "text", text: "Focus on impacted tests" },
|
|
511
|
-
]);
|
|
512
|
-
|
|
513
|
-
const payload = requests[0];
|
|
514
|
-
expect(payload).toBeDefined();
|
|
515
|
-
const lastUser = payload!.json.input.at(-1);
|
|
516
|
-
expect(lastUser?.content?.[0]?.text).toBe("Describe file changes\n\nFocus on impacted tests");
|
|
517
|
-
} finally {
|
|
518
|
-
await close();
|
|
519
|
-
}
|
|
520
|
-
});
|
|
521
|
-
it("forwards images to exec", async () => {
|
|
522
|
-
const { url, close } = await startResponsesTestProxy({
|
|
523
|
-
statusCode: 200,
|
|
524
|
-
responseBodies: [
|
|
525
|
-
sse(
|
|
526
|
-
responseStarted("response_1"),
|
|
527
|
-
assistantMessage("Images applied", "item_1"),
|
|
528
|
-
responseCompleted("response_1"),
|
|
529
|
-
),
|
|
530
|
-
],
|
|
531
|
-
});
|
|
532
|
-
|
|
533
|
-
const { args: spawnArgs, restore } = codexExecSpy();
|
|
534
|
-
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "codex-images-"));
|
|
535
|
-
const imagesDirectoryEntries: [string, string] = [
|
|
536
|
-
path.join(tempDir, "first.png"),
|
|
537
|
-
path.join(tempDir, "second.jpg"),
|
|
538
|
-
];
|
|
539
|
-
imagesDirectoryEntries.forEach((image, index) => {
|
|
540
|
-
fs.writeFileSync(image, `image-${index}`);
|
|
541
|
-
});
|
|
542
|
-
|
|
543
|
-
try {
|
|
544
|
-
const client = new Codex({ codexPathOverride: codexExecPath, baseUrl: url, apiKey: "test" });
|
|
545
|
-
|
|
546
|
-
const thread = client.startThread();
|
|
547
|
-
await thread.run([
|
|
548
|
-
{ type: "text", text: "describe the images" },
|
|
549
|
-
{ type: "local_image", path: imagesDirectoryEntries[0] },
|
|
550
|
-
{ type: "local_image", path: imagesDirectoryEntries[1] },
|
|
551
|
-
]);
|
|
552
|
-
|
|
553
|
-
const commandArgs = spawnArgs[0];
|
|
554
|
-
expect(commandArgs).toBeDefined();
|
|
555
|
-
const forwardedImages: string[] = [];
|
|
556
|
-
for (let i = 0; i < commandArgs!.length; i += 1) {
|
|
557
|
-
if (commandArgs![i] === "--image") {
|
|
558
|
-
forwardedImages.push(commandArgs![i + 1] ?? "");
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
expect(forwardedImages).toEqual(imagesDirectoryEntries);
|
|
562
|
-
} finally {
|
|
563
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
564
|
-
restore();
|
|
565
|
-
await close();
|
|
566
|
-
}
|
|
567
|
-
});
|
|
568
|
-
it("runs in provided working directory", async () => {
|
|
569
|
-
const { url, close } = await startResponsesTestProxy({
|
|
570
|
-
statusCode: 200,
|
|
571
|
-
responseBodies: [
|
|
572
|
-
sse(
|
|
573
|
-
responseStarted("response_1"),
|
|
574
|
-
assistantMessage("Working directory applied", "item_1"),
|
|
575
|
-
responseCompleted("response_1"),
|
|
576
|
-
),
|
|
577
|
-
],
|
|
578
|
-
});
|
|
579
|
-
|
|
580
|
-
const { args: spawnArgs, restore } = codexExecSpy();
|
|
581
|
-
|
|
582
|
-
try {
|
|
583
|
-
const workingDirectory = fs.mkdtempSync(path.join(os.tmpdir(), "codex-working-dir-"));
|
|
584
|
-
const client = new Codex({
|
|
585
|
-
codexPathOverride: codexExecPath,
|
|
586
|
-
baseUrl: url,
|
|
587
|
-
apiKey: "test",
|
|
588
|
-
});
|
|
589
|
-
|
|
590
|
-
const thread = client.startThread({
|
|
591
|
-
workingDirectory,
|
|
592
|
-
skipGitRepoCheck: true,
|
|
593
|
-
});
|
|
594
|
-
await thread.run("use custom working directory");
|
|
595
|
-
|
|
596
|
-
const commandArgs = spawnArgs[0];
|
|
597
|
-
expectPair(commandArgs, ["--cd", workingDirectory]);
|
|
598
|
-
} finally {
|
|
599
|
-
restore();
|
|
600
|
-
await close();
|
|
601
|
-
}
|
|
602
|
-
});
|
|
603
|
-
|
|
604
|
-
it("throws if working directory is not git and no skipGitRepoCheck is provided", async () => {
|
|
605
|
-
const { url, close } = await startResponsesTestProxy({
|
|
606
|
-
statusCode: 200,
|
|
607
|
-
responseBodies: [
|
|
608
|
-
sse(
|
|
609
|
-
responseStarted("response_1"),
|
|
610
|
-
assistantMessage("Working directory applied", "item_1"),
|
|
611
|
-
responseCompleted("response_1"),
|
|
612
|
-
),
|
|
613
|
-
],
|
|
614
|
-
});
|
|
615
|
-
|
|
616
|
-
try {
|
|
617
|
-
const workingDirectory = fs.mkdtempSync(path.join(os.tmpdir(), "codex-working-dir-"));
|
|
618
|
-
const client = new Codex({
|
|
619
|
-
codexPathOverride: codexExecPath,
|
|
620
|
-
baseUrl: url,
|
|
621
|
-
apiKey: "test",
|
|
622
|
-
});
|
|
623
|
-
|
|
624
|
-
const thread = client.startThread({
|
|
625
|
-
workingDirectory,
|
|
626
|
-
});
|
|
627
|
-
await expect(thread.run("use custom working directory")).rejects.toThrow(
|
|
628
|
-
/Not inside a trusted directory/,
|
|
629
|
-
);
|
|
630
|
-
} finally {
|
|
631
|
-
await close();
|
|
632
|
-
}
|
|
633
|
-
});
|
|
634
|
-
|
|
635
|
-
it("sets the codex sdk originator header", async () => {
|
|
636
|
-
const { url, close, requests } = await startResponsesTestProxy({
|
|
637
|
-
statusCode: 200,
|
|
638
|
-
responseBodies: [sse(responseStarted(), assistantMessage("Hi!"), responseCompleted())],
|
|
639
|
-
});
|
|
640
|
-
|
|
641
|
-
try {
|
|
642
|
-
const client = new Codex({ codexPathOverride: codexExecPath, baseUrl: url, apiKey: "test" });
|
|
643
|
-
|
|
644
|
-
const thread = client.startThread();
|
|
645
|
-
await thread.run("Hello, originator!");
|
|
646
|
-
|
|
647
|
-
expect(requests.length).toBeGreaterThan(0);
|
|
648
|
-
const originatorHeader = requests[0]!.headers["originator"];
|
|
649
|
-
if (Array.isArray(originatorHeader)) {
|
|
650
|
-
expect(originatorHeader).toContain("codex_sdk_ts");
|
|
651
|
-
} else {
|
|
652
|
-
expect(originatorHeader).toBe("codex_sdk_ts");
|
|
653
|
-
}
|
|
654
|
-
} finally {
|
|
655
|
-
await close();
|
|
656
|
-
}
|
|
657
|
-
});
|
|
658
|
-
it("throws ThreadRunError on turn failures", async () => {
|
|
659
|
-
const { url, close } = await startResponsesTestProxy({
|
|
660
|
-
statusCode: 200,
|
|
661
|
-
responseBodies: (function* (): Generator<SseResponseBody> {
|
|
662
|
-
yield sse(responseStarted("response_1"));
|
|
663
|
-
while (true) {
|
|
664
|
-
yield sse(responseFailed("rate limit exceeded"));
|
|
665
|
-
}
|
|
666
|
-
})(),
|
|
667
|
-
});
|
|
668
|
-
|
|
669
|
-
try {
|
|
670
|
-
const client = new Codex({ codexPathOverride: codexExecPath, baseUrl: url, apiKey: "test" });
|
|
671
|
-
const thread = client.startThread();
|
|
672
|
-
await expect(thread.run("fail")).rejects.toThrow("stream disconnected before completion:");
|
|
673
|
-
} finally {
|
|
674
|
-
await close();
|
|
675
|
-
}
|
|
676
|
-
}, 10000); // TODO(pakrym): remove timeout
|
|
677
|
-
});
|
|
678
|
-
function expectPair(args: string[] | undefined, pair: [string, string]) {
|
|
679
|
-
if (!args) {
|
|
680
|
-
throw new Error("Args is undefined");
|
|
681
|
-
}
|
|
682
|
-
const index = args.indexOf(pair[0]);
|
|
683
|
-
if (index === -1) {
|
|
684
|
-
throw new Error(`Pair ${pair[0]} not found in args`);
|
|
685
|
-
}
|
|
686
|
-
expect(args[index + 1]).toBe(pair[1]);
|
|
687
|
-
}
|