@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,517 @@
|
|
|
1
|
+
import { AsyncQueue } from "./async-queue.js";
|
|
2
|
+
import { resolveRuntimeOptions } from "./options.js";
|
|
3
|
+
import { startStructuredTurn } from "./structured.js";
|
|
4
|
+
const DEFAULT_ACCEPT_TIMEOUT_MS = 30_000;
|
|
5
|
+
export function createLifecyclePort(adapter) {
|
|
6
|
+
return new LifecycleRuntime(adapter).port;
|
|
7
|
+
}
|
|
8
|
+
class LifecycleRuntime {
|
|
9
|
+
#adapter;
|
|
10
|
+
#sessions = new Map();
|
|
11
|
+
#turns = new Map();
|
|
12
|
+
#eventLog = [];
|
|
13
|
+
#subscribers = new Set();
|
|
14
|
+
#seq = 0;
|
|
15
|
+
constructor(adapter) {
|
|
16
|
+
this.#adapter = adapter;
|
|
17
|
+
}
|
|
18
|
+
get port() {
|
|
19
|
+
return {
|
|
20
|
+
provider: this.#adapter.id,
|
|
21
|
+
capabilities: this.#adapter.capabilities,
|
|
22
|
+
sessions: {
|
|
23
|
+
create: (input = {}) => this.#createSession(input),
|
|
24
|
+
resume: (input) => this.#resumeSession(input),
|
|
25
|
+
attach: (input) => this.#resumeSession(input),
|
|
26
|
+
status: (input) => this.#sessionStatus(input),
|
|
27
|
+
list: (input = {}) => this.#sessionList(input),
|
|
28
|
+
close: (input) => this.#closeSession(input),
|
|
29
|
+
},
|
|
30
|
+
turns: {
|
|
31
|
+
start: (input) => this.#startTurn(input),
|
|
32
|
+
submit: (input) => this.#submitTurn(input),
|
|
33
|
+
status: (input) => this.#turnStatus(input),
|
|
34
|
+
stream: (input) => this.#events(input),
|
|
35
|
+
wait: (input) => this.#waitTurn(input),
|
|
36
|
+
startStructured: (input) => startStructuredTurn(this.#managedAdapter(), input),
|
|
37
|
+
cancel: (input) => this.#cancelTurn(input),
|
|
38
|
+
},
|
|
39
|
+
events: (input = {}) => this.#events(input),
|
|
40
|
+
close: () => this.#close(),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
#managedAdapter() {
|
|
44
|
+
return {
|
|
45
|
+
...this.#adapter,
|
|
46
|
+
startTurn: (input) => this.#startTurn(input),
|
|
47
|
+
cancelTurn: (input) => this.#cancelTurn(input),
|
|
48
|
+
events: () => this.#events(),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
async #createSession(input) {
|
|
52
|
+
const session = await this.#adapter.createSession(input);
|
|
53
|
+
this.#upsertSession(session, "idle");
|
|
54
|
+
this.#record({
|
|
55
|
+
type: "session.started",
|
|
56
|
+
sessionId: session.id,
|
|
57
|
+
provider: session.provider,
|
|
58
|
+
...(session.native ? { native: session.native } : {}),
|
|
59
|
+
});
|
|
60
|
+
return session;
|
|
61
|
+
}
|
|
62
|
+
async #resumeSession(input) {
|
|
63
|
+
const session = await this.#adapter.resumeSession(input);
|
|
64
|
+
this.#upsertSession(session, "idle");
|
|
65
|
+
return session;
|
|
66
|
+
}
|
|
67
|
+
async #sessionStatus(input) {
|
|
68
|
+
const session = this.#sessions.get(input.id);
|
|
69
|
+
if (!session) {
|
|
70
|
+
return {
|
|
71
|
+
id: input.id,
|
|
72
|
+
provider: this.#adapter.id,
|
|
73
|
+
status: "unknown",
|
|
74
|
+
activeTurnIds: [],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return snapshotSession(session);
|
|
78
|
+
}
|
|
79
|
+
async #sessionList(input) {
|
|
80
|
+
const snapshots = [...this.#sessions.values()].map(snapshotSession);
|
|
81
|
+
return input.status ? snapshots.filter((session) => session.status === input.status) : snapshots;
|
|
82
|
+
}
|
|
83
|
+
async #closeSession(input) {
|
|
84
|
+
const session = this.#sessions.get(input.id);
|
|
85
|
+
if (!session)
|
|
86
|
+
return;
|
|
87
|
+
if (input.cancelActiveTurns !== false) {
|
|
88
|
+
const activeTurnIds = [...session.activeTurnIds];
|
|
89
|
+
await Promise.all(activeTurnIds.map((turnId) => this.#cancelTurn({ sessionId: input.id, turnId }).catch(() => undefined)));
|
|
90
|
+
}
|
|
91
|
+
session.status = "closed";
|
|
92
|
+
session.activeTurnIds.clear();
|
|
93
|
+
session.updatedAt = nowIso();
|
|
94
|
+
}
|
|
95
|
+
#startTurn(input) {
|
|
96
|
+
const runtime = this;
|
|
97
|
+
return {
|
|
98
|
+
async *[Symbol.asyncIterator]() {
|
|
99
|
+
let currentTurnId;
|
|
100
|
+
let terminalSeen = false;
|
|
101
|
+
try {
|
|
102
|
+
for await (const raw of runtime.#adapter.startTurn(input)) {
|
|
103
|
+
const event = runtime.#withContext(raw, input.sessionId, currentTurnId);
|
|
104
|
+
const ref = eventToRef(runtime.#adapter.id, event, input.sessionId);
|
|
105
|
+
if (ref && !currentTurnId)
|
|
106
|
+
currentTurnId = ref.turnId;
|
|
107
|
+
if (event.type === "turn.completed")
|
|
108
|
+
terminalSeen = true;
|
|
109
|
+
runtime.#record(event, input);
|
|
110
|
+
yield event;
|
|
111
|
+
}
|
|
112
|
+
if (currentTurnId && !terminalSeen) {
|
|
113
|
+
const completed = completedEvent(input.sessionId, currentTurnId, "completed");
|
|
114
|
+
runtime.#record(completed, input);
|
|
115
|
+
yield completed;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
120
|
+
if (currentTurnId) {
|
|
121
|
+
const errorEvent = { type: "error", sessionId: input.sessionId, turnId: currentTurnId, message };
|
|
122
|
+
const completed = completedEvent(input.sessionId, currentTurnId, "failed", message);
|
|
123
|
+
runtime.#record(errorEvent, input);
|
|
124
|
+
runtime.#record(completed, input);
|
|
125
|
+
yield errorEvent;
|
|
126
|
+
yield completed;
|
|
127
|
+
}
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
async #submitTurn(input) {
|
|
134
|
+
let accepted = false;
|
|
135
|
+
let acceptTimer;
|
|
136
|
+
const acceptedPromise = new Promise((resolve, reject) => {
|
|
137
|
+
acceptTimer = setTimeout(() => {
|
|
138
|
+
if (!accepted)
|
|
139
|
+
reject(new Error(`turn was not accepted within ${input.acceptTimeoutMs ?? DEFAULT_ACCEPT_TIMEOUT_MS}ms`));
|
|
140
|
+
}, input.acceptTimeoutMs ?? DEFAULT_ACCEPT_TIMEOUT_MS);
|
|
141
|
+
this.#consumeSubmittedTurn(input, (ref) => {
|
|
142
|
+
accepted = true;
|
|
143
|
+
if (acceptTimer)
|
|
144
|
+
clearTimeout(acceptTimer);
|
|
145
|
+
resolve(ref);
|
|
146
|
+
}, (error) => {
|
|
147
|
+
if (acceptTimer)
|
|
148
|
+
clearTimeout(acceptTimer);
|
|
149
|
+
reject(error);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
return acceptedPromise;
|
|
153
|
+
}
|
|
154
|
+
#consumeSubmittedTurn(input, onAccepted, onRejected) {
|
|
155
|
+
void (async () => {
|
|
156
|
+
let currentTurnId;
|
|
157
|
+
let accepted = false;
|
|
158
|
+
let terminalSeen = false;
|
|
159
|
+
let timeoutTimer;
|
|
160
|
+
const clearRuntimeTimeout = () => {
|
|
161
|
+
if (timeoutTimer)
|
|
162
|
+
clearTimeout(timeoutTimer);
|
|
163
|
+
timeoutTimer = undefined;
|
|
164
|
+
};
|
|
165
|
+
const armRuntimeTimeout = () => {
|
|
166
|
+
if (typeof input.timeoutMs !== "number" || timeoutTimer)
|
|
167
|
+
return;
|
|
168
|
+
timeoutTimer = setTimeout(() => {
|
|
169
|
+
if (!currentTurnId)
|
|
170
|
+
return;
|
|
171
|
+
const current = this.#turns.get(turnKey(input.sessionId, currentTurnId));
|
|
172
|
+
if (current && isTerminalStatus(current.status))
|
|
173
|
+
return;
|
|
174
|
+
const message = `turn timed out after ${input.timeoutMs}ms`;
|
|
175
|
+
this.#record({ type: "error", sessionId: input.sessionId, turnId: currentTurnId, message }, input);
|
|
176
|
+
this.#record(completedEvent(input.sessionId, currentTurnId, "timeout", message), input);
|
|
177
|
+
void this.#adapter.cancelTurn({ sessionId: input.sessionId, turnId: currentTurnId });
|
|
178
|
+
}, input.timeoutMs);
|
|
179
|
+
};
|
|
180
|
+
try {
|
|
181
|
+
for await (const raw of this.#adapter.startTurn(input)) {
|
|
182
|
+
const event = this.#withContext(raw, input.sessionId, currentTurnId);
|
|
183
|
+
const ref = eventToRef(this.#adapter.id, event, input.sessionId);
|
|
184
|
+
if (ref && !currentTurnId) {
|
|
185
|
+
currentTurnId = ref.turnId;
|
|
186
|
+
armRuntimeTimeout();
|
|
187
|
+
}
|
|
188
|
+
this.#record(event, input);
|
|
189
|
+
if (ref && !accepted) {
|
|
190
|
+
accepted = true;
|
|
191
|
+
onAccepted(ref);
|
|
192
|
+
}
|
|
193
|
+
if (event.type === "turn.completed") {
|
|
194
|
+
terminalSeen = true;
|
|
195
|
+
clearRuntimeTimeout();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (!accepted) {
|
|
199
|
+
onRejected(new Error("provider turn ended without exposing a turn id"));
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (currentTurnId && !terminalSeen) {
|
|
203
|
+
this.#record(completedEvent(input.sessionId, currentTurnId, "completed"), input);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
208
|
+
if (!accepted) {
|
|
209
|
+
onRejected(err);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (currentTurnId) {
|
|
213
|
+
this.#record({ type: "error", sessionId: input.sessionId, turnId: currentTurnId, message: err.message }, input);
|
|
214
|
+
this.#record(completedEvent(input.sessionId, currentTurnId, "failed", err.message), input);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
finally {
|
|
218
|
+
clearRuntimeTimeout();
|
|
219
|
+
}
|
|
220
|
+
})();
|
|
221
|
+
}
|
|
222
|
+
async #turnStatus(input) {
|
|
223
|
+
const turn = this.#turns.get(turnKey(input.sessionId, input.turnId));
|
|
224
|
+
if (!turn) {
|
|
225
|
+
return {
|
|
226
|
+
provider: this.#adapter.id,
|
|
227
|
+
sessionId: input.sessionId,
|
|
228
|
+
turnId: input.turnId,
|
|
229
|
+
status: "unknown",
|
|
230
|
+
eventCount: 0,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
return snapshotTurn(turn);
|
|
234
|
+
}
|
|
235
|
+
async #waitTurn(input) {
|
|
236
|
+
const turn = this.#turns.get(turnKey(input.sessionId, input.turnId));
|
|
237
|
+
if (!turn)
|
|
238
|
+
throw new Error(`unknown turn: ${input.sessionId}/${input.turnId}`);
|
|
239
|
+
if (isTerminalStatus(turn.status))
|
|
240
|
+
return resultTurn(turn);
|
|
241
|
+
return new Promise((resolve, reject) => {
|
|
242
|
+
let timer;
|
|
243
|
+
const done = (result) => {
|
|
244
|
+
if (timer)
|
|
245
|
+
clearTimeout(timer);
|
|
246
|
+
resolve(result);
|
|
247
|
+
};
|
|
248
|
+
turn.waiters.push(done);
|
|
249
|
+
if (typeof input.timeoutMs === "number") {
|
|
250
|
+
timer = setTimeout(() => {
|
|
251
|
+
const index = turn.waiters.indexOf(done);
|
|
252
|
+
if (index >= 0)
|
|
253
|
+
turn.waiters.splice(index, 1);
|
|
254
|
+
reject(new Error(`wait timed out after ${input.timeoutMs}ms`));
|
|
255
|
+
}, input.timeoutMs);
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
#events(input = {}) {
|
|
260
|
+
const queue = new AsyncQueue();
|
|
261
|
+
const replay = input.replay !== false;
|
|
262
|
+
if (replay) {
|
|
263
|
+
for (const event of this.#eventLog) {
|
|
264
|
+
if (event.seq > (input.afterSeq ?? 0) && eventMatches(event, input))
|
|
265
|
+
queue.push(event);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const subscriber = { input, queue };
|
|
269
|
+
this.#subscribers.add(subscriber);
|
|
270
|
+
const subscribers = this.#subscribers;
|
|
271
|
+
return {
|
|
272
|
+
[Symbol.asyncIterator]() {
|
|
273
|
+
const iterator = queue[Symbol.asyncIterator]();
|
|
274
|
+
return {
|
|
275
|
+
next: () => iterator.next(),
|
|
276
|
+
return: async () => {
|
|
277
|
+
subscribers.delete(subscriber);
|
|
278
|
+
queue.close();
|
|
279
|
+
return { value: undefined, done: true };
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
async #cancelTurn(input) {
|
|
286
|
+
const cancelInput = { sessionId: input.sessionId, turnId: input.turnId };
|
|
287
|
+
await this.#adapter.cancelTurn(cancelInput);
|
|
288
|
+
const turn = this.#turns.get(turnKey(input.sessionId, input.turnId));
|
|
289
|
+
if (!turn || isTerminalStatus(turn.status))
|
|
290
|
+
return;
|
|
291
|
+
this.#record(completedEvent(input.sessionId, input.turnId, "cancelled"));
|
|
292
|
+
}
|
|
293
|
+
async #close() {
|
|
294
|
+
for (const session of this.#sessions.values()) {
|
|
295
|
+
session.status = "closed";
|
|
296
|
+
session.activeTurnIds.clear();
|
|
297
|
+
session.updatedAt = nowIso();
|
|
298
|
+
}
|
|
299
|
+
for (const subscriber of this.#subscribers)
|
|
300
|
+
subscriber.queue.close();
|
|
301
|
+
this.#subscribers.clear();
|
|
302
|
+
await this.#adapter.close();
|
|
303
|
+
}
|
|
304
|
+
#upsertSession(ref, status) {
|
|
305
|
+
const existing = this.#sessions.get(ref.id);
|
|
306
|
+
const updatedAt = nowIso();
|
|
307
|
+
if (existing) {
|
|
308
|
+
existing.ref = ref;
|
|
309
|
+
existing.status = existing.activeTurnIds.size > 0 && status !== "closed" ? "running" : status;
|
|
310
|
+
existing.updatedAt = updatedAt;
|
|
311
|
+
return existing;
|
|
312
|
+
}
|
|
313
|
+
const session = {
|
|
314
|
+
ref,
|
|
315
|
+
status,
|
|
316
|
+
createdAt: updatedAt,
|
|
317
|
+
updatedAt,
|
|
318
|
+
activeTurnIds: new Set(),
|
|
319
|
+
};
|
|
320
|
+
this.#sessions.set(ref.id, session);
|
|
321
|
+
return session;
|
|
322
|
+
}
|
|
323
|
+
#ensureSession(sessionId, status, recordedAt) {
|
|
324
|
+
const existing = this.#sessions.get(sessionId);
|
|
325
|
+
if (existing) {
|
|
326
|
+
if (existing.status !== "closed")
|
|
327
|
+
existing.status = status;
|
|
328
|
+
existing.updatedAt = recordedAt;
|
|
329
|
+
return existing;
|
|
330
|
+
}
|
|
331
|
+
const session = {
|
|
332
|
+
ref: { id: sessionId, provider: this.#adapter.id },
|
|
333
|
+
status,
|
|
334
|
+
createdAt: recordedAt,
|
|
335
|
+
updatedAt: recordedAt,
|
|
336
|
+
activeTurnIds: new Set(),
|
|
337
|
+
};
|
|
338
|
+
this.#sessions.set(sessionId, session);
|
|
339
|
+
return session;
|
|
340
|
+
}
|
|
341
|
+
#ensureTurn(ref, input, recordedAt) {
|
|
342
|
+
const key = turnKey(ref.sessionId, ref.turnId);
|
|
343
|
+
const existing = this.#turns.get(key);
|
|
344
|
+
if (existing) {
|
|
345
|
+
if (input && !existing.input)
|
|
346
|
+
existing.input = input;
|
|
347
|
+
return existing;
|
|
348
|
+
}
|
|
349
|
+
const turn = {
|
|
350
|
+
ref,
|
|
351
|
+
status: "running",
|
|
352
|
+
...(input ? { input } : {}),
|
|
353
|
+
events: [],
|
|
354
|
+
text: "",
|
|
355
|
+
startedAt: recordedAt,
|
|
356
|
+
updatedAt: recordedAt,
|
|
357
|
+
waiters: [],
|
|
358
|
+
};
|
|
359
|
+
this.#turns.set(key, turn);
|
|
360
|
+
const session = this.#ensureSession(ref.sessionId, "running", recordedAt);
|
|
361
|
+
session.activeTurnIds.add(ref.turnId);
|
|
362
|
+
session.status = "running";
|
|
363
|
+
session.updatedAt = recordedAt;
|
|
364
|
+
return turn;
|
|
365
|
+
}
|
|
366
|
+
#record(event, input) {
|
|
367
|
+
const recorded = {
|
|
368
|
+
...event,
|
|
369
|
+
seq: ++this.#seq,
|
|
370
|
+
recordedAt: nowIso(),
|
|
371
|
+
};
|
|
372
|
+
this.#eventLog.push(recorded);
|
|
373
|
+
this.#applyEvent(recorded, input);
|
|
374
|
+
for (const subscriber of this.#subscribers) {
|
|
375
|
+
if (recorded.seq > (subscriber.input.afterSeq ?? 0) && eventMatches(recorded, subscriber.input)) {
|
|
376
|
+
subscriber.queue.push(recorded);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return recorded;
|
|
380
|
+
}
|
|
381
|
+
#applyEvent(event, input) {
|
|
382
|
+
if (event.type === "session.started") {
|
|
383
|
+
this.#upsertSession({ id: event.sessionId, provider: this.#adapter.id, native: event.native }, "idle");
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const ref = eventToRef(this.#adapter.id, event, input?.sessionId);
|
|
387
|
+
if (!ref)
|
|
388
|
+
return;
|
|
389
|
+
const turn = this.#ensureTurn(ref, input, event.recordedAt);
|
|
390
|
+
turn.events.push(event);
|
|
391
|
+
turn.updatedAt = event.recordedAt;
|
|
392
|
+
if (event.type === "turn.started") {
|
|
393
|
+
turn.status = "running";
|
|
394
|
+
turn.startedAt = event.recordedAt;
|
|
395
|
+
}
|
|
396
|
+
else if (event.type === "message.delta") {
|
|
397
|
+
turn.text += event.text;
|
|
398
|
+
}
|
|
399
|
+
else if (event.type === "token.usage") {
|
|
400
|
+
turn.usage = event.usage;
|
|
401
|
+
if (event.totalUsage)
|
|
402
|
+
turn.totalUsage = event.totalUsage;
|
|
403
|
+
if ("contextWindow" in event)
|
|
404
|
+
turn.contextWindow = event.contextWindow;
|
|
405
|
+
}
|
|
406
|
+
else if (event.type === "error") {
|
|
407
|
+
turn.error = event.message;
|
|
408
|
+
}
|
|
409
|
+
else if (event.type === "turn.completed") {
|
|
410
|
+
if (!isTerminalStatus(turn.status)) {
|
|
411
|
+
turn.status = event.status;
|
|
412
|
+
turn.completedAt = event.recordedAt;
|
|
413
|
+
if (event.error)
|
|
414
|
+
turn.error = event.error;
|
|
415
|
+
this.#finishTurn(turn);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
const session = this.#ensureSession(ref.sessionId, "running", event.recordedAt);
|
|
419
|
+
if (isTerminalStatus(turn.status)) {
|
|
420
|
+
session.activeTurnIds.delete(ref.turnId);
|
|
421
|
+
if (session.status !== "closed")
|
|
422
|
+
session.status = session.activeTurnIds.size > 0 ? "running" : "idle";
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
session.activeTurnIds.add(ref.turnId);
|
|
426
|
+
if (session.status !== "closed")
|
|
427
|
+
session.status = "running";
|
|
428
|
+
}
|
|
429
|
+
session.updatedAt = event.recordedAt;
|
|
430
|
+
}
|
|
431
|
+
#finishTurn(turn) {
|
|
432
|
+
const result = resultTurn(turn);
|
|
433
|
+
for (const waiter of turn.waiters.splice(0))
|
|
434
|
+
waiter(result);
|
|
435
|
+
}
|
|
436
|
+
#withContext(event, sessionId, turnId) {
|
|
437
|
+
if (event.type === "message.delta" || event.type === "reasoning.delta" || event.type === "tool.started" || event.type === "tool.completed" || event.type === "token.usage" || event.type === "error") {
|
|
438
|
+
return {
|
|
439
|
+
...event,
|
|
440
|
+
sessionId: event.sessionId ?? sessionId,
|
|
441
|
+
...(turnId && !event.turnId ? { turnId } : {}),
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
return event;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
function eventToRef(provider, event, fallbackSessionId) {
|
|
448
|
+
if (!("turnId" in event) || !event.turnId)
|
|
449
|
+
return undefined;
|
|
450
|
+
const sessionId = "sessionId" in event && event.sessionId ? event.sessionId : fallbackSessionId;
|
|
451
|
+
if (!sessionId)
|
|
452
|
+
return undefined;
|
|
453
|
+
return { provider, sessionId, turnId: event.turnId };
|
|
454
|
+
}
|
|
455
|
+
function eventMatches(event, input) {
|
|
456
|
+
if (input.sessionId) {
|
|
457
|
+
if (!("sessionId" in event) || event.sessionId !== input.sessionId)
|
|
458
|
+
return false;
|
|
459
|
+
}
|
|
460
|
+
if (input.turnId) {
|
|
461
|
+
if (!("turnId" in event) || event.turnId !== input.turnId)
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
464
|
+
return true;
|
|
465
|
+
}
|
|
466
|
+
function snapshotSession(session) {
|
|
467
|
+
return {
|
|
468
|
+
...session.ref,
|
|
469
|
+
status: session.status,
|
|
470
|
+
createdAt: session.createdAt,
|
|
471
|
+
updatedAt: session.updatedAt,
|
|
472
|
+
activeTurnIds: [...session.activeTurnIds],
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
function snapshotTurn(turn) {
|
|
476
|
+
const runtimeOptions = turn.input ? resolveRuntimeOptions(turn.input) : {};
|
|
477
|
+
return {
|
|
478
|
+
...turn.ref,
|
|
479
|
+
status: turn.status,
|
|
480
|
+
...(runtimeOptions.cwd ? { cwd: runtimeOptions.cwd } : {}),
|
|
481
|
+
...(runtimeOptions.model ? { model: runtimeOptions.model } : {}),
|
|
482
|
+
...(turn.input?.options ? { options: turn.input.options } : {}),
|
|
483
|
+
...(turn.usage ? { usage: turn.usage } : {}),
|
|
484
|
+
...(turn.totalUsage ? { totalUsage: turn.totalUsage } : {}),
|
|
485
|
+
...(turn.contextWindow !== undefined ? { contextWindow: turn.contextWindow } : {}),
|
|
486
|
+
...(turn.startedAt ? { startedAt: turn.startedAt } : {}),
|
|
487
|
+
updatedAt: turn.updatedAt,
|
|
488
|
+
...(turn.completedAt ? { completedAt: turn.completedAt } : {}),
|
|
489
|
+
...(turn.error ? { error: turn.error } : {}),
|
|
490
|
+
eventCount: turn.events.length,
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
function resultTurn(turn) {
|
|
494
|
+
return {
|
|
495
|
+
...snapshotTurn(turn),
|
|
496
|
+
text: turn.text,
|
|
497
|
+
events: [...turn.events],
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
function completedEvent(sessionId, turnId, status, error) {
|
|
501
|
+
return {
|
|
502
|
+
type: "turn.completed",
|
|
503
|
+
sessionId,
|
|
504
|
+
turnId,
|
|
505
|
+
status,
|
|
506
|
+
...(error ? { error } : {}),
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
function isTerminalStatus(status) {
|
|
510
|
+
return status === "completed" || status === "failed" || status === "cancelled" || status === "timeout";
|
|
511
|
+
}
|
|
512
|
+
function turnKey(sessionId, turnId) {
|
|
513
|
+
return `${sessionId}\0${turnId}`;
|
|
514
|
+
}
|
|
515
|
+
function nowIso() {
|
|
516
|
+
return new Date().toISOString();
|
|
517
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ApprovalPolicy, ProviderId, ProviderRuntimeOptionsFor, RuntimeOptions, SandboxMode } from "./types.ts";
|
|
2
|
+
export type RuntimeOptionCarrier<P extends ProviderId = ProviderId> = {
|
|
3
|
+
options?: RuntimeOptions<P>;
|
|
4
|
+
cwd?: string;
|
|
5
|
+
model?: string;
|
|
6
|
+
approvalPolicy?: ApprovalPolicy;
|
|
7
|
+
sandbox?: SandboxMode;
|
|
8
|
+
};
|
|
9
|
+
export declare function resolveRuntimeOptions<P extends ProviderId>(input?: RuntimeOptionCarrier<P>, defaults?: RuntimeOptions<P>): RuntimeOptions<P>;
|
|
10
|
+
export declare function providerRuntimeOptions<P extends ProviderId>(options: RuntimeOptions<P>): ProviderRuntimeOptionsFor<P>;
|
|
11
|
+
export declare function mergeRuntimeOptions<P extends ProviderId>(...items: Array<RuntimeOptions<P> | undefined>): RuntimeOptions<P>;
|
|
12
|
+
//# sourceMappingURL=options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,UAAU,EACV,yBAAyB,EACzB,cAAc,EACd,WAAW,EACZ,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,oBAAoB,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU,IAAI;IACpE,OAAO,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,UAAU,EACxD,KAAK,GAAE,oBAAoB,CAAC,CAAC,CAAM,EACnC,QAAQ,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,GAC3B,cAAc,CAAC,CAAC,CAAC,CAEnB;AAED,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,UAAU,EACzD,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,GACzB,yBAAyB,CAAC,CAAC,CAAC,CAE9B;AAED,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,UAAU,EAAE,GAAG,KAAK,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAc3H"}
|
package/dist/options.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export function resolveRuntimeOptions(input = {}, defaults) {
|
|
2
|
+
return mergeRuntimeOptions(defaults, legacyRuntimeOptions(input), input.options);
|
|
3
|
+
}
|
|
4
|
+
export function providerRuntimeOptions(options) {
|
|
5
|
+
return options;
|
|
6
|
+
}
|
|
7
|
+
export function mergeRuntimeOptions(...items) {
|
|
8
|
+
const result = {};
|
|
9
|
+
for (const item of items) {
|
|
10
|
+
if (!item)
|
|
11
|
+
continue;
|
|
12
|
+
for (const [key, value] of Object.entries(item)) {
|
|
13
|
+
if (value === undefined)
|
|
14
|
+
continue;
|
|
15
|
+
if (key === "instructions") {
|
|
16
|
+
result.instructions = { ...result.instructions, ...item.instructions };
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
result[key] = value;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
function legacyRuntimeOptions(input) {
|
|
26
|
+
return {
|
|
27
|
+
...(input.cwd ? { cwd: input.cwd } : {}),
|
|
28
|
+
...(input.model ? { model: input.model } : {}),
|
|
29
|
+
...(input.approvalPolicy ? { approvalPolicy: input.approvalPolicy } : {}),
|
|
30
|
+
...(input.sandbox ? { sandbox: input.sandbox } : {}),
|
|
31
|
+
};
|
|
32
|
+
}
|
package/dist/port.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"port.d.ts","sourceRoot":"","sources":["../src/port.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEnD,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,UAAU,EAAE,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAEhG"}
|
package/dist/port.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider-options.typecheck.d.ts","sourceRoot":"","sources":["../src/provider-options.typecheck.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createPort } from "./index.js";
|
|
2
|
+
void codexPort.sessions.create({
|
|
3
|
+
options: {
|
|
4
|
+
effort: "high",
|
|
5
|
+
config: { model_reasoning_effort: "high" },
|
|
6
|
+
},
|
|
7
|
+
});
|
|
8
|
+
void claudePort.sessions.create({
|
|
9
|
+
options: {
|
|
10
|
+
effort: "xhigh",
|
|
11
|
+
permissionMode: "plan",
|
|
12
|
+
thinking: { type: "adaptive", display: "summarized" },
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
const codexInput = {
|
|
16
|
+
provider: "codex",
|
|
17
|
+
options: {
|
|
18
|
+
effort: "minimal",
|
|
19
|
+
reasoningSummary: "concise",
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
void codexInput;
|
|
23
|
+
// @ts-expect-error Claude-only options must not be accepted on a Codex port.
|
|
24
|
+
void codexPort.sessions.create({ options: { permissionMode: "plan" } });
|
|
25
|
+
// @ts-expect-error Provider-specific options are bound to createPort's selected provider.
|
|
26
|
+
void createPort({ provider: "codex", options: { provider: "claude", permissionMode: "plan" } });
|
|
27
|
+
// @ts-expect-error The public discriminated union must reject mismatched provider options.
|
|
28
|
+
const invalidInput = { provider: "codex", options: { provider: "claude", permissionMode: "plan" } };
|
|
29
|
+
void invalidInput;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AgentEvent, CancelTurnInput, CapabilitySet, CreateSessionInput, ProviderId, SessionRef, StartTurnInput } from "../types.ts";
|
|
2
|
+
export interface ProviderAdapter<P extends ProviderId = ProviderId> {
|
|
3
|
+
id: P;
|
|
4
|
+
capabilities: CapabilitySet<P>;
|
|
5
|
+
createSession(input?: CreateSessionInput<P>): Promise<SessionRef<P>>;
|
|
6
|
+
resumeSession(input: CreateSessionInput<P> & {
|
|
7
|
+
id: string;
|
|
8
|
+
}): Promise<SessionRef<P>>;
|
|
9
|
+
startTurn(input: StartTurnInput<P>): AsyncIterable<AgentEvent>;
|
|
10
|
+
cancelTurn(input: CancelTurnInput): Promise<void>;
|
|
11
|
+
events(): AsyncIterable<AgentEvent>;
|
|
12
|
+
close(): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../src/providers/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,eAAe,EACf,aAAa,EACb,kBAAkB,EAClB,UAAU,EACV,UAAU,EACV,cAAc,EACf,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,eAAe,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU;IAChE,EAAE,EAAE,CAAC,CAAC;IACN,YAAY,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;IAC/B,aAAa,CAAC,KAAK,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IACrE,aAAa,CAAC,KAAK,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IACrF,SAAS,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;IAC/D,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,MAAM,IAAI,aAAa,CAAC,UAAU,CAAC,CAAC;IACpC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CapabilitySet, CreatePortInputFor, Port, RuntimeOptions, StartTurnInput } from "../types.ts";
|
|
2
|
+
import type { ProviderAdapter } from "./adapter.ts";
|
|
3
|
+
type ClaudePortOptions = CreatePortInputFor<"claude">;
|
|
4
|
+
export declare const claudeCapabilities: CapabilitySet<"claude">;
|
|
5
|
+
export declare function createClaudePort(options: ClaudePortOptions): Promise<Port<"claude">>;
|
|
6
|
+
export declare function createClaudeAdapter(options: ClaudePortOptions): Promise<ProviderAdapter<"claude">>;
|
|
7
|
+
export declare function buildClaudeArgs(input: StartTurnInput<"claude">, defaults?: RuntimeOptions<"claude">): string[];
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=claude.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../src/providers/claude.ts"],"names":[],"mappings":"AAOA,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;AA0ID,wBAAgB,eAAe,CAAC,KAAK,EAAE,cAAc,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,EAAE,cAAc,CAAC,QAAQ,CAAC,GAAG,MAAM,EAAE,CAe9G"}
|