@harness-control/runner 0.1.0
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/LICENSE +202 -0
- package/README.md +21 -0
- package/dist/audit/index.d.ts +18 -0
- package/dist/audit/index.js +28 -0
- package/dist/config/index.d.ts +179 -0
- package/dist/config/index.js +124 -0
- package/dist/connection/index.d.ts +2 -0
- package/dist/connection/index.js +2 -0
- package/dist/connection/runner-connection.d.ts +22 -0
- package/dist/connection/runner-connection.js +631 -0
- package/dist/harnesses/adapters/providers/claude-runtime.d.ts +5 -0
- package/dist/harnesses/adapters/providers/claude-runtime.js +186 -0
- package/dist/harnesses/adapters/providers/claude.d.ts +24 -0
- package/dist/harnesses/adapters/providers/claude.js +189 -0
- package/dist/harnesses/adapters/providers/cli-process.d.ts +44 -0
- package/dist/harnesses/adapters/providers/cli-process.js +195 -0
- package/dist/harnesses/adapters/providers/codex-models.d.ts +4 -0
- package/dist/harnesses/adapters/providers/codex-models.js +62 -0
- package/dist/harnesses/adapters/providers/codex-rpc.d.ts +21 -0
- package/dist/harnesses/adapters/providers/codex-rpc.js +114 -0
- package/dist/harnesses/adapters/providers/codex-runtime.d.ts +3 -0
- package/dist/harnesses/adapters/providers/codex-runtime.js +267 -0
- package/dist/harnesses/adapters/providers/codex.d.ts +22 -0
- package/dist/harnesses/adapters/providers/codex.js +161 -0
- package/dist/harnesses/adapters/providers/mock.d.ts +13 -0
- package/dist/harnesses/adapters/providers/mock.js +64 -0
- package/dist/harnesses/adapters/providers/native-process.d.ts +9 -0
- package/dist/harnesses/adapters/providers/native-process.js +41 -0
- package/dist/harnesses/adapters/providers/native-turn.d.ts +17 -0
- package/dist/harnesses/adapters/providers/native-turn.js +139 -0
- package/dist/harnesses/adapters/providers/opencode.d.ts +44 -0
- package/dist/harnesses/adapters/providers/opencode.js +416 -0
- package/dist/harnesses/adapters/providers/shared.d.ts +9 -0
- package/dist/harnesses/adapters/providers/shared.js +97 -0
- package/dist/harnesses/adapters/registry.d.ts +12 -0
- package/dist/harnesses/adapters/registry.js +47 -0
- package/dist/harnesses/adapters/types.d.ts +54 -0
- package/dist/harnesses/adapters/types.js +9 -0
- package/dist/harnesses/adapters.d.ts +8 -0
- package/dist/harnesses/adapters.js +8 -0
- package/dist/harnesses/index.d.ts +93 -0
- package/dist/harnesses/index.js +620 -0
- package/dist/host/provider-registry.d.ts +34 -0
- package/dist/host/provider-registry.js +162 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +201 -0
- package/dist/local-actions/dispatcher.d.ts +28 -0
- package/dist/local-actions/dispatcher.js +407 -0
- package/dist/local-actions/executors.d.ts +159 -0
- package/dist/local-actions/executors.js +1103 -0
- package/dist/local-actions/index.d.ts +74 -0
- package/dist/local-actions/index.js +275 -0
- package/dist/logs/index.d.ts +6 -0
- package/dist/logs/index.js +9 -0
- package/dist/mcp/McpAttachmentClient.d.ts +111 -0
- package/dist/mcp/McpAttachmentClient.js +345 -0
- package/dist/mcp/McpProxyServer.d.ts +18 -0
- package/dist/mcp/McpProxyServer.js +188 -0
- package/dist/mcp/McpStdioProfileClient.d.ts +19 -0
- package/dist/mcp/McpStdioProfileClient.js +91 -0
- package/dist/mcp/index.d.ts +5 -0
- package/dist/mcp/index.js +5 -0
- package/dist/mcp/redaction.d.ts +3 -0
- package/dist/mcp/redaction.js +40 -0
- package/dist/pairing/index.d.ts +38 -0
- package/dist/pairing/index.js +180 -0
- package/dist/state/index.d.ts +76 -0
- package/dist/state/index.js +242 -0
- package/dist/workspaces/index.d.ts +13 -0
- package/dist/workspaces/index.js +110 -0
- package/package.json +76 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { CodexRpc } from "./codex-rpc.js";
|
|
3
|
+
const pageSchema = z.object({
|
|
4
|
+
data: z.array(z.object({
|
|
5
|
+
id: z.string(),
|
|
6
|
+
model: z.string(),
|
|
7
|
+
displayName: z.string(),
|
|
8
|
+
isDefault: z.boolean(),
|
|
9
|
+
supportedReasoningEfforts: z.array(z.object({ reasoningEffort: z.string() })),
|
|
10
|
+
defaultReasoningEffort: z.string(),
|
|
11
|
+
})),
|
|
12
|
+
nextCursor: z.string().nullable().optional(),
|
|
13
|
+
});
|
|
14
|
+
export async function codexModels(provider, timeoutMs) {
|
|
15
|
+
const rpc = new CodexRpc(provider.executable_path ?? "codex", process.cwd(), {
|
|
16
|
+
...process.env,
|
|
17
|
+
...provider.env,
|
|
18
|
+
...(provider.home ? { CODEX_HOME: provider.home } : {}),
|
|
19
|
+
});
|
|
20
|
+
const timer = setTimeout(() => {
|
|
21
|
+
void rpc.process.stop();
|
|
22
|
+
}, timeoutMs);
|
|
23
|
+
try {
|
|
24
|
+
await rpc.request("initialize", {
|
|
25
|
+
clientInfo: { name: "hcp-runner", version: "0.0.0" },
|
|
26
|
+
capabilities: {},
|
|
27
|
+
});
|
|
28
|
+
rpc.notify("initialized");
|
|
29
|
+
const models = [];
|
|
30
|
+
let cursor;
|
|
31
|
+
do {
|
|
32
|
+
const page = pageSchema.parse(await rpc.request("model/list", { ...(cursor ? { cursor } : {}) }));
|
|
33
|
+
for (const model of page.data)
|
|
34
|
+
models.push({
|
|
35
|
+
id: model.model,
|
|
36
|
+
label: model.displayName,
|
|
37
|
+
is_default: model.isDefault,
|
|
38
|
+
capabilities: {
|
|
39
|
+
option_descriptors: [
|
|
40
|
+
{
|
|
41
|
+
id: "reasoningEffort",
|
|
42
|
+
label: "Reasoning effort",
|
|
43
|
+
type: "select",
|
|
44
|
+
default_value: model.defaultReasoningEffort,
|
|
45
|
+
values: model.supportedReasoningEfforts.map(({ reasoningEffort }) => ({
|
|
46
|
+
value: reasoningEffort,
|
|
47
|
+
label: reasoningEffort,
|
|
48
|
+
})),
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
cursor = page.nextCursor ?? undefined;
|
|
54
|
+
} while (cursor);
|
|
55
|
+
return models;
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
clearTimeout(timer);
|
|
59
|
+
await rpc.process.stop();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=codex-models.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { NativeProcess } from "./native-process.js";
|
|
3
|
+
declare const messageSchema: z.ZodObject<{
|
|
4
|
+
id: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
5
|
+
method: z.ZodOptional<z.ZodString>;
|
|
6
|
+
params: z.ZodOptional<z.ZodUnknown>;
|
|
7
|
+
result: z.ZodOptional<z.ZodUnknown>;
|
|
8
|
+
error: z.ZodOptional<z.ZodUnknown>;
|
|
9
|
+
}, z.core.$strip>;
|
|
10
|
+
export type RpcMessage = z.infer<typeof messageSchema>;
|
|
11
|
+
export declare class CodexRpc {
|
|
12
|
+
#private;
|
|
13
|
+
readonly process: NativeProcess;
|
|
14
|
+
onNotification: (message: RpcMessage) => void;
|
|
15
|
+
onFailure: (error: Error) => void;
|
|
16
|
+
constructor(executable: string, cwd: string, env: NodeJS.ProcessEnv);
|
|
17
|
+
request(method: string, params: unknown): Promise<unknown>;
|
|
18
|
+
notify(method: string): void;
|
|
19
|
+
}
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=codex-rpc.d.ts.map
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { StringDecoder } from "node:string_decoder";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { HarnessAdapterError } from "../types.js";
|
|
4
|
+
import { NativeProcess } from "./native-process.js";
|
|
5
|
+
import { processFailureMessage } from "./cli-process.js";
|
|
6
|
+
const messageSchema = z.object({
|
|
7
|
+
id: z.union([z.string(), z.number()]).optional(),
|
|
8
|
+
method: z.string().optional(),
|
|
9
|
+
params: z.unknown().optional(),
|
|
10
|
+
result: z.unknown().optional(),
|
|
11
|
+
error: z.unknown().optional(),
|
|
12
|
+
});
|
|
13
|
+
export class CodexRpc {
|
|
14
|
+
process;
|
|
15
|
+
#pending = new Map();
|
|
16
|
+
#nextId = 1;
|
|
17
|
+
#failure;
|
|
18
|
+
#buffer = "";
|
|
19
|
+
#decoder = new StringDecoder("utf8");
|
|
20
|
+
onNotification = () => { };
|
|
21
|
+
onFailure = () => { };
|
|
22
|
+
constructor(executable, cwd, env) {
|
|
23
|
+
this.process = new NativeProcess(executable, ["app-server", "--listen", "stdio://"], cwd, env);
|
|
24
|
+
this.process.child.stderr.resume();
|
|
25
|
+
this.process.child.stdout.on("data", (chunk) => {
|
|
26
|
+
try {
|
|
27
|
+
this.#buffer += this.#decoder.write(chunk);
|
|
28
|
+
let newline;
|
|
29
|
+
while ((newline = this.#buffer.indexOf("\n")) >= 0) {
|
|
30
|
+
if (newline > 8 * 1024 * 1024)
|
|
31
|
+
throw new Error("Oversized frame");
|
|
32
|
+
const line = this.#buffer.slice(0, newline);
|
|
33
|
+
this.#buffer = this.#buffer.slice(newline + 1);
|
|
34
|
+
this.#receive(messageSchema.parse(JSON.parse(line)));
|
|
35
|
+
}
|
|
36
|
+
if (Buffer.byteLength(this.#buffer) > 8 * 1024 * 1024)
|
|
37
|
+
throw new Error("Oversized frame");
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
this.#fail(new HarnessAdapterError("codex_protocol_error", "Codex returned an invalid protocol message."));
|
|
41
|
+
void this.process.stop();
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
void this.process.closed.then(() => this.#fail(new HarnessAdapterError("codex_process_closed", "Codex process closed before the operation finished.")));
|
|
45
|
+
}
|
|
46
|
+
request(method, params) {
|
|
47
|
+
if (this.#failure)
|
|
48
|
+
return Promise.reject(this.#failure);
|
|
49
|
+
const id = this.#nextId++;
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
this.#pending.set(id, { resolve, reject });
|
|
52
|
+
this.#write({ id, method, params });
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
notify(method) {
|
|
56
|
+
this.#write({ method });
|
|
57
|
+
}
|
|
58
|
+
#write(message) {
|
|
59
|
+
this.process.child.stdin.write(`${JSON.stringify(message)}\n`, (error) => {
|
|
60
|
+
if (error)
|
|
61
|
+
this.#fail(new HarnessAdapterError("codex_transport_closed", "Codex input transport closed."));
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
#receive(message) {
|
|
65
|
+
if (this.#failure)
|
|
66
|
+
return;
|
|
67
|
+
if (message.method && message.id !== undefined) {
|
|
68
|
+
this.#write({
|
|
69
|
+
id: message.id,
|
|
70
|
+
error: {
|
|
71
|
+
code: -32601,
|
|
72
|
+
message: "Interactive provider requests are not supported by this runner profile.",
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
this.#fail(new HarnessAdapterError("unsupported_provider_request", "Codex requested unsupported interactive input."));
|
|
76
|
+
void this.process.stop();
|
|
77
|
+
}
|
|
78
|
+
else if (message.method) {
|
|
79
|
+
this.onNotification(message);
|
|
80
|
+
}
|
|
81
|
+
else if (typeof message.id === "number") {
|
|
82
|
+
const pending = this.#pending.get(message.id);
|
|
83
|
+
if (!pending)
|
|
84
|
+
return;
|
|
85
|
+
this.#pending.delete(message.id);
|
|
86
|
+
if (message.error !== undefined) {
|
|
87
|
+
const error = z
|
|
88
|
+
.object({ message: z.string() })
|
|
89
|
+
.safeParse(message.error);
|
|
90
|
+
const detail = processFailureMessage({
|
|
91
|
+
exitCode: null,
|
|
92
|
+
signal: null,
|
|
93
|
+
stdout: "",
|
|
94
|
+
stderr: "",
|
|
95
|
+
error: error.success ? error.data.message : undefined,
|
|
96
|
+
timedOut: false,
|
|
97
|
+
}, "Codex rejected a native request.", []);
|
|
98
|
+
pending.reject(new HarnessAdapterError("codex_request_failed", detail));
|
|
99
|
+
}
|
|
100
|
+
else
|
|
101
|
+
pending.resolve(message.result);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
#fail(error) {
|
|
105
|
+
if (this.#failure)
|
|
106
|
+
return;
|
|
107
|
+
this.#failure = error;
|
|
108
|
+
for (const pending of this.#pending.values())
|
|
109
|
+
pending.reject(error);
|
|
110
|
+
this.#pending.clear();
|
|
111
|
+
this.onFailure(error);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=codex-rpc.js.map
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { realpath } from "node:fs/promises";
|
|
3
|
+
import { HarnessAdapterError } from "../types.js";
|
|
4
|
+
import { adapterMcpServers, assertCliMcpAttachmentProxied } from "./shared.js";
|
|
5
|
+
import { selectedEffort } from "./native-turn.js";
|
|
6
|
+
import { CodexRpc } from "./codex-rpc.js";
|
|
7
|
+
const object = z.record(z.string(), z.unknown());
|
|
8
|
+
const idObject = z.object({ id: z.string() });
|
|
9
|
+
const startedSchema = z.object({
|
|
10
|
+
thread: idObject,
|
|
11
|
+
sandbox: z.object({
|
|
12
|
+
type: z.string(),
|
|
13
|
+
writableRoots: z.array(z.string()).optional(),
|
|
14
|
+
excludeTmpdirEnvVar: z.boolean().optional(),
|
|
15
|
+
excludeSlashTmp: z.boolean().optional(),
|
|
16
|
+
}),
|
|
17
|
+
approvalPolicy: z.string(),
|
|
18
|
+
});
|
|
19
|
+
const deltaSchema = z.object({
|
|
20
|
+
threadId: z.string(),
|
|
21
|
+
turnId: z.string(),
|
|
22
|
+
delta: z.string(),
|
|
23
|
+
});
|
|
24
|
+
const itemSchema = z.object({
|
|
25
|
+
threadId: z.string(),
|
|
26
|
+
turnId: z.string(),
|
|
27
|
+
item: z.object({
|
|
28
|
+
id: z.string(),
|
|
29
|
+
type: z.string(),
|
|
30
|
+
text: z.string().optional(),
|
|
31
|
+
phase: z.string().nullable().optional(),
|
|
32
|
+
}),
|
|
33
|
+
});
|
|
34
|
+
const terminalSchema = z.object({
|
|
35
|
+
threadId: z.string(),
|
|
36
|
+
turn: z.object({
|
|
37
|
+
id: z.string(),
|
|
38
|
+
status: z.string(),
|
|
39
|
+
error: z.unknown().nullable().optional(),
|
|
40
|
+
}),
|
|
41
|
+
});
|
|
42
|
+
export const runCodexTurn = async (input, signal, emit) => {
|
|
43
|
+
signal.throwIfAborted();
|
|
44
|
+
const selection = input.payload.model_selection ?? input.startPayload.model_selection;
|
|
45
|
+
const effort = selectedEffort(selection, "codex");
|
|
46
|
+
const rpc = new CodexRpc(input.provider.executable_path ?? "codex", input.startPayload.cwd, {
|
|
47
|
+
...process.env,
|
|
48
|
+
...input.provider.env,
|
|
49
|
+
...(input.provider.home ? { CODEX_HOME: input.provider.home } : {}),
|
|
50
|
+
});
|
|
51
|
+
const abort = () => {
|
|
52
|
+
void rpc.process.stop();
|
|
53
|
+
};
|
|
54
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
55
|
+
if (signal.aborted)
|
|
56
|
+
abort();
|
|
57
|
+
try {
|
|
58
|
+
await rpc.request("initialize", {
|
|
59
|
+
clientInfo: { name: "hcp-runner", version: "0.0.0" },
|
|
60
|
+
capabilities: {},
|
|
61
|
+
});
|
|
62
|
+
rpc.notify("initialized");
|
|
63
|
+
const configResult = z.object({ config: object }).parse(await rpc.request("config/read", {
|
|
64
|
+
cwd: input.startPayload.cwd,
|
|
65
|
+
includeLayers: false,
|
|
66
|
+
}));
|
|
67
|
+
const inherited = object.parse(configResult.config.mcp_servers ?? {});
|
|
68
|
+
const servers = {};
|
|
69
|
+
for (const name of Object.keys(inherited))
|
|
70
|
+
servers[name] = { enabled: false };
|
|
71
|
+
for (const attachment of adapterMcpServers(input.mcpServers, input.startPayload)) {
|
|
72
|
+
assertCliMcpAttachmentProxied(attachment, "Codex", "codex");
|
|
73
|
+
if (Object.hasOwn(inherited, attachment.name)) {
|
|
74
|
+
throw new HarnessAdapterError("mcp_name_conflict", "An attachment conflicts with an inherited MCP server name.");
|
|
75
|
+
}
|
|
76
|
+
servers[attachment.name] = {
|
|
77
|
+
url: attachment.url,
|
|
78
|
+
enabled: true,
|
|
79
|
+
default_tools_approval_mode: "approve",
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const sandbox = input.startPayload.sandbox_mode.replaceAll("_", "-");
|
|
83
|
+
const started = startedSchema.parse(await rpc.request("thread/start", {
|
|
84
|
+
cwd: input.startPayload.cwd,
|
|
85
|
+
model: selection.model,
|
|
86
|
+
sandbox,
|
|
87
|
+
approvalPolicy: "never",
|
|
88
|
+
ephemeral: true,
|
|
89
|
+
config: {
|
|
90
|
+
mcp_servers: servers,
|
|
91
|
+
"features.apps": false,
|
|
92
|
+
"features.multi_agent": false,
|
|
93
|
+
"sandbox_workspace_write.writable_roots": [],
|
|
94
|
+
"sandbox_workspace_write.exclude_tmpdir_env_var": true,
|
|
95
|
+
"sandbox_workspace_write.exclude_slash_tmp": true,
|
|
96
|
+
},
|
|
97
|
+
}));
|
|
98
|
+
const expectedSandbox = {
|
|
99
|
+
read_only: "readOnly",
|
|
100
|
+
workspace_write: "workspaceWrite",
|
|
101
|
+
danger_full_access: "dangerFullAccess",
|
|
102
|
+
}[input.startPayload.sandbox_mode];
|
|
103
|
+
if (started.sandbox.type !== expectedSandbox ||
|
|
104
|
+
started.approvalPolicy !== "never") {
|
|
105
|
+
throw new HarnessAdapterError("policy_mismatch", "Codex did not accept the requested execution policy.");
|
|
106
|
+
}
|
|
107
|
+
if (started.sandbox.type === "workspaceWrite") {
|
|
108
|
+
const cwd = await realpath(input.startPayload.cwd);
|
|
109
|
+
const roots = await Promise.all((started.sandbox.writableRoots ?? []).map((root) => realpath(root)));
|
|
110
|
+
if (started.sandbox.writableRoots === undefined ||
|
|
111
|
+
roots.some((root) => root !== cwd) ||
|
|
112
|
+
started.sandbox.excludeTmpdirEnvVar !== true ||
|
|
113
|
+
started.sandbox.excludeSlashTmp !== true) {
|
|
114
|
+
throw new HarnessAdapterError("policy_mismatch", "Codex granted writes beyond the requested workspace.");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const threadId = started.thread.id;
|
|
118
|
+
const allowedServers = new Set(adapterMcpServers(input.mcpServers, input.startPayload).map((attachment) => attachment.name));
|
|
119
|
+
let cursor;
|
|
120
|
+
do {
|
|
121
|
+
const inventory = z
|
|
122
|
+
.object({
|
|
123
|
+
data: z.array(z.object({
|
|
124
|
+
name: z.string(),
|
|
125
|
+
runtimeStatus: z.string().nullable().optional(),
|
|
126
|
+
tools: object.optional(),
|
|
127
|
+
})),
|
|
128
|
+
nextCursor: z.string().nullable().optional(),
|
|
129
|
+
})
|
|
130
|
+
.parse(await rpc.request("mcpServerStatus/list", {
|
|
131
|
+
threadId,
|
|
132
|
+
...(cursor ? { cursor } : {}),
|
|
133
|
+
}));
|
|
134
|
+
if (inventory.data.some((server) => !allowedServers.has(server.name) &&
|
|
135
|
+
!(Object.hasOwn(inherited, server.name) &&
|
|
136
|
+
server.runtimeStatus === "disabled" &&
|
|
137
|
+
Object.keys(server.tools ?? {}).length === 0))) {
|
|
138
|
+
throw new HarnessAdapterError("mcp_scope_mismatch", "Codex exposed an MCP server outside the selected attachment scope.");
|
|
139
|
+
}
|
|
140
|
+
cursor = inventory.nextCursor ?? undefined;
|
|
141
|
+
} while (cursor);
|
|
142
|
+
let nativeTurnId;
|
|
143
|
+
let finalText;
|
|
144
|
+
let usage;
|
|
145
|
+
let settled = false;
|
|
146
|
+
let resolve;
|
|
147
|
+
let reject;
|
|
148
|
+
const terminal = new Promise((yes, no) => {
|
|
149
|
+
resolve = yes;
|
|
150
|
+
reject = no;
|
|
151
|
+
});
|
|
152
|
+
// Attach immediately: the server can emit a terminal event before turn/start replies.
|
|
153
|
+
void terminal.catch(() => { });
|
|
154
|
+
rpc.onFailure = (error) => {
|
|
155
|
+
if (!settled) {
|
|
156
|
+
settled = true;
|
|
157
|
+
reject(error);
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
rpc.onNotification = (message) => {
|
|
161
|
+
if (settled)
|
|
162
|
+
return;
|
|
163
|
+
if (message.method === "turn/started") {
|
|
164
|
+
const event = z
|
|
165
|
+
.object({ threadId: z.string(), turn: idObject })
|
|
166
|
+
.parse(message.params);
|
|
167
|
+
if (event.threadId === threadId)
|
|
168
|
+
nativeTurnId = event.turn.id;
|
|
169
|
+
}
|
|
170
|
+
else if (message.method === "item/agentMessage/delta" ||
|
|
171
|
+
message.method === "item/reasoning/summaryTextDelta") {
|
|
172
|
+
const event = deltaSchema.parse(message.params);
|
|
173
|
+
if (event.threadId !== threadId ||
|
|
174
|
+
(nativeTurnId && event.turnId !== nativeTurnId))
|
|
175
|
+
return;
|
|
176
|
+
emit({
|
|
177
|
+
event_type: message.method === "item/agentMessage/delta"
|
|
178
|
+
? "content.delta"
|
|
179
|
+
: "reasoning.delta",
|
|
180
|
+
turn_id: input.payload.turn_id,
|
|
181
|
+
data: { delta: event.delta },
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
else if (message.method === "item/started" ||
|
|
185
|
+
message.method === "item/completed") {
|
|
186
|
+
const event = itemSchema.parse(message.params);
|
|
187
|
+
if (event.threadId !== threadId ||
|
|
188
|
+
(nativeTurnId && event.turnId !== nativeTurnId))
|
|
189
|
+
return;
|
|
190
|
+
emit({
|
|
191
|
+
event_type: message.method === "item/started"
|
|
192
|
+
? "item.started"
|
|
193
|
+
: "item.completed",
|
|
194
|
+
turn_id: input.payload.turn_id,
|
|
195
|
+
data: {
|
|
196
|
+
item_id: event.item.id,
|
|
197
|
+
item_type: event.item.type,
|
|
198
|
+
...(event.item.text !== undefined
|
|
199
|
+
? { content: event.item.text }
|
|
200
|
+
: {}),
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
if (message.method === "item/completed" &&
|
|
204
|
+
event.item.type === "agentMessage" &&
|
|
205
|
+
event.item.phase !== "commentary")
|
|
206
|
+
finalText = event.item.text;
|
|
207
|
+
}
|
|
208
|
+
else if (message.method === "thread/tokenUsage/updated") {
|
|
209
|
+
const event = z
|
|
210
|
+
.object({
|
|
211
|
+
threadId: z.string(),
|
|
212
|
+
turnId: z.string(),
|
|
213
|
+
tokenUsage: z.object({
|
|
214
|
+
total: z.object({
|
|
215
|
+
inputTokens: z.number().int().nonnegative(),
|
|
216
|
+
outputTokens: z.number().int().nonnegative(),
|
|
217
|
+
totalTokens: z.number().int().nonnegative(),
|
|
218
|
+
}),
|
|
219
|
+
}),
|
|
220
|
+
})
|
|
221
|
+
.parse(message.params);
|
|
222
|
+
if (event.threadId !== threadId ||
|
|
223
|
+
(nativeTurnId && event.turnId !== nativeTurnId))
|
|
224
|
+
return;
|
|
225
|
+
usage = {
|
|
226
|
+
input_tokens: event.tokenUsage.total.inputTokens,
|
|
227
|
+
output_tokens: event.tokenUsage.total.outputTokens,
|
|
228
|
+
total_tokens: event.tokenUsage.total.totalTokens,
|
|
229
|
+
};
|
|
230
|
+
emit({
|
|
231
|
+
event_type: "usage.updated",
|
|
232
|
+
turn_id: input.payload.turn_id,
|
|
233
|
+
data: { ...usage },
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
else if (message.method === "turn/completed") {
|
|
237
|
+
const event = terminalSchema.parse(message.params);
|
|
238
|
+
if (event.threadId !== threadId ||
|
|
239
|
+
(nativeTurnId && event.turn.id !== nativeTurnId))
|
|
240
|
+
return;
|
|
241
|
+
settled = true;
|
|
242
|
+
if (event.turn.status !== "completed" ||
|
|
243
|
+
event.turn.error != null ||
|
|
244
|
+
finalText === undefined) {
|
|
245
|
+
reject(new HarnessAdapterError("codex_turn_failed", "Codex ended without a successful final answer."));
|
|
246
|
+
}
|
|
247
|
+
else
|
|
248
|
+
resolve({ final_text: finalText, ...(usage ? { usage } : {}) });
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
const turn = z.object({ turn: idObject }).parse(await rpc.request("turn/start", {
|
|
252
|
+
threadId,
|
|
253
|
+
model: selection.model,
|
|
254
|
+
...(effort ? { effort } : {}),
|
|
255
|
+
input: [{ type: "text", text: input.payload.input, text_elements: [] }],
|
|
256
|
+
}));
|
|
257
|
+
if (nativeTurnId !== undefined && nativeTurnId !== turn.turn.id)
|
|
258
|
+
throw new HarnessAdapterError("codex_turn_mismatch", "Codex returned conflicting turn identities.");
|
|
259
|
+
nativeTurnId = turn.turn.id;
|
|
260
|
+
return await terminal;
|
|
261
|
+
}
|
|
262
|
+
finally {
|
|
263
|
+
signal.removeEventListener("abort", abort);
|
|
264
|
+
await rpc.process.stop();
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
//# sourceMappingURL=codex-runtime.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ProviderInstanceConfig } from "../../../config/index.js";
|
|
2
|
+
import type { ProviderDriverStatus } from "../../../host/provider-registry.js";
|
|
3
|
+
import { type HarnessAdapter, type HarnessAdapterStartInput, type HarnessAdapterSession, type HarnessAdapterTurnInput, type HarnessAdapterEvent, type HarnessAdapterCancelInput, type HarnessAdapterStopInput } from "../types.js";
|
|
4
|
+
import { type CliProcessSpawner } from "./cli-process.js";
|
|
5
|
+
export type CodexHarnessAdapterOptions = {
|
|
6
|
+
processSpawner?: CliProcessSpawner;
|
|
7
|
+
probeTimeoutMs?: number;
|
|
8
|
+
turnTimeoutMs?: number;
|
|
9
|
+
processKillGraceMs?: number;
|
|
10
|
+
};
|
|
11
|
+
export declare class CodexHarnessAdapter implements HarnessAdapter {
|
|
12
|
+
#private;
|
|
13
|
+
readonly driverKind = "codex";
|
|
14
|
+
constructor(options?: CodexHarnessAdapterOptions);
|
|
15
|
+
probe(provider: ProviderInstanceConfig): Promise<ProviderDriverStatus>;
|
|
16
|
+
validateStart(input: HarnessAdapterStartInput): Promise<void>;
|
|
17
|
+
startSession(input: HarnessAdapterStartInput): Promise<HarnessAdapterSession>;
|
|
18
|
+
sendTurn(input: HarnessAdapterTurnInput): Promise<HarnessAdapterEvent[]>;
|
|
19
|
+
cancelTurn(input: HarnessAdapterCancelInput): Promise<HarnessAdapterEvent[]>;
|
|
20
|
+
stopSession(input: HarnessAdapterStopInput): Promise<HarnessAdapterEvent[]>;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=codex.d.ts.map
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { codexModels } from "./codex-models.js";
|
|
2
|
+
import { firstLine, processFailureMessage, spawnProviderCliProcess, startManagedCliProcess, } from "./cli-process.js";
|
|
3
|
+
import { normalizeProviderModels, adapterMcpServers, assertCliMcpAttachmentProxied, } from "./shared.js";
|
|
4
|
+
import { NativeTurns, nativeExecutionCapabilities, validateNativeStart, } from "./native-turn.js";
|
|
5
|
+
import { runCodexTurn } from "./codex-runtime.js";
|
|
6
|
+
export class CodexHarnessAdapter {
|
|
7
|
+
driverKind = "codex";
|
|
8
|
+
#processSpawner;
|
|
9
|
+
#probeTimeoutMs;
|
|
10
|
+
#processKillGraceMs;
|
|
11
|
+
#turns;
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
this.#processSpawner = options.processSpawner ?? spawnProviderCliProcess;
|
|
14
|
+
this.#probeTimeoutMs = options.probeTimeoutMs ?? 5_000;
|
|
15
|
+
this.#processKillGraceMs = options.processKillGraceMs ?? 1_000;
|
|
16
|
+
this.#turns = new NativeTurns("codex", options.turnTimeoutMs ?? 10 * 60 * 1000);
|
|
17
|
+
}
|
|
18
|
+
async probe(provider) {
|
|
19
|
+
const executable = provider.executable_path ?? "codex";
|
|
20
|
+
const diagnosticPaths = codexDiagnosticPaths(provider, executable, process.cwd());
|
|
21
|
+
const launchArgs = codexLaunchArgs(provider);
|
|
22
|
+
const versionResult = await this.#runProcess(executable, [...launchArgs, "--version"], {
|
|
23
|
+
cwd: process.cwd(),
|
|
24
|
+
env: providerEnvironment(provider),
|
|
25
|
+
}, this.#probeTimeoutMs);
|
|
26
|
+
if (versionResult.timedOut ||
|
|
27
|
+
versionResult.error ||
|
|
28
|
+
versionResult.exitCode !== 0) {
|
|
29
|
+
return {
|
|
30
|
+
provider_instance_id: provider.id,
|
|
31
|
+
driver_kind: "codex",
|
|
32
|
+
execution_capabilities: nativeExecutionCapabilities("codex"),
|
|
33
|
+
installed: false,
|
|
34
|
+
available: false,
|
|
35
|
+
status: "unavailable",
|
|
36
|
+
message: versionResult.timedOut
|
|
37
|
+
? "Codex version probe timed out."
|
|
38
|
+
: processFailureMessage(versionResult, "Codex executable is not available.", diagnosticPaths),
|
|
39
|
+
models: normalizeProviderModels(provider.models),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const authResult = await this.#runProcess(executable, [...launchArgs, "login", "status"], {
|
|
43
|
+
cwd: process.cwd(),
|
|
44
|
+
env: providerEnvironment(provider),
|
|
45
|
+
}, this.#probeTimeoutMs);
|
|
46
|
+
if (authResult.timedOut || authResult.error) {
|
|
47
|
+
const version = firstLine(versionResult.stdout);
|
|
48
|
+
return {
|
|
49
|
+
provider_instance_id: provider.id,
|
|
50
|
+
driver_kind: "codex",
|
|
51
|
+
execution_capabilities: nativeExecutionCapabilities("codex"),
|
|
52
|
+
installed: true,
|
|
53
|
+
available: false,
|
|
54
|
+
status: "unavailable",
|
|
55
|
+
...(version ? { version } : {}),
|
|
56
|
+
message: authResult.timedOut
|
|
57
|
+
? "Codex authentication probe timed out."
|
|
58
|
+
: processFailureMessage(authResult, "Codex authentication probe failed.", diagnosticPaths),
|
|
59
|
+
models: normalizeProviderModels(provider.models),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (authResult.exitCode !== 0) {
|
|
63
|
+
const version = firstLine(versionResult.stdout);
|
|
64
|
+
return {
|
|
65
|
+
provider_instance_id: provider.id,
|
|
66
|
+
driver_kind: "codex",
|
|
67
|
+
execution_capabilities: nativeExecutionCapabilities("codex"),
|
|
68
|
+
installed: true,
|
|
69
|
+
available: false,
|
|
70
|
+
status: "unauthenticated",
|
|
71
|
+
...(version ? { version } : {}),
|
|
72
|
+
message: processFailureMessage(authResult, "Codex is not authenticated.", diagnosticPaths),
|
|
73
|
+
models: normalizeProviderModels(provider.models),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
let models;
|
|
77
|
+
try {
|
|
78
|
+
models =
|
|
79
|
+
provider.models.length > 0
|
|
80
|
+
? normalizeProviderModels(provider.models)
|
|
81
|
+
: await codexModels(provider, this.#probeTimeoutMs);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return {
|
|
85
|
+
provider_instance_id: provider.id,
|
|
86
|
+
driver_kind: "codex",
|
|
87
|
+
installed: true,
|
|
88
|
+
available: false,
|
|
89
|
+
status: "unavailable",
|
|
90
|
+
message: "Codex native model discovery failed.",
|
|
91
|
+
models: [],
|
|
92
|
+
execution_capabilities: nativeExecutionCapabilities("codex"),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
const version = firstLine(versionResult.stdout);
|
|
96
|
+
return {
|
|
97
|
+
provider_instance_id: provider.id,
|
|
98
|
+
driver_kind: "codex",
|
|
99
|
+
execution_capabilities: nativeExecutionCapabilities("codex"),
|
|
100
|
+
installed: true,
|
|
101
|
+
available: true,
|
|
102
|
+
status: "ready",
|
|
103
|
+
...(version ? { version } : {}),
|
|
104
|
+
authStatus: "authenticated",
|
|
105
|
+
models,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
async validateStart(input) {
|
|
109
|
+
validateNativeStart(input, "codex");
|
|
110
|
+
}
|
|
111
|
+
async startSession(input) {
|
|
112
|
+
await this.validateStart(input);
|
|
113
|
+
for (const attachment of adapterMcpServers(input.mcpServers, input.payload))
|
|
114
|
+
assertCliMcpAttachmentProxied(attachment, "Codex", "codex");
|
|
115
|
+
return { adapter_session_id: input.payload.session_id };
|
|
116
|
+
}
|
|
117
|
+
async sendTurn(input) {
|
|
118
|
+
return this.#turns.run(input, async (request, signal, emit) => {
|
|
119
|
+
await this.validateStart({
|
|
120
|
+
payload: request.startPayload,
|
|
121
|
+
provider: request.provider,
|
|
122
|
+
});
|
|
123
|
+
return runCodexTurn(request, signal, emit);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
async cancelTurn(input) {
|
|
127
|
+
return this.#turns.cancel(input.sessionId, input.turnId);
|
|
128
|
+
}
|
|
129
|
+
async stopSession(input) {
|
|
130
|
+
return this.#turns.stop(input.sessionId);
|
|
131
|
+
}
|
|
132
|
+
#runProcess(executable, argv, options, timeoutMs) {
|
|
133
|
+
return this.#startProcess(executable, argv, options, timeoutMs).completion;
|
|
134
|
+
}
|
|
135
|
+
#startProcess(executable, argv, options, timeoutMs) {
|
|
136
|
+
return startManagedCliProcess({
|
|
137
|
+
processSpawner: this.#processSpawner,
|
|
138
|
+
executable,
|
|
139
|
+
argv,
|
|
140
|
+
runOptions: options,
|
|
141
|
+
timeoutMs,
|
|
142
|
+
processKillGraceMs: this.#processKillGraceMs,
|
|
143
|
+
timeoutErrorMessage: "Codex execution timed out.",
|
|
144
|
+
terminatedErrorMessage: "Codex process was terminated.",
|
|
145
|
+
startFailureMessage: "Codex process failed before start.",
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function codexDiagnosticPaths(provider, executable, ...paths) {
|
|
150
|
+
return [provider.executable_path, provider.home, executable, ...paths].filter((value) => value !== undefined && value.length > 0);
|
|
151
|
+
}
|
|
152
|
+
function providerEnvironment(provider) {
|
|
153
|
+
return {
|
|
154
|
+
...provider.env,
|
|
155
|
+
...(provider.home ? { CODEX_HOME: provider.home } : {}),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function codexLaunchArgs(provider) {
|
|
159
|
+
return provider.launch_args;
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=codex.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ProviderInstanceConfig } from "../../../config/index.js";
|
|
2
|
+
import type { ProviderDriverStatus } from "../../../host/provider-registry.js";
|
|
3
|
+
import type { HarnessAdapter, HarnessAdapterCancelInput, HarnessAdapterEvent, HarnessAdapterSession, HarnessAdapterStartInput, HarnessAdapterTurnInput } from "../types.js";
|
|
4
|
+
export declare class MockHarnessAdapter implements HarnessAdapter {
|
|
5
|
+
readonly driverKind = "mock";
|
|
6
|
+
probe(provider: ProviderInstanceConfig): Promise<ProviderDriverStatus>;
|
|
7
|
+
validateStart(): Promise<void>;
|
|
8
|
+
startSession(input: HarnessAdapterStartInput): Promise<HarnessAdapterSession>;
|
|
9
|
+
sendTurn(input: HarnessAdapterTurnInput): Promise<HarnessAdapterEvent[]>;
|
|
10
|
+
cancelTurn(input: HarnessAdapterCancelInput): Promise<HarnessAdapterEvent[]>;
|
|
11
|
+
stopSession(): Promise<HarnessAdapterEvent[]>;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=mock.d.ts.map
|