@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.
Files changed (50) hide show
  1. package/README.md +203 -0
  2. package/dist/async-queue.d.ts +7 -0
  3. package/dist/async-queue.d.ts.map +1 -0
  4. package/dist/async-queue.js +37 -0
  5. package/dist/index.d.ts +14 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +22 -0
  8. package/dist/json-rpc.d.ts +31 -0
  9. package/dist/json-rpc.d.ts.map +1 -0
  10. package/dist/json-rpc.js +122 -0
  11. package/dist/lifecycle.d.ts +4 -0
  12. package/dist/lifecycle.d.ts.map +1 -0
  13. package/dist/lifecycle.js +517 -0
  14. package/dist/options.d.ts +12 -0
  15. package/dist/options.d.ts.map +1 -0
  16. package/dist/options.js +32 -0
  17. package/dist/port.d.ts +4 -0
  18. package/dist/port.d.ts.map +1 -0
  19. package/dist/port.js +4 -0
  20. package/dist/provider-options.typecheck.d.ts +2 -0
  21. package/dist/provider-options.typecheck.d.ts.map +1 -0
  22. package/dist/provider-options.typecheck.js +29 -0
  23. package/dist/providers/adapter.d.ts +14 -0
  24. package/dist/providers/adapter.d.ts.map +1 -0
  25. package/dist/providers/adapter.js +1 -0
  26. package/dist/providers/claude.d.ts +9 -0
  27. package/dist/providers/claude.d.ts.map +1 -0
  28. package/dist/providers/claude.js +300 -0
  29. package/dist/providers/codex-normalizer.d.ts +7 -0
  30. package/dist/providers/codex-normalizer.d.ts.map +1 -0
  31. package/dist/providers/codex-normalizer.js +287 -0
  32. package/dist/providers/codex.d.ts +19 -0
  33. package/dist/providers/codex.d.ts.map +1 -0
  34. package/dist/providers/codex.js +300 -0
  35. package/dist/providers/common.d.ts +6 -0
  36. package/dist/providers/common.d.ts.map +1 -0
  37. package/dist/providers/common.js +67 -0
  38. package/dist/providers/gemini.d.ts +9 -0
  39. package/dist/providers/gemini.d.ts.map +1 -0
  40. package/dist/providers/gemini.js +378 -0
  41. package/dist/structured.d.ts +8 -0
  42. package/dist/structured.d.ts.map +1 -0
  43. package/dist/structured.js +127 -0
  44. package/dist/token-usage.d.ts +4 -0
  45. package/dist/token-usage.d.ts.map +1 -0
  46. package/dist/token-usage.js +59 -0
  47. package/dist/types.d.ts +407 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/dist/types.js +1 -0
  50. package/package.json +35 -0
