@glubean/port 0.1.0 → 0.2.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 +20 -0
- package/dist/lifecycle.js +26 -0
- package/dist/providers/claude.d.ts.map +1 -1
- package/dist/providers/claude.js +57 -27
- package/dist/providers/codex.d.ts.map +1 -1
- package/dist/providers/codex.js +109 -20
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -86,6 +86,26 @@ const result = await port.turns.startStructured({
|
|
|
86
86
|
console.log(result.data)
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
+
Streaming partial JSON: when callers want progressive structure (rather than
|
|
90
|
+
the all-or-nothing `startStructured` contract), accumulate `message.delta.text`
|
|
91
|
+
and feed it to a partial-JSON parser. [`partial-json`](https://www.npmjs.com/package/partial-json)
|
|
92
|
+
is a zero-dependency option designed for LLM streaming:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { parse, Allow } from "partial-json"
|
|
96
|
+
|
|
97
|
+
let accumulated = ""
|
|
98
|
+
for await (const event of port.turns.start({ sessionId: session.id, input })) {
|
|
99
|
+
if (event.type !== "message.delta") continue
|
|
100
|
+
accumulated += event.text
|
|
101
|
+
const partial = parse(accumulated, Allow.ALL)
|
|
102
|
+
// push `partial` to the UI as it grows
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Use `startStructured` when you need final-shape validation or retry on malformed
|
|
107
|
+
output; use the partial-JSON pattern when you need to render incrementally.
|
|
108
|
+
|
|
89
109
|
## Local Development
|
|
90
110
|
|
|
91
111
|
Node 24 can run the TypeScript sources directly.
|
package/dist/lifecycle.js
CHANGED
|
@@ -104,6 +104,10 @@ class LifecycleRuntime {
|
|
|
104
104
|
const ref = eventToRef(runtime.#adapter.id, event, input.sessionId);
|
|
105
105
|
if (ref && !currentTurnId)
|
|
106
106
|
currentTurnId = ref.turnId;
|
|
107
|
+
// Set terminalSeen BEFORE yielding so that if the consumer
|
|
108
|
+
// breaks on the terminal event, the finally below does not
|
|
109
|
+
// append a spurious cancellation. Same reasoning applies to
|
|
110
|
+
// the synthesized terminals further down.
|
|
107
111
|
if (event.type === "turn.completed")
|
|
108
112
|
terminalSeen = true;
|
|
109
113
|
runtime.#record(event, input);
|
|
@@ -111,6 +115,7 @@ class LifecycleRuntime {
|
|
|
111
115
|
}
|
|
112
116
|
if (currentTurnId && !terminalSeen) {
|
|
113
117
|
const completed = completedEvent(input.sessionId, currentTurnId, "completed");
|
|
118
|
+
terminalSeen = true;
|
|
114
119
|
runtime.#record(completed, input);
|
|
115
120
|
yield completed;
|
|
116
121
|
}
|
|
@@ -120,6 +125,7 @@ class LifecycleRuntime {
|
|
|
120
125
|
if (currentTurnId) {
|
|
121
126
|
const errorEvent = { type: "error", sessionId: input.sessionId, turnId: currentTurnId, message };
|
|
122
127
|
const completed = completedEvent(input.sessionId, currentTurnId, "failed", message);
|
|
128
|
+
terminalSeen = true;
|
|
123
129
|
runtime.#record(errorEvent, input);
|
|
124
130
|
runtime.#record(completed, input);
|
|
125
131
|
yield errorEvent;
|
|
@@ -127,6 +133,26 @@ class LifecycleRuntime {
|
|
|
127
133
|
}
|
|
128
134
|
throw error;
|
|
129
135
|
}
|
|
136
|
+
finally {
|
|
137
|
+
// If the consumer abandons the iterator after the turn was accepted
|
|
138
|
+
// but before a terminal event, synthesize a cancellation so
|
|
139
|
+
// turns.status / turns.wait don't report "running" forever, and
|
|
140
|
+
// ALWAYS poke the adapter to stop work for that turn. We can't
|
|
141
|
+
// tell from inside lifecycle whether a previously-recorded error
|
|
142
|
+
// event was a terminal failure (claude spawn ENOENT) or a benign
|
|
143
|
+
// diagnostic (codex/gemini stderr surfaced as error) — so we
|
|
144
|
+
// treat the consumer's break as the authoritative termination
|
|
145
|
+
// signal, preserving the recorded error in the snapshot for
|
|
146
|
+
// informational purposes.
|
|
147
|
+
if (currentTurnId && !terminalSeen) {
|
|
148
|
+
const recorded = runtime.#turns.get(turnKey(input.sessionId, currentTurnId));
|
|
149
|
+
if (!recorded || !isTerminalStatus(recorded.status)) {
|
|
150
|
+
const cancelled = completedEvent(input.sessionId, currentTurnId, "cancelled", recorded?.error);
|
|
151
|
+
runtime.#record(cancelled, input);
|
|
152
|
+
void runtime.#adapter.cancelTurn({ sessionId: input.sessionId, turnId: currentTurnId }).catch(() => undefined);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
130
156
|
},
|
|
131
157
|
};
|
|
132
158
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../src/providers/claude.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../src/providers/claude.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAGV,aAAa,EACb,kBAAkB,EAGlB,IAAI,EACJ,cAAc,EAEd,cAAc,EAEf,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,CAGxG;AA2KD,wBAAgB,eAAe,CAAC,KAAK,EAAE,cAAc,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,EAAE,cAAc,CAAC,QAAQ,CAAC,GAAG,MAAM,EAAE,CAe9G"}
|
package/dist/providers/claude.js
CHANGED
|
@@ -2,6 +2,7 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { EventEmitter } from "node:events";
|
|
4
4
|
import process from "node:process";
|
|
5
|
+
import { AsyncQueue } from "../async-queue.js";
|
|
5
6
|
import { createPortFromAdapter } from "../port.js";
|
|
6
7
|
import { providerRuntimeOptions, resolveRuntimeOptions } from "../options.js";
|
|
7
8
|
import { normalizeTokenUsage } from "../token-usage.js";
|
|
@@ -93,14 +94,23 @@ class ClaudeRuntime {
|
|
|
93
94
|
});
|
|
94
95
|
this.#processes.set(turnId, child);
|
|
95
96
|
child.stdin.end(inputToPrompt(input.input));
|
|
97
|
+
const queue = new AsyncQueue();
|
|
96
98
|
let buffered = "";
|
|
97
99
|
let sawMessage = false;
|
|
98
100
|
const stderr = [];
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
101
|
+
let exitInfo = { code: null, signal: null };
|
|
102
|
+
let spawnError;
|
|
103
|
+
const emit = (event) => {
|
|
104
|
+
queue.push(event);
|
|
102
105
|
this.#events.emit("event", event);
|
|
103
106
|
};
|
|
107
|
+
const drainLine = (line) => {
|
|
108
|
+
for (const event of normalizeClaudeLine(line, input.sessionId, turnId, sawMessage)) {
|
|
109
|
+
if (event.type === "message.delta")
|
|
110
|
+
sawMessage = true;
|
|
111
|
+
emit(event);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
104
114
|
child.stdout.setEncoding("utf8");
|
|
105
115
|
child.stdout.on("data", (chunk) => {
|
|
106
116
|
buffered += chunk;
|
|
@@ -112,34 +122,57 @@ class ClaudeRuntime {
|
|
|
112
122
|
buffered = buffered.slice(newlineIndex + 1);
|
|
113
123
|
if (!line)
|
|
114
124
|
continue;
|
|
115
|
-
|
|
116
|
-
if (event.type === "message.delta")
|
|
117
|
-
sawMessage = true;
|
|
118
|
-
push(event);
|
|
119
|
-
}
|
|
125
|
+
drainLine(line);
|
|
120
126
|
}
|
|
121
127
|
});
|
|
122
128
|
child.stderr.setEncoding("utf8");
|
|
123
129
|
child.stderr.on("data", (chunk) => stderr.push(chunk));
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
130
|
+
// Delete from #processes only once the OS reports the child is gone
|
|
131
|
+
// (close fires after stdio is drained; error covers spawn failures).
|
|
132
|
+
// Doing this in handlers keeps the map entry reachable for
|
|
133
|
+
// adapter.close() / cancelTurn() during the gap between SIGTERM and
|
|
134
|
+
// actual exit, in case the binary ignores or delays the signal.
|
|
135
|
+
child.on("error", (err) => {
|
|
136
|
+
spawnError = err;
|
|
137
|
+
this.#processes.delete(turnId);
|
|
138
|
+
queue.close();
|
|
139
|
+
});
|
|
140
|
+
child.on("close", (code, signal) => {
|
|
141
|
+
exitInfo = { code, signal };
|
|
142
|
+
const trailing = buffered.trim();
|
|
143
|
+
buffered = "";
|
|
144
|
+
if (trailing)
|
|
145
|
+
drainLine(trailing);
|
|
146
|
+
this.#processes.delete(turnId);
|
|
147
|
+
queue.close();
|
|
148
|
+
});
|
|
149
|
+
try {
|
|
150
|
+
for await (const event of queue)
|
|
151
|
+
yield event;
|
|
152
|
+
if (spawnError) {
|
|
153
|
+
const message = spawnError.message;
|
|
154
|
+
yield { type: "error", sessionId: input.sessionId, turnId, message };
|
|
155
|
+
yield completedTurn(input.sessionId, turnId, "failed", message);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (exitInfo.code === 0) {
|
|
159
|
+
yield completedTurn(input.sessionId, turnId, "completed");
|
|
160
|
+
return;
|
|
132
161
|
}
|
|
162
|
+
const message = stderr.join("").trim() || `Claude Code exited with code ${exitInfo.code ?? "null"} signal=${exitInfo.signal ?? "null"}`;
|
|
163
|
+
yield { type: "error", sessionId: input.sessionId, turnId, message };
|
|
164
|
+
yield completedTurn(input.sessionId, turnId, "failed", message);
|
|
133
165
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
166
|
+
finally {
|
|
167
|
+
// If the consumer aborts the iterator while the child is still
|
|
168
|
+
// alive, send SIGTERM. The close handler above is the single owner
|
|
169
|
+
// of the #processes entry and will delete it once the kernel
|
|
170
|
+
// confirms exit — so callers retain a working handle for graceful
|
|
171
|
+
// shutdown (close()) or escalation (cancelTurn → repeat kill).
|
|
172
|
+
if (child.exitCode === null && child.signalCode === null) {
|
|
173
|
+
child.kill();
|
|
174
|
+
}
|
|
139
175
|
}
|
|
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
176
|
}
|
|
144
177
|
async #cancelTurn(input) {
|
|
145
178
|
this.#processes.get(input.turnId)?.kill();
|
|
@@ -284,9 +317,6 @@ function textFromClaudeMessage(value) {
|
|
|
284
317
|
return text ? [text] : [];
|
|
285
318
|
});
|
|
286
319
|
}
|
|
287
|
-
function waitForExit(child) {
|
|
288
|
-
return new Promise((resolve) => child.on("exit", (code, signal) => resolve({ code, signal })));
|
|
289
|
-
}
|
|
290
320
|
function asRecord(value) {
|
|
291
321
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
292
322
|
}
|
|
@@ -1 +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;
|
|
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;AAuUD,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"}
|
package/dist/providers/codex.js
CHANGED
|
@@ -55,6 +55,13 @@ export async function createCodexAdapter(options) {
|
|
|
55
55
|
await runtime.start();
|
|
56
56
|
return runtime.adapter;
|
|
57
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Cap stderr buffer to a bounded number of lines so a chatty codex
|
|
60
|
+
* process cannot grow memory unbounded over a long-lived session. The
|
|
61
|
+
* tail (most recent N lines) is what we surface in error messages —
|
|
62
|
+
* head lines from a healthy startup are rarely useful for diagnosis.
|
|
63
|
+
*/
|
|
64
|
+
const STDERR_BUFFER_MAX_LINES = 200;
|
|
58
65
|
class CodexRuntime {
|
|
59
66
|
#options;
|
|
60
67
|
#process;
|
|
@@ -62,6 +69,17 @@ class CodexRuntime {
|
|
|
62
69
|
#normalizer = new CodexEventNormalizer();
|
|
63
70
|
#events = new EventEmitter();
|
|
64
71
|
#closed = false;
|
|
72
|
+
/**
|
|
73
|
+
* Bounded ring buffer of recent codex stderr lines, surfaced in
|
|
74
|
+
* augmented error messages so a 30ms "JSON-RPC input closed" failure
|
|
75
|
+
* carries the actual codex stderr (auth error, bad flag, missing
|
|
76
|
+
* config, etc.) instead of disappearing into the void.
|
|
77
|
+
*/
|
|
78
|
+
#stderrBuffer = [];
|
|
79
|
+
/** Captured at process 'exit' so subsequent rpc.request rejections can cite it. */
|
|
80
|
+
#exitInfo = null;
|
|
81
|
+
/** Captured at process 'error' (e.g. spawn ENOENT) for the same reason. */
|
|
82
|
+
#spawnError = null;
|
|
65
83
|
constructor(options) {
|
|
66
84
|
this.#options = options;
|
|
67
85
|
}
|
|
@@ -104,27 +122,84 @@ class CodexRuntime {
|
|
|
104
122
|
shell: false,
|
|
105
123
|
});
|
|
106
124
|
this.#process.stderr.on("data", (chunk) => {
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
125
|
+
const text = chunk.toString("utf8");
|
|
126
|
+
// Buffer stderr line-by-line so #augmentError can surface a
|
|
127
|
+
// bounded tail. We still emit the chunk as an error event so
|
|
128
|
+
// existing event-stream consumers aren't broken.
|
|
129
|
+
for (const line of text.split(/\r?\n/)) {
|
|
130
|
+
if (!line)
|
|
131
|
+
continue;
|
|
132
|
+
this.#stderrBuffer.push(line);
|
|
133
|
+
if (this.#stderrBuffer.length > STDERR_BUFFER_MAX_LINES) {
|
|
134
|
+
this.#stderrBuffer.splice(0, this.#stderrBuffer.length - STDERR_BUFFER_MAX_LINES);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const trimmed = text.trim();
|
|
138
|
+
if (trimmed)
|
|
139
|
+
this.#emit({ type: "error", message: trimmed });
|
|
110
140
|
});
|
|
111
141
|
this.#process.on("exit", (code, signal) => {
|
|
142
|
+
this.#exitInfo = { code, signal };
|
|
112
143
|
if (!this.#closed) {
|
|
113
144
|
this.#emit({ type: "error", message: `Codex app-server exited: code=${code ?? "null"} signal=${signal ?? "null"}` });
|
|
114
145
|
}
|
|
115
146
|
});
|
|
147
|
+
// 'error' fires on spawn failures (e.g. ENOENT when codex binary is
|
|
148
|
+
// missing from PATH). Without a listener Node would crash the
|
|
149
|
+
// process on the unhandled emit; instead capture and let the
|
|
150
|
+
// pending rpc.request rejection surface the spawn error.
|
|
151
|
+
this.#process.on("error", (err) => {
|
|
152
|
+
this.#spawnError = err;
|
|
153
|
+
if (!this.#closed) {
|
|
154
|
+
this.#emit({ type: "error", message: `Codex app-server spawn error: ${err.message}` });
|
|
155
|
+
}
|
|
156
|
+
});
|
|
116
157
|
this.#rpc = new JsonRpcLineConnection(this.#process.stdout, this.#process.stdin);
|
|
117
158
|
this.#rpc.onNotification((notification) => {
|
|
118
159
|
for (const event of this.#normalizer.normalize(notification)) {
|
|
119
160
|
this.#emit(event);
|
|
120
161
|
}
|
|
121
162
|
});
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
163
|
+
try {
|
|
164
|
+
await this.#rpc.request("initialize", {
|
|
165
|
+
clientInfo: { name: "glubean-port", version: "0.0.0" },
|
|
166
|
+
capabilities: { experimentalApi: true },
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
throw await this.#augmentError(err);
|
|
171
|
+
}
|
|
126
172
|
this.#rpc.notify("initialized", {});
|
|
127
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* Wrap an rpc rejection with codex process diagnostics so callers see
|
|
176
|
+
* (a) why the process closed if it did, (b) any spawn 'error' (e.g.
|
|
177
|
+
* ENOENT), and (c) the most recent stderr lines. The original error
|
|
178
|
+
* is preserved as `cause` for callers that introspect it.
|
|
179
|
+
*
|
|
180
|
+
* Awaits a microtask so any racing `process.on("exit")` fires before
|
|
181
|
+
* we read `#exitInfo` — JsonRpcLineConnection rejects synchronously
|
|
182
|
+
* from `input.on("close")`, but Node may fire stream 'close' before
|
|
183
|
+
* the matching process 'exit' on some versions.
|
|
184
|
+
*/
|
|
185
|
+
async #augmentError(err) {
|
|
186
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
187
|
+
const original = err instanceof Error ? err : new Error(String(err));
|
|
188
|
+
const parts = [original.message];
|
|
189
|
+
if (this.#spawnError) {
|
|
190
|
+
parts.push(`(codex spawn error: ${this.#spawnError.message})`);
|
|
191
|
+
}
|
|
192
|
+
if (this.#exitInfo) {
|
|
193
|
+
parts.push(`(codex process exited: code=${this.#exitInfo.code ?? "null"} signal=${this.#exitInfo.signal ?? "null"})`);
|
|
194
|
+
}
|
|
195
|
+
if (this.#stderrBuffer.length > 0) {
|
|
196
|
+
const tail = this.#stderrBuffer.slice(-20).join("\n");
|
|
197
|
+
parts.push(`(codex stderr tail:\n${tail})`);
|
|
198
|
+
}
|
|
199
|
+
const augmented = new Error(parts.join(" "));
|
|
200
|
+
augmented.cause = original;
|
|
201
|
+
return augmented;
|
|
202
|
+
}
|
|
128
203
|
async close() {
|
|
129
204
|
this.#closed = true;
|
|
130
205
|
this.#rpc?.close();
|
|
@@ -152,7 +227,14 @@ class CodexRuntime {
|
|
|
152
227
|
});
|
|
153
228
|
if (method === "thread/resume")
|
|
154
229
|
params.threadId = input.id ?? "";
|
|
155
|
-
|
|
230
|
+
let rawResult;
|
|
231
|
+
try {
|
|
232
|
+
rawResult = await rpc.request(method, params);
|
|
233
|
+
}
|
|
234
|
+
catch (err) {
|
|
235
|
+
throw await this.#augmentError(err);
|
|
236
|
+
}
|
|
237
|
+
const result = asRecord(rawResult);
|
|
156
238
|
const thread = asRecord(result.thread);
|
|
157
239
|
const id = asString(thread.id) ?? input.id;
|
|
158
240
|
if (!id)
|
|
@@ -183,18 +265,25 @@ class CodexRuntime {
|
|
|
183
265
|
const runtimeOptions = resolveRuntimeOptions(input, this.#options.options);
|
|
184
266
|
const codexOptions = providerRuntimeOptions(runtimeOptions);
|
|
185
267
|
const effort = toCodexReasoningEffort(codexOptions.effort);
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
268
|
+
let rawResult;
|
|
269
|
+
try {
|
|
270
|
+
rawResult = await rpc.request("turn/start", {
|
|
271
|
+
threadId: input.sessionId,
|
|
272
|
+
input: normalizeInput(input.input),
|
|
273
|
+
...(runtimeOptions.cwd ? { cwd: runtimeOptions.cwd } : {}),
|
|
274
|
+
...(runtimeOptions.model ? { model: runtimeOptions.model } : {}),
|
|
275
|
+
...(runtimeOptions.approvalPolicy ? { approvalPolicy: runtimeOptions.approvalPolicy } : {}),
|
|
276
|
+
...(runtimeOptions.sandbox ? { sandboxPolicy: toCodexSandboxPolicy(runtimeOptions.sandbox) } : {}),
|
|
277
|
+
...(effort ? { effort } : {}),
|
|
278
|
+
...(codexOptions?.reasoningSummary ? { summary: codexOptions.reasoningSummary } : {}),
|
|
279
|
+
...(codexOptions?.serviceTier ? { serviceTier: codexOptions.serviceTier } : {}),
|
|
280
|
+
...(input.outputSchema ? { outputSchema: input.outputSchema } : {}),
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
catch (err) {
|
|
284
|
+
throw await this.#augmentError(err);
|
|
285
|
+
}
|
|
286
|
+
const result = asRecord(rawResult);
|
|
198
287
|
expectedTurnId = readTurnId(result);
|
|
199
288
|
for await (const event of queue) {
|
|
200
289
|
yield event;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glubean/port",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Neutral runtime adapter for local coding agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@types/node": "^25.6.0",
|
|
32
|
+
"partial-json": "^0.1.7",
|
|
32
33
|
"typescript": "^6.0.3",
|
|
33
34
|
"vitest": "^4.1.5"
|
|
34
35
|
}
|