@glubean/port 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/README.md +203 -0
- package/dist/async-queue.d.ts +7 -0
- package/dist/async-queue.d.ts.map +1 -0
- package/dist/async-queue.js +37 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/json-rpc.d.ts +31 -0
- package/dist/json-rpc.d.ts.map +1 -0
- package/dist/json-rpc.js +122 -0
- package/dist/lifecycle.d.ts +4 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +517 -0
- package/dist/options.d.ts +12 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +32 -0
- package/dist/port.d.ts +4 -0
- package/dist/port.d.ts.map +1 -0
- package/dist/port.js +4 -0
- package/dist/provider-options.typecheck.d.ts +2 -0
- package/dist/provider-options.typecheck.d.ts.map +1 -0
- package/dist/provider-options.typecheck.js +29 -0
- package/dist/providers/adapter.d.ts +14 -0
- package/dist/providers/adapter.d.ts.map +1 -0
- package/dist/providers/adapter.js +1 -0
- package/dist/providers/claude.d.ts +9 -0
- package/dist/providers/claude.d.ts.map +1 -0
- package/dist/providers/claude.js +300 -0
- package/dist/providers/codex-normalizer.d.ts +7 -0
- package/dist/providers/codex-normalizer.d.ts.map +1 -0
- package/dist/providers/codex-normalizer.js +287 -0
- package/dist/providers/codex.d.ts +19 -0
- package/dist/providers/codex.d.ts.map +1 -0
- package/dist/providers/codex.js +300 -0
- package/dist/providers/common.d.ts +6 -0
- package/dist/providers/common.d.ts.map +1 -0
- package/dist/providers/common.js +67 -0
- package/dist/providers/gemini.d.ts +9 -0
- package/dist/providers/gemini.d.ts.map +1 -0
- package/dist/providers/gemini.js +378 -0
- package/dist/structured.d.ts +8 -0
- package/dist/structured.d.ts.map +1 -0
- package/dist/structured.js +127 -0
- package/dist/token-usage.d.ts +4 -0
- package/dist/token-usage.d.ts.map +1 -0
- package/dist/token-usage.js +59 -0
- package/dist/types.d.ts +407 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +35 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { EventEmitter } from "node:events";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import { createPortFromAdapter } from "../port.js";
|
|
6
|
+
import { providerRuntimeOptions, resolveRuntimeOptions } from "../options.js";
|
|
7
|
+
import { normalizeTokenUsage } from "../token-usage.js";
|
|
8
|
+
import { completedTurn, inputToPrompt, syntheticSession } from "./common.js";
|
|
9
|
+
export const claudeCapabilities = {
|
|
10
|
+
provider: "claude",
|
|
11
|
+
nativeRpc: false,
|
|
12
|
+
transport: "stdio",
|
|
13
|
+
sessions: {
|
|
14
|
+
create: true,
|
|
15
|
+
resume: true,
|
|
16
|
+
attach: true,
|
|
17
|
+
status: true,
|
|
18
|
+
list: true,
|
|
19
|
+
close: true,
|
|
20
|
+
},
|
|
21
|
+
turns: {
|
|
22
|
+
start: true,
|
|
23
|
+
submit: true,
|
|
24
|
+
status: true,
|
|
25
|
+
wait: true,
|
|
26
|
+
cancel: true,
|
|
27
|
+
steer: false,
|
|
28
|
+
outputSchema: true,
|
|
29
|
+
},
|
|
30
|
+
events: {
|
|
31
|
+
messageDelta: true,
|
|
32
|
+
reasoningDelta: true,
|
|
33
|
+
toolLifecycle: true,
|
|
34
|
+
tokenUsage: true,
|
|
35
|
+
planUpdates: false,
|
|
36
|
+
diffUpdates: false,
|
|
37
|
+
},
|
|
38
|
+
structuredOutput: {
|
|
39
|
+
jsonMode: true,
|
|
40
|
+
nativeJsonSchema: true,
|
|
41
|
+
localValidation: true,
|
|
42
|
+
retry: true,
|
|
43
|
+
},
|
|
44
|
+
lifecycle: {
|
|
45
|
+
localState: true,
|
|
46
|
+
eventReplay: "memory",
|
|
47
|
+
status: "local",
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
export async function createClaudePort(options) {
|
|
51
|
+
return createPortFromAdapter(await createClaudeAdapter(options));
|
|
52
|
+
}
|
|
53
|
+
export async function createClaudeAdapter(options) {
|
|
54
|
+
const runtime = new ClaudeRuntime(options);
|
|
55
|
+
return runtime.adapter;
|
|
56
|
+
}
|
|
57
|
+
class ClaudeRuntime {
|
|
58
|
+
#options;
|
|
59
|
+
#events = new EventEmitter();
|
|
60
|
+
#processes = new Map();
|
|
61
|
+
constructor(options) {
|
|
62
|
+
this.#options = options;
|
|
63
|
+
}
|
|
64
|
+
get adapter() {
|
|
65
|
+
return {
|
|
66
|
+
id: "claude",
|
|
67
|
+
capabilities: claudeCapabilities,
|
|
68
|
+
createSession: (input = {}) => this.#createSession(input),
|
|
69
|
+
resumeSession: (input) => this.#resumeSession(input),
|
|
70
|
+
startTurn: (input) => this.#startTurn(input),
|
|
71
|
+
cancelTurn: (input) => this.#cancelTurn(input),
|
|
72
|
+
events: () => this.#allEvents(),
|
|
73
|
+
close: () => this.close(),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
async #createSession(input) {
|
|
77
|
+
return syntheticSession("claude", input.id ?? randomUUID());
|
|
78
|
+
}
|
|
79
|
+
async #resumeSession(input) {
|
|
80
|
+
return syntheticSession("claude", input.id);
|
|
81
|
+
}
|
|
82
|
+
async *#startTurn(input) {
|
|
83
|
+
const turnId = randomUUID();
|
|
84
|
+
yield { type: "turn.started", sessionId: input.sessionId, turnId };
|
|
85
|
+
const command = this.#options.command ?? "claude";
|
|
86
|
+
const runtimeOptions = resolveRuntimeOptions(input, this.#options.options);
|
|
87
|
+
const args = buildClaudeArgs(input, this.#options.options);
|
|
88
|
+
const child = spawn(command, args, {
|
|
89
|
+
cwd: runtimeOptions.cwd ?? this.#options.cwd ?? this.#options.options?.cwd ?? process.cwd(),
|
|
90
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
91
|
+
env: { ...process.env, ...this.#options.env, NO_COLOR: "1" },
|
|
92
|
+
shell: false,
|
|
93
|
+
});
|
|
94
|
+
this.#processes.set(turnId, child);
|
|
95
|
+
child.stdin.end(inputToPrompt(input.input));
|
|
96
|
+
let buffered = "";
|
|
97
|
+
let sawMessage = false;
|
|
98
|
+
const stderr = [];
|
|
99
|
+
const events = [];
|
|
100
|
+
const push = (event) => {
|
|
101
|
+
events.push(event);
|
|
102
|
+
this.#events.emit("event", event);
|
|
103
|
+
};
|
|
104
|
+
child.stdout.setEncoding("utf8");
|
|
105
|
+
child.stdout.on("data", (chunk) => {
|
|
106
|
+
buffered += chunk;
|
|
107
|
+
while (true) {
|
|
108
|
+
const newlineIndex = buffered.indexOf("\n");
|
|
109
|
+
if (newlineIndex === -1)
|
|
110
|
+
break;
|
|
111
|
+
const line = buffered.slice(0, newlineIndex).trim();
|
|
112
|
+
buffered = buffered.slice(newlineIndex + 1);
|
|
113
|
+
if (!line)
|
|
114
|
+
continue;
|
|
115
|
+
for (const event of normalizeClaudeLine(line, input.sessionId, turnId, sawMessage)) {
|
|
116
|
+
if (event.type === "message.delta")
|
|
117
|
+
sawMessage = true;
|
|
118
|
+
push(event);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
child.stderr.setEncoding("utf8");
|
|
123
|
+
child.stderr.on("data", (chunk) => stderr.push(chunk));
|
|
124
|
+
const exit = await waitForExit(child);
|
|
125
|
+
this.#processes.delete(turnId);
|
|
126
|
+
const trailing = buffered.trim();
|
|
127
|
+
if (trailing) {
|
|
128
|
+
for (const event of normalizeClaudeLine(trailing, input.sessionId, turnId, sawMessage)) {
|
|
129
|
+
if (event.type === "message.delta")
|
|
130
|
+
sawMessage = true;
|
|
131
|
+
push(event);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
for (const event of events)
|
|
135
|
+
yield event;
|
|
136
|
+
if (exit.code === 0) {
|
|
137
|
+
yield completedTurn(input.sessionId, turnId, "completed");
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const message = stderr.join("").trim() || `Claude Code exited with code ${exit.code ?? "null"} signal=${exit.signal ?? "null"}`;
|
|
141
|
+
yield { type: "error", sessionId: input.sessionId, turnId, message };
|
|
142
|
+
yield completedTurn(input.sessionId, turnId, "failed", message);
|
|
143
|
+
}
|
|
144
|
+
async #cancelTurn(input) {
|
|
145
|
+
this.#processes.get(input.turnId)?.kill();
|
|
146
|
+
}
|
|
147
|
+
async close() {
|
|
148
|
+
for (const child of this.#processes.values())
|
|
149
|
+
child.kill();
|
|
150
|
+
this.#processes.clear();
|
|
151
|
+
}
|
|
152
|
+
#allEvents() {
|
|
153
|
+
const events = this.#events;
|
|
154
|
+
return {
|
|
155
|
+
[Symbol.asyncIterator]() {
|
|
156
|
+
const queue = [];
|
|
157
|
+
let waiter;
|
|
158
|
+
const listener = (event) => {
|
|
159
|
+
if (waiter) {
|
|
160
|
+
waiter({ value: event, done: false });
|
|
161
|
+
waiter = undefined;
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
queue.push(event);
|
|
165
|
+
};
|
|
166
|
+
events.on("event", listener);
|
|
167
|
+
return {
|
|
168
|
+
next: () => {
|
|
169
|
+
const event = queue.shift();
|
|
170
|
+
if (event)
|
|
171
|
+
return Promise.resolve({ value: event, done: false });
|
|
172
|
+
return new Promise((resolve) => {
|
|
173
|
+
waiter = resolve;
|
|
174
|
+
});
|
|
175
|
+
},
|
|
176
|
+
return: async () => {
|
|
177
|
+
events.off("event", listener);
|
|
178
|
+
return { value: undefined, done: true };
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
export function buildClaudeArgs(input, defaults) {
|
|
186
|
+
const runtimeOptions = resolveRuntimeOptions(input, defaults);
|
|
187
|
+
const claudeOptions = providerRuntimeOptions(runtimeOptions);
|
|
188
|
+
const args = ["-p", "--verbose", "--output-format", "stream-json", "--include-partial-messages", "--session-id", input.sessionId];
|
|
189
|
+
if (runtimeOptions.model)
|
|
190
|
+
args.push("--model", runtimeOptions.model);
|
|
191
|
+
if (input.outputSchema)
|
|
192
|
+
args.push("--json-schema", JSON.stringify(input.outputSchema));
|
|
193
|
+
const systemPrompt = claudeOptions.systemPrompt ?? runtimeOptions.instructions?.system;
|
|
194
|
+
const appendSystemPrompt = claudeOptions.appendSystemPrompt ?? runtimeOptions.instructions?.append;
|
|
195
|
+
if (systemPrompt)
|
|
196
|
+
args.push("--system-prompt", systemPrompt);
|
|
197
|
+
if (appendSystemPrompt)
|
|
198
|
+
args.push("--append-system-prompt", appendSystemPrompt);
|
|
199
|
+
if (claudeOptions.effort)
|
|
200
|
+
args.push("--effort", claudeOptions.effort);
|
|
201
|
+
appendClaudeThinkingArgs(args, claudeOptions.thinking);
|
|
202
|
+
const permissionMode = claudeOptions.permissionMode ?? toClaudePermissionMode(runtimeOptions);
|
|
203
|
+
if (permissionMode)
|
|
204
|
+
args.push("--permission-mode", permissionMode);
|
|
205
|
+
return args;
|
|
206
|
+
}
|
|
207
|
+
function appendClaudeThinkingArgs(args, thinking) {
|
|
208
|
+
if (!thinking)
|
|
209
|
+
return;
|
|
210
|
+
if (thinking.type === "disabled") {
|
|
211
|
+
args.push("--thinking", "disabled");
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
args.push("--thinking", thinking.type);
|
|
215
|
+
if (thinking.type === "enabled" && typeof thinking.budgetTokens === "number") {
|
|
216
|
+
args.push("--max-thinking-tokens", String(thinking.budgetTokens));
|
|
217
|
+
}
|
|
218
|
+
if (thinking.display)
|
|
219
|
+
args.push("--thinking-display", thinking.display);
|
|
220
|
+
}
|
|
221
|
+
function toClaudePermissionMode(options) {
|
|
222
|
+
if (options.sandbox === "read-only")
|
|
223
|
+
return "plan";
|
|
224
|
+
if (options.approvalPolicy === "never")
|
|
225
|
+
return "dontAsk";
|
|
226
|
+
return undefined;
|
|
227
|
+
}
|
|
228
|
+
function normalizeClaudeLine(line, sessionId, turnId, sawMessage) {
|
|
229
|
+
let value;
|
|
230
|
+
try {
|
|
231
|
+
value = JSON.parse(line);
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
return [{ type: "message.delta", sessionId, turnId, role: "assistant", text: line }];
|
|
235
|
+
}
|
|
236
|
+
const record = asRecord(value);
|
|
237
|
+
const type = asString(record.type);
|
|
238
|
+
if (type === "assistant") {
|
|
239
|
+
const message = asRecord(record.message);
|
|
240
|
+
return [
|
|
241
|
+
...textFromClaudeMessage(message).map((text) => ({ type: "message.delta", sessionId, turnId, role: "assistant", text })),
|
|
242
|
+
...tokenUsageEvents(message.usage, sessionId, turnId),
|
|
243
|
+
];
|
|
244
|
+
}
|
|
245
|
+
if (type === "result") {
|
|
246
|
+
const usageEvents = tokenUsageEvents(record.usage ?? record.usageMetadata, sessionId, turnId);
|
|
247
|
+
if (record.is_error === true) {
|
|
248
|
+
return [
|
|
249
|
+
...usageEvents,
|
|
250
|
+
{ type: "error", sessionId, turnId, message: asString(record.result) ?? "Claude Code result error", native: toJsonValue(record) },
|
|
251
|
+
];
|
|
252
|
+
}
|
|
253
|
+
const structured = record.structured_output ?? record.structuredOutput;
|
|
254
|
+
if (structured !== undefined && !sawMessage) {
|
|
255
|
+
return [
|
|
256
|
+
{ type: "message.delta", sessionId, turnId, role: "assistant", text: JSON.stringify(structured) },
|
|
257
|
+
...usageEvents,
|
|
258
|
+
];
|
|
259
|
+
}
|
|
260
|
+
const result = asString(record.result);
|
|
261
|
+
return [
|
|
262
|
+
...(result && !sawMessage ? [{ type: "message.delta", sessionId, turnId, role: "assistant", text: result }] : []),
|
|
263
|
+
...usageEvents,
|
|
264
|
+
];
|
|
265
|
+
}
|
|
266
|
+
if (type === "system" || type === "user")
|
|
267
|
+
return [];
|
|
268
|
+
return [];
|
|
269
|
+
}
|
|
270
|
+
function tokenUsageEvents(value, sessionId, turnId) {
|
|
271
|
+
const usage = normalizeTokenUsage(value);
|
|
272
|
+
return usage ? [{ type: "token.usage", sessionId, turnId, usage, native: toJsonValue(value) }] : [];
|
|
273
|
+
}
|
|
274
|
+
function textFromClaudeMessage(value) {
|
|
275
|
+
const message = asRecord(value);
|
|
276
|
+
const content = message.content;
|
|
277
|
+
if (!Array.isArray(content))
|
|
278
|
+
return [];
|
|
279
|
+
return content.flatMap((item) => {
|
|
280
|
+
const record = asRecord(item);
|
|
281
|
+
if (asString(record.type) !== "text")
|
|
282
|
+
return [];
|
|
283
|
+
const text = asString(record.text);
|
|
284
|
+
return text ? [text] : [];
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
function waitForExit(child) {
|
|
288
|
+
return new Promise((resolve) => child.on("exit", (code, signal) => resolve({ code, signal })));
|
|
289
|
+
}
|
|
290
|
+
function asRecord(value) {
|
|
291
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
292
|
+
}
|
|
293
|
+
function asString(value) {
|
|
294
|
+
return typeof value === "string" ? value : undefined;
|
|
295
|
+
}
|
|
296
|
+
function toJsonValue(value) {
|
|
297
|
+
if (value === undefined)
|
|
298
|
+
return undefined;
|
|
299
|
+
return JSON.parse(JSON.stringify(value));
|
|
300
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AgentEvent } from "../types.ts";
|
|
2
|
+
import type { JsonRpcNotification } from "../json-rpc.ts";
|
|
3
|
+
export declare class CodexEventNormalizer {
|
|
4
|
+
#private;
|
|
5
|
+
normalize(notification: JsonRpcNotification): AgentEvent[];
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=codex-normalizer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex-normalizer.d.ts","sourceRoot":"","sources":["../../src/providers/codex-normalizer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAuB,MAAM,aAAa,CAAC;AACnE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAuB1D,qBAAa,oBAAoB;;IAK/B,SAAS,CAAC,YAAY,EAAE,mBAAmB,GAAG,UAAU,EAAE;CAyO3D"}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { normalizeTokenUsage } from "../token-usage.js";
|
|
2
|
+
export class CodexEventNormalizer {
|
|
3
|
+
#provider = "codex";
|
|
4
|
+
#streamedMessageIds = new Set();
|
|
5
|
+
#streamedReasoningIds = new Set();
|
|
6
|
+
normalize(notification) {
|
|
7
|
+
const params = asRecord(notification.params);
|
|
8
|
+
switch (notification.method) {
|
|
9
|
+
case "thread/started": {
|
|
10
|
+
const thread = asRecord(params.thread);
|
|
11
|
+
const sessionId = asString(thread.id);
|
|
12
|
+
if (!sessionId)
|
|
13
|
+
return [];
|
|
14
|
+
return [
|
|
15
|
+
{
|
|
16
|
+
type: "session.started",
|
|
17
|
+
provider: this.#provider,
|
|
18
|
+
sessionId,
|
|
19
|
+
native: { threadId: sessionId },
|
|
20
|
+
},
|
|
21
|
+
];
|
|
22
|
+
}
|
|
23
|
+
case "turn/started": {
|
|
24
|
+
const turn = asRecord(params.turn);
|
|
25
|
+
const turnId = asString(turn.id);
|
|
26
|
+
const sessionId = asString(params.threadId);
|
|
27
|
+
if (!sessionId || !turnId)
|
|
28
|
+
return [];
|
|
29
|
+
return [{ type: "turn.started", sessionId, turnId }];
|
|
30
|
+
}
|
|
31
|
+
case "item/agentMessage/delta": {
|
|
32
|
+
const itemId = asString(params.itemId);
|
|
33
|
+
const delta = asString(params.delta);
|
|
34
|
+
if (itemId)
|
|
35
|
+
this.#streamedMessageIds.add(itemId);
|
|
36
|
+
if (!delta)
|
|
37
|
+
return [];
|
|
38
|
+
return [
|
|
39
|
+
{
|
|
40
|
+
type: "message.delta",
|
|
41
|
+
role: "assistant",
|
|
42
|
+
sessionId: asString(params.threadId),
|
|
43
|
+
turnId: asString(params.turnId),
|
|
44
|
+
text: delta,
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
case "item/reasoning/summaryTextDelta":
|
|
49
|
+
case "item/reasoning/textDelta": {
|
|
50
|
+
const itemId = asString(params.itemId);
|
|
51
|
+
const delta = asString(params.delta);
|
|
52
|
+
if (itemId)
|
|
53
|
+
this.#streamedReasoningIds.add(itemId);
|
|
54
|
+
if (!delta)
|
|
55
|
+
return [];
|
|
56
|
+
return [
|
|
57
|
+
{
|
|
58
|
+
type: "reasoning.delta",
|
|
59
|
+
sessionId: asString(params.threadId),
|
|
60
|
+
turnId: asString(params.turnId),
|
|
61
|
+
text: delta,
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
}
|
|
65
|
+
case "turn/plan/updated": {
|
|
66
|
+
const sessionId = asString(params.threadId);
|
|
67
|
+
const turnId = asString(params.turnId);
|
|
68
|
+
if (!sessionId || !turnId)
|
|
69
|
+
return [];
|
|
70
|
+
return [
|
|
71
|
+
{
|
|
72
|
+
type: "plan.updated",
|
|
73
|
+
sessionId,
|
|
74
|
+
turnId,
|
|
75
|
+
explanation: asString(params.explanation),
|
|
76
|
+
steps: normalizePlan(params.plan),
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
}
|
|
80
|
+
case "turn/diff/updated": {
|
|
81
|
+
const sessionId = asString(params.threadId);
|
|
82
|
+
const turnId = asString(params.turnId);
|
|
83
|
+
const diff = asString(params.diff);
|
|
84
|
+
if (!sessionId || !turnId || diff === undefined)
|
|
85
|
+
return [];
|
|
86
|
+
return [{ type: "diff.updated", sessionId, turnId, diff }];
|
|
87
|
+
}
|
|
88
|
+
case "thread/tokenUsage/updated": {
|
|
89
|
+
const sessionId = asString(params.threadId);
|
|
90
|
+
const turnId = asString(params.turnId);
|
|
91
|
+
const tokenUsage = asRecord(params.tokenUsage);
|
|
92
|
+
const usage = normalizeTokenUsage(tokenUsage.last);
|
|
93
|
+
const totalUsage = normalizeTokenUsage(tokenUsage.total);
|
|
94
|
+
if (!sessionId || !turnId || (!usage && !totalUsage))
|
|
95
|
+
return [];
|
|
96
|
+
const contextWindow = asNullableNumber(tokenUsage.modelContextWindow);
|
|
97
|
+
return [
|
|
98
|
+
{
|
|
99
|
+
type: "token.usage",
|
|
100
|
+
sessionId,
|
|
101
|
+
turnId,
|
|
102
|
+
usage: usage ?? totalUsage ?? {},
|
|
103
|
+
...(totalUsage ? { totalUsage } : {}),
|
|
104
|
+
...(contextWindow !== undefined ? { contextWindow } : {}),
|
|
105
|
+
native: toJsonValue(params),
|
|
106
|
+
},
|
|
107
|
+
];
|
|
108
|
+
}
|
|
109
|
+
case "item/started":
|
|
110
|
+
case "item/completed": {
|
|
111
|
+
const item = asRecord(params.item);
|
|
112
|
+
return this.#normalizeItem(notification.method, params, item);
|
|
113
|
+
}
|
|
114
|
+
case "turn/completed": {
|
|
115
|
+
const sessionId = asString(params.threadId);
|
|
116
|
+
const turn = asRecord(params.turn);
|
|
117
|
+
const turnId = asString(turn.id);
|
|
118
|
+
if (!sessionId || !turnId)
|
|
119
|
+
return [];
|
|
120
|
+
this.#streamedMessageIds.clear();
|
|
121
|
+
this.#streamedReasoningIds.clear();
|
|
122
|
+
const status = normalizeTurnStatus(asString(turn.status));
|
|
123
|
+
const error = asString(asRecord(turn.error).message);
|
|
124
|
+
return [
|
|
125
|
+
{
|
|
126
|
+
type: "turn.completed",
|
|
127
|
+
sessionId,
|
|
128
|
+
turnId,
|
|
129
|
+
status,
|
|
130
|
+
...(error ? { error } : {}),
|
|
131
|
+
},
|
|
132
|
+
];
|
|
133
|
+
}
|
|
134
|
+
case "error":
|
|
135
|
+
return [
|
|
136
|
+
{
|
|
137
|
+
type: "error",
|
|
138
|
+
message: readErrorMessage(params),
|
|
139
|
+
native: toJsonValue(params),
|
|
140
|
+
},
|
|
141
|
+
];
|
|
142
|
+
default:
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
#normalizeItem(method, params, item) {
|
|
147
|
+
const isStarted = method === "item/started";
|
|
148
|
+
const isCompleted = method === "item/completed";
|
|
149
|
+
const itemType = asString(item.type);
|
|
150
|
+
const itemId = asString(item.id);
|
|
151
|
+
const sessionId = asString(params.threadId);
|
|
152
|
+
const turnId = asString(params.turnId);
|
|
153
|
+
if (!itemType || !itemId)
|
|
154
|
+
return [];
|
|
155
|
+
if (itemType === "agentMessage" && isCompleted) {
|
|
156
|
+
if (this.#streamedMessageIds.has(itemId)) {
|
|
157
|
+
this.#streamedMessageIds.delete(itemId);
|
|
158
|
+
return [];
|
|
159
|
+
}
|
|
160
|
+
const text = asString(item.text);
|
|
161
|
+
return text ? [{ type: "message.delta", role: "assistant", sessionId, turnId, text }] : [];
|
|
162
|
+
}
|
|
163
|
+
if (itemType === "reasoning" && isCompleted) {
|
|
164
|
+
if (this.#streamedReasoningIds.has(itemId)) {
|
|
165
|
+
this.#streamedReasoningIds.delete(itemId);
|
|
166
|
+
return [];
|
|
167
|
+
}
|
|
168
|
+
const text = [...asStringArray(item.summary), ...asStringArray(item.content)].join("\n").trim();
|
|
169
|
+
return text ? [{ type: "reasoning.delta", sessionId, turnId, text }] : [];
|
|
170
|
+
}
|
|
171
|
+
if (itemType === "commandExecution") {
|
|
172
|
+
if (isStarted) {
|
|
173
|
+
const command = asString(item.command);
|
|
174
|
+
return command
|
|
175
|
+
? [{ type: "tool.started", sessionId, turnId, toolCall: { id: itemId, name: "shell", input: { command } } }]
|
|
176
|
+
: [];
|
|
177
|
+
}
|
|
178
|
+
if (isCompleted) {
|
|
179
|
+
return [{ type: "tool.completed", sessionId, turnId, toolCallId: itemId, name: "shell" }];
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (itemType === "fileChange") {
|
|
183
|
+
if (isStarted) {
|
|
184
|
+
const changes = toJsonValue(item.changes);
|
|
185
|
+
return [
|
|
186
|
+
{
|
|
187
|
+
type: "tool.started",
|
|
188
|
+
sessionId,
|
|
189
|
+
turnId,
|
|
190
|
+
toolCall: {
|
|
191
|
+
id: itemId,
|
|
192
|
+
name: "file_change",
|
|
193
|
+
...(changes === undefined ? {} : { input: { changes } }),
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
];
|
|
197
|
+
}
|
|
198
|
+
if (isCompleted) {
|
|
199
|
+
return [{ type: "tool.completed", sessionId, turnId, toolCallId: itemId, name: "file_change" }];
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (itemType === "mcpToolCall") {
|
|
203
|
+
const server = asString(item.server);
|
|
204
|
+
const tool = asString(item.tool);
|
|
205
|
+
const name = server && tool ? `mcp.${server}.${tool}` : "mcp";
|
|
206
|
+
if (isStarted) {
|
|
207
|
+
return [
|
|
208
|
+
{
|
|
209
|
+
type: "tool.started",
|
|
210
|
+
sessionId,
|
|
211
|
+
turnId,
|
|
212
|
+
toolCall: { id: itemId, name, input: toJsonValue(item.arguments) },
|
|
213
|
+
},
|
|
214
|
+
];
|
|
215
|
+
}
|
|
216
|
+
if (isCompleted) {
|
|
217
|
+
return [
|
|
218
|
+
{
|
|
219
|
+
type: "tool.completed",
|
|
220
|
+
sessionId,
|
|
221
|
+
turnId,
|
|
222
|
+
toolCallId: itemId,
|
|
223
|
+
name,
|
|
224
|
+
result: toJsonValue(item.result),
|
|
225
|
+
},
|
|
226
|
+
];
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (itemType === "webSearch" && isStarted) {
|
|
230
|
+
return [
|
|
231
|
+
{
|
|
232
|
+
type: "tool.started",
|
|
233
|
+
sessionId,
|
|
234
|
+
turnId,
|
|
235
|
+
toolCall: { id: itemId, name: "web_search" },
|
|
236
|
+
},
|
|
237
|
+
];
|
|
238
|
+
}
|
|
239
|
+
return [];
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
function normalizePlan(value) {
|
|
243
|
+
if (!Array.isArray(value))
|
|
244
|
+
return [];
|
|
245
|
+
return value.flatMap((entry) => {
|
|
246
|
+
const record = asRecord(entry);
|
|
247
|
+
const step = asString(record.step) ?? asString(record.text) ?? asString(record.description);
|
|
248
|
+
const status = normalizePlanStatus(asString(record.status));
|
|
249
|
+
return step ? [{ step, status }] : [];
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
function normalizePlanStatus(value) {
|
|
253
|
+
if (value === "in_progress" || value === "completed")
|
|
254
|
+
return value;
|
|
255
|
+
return "pending";
|
|
256
|
+
}
|
|
257
|
+
function normalizeTurnStatus(value) {
|
|
258
|
+
if (value === "failed")
|
|
259
|
+
return "failed";
|
|
260
|
+
if (value === "cancelled" || value === "canceled")
|
|
261
|
+
return "cancelled";
|
|
262
|
+
return "completed";
|
|
263
|
+
}
|
|
264
|
+
function readErrorMessage(params) {
|
|
265
|
+
return (asString(params.message) ??
|
|
266
|
+
asString(asRecord(params.error).message) ??
|
|
267
|
+
"Codex app-server error");
|
|
268
|
+
}
|
|
269
|
+
function asRecord(value) {
|
|
270
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
271
|
+
}
|
|
272
|
+
function asString(value) {
|
|
273
|
+
return typeof value === "string" ? value : undefined;
|
|
274
|
+
}
|
|
275
|
+
function asNullableNumber(value) {
|
|
276
|
+
if (value === null)
|
|
277
|
+
return null;
|
|
278
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
279
|
+
}
|
|
280
|
+
function asStringArray(value) {
|
|
281
|
+
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
282
|
+
}
|
|
283
|
+
function toJsonValue(value) {
|
|
284
|
+
if (value === undefined)
|
|
285
|
+
return undefined;
|
|
286
|
+
return JSON.parse(JSON.stringify(value));
|
|
287
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ApprovalPolicy, CapabilitySet, CreatePortInputFor, JsonValue, Port, SandboxMode } from "../types.ts";
|
|
2
|
+
import type { ProviderAdapter } from "./adapter.ts";
|
|
3
|
+
type CodexPortOptions = CreatePortInputFor<"codex">;
|
|
4
|
+
export declare const codexCapabilities: CapabilitySet<"codex">;
|
|
5
|
+
export declare function createCodexPort(options: CodexPortOptions): Promise<Port<"codex">>;
|
|
6
|
+
export declare function createCodexAdapter(options: CodexPortOptions): Promise<ProviderAdapter<"codex">>;
|
|
7
|
+
export declare function createCodexThreadParams(input: {
|
|
8
|
+
cwd: string;
|
|
9
|
+
model?: string;
|
|
10
|
+
modelProvider?: string;
|
|
11
|
+
approvalPolicy?: ApprovalPolicy;
|
|
12
|
+
sandbox?: SandboxMode;
|
|
13
|
+
baseInstructions?: string;
|
|
14
|
+
developerInstructions?: string;
|
|
15
|
+
serviceTier?: string;
|
|
16
|
+
config?: Record<string, JsonValue>;
|
|
17
|
+
}): JsonValue;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=codex.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex.d.ts","sourceRoot":"","sources":["../../src/providers/codex.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAGV,cAAc,EAEd,aAAa,EAEb,kBAAkB,EAElB,SAAS,EACT,IAAI,EACJ,WAAW,EAGZ,MAAM,aAAa,CAAC;AAErB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAGpD,KAAK,gBAAgB,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;AAEpD,eAAO,MAAM,iBAAiB,EAAE,aAAa,CAAC,OAAO,CAwCpD,CAAC;AAEF,wBAAsB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAEvF;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAIrG;AAiPD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE;IAC7C,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;CACpC,GAAG,SAAS,CAYZ"}
|