@@ -0,0 +1,300 @@
1
+ import { spawn } from "node:child_process";
2
+ import { EventEmitter } from "node:events";
3
+ import process from "node:process";
4
+ import { AsyncQueue } from "../async-queue.js";
5
+ import { JsonRpcLineConnection } from "../json-rpc.js";
6
+ import { providerRuntimeOptions, resolveRuntimeOptions } from "../options.js";
7
+ import { createPortFromAdapter } from "../port.js";
8
+ import { CodexEventNormalizer } from "./codex-normalizer.js";
9
+ export const codexCapabilities = {
10
+ provider: "codex",
11
+ nativeRpc: true,
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: true,
36
+ diffUpdates: true,
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 createCodexPort(options) {
51
+ return createPortFromAdapter(await createCodexAdapter(options));
52
+ }
53
+ export async function createCodexAdapter(options) {
54
+ const runtime = new CodexRuntime(options);
55
+ await runtime.start();
56
+ return runtime.adapter;
57
+ }
58
+ class CodexRuntime {
59
+ #options;
60
+ #process;
61
+ #rpc;
62
+ #normalizer = new CodexEventNormalizer();
63
+ #events = new EventEmitter();
64
+ #closed = false;
65
+ constructor(options) {
66
+ this.#options = options;
67
+ }
68
+ get adapter() {
69
+ return {
70
+ id: "codex",
71
+ capabilities: codexCapabilities,
72
+ createSession: (input = {}) => this.#createOrResumeSession("thread/start", input),
73
+ resumeSession: (input) => this.#createOrResumeSession("thread/resume", input),
74
+ startTurn: (input) => this.#startTurn(input),
75
+ cancelTurn: (input) => this.#cancelTurn(input),
76
+ events: () => this.#allEvents(),
77
+ close: () => this.close(),
78
+ };
79
+ }
80
+ async start() {
81
+ if (this.#rpc)
82
+ return;
83
+ const command = this.#options.command ?? "codex";
84
+ const args = this.#options.args ?? ["app-server", "--listen", "stdio://"];
85
+ // Top-level codex flags (--disable / -c) must come before the subcommand,
86
+ // so prepend disableFeatures + webSearchMode as args even when the caller
87
+ // supplied a custom args array (they apply to whatever subcommand follows).
88
+ // Source of truth: spawnOptions (codex.ts adapter is per-process; spawn-
89
+ // only fields cannot be honored per-session or per-turn — see
90
+ // CodexSpawnOptions docstring in types.ts).
91
+ const spawnOpts = this.#options.spawnOptions;
92
+ const featureArgs = [];
93
+ for (const feature of spawnOpts?.disableFeatures ?? []) {
94
+ featureArgs.push("--disable", feature);
95
+ }
96
+ if (spawnOpts?.webSearchMode) {
97
+ featureArgs.push("-c", `web_search_mode=${spawnOpts.webSearchMode}`);
98
+ }
99
+ const finalArgs = featureArgs.length > 0 ? [...featureArgs, ...args] : args;
100
+ this.#process = spawn(command, finalArgs, {
101
+ cwd: this.#options.cwd ?? this.#options.options?.cwd ?? process.cwd(),
102
+ stdio: ["pipe", "pipe", "pipe"],
103
+ env: { ...process.env, ...this.#options.env, NO_COLOR: "1" },
104
+ shell: false,
105
+ });
106
+ this.#process.stderr.on("data", (chunk) => {
107
+ const message = chunk.toString("utf8").trim();
108
+ if (message)
109
+ this.#emit({ type: "error", message });
110
+ });
111
+ this.#process.on("exit", (code, signal) => {
112
+ if (!this.#closed) {
113
+ this.#emit({ type: "error", message: `Codex app-server exited: code=${code ?? "null"} signal=${signal ?? "null"}` });
114
+ }
115
+ });
116
+ this.#rpc = new JsonRpcLineConnection(this.#process.stdout, this.#process.stdin);
117
+ this.#rpc.onNotification((notification) => {
118
+ for (const event of this.#normalizer.normalize(notification)) {
119
+ this.#emit(event);
120
+ }
121
+ });
122
+ await this.#rpc.request("initialize", {
123
+ clientInfo: { name: "glubean-port", version: "0.0.0" },
124
+ capabilities: { experimentalApi: true },
125
+ });
126
+ this.#rpc.notify("initialized", {});
127
+ }
128
+ async close() {
129
+ this.#closed = true;
130
+ this.#rpc?.close();
131
+ this.#process?.kill();
132
+ }
133
+ async #createOrResumeSession(method, input) {
134
+ await this.start();
135
+ const rpc = this.#requireRpc();
136
+ const runtimeOptions = resolveRuntimeOptions(input, this.#options.options);
137
+ const codexOptions = providerRuntimeOptions(runtimeOptions);
138
+ const config = {
139
+ ...codexOptions?.config,
140
+ ...codexReasoningEffortConfig(toCodexReasoningEffort(codexOptions.effort)),
141
+ };
142
+ const params = createCodexThreadParams({
143
+ cwd: runtimeOptions.cwd ?? this.#options.cwd ?? this.#options.options?.cwd ?? process.cwd(),
144
+ model: runtimeOptions.model,
145
+ modelProvider: codexOptions?.modelProvider,
146
+ approvalPolicy: runtimeOptions.approvalPolicy,
147
+ sandbox: runtimeOptions.sandbox,
148
+ baseInstructions: codexOptions?.baseInstructions ?? runtimeOptions.instructions?.system,
149
+ developerInstructions: codexOptions?.developerInstructions ?? runtimeOptions.instructions?.append,
150
+ serviceTier: codexOptions?.serviceTier,
151
+ config,
152
+ });
153
+ if (method === "thread/resume")
154
+ params.threadId = input.id ?? "";
155
+ const result = asRecord(await rpc.request(method, params));
156
+ const thread = asRecord(result.thread);
157
+ const id = asString(thread.id) ?? input.id;
158
+ if (!id)
159
+ throw new Error(`Codex ${method} response did not include thread.id`);
160
+ return {
161
+ id,
162
+ provider: "codex",
163
+ native: { threadId: id },
164
+ };
165
+ }
166
+ async *#startTurn(input) {
167
+ await this.start();
168
+ const rpc = this.#requireRpc();
169
+ const queue = new AsyncQueue();
170
+ let expectedTurnId;
171
+ const unsubscribe = this.#subscribe((event) => {
172
+ if (!eventBelongsToSession(event, input.sessionId))
173
+ return;
174
+ const eventTurnId = "turnId" in event ? event.turnId : undefined;
175
+ if (expectedTurnId && eventTurnId && eventTurnId !== expectedTurnId)
176
+ return;
177
+ queue.push(event);
178
+ if (event.type === "turn.completed" && (!expectedTurnId || event.turnId === expectedTurnId)) {
179
+ queue.close();
180
+ }
181
+ });
182
+ try {
183
+ const runtimeOptions = resolveRuntimeOptions(input, this.#options.options);
184
+ const codexOptions = providerRuntimeOptions(runtimeOptions);
185
+ const effort = toCodexReasoningEffort(codexOptions.effort);
186
+ const result = asRecord(await rpc.request("turn/start", {
187
+ threadId: input.sessionId,
188
+ input: normalizeInput(input.input),
189
+ ...(runtimeOptions.cwd ? { cwd: runtimeOptions.cwd } : {}),
190
+ ...(runtimeOptions.model ? { model: runtimeOptions.model } : {}),
191
+ ...(runtimeOptions.approvalPolicy ? { approvalPolicy: runtimeOptions.approvalPolicy } : {}),
192
+ ...(runtimeOptions.sandbox ? { sandboxPolicy: toCodexSandboxPolicy(runtimeOptions.sandbox) } : {}),
193
+ ...(effort ? { effort } : {}),
194
+ ...(codexOptions?.reasoningSummary ? { summary: codexOptions.reasoningSummary } : {}),
195
+ ...(codexOptions?.serviceTier ? { serviceTier: codexOptions.serviceTier } : {}),
196
+ ...(input.outputSchema ? { outputSchema: input.outputSchema } : {}),
197
+ }));
198
+ expectedTurnId = readTurnId(result);
199
+ for await (const event of queue) {
200
+ yield event;
201
+ }
202
+ }
203
+ finally {
204
+ unsubscribe();
205
+ queue.close();
206
+ }
207
+ }
208
+ async #cancelTurn(input) {
209
+ await this.start();
210
+ await this.#requireRpc().request("turn/interrupt", {
211
+ threadId: input.sessionId,
212
+ turnId: input.turnId,
213
+ });
214
+ }
215
+ #allEvents() {
216
+ const queue = new AsyncQueue();
217
+ const unsubscribe = this.#subscribe((event) => queue.push(event));
218
+ return {
219
+ [Symbol.asyncIterator]() {
220
+ const iterator = queue[Symbol.asyncIterator]();
221
+ return {
222
+ next: () => iterator.next(),
223
+ return: async () => {
224
+ unsubscribe();
225
+ queue.close();
226
+ return { value: undefined, done: true };
227
+ },
228
+ };
229
+ },
230
+ };
231
+ }
232
+ #subscribe(listener) {
233
+ this.#events.on("event", listener);
234
+ return () => this.#events.off("event", listener);
235
+ }
236
+ #emit(event) {
237
+ this.#events.emit("event", event);
238
+ }
239
+ #requireRpc() {
240
+ if (!this.#rpc)
241
+ throw new Error("Codex runtime is not started");
242
+ return this.#rpc;
243
+ }
244
+ }
245
+ function normalizeInput(input) {
246
+ return input.map((item) => {
247
+ if (item.type === "text")
248
+ return { type: "text", text: item.text, text_elements: [] };
249
+ return { type: "localImage", path: item.path };
250
+ });
251
+ }
252
+ function readTurnId(result) {
253
+ const turn = asRecord(result.turn);
254
+ return asString(turn.id) ?? asString(result.turnId);
255
+ }
256
+ function toCodexSandboxPolicy(sandbox) {
257
+ if (sandbox === "danger-full-access")
258
+ return { type: "dangerFullAccess" };
259
+ if (sandbox === "read-only")
260
+ return { type: "readOnly", networkAccess: false };
261
+ return {
262
+ type: "workspaceWrite",
263
+ writableRoots: [],
264
+ networkAccess: false,
265
+ excludeTmpdirEnvVar: false,
266
+ excludeSlashTmp: false,
267
+ };
268
+ }
269
+ function eventBelongsToSession(event, sessionId) {
270
+ if ("sessionId" in event && event.sessionId)
271
+ return event.sessionId === sessionId;
272
+ return true;
273
+ }
274
+ function asRecord(value) {
275
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
276
+ }
277
+ function asString(value) {
278
+ return typeof value === "string" ? value : undefined;
279
+ }
280
+ export function createCodexThreadParams(input) {
281
+ return {
282
+ cwd: input.cwd,
283
+ ...(input.model ? { model: input.model } : {}),
284
+ ...(input.modelProvider ? { modelProvider: input.modelProvider } : {}),
285
+ ...(input.serviceTier ? { serviceTier: input.serviceTier } : {}),
286
+ ...(input.baseInstructions ? { baseInstructions: input.baseInstructions } : {}),
287
+ ...(input.developerInstructions ? { developerInstructions: input.developerInstructions } : {}),
288
+ ...(input.approvalPolicy ? { approvalPolicy: input.approvalPolicy } : {}),
289
+ ...(input.sandbox ? { sandbox: input.sandbox } : {}),
290
+ ...(input.config && Object.keys(input.config).length > 0 ? { config: input.config } : {}),
291
+ };
292
+ }
293
+ function toCodexReasoningEffort(effort) {
294
+ if (!effort)
295
+ return undefined;
296
+ return effort === "max" ? "xhigh" : effort;
297
+ }
298
+ function codexReasoningEffortConfig(effort) {
299
+ return effort ? { model_reasoning_effort: effort } : {};
300
+ }
@@ -0,0 +1,6 @@
1
+ import type { AgentEvent, AgentInput, CapabilitySet, ProviderId, SessionRef } from "../types.ts";
2
+ export declare function syntheticSession<P extends ProviderId>(provider: P, id?: string): SessionRef<P>;
3
+ export declare function inputToPrompt(input: AgentInput[]): string;
4
+ export declare function baseCapabilities<P extends ProviderId>(provider: P): CapabilitySet<P>;
5
+ export declare function completedTurn(sessionId: string, turnId: string, status: "completed" | "failed" | "cancelled" | "timeout", error?: string): AgentEvent;
6
+ //# sourceMappingURL=common.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/providers/common.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEjG,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,UAAU,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE,GAAE,MAAqB,GAAG,UAAU,CAAC,CAAC,CAAC,CAM5G;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,MAAM,CAKzD;AAED,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,UAAU,EAAE,QAAQ,EAAE,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CA0CpF;AAED,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,WAAW,GAAG,QAAQ,GAAG,WAAW,GAAG,SAAS,EACxD,KAAK,CAAC,EAAE,MAAM,GACb,UAAU,CAQZ"}
@@ -0,0 +1,67 @@
1
+ import { randomUUID } from "node:crypto";
2
+ export function syntheticSession(provider, id = randomUUID()) {
3
+ return {
4
+ id,
5
+ provider,
6
+ native: { sessionId: id },
7
+ };
8
+ }
9
+ export function inputToPrompt(input) {
10
+ return input.map((item) => {
11
+ if (item.type === "text")
12
+ return item.text;
13
+ return `[local image: ${item.path}]`;
14
+ }).join("\n\n");
15
+ }
16
+ export function baseCapabilities(provider) {
17
+ return {
18
+ provider,
19
+ nativeRpc: false,
20
+ transport: "stdio",
21
+ sessions: {
22
+ create: true,
23
+ resume: true,
24
+ attach: true,
25
+ status: true,
26
+ list: true,
27
+ close: true,
28
+ },
29
+ turns: {
30
+ start: true,
31
+ submit: true,
32
+ status: true,
33
+ wait: true,
34
+ cancel: false,
35
+ steer: false,
36
+ outputSchema: false,
37
+ },
38
+ events: {
39
+ messageDelta: true,
40
+ reasoningDelta: false,
41
+ toolLifecycle: false,
42
+ tokenUsage: false,
43
+ planUpdates: false,
44
+ diffUpdates: false,
45
+ },
46
+ structuredOutput: {
47
+ jsonMode: true,
48
+ nativeJsonSchema: false,
49
+ localValidation: true,
50
+ retry: true,
51
+ },
52
+ lifecycle: {
53
+ localState: true,
54
+ eventReplay: "memory",
55
+ status: "local",
56
+ },
57
+ };
58
+ }
59
+ export function completedTurn(sessionId, turnId, status, error) {
60
+ return {
61
+ type: "turn.completed",
62
+ sessionId,
63
+ turnId,
64
+ status,
65
+ ...(error ? { error } : {}),
66
+ };
67
+ }
@@ -0,0 +1,9 @@
1
+ import type { CapabilitySet, CreatePortInputFor, Port, RuntimeOptions } from "../types.ts";
2
+ import type { ProviderAdapter } from "./adapter.ts";
3
+ type GeminiPortOptions = CreatePortInputFor<"gemini">;
4
+ export declare const geminiCapabilities: CapabilitySet<"gemini">;
5
+ export declare function createGeminiPort(options: GeminiPortOptions): Promise<Port<"gemini">>;
6
+ export declare function createGeminiAdapter(options: GeminiPortOptions): Promise<ProviderAdapter<"gemini">>;
7
+ export declare function buildGeminiArgs(options?: RuntimeOptions<"gemini">): string[];
8
+ export {};
9
+ //# sourceMappingURL=gemini.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gemini.d.ts","sourceRoot":"","sources":["../../src/providers/gemini.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAIV,aAAa,EACb,kBAAkB,EAIlB,IAAI,EACJ,cAAc,EAGf,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAGpD,KAAK,iBAAiB,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAC;AAEtD,eAAO,MAAM,kBAAkB,EAAE,aAAa,CAAC,QAAQ,CAwCtD,CAAC;AAEF,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAE1F;AAED,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC,CAIxG;AAwRD,wBAAgB,eAAe,CAAC,OAAO,GAAE,cAAc,CAAC,QAAQ,CAAkC,GAAG,MAAM,EAAE,CAQ5G"}