@abelxiaoxing/cadence 1.0.1 → 1.0.3
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/agents/contract-reviewer.md +2 -2
- package/agents/diagnosis-worker.md +3 -2
- package/agents/implementation-worker.md +3 -2
- package/package.json +1 -1
- package/prompts/abel-design.md +13 -3
- package/prompts/abel-implement.md +53 -13
- package/skills/abel-workflow/SKILL.md +60 -22
- package/src/candidate-preflight.ts +1468 -0
- package/src/child-session.ts +136 -30
- package/src/contracts.ts +1343 -63
- package/src/file-snapshot.ts +62 -8
- package/src/index.ts +99 -28
- package/src/parent-payload-bridge.ts +442 -0
- package/src/parent-provider.ts +166 -21
- package/src/patch.ts +331 -33
- package/src/result-store.ts +282 -7
- package/src/runtime.ts +1584 -154
- package/src/scheduler.ts +60 -16
- package/src/scoped-tools.ts +27 -6
- package/src/subagent-activity.ts +56 -25
- package/src/submit-tool.ts +15 -3
- package/src/worker.ts +114 -85
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AssistantMessage,
|
|
3
|
+
type AssistantMessageEventStream,
|
|
4
|
+
type Context,
|
|
5
|
+
createAssistantMessageEventStream,
|
|
6
|
+
type Model,
|
|
7
|
+
type Provider,
|
|
8
|
+
} from "@earendil-works/pi-ai";
|
|
9
|
+
|
|
10
|
+
export type ParentPayloadCallback = (
|
|
11
|
+
payload: unknown,
|
|
12
|
+
model: Model<string>,
|
|
13
|
+
) => unknown | undefined | Promise<unknown | undefined>;
|
|
14
|
+
|
|
15
|
+
export interface ParentModelKey {
|
|
16
|
+
provider: string;
|
|
17
|
+
id: string;
|
|
18
|
+
api: string;
|
|
19
|
+
baseUrl: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ParentPayloadCapture {
|
|
23
|
+
readonly generation: number;
|
|
24
|
+
readonly sessionId: string;
|
|
25
|
+
readonly modelKey: Readonly<ParentModelKey>;
|
|
26
|
+
readonly delegate: Provider;
|
|
27
|
+
readonly onPayload: ParentPayloadCallback;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ParentProviderRegistry {
|
|
31
|
+
getProvider(provider: string): Provider | undefined;
|
|
32
|
+
registerProvider(provider: Provider): void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface GenerationState {
|
|
36
|
+
readonly generation: number;
|
|
37
|
+
readonly sessionId: string;
|
|
38
|
+
active: boolean;
|
|
39
|
+
readonly ready: Map<string, ParentPayloadCapture>;
|
|
40
|
+
readonly installed: Map<string, Provider>;
|
|
41
|
+
tail: Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface ProvisionalCapture {
|
|
45
|
+
readonly state: GenerationState;
|
|
46
|
+
readonly modelKey: ParentModelKey;
|
|
47
|
+
readonly delegate: Provider;
|
|
48
|
+
readonly onPayload: ParentPayloadCallback;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const PROVIDER_WRAPPER = Symbol("cadence-parent-payload-provider");
|
|
52
|
+
|
|
53
|
+
function streamFailure(model: Model<string>): AssistantMessage {
|
|
54
|
+
return {
|
|
55
|
+
role: "assistant",
|
|
56
|
+
content: [],
|
|
57
|
+
api: model.api,
|
|
58
|
+
provider: model.provider,
|
|
59
|
+
model: model.id,
|
|
60
|
+
usage: {
|
|
61
|
+
input: 0,
|
|
62
|
+
output: 0,
|
|
63
|
+
cacheRead: 0,
|
|
64
|
+
cacheWrite: 0,
|
|
65
|
+
totalTokens: 0,
|
|
66
|
+
cost: {
|
|
67
|
+
input: 0,
|
|
68
|
+
output: 0,
|
|
69
|
+
cacheRead: 0,
|
|
70
|
+
cacheWrite: 0,
|
|
71
|
+
total: 0,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
stopReason: "error",
|
|
75
|
+
errorMessage: "parent provider stream failed",
|
|
76
|
+
timestamp: Date.now(),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function abortable<T>(
|
|
81
|
+
value: T | PromiseLike<T>,
|
|
82
|
+
signal?: AbortSignal,
|
|
83
|
+
): Promise<T> {
|
|
84
|
+
if (!signal) return Promise.resolve(value);
|
|
85
|
+
signal.throwIfAborted();
|
|
86
|
+
return new Promise<T>((resolve, reject) => {
|
|
87
|
+
let settled = false;
|
|
88
|
+
const finish = (callback: () => void) => {
|
|
89
|
+
if (settled) return;
|
|
90
|
+
settled = true;
|
|
91
|
+
signal.removeEventListener("abort", onAbort);
|
|
92
|
+
callback();
|
|
93
|
+
};
|
|
94
|
+
const onAbort = () => {
|
|
95
|
+
try {
|
|
96
|
+
signal.throwIfAborted();
|
|
97
|
+
} catch (error) {
|
|
98
|
+
finish(() => reject(error));
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
102
|
+
if (signal.aborted) onAbort();
|
|
103
|
+
Promise.resolve(value).then(
|
|
104
|
+
(result) => finish(() => resolve(result)),
|
|
105
|
+
(error) => finish(() => reject(error)),
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function modelKeyFor(model: Model<string>): ParentModelKey {
|
|
111
|
+
return {
|
|
112
|
+
provider: model.provider,
|
|
113
|
+
id: model.id,
|
|
114
|
+
api: model.api,
|
|
115
|
+
baseUrl: model.baseUrl,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function modelKeyId(modelKey: Readonly<ParentModelKey>): string {
|
|
120
|
+
return JSON.stringify([
|
|
121
|
+
modelKey.provider,
|
|
122
|
+
modelKey.id,
|
|
123
|
+
modelKey.api,
|
|
124
|
+
modelKey.baseUrl,
|
|
125
|
+
]);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function sameModelKey(
|
|
129
|
+
left: Readonly<ParentModelKey>,
|
|
130
|
+
right: Readonly<ParentModelKey>,
|
|
131
|
+
): boolean {
|
|
132
|
+
return (
|
|
133
|
+
left.provider === right.provider &&
|
|
134
|
+
left.id === right.id &&
|
|
135
|
+
left.api === right.api &&
|
|
136
|
+
left.baseUrl === right.baseUrl
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function requireSafePayload(
|
|
141
|
+
payload: unknown,
|
|
142
|
+
): asserts payload is Record<string, unknown> {
|
|
143
|
+
if (
|
|
144
|
+
typeof payload !== "object" ||
|
|
145
|
+
payload === null ||
|
|
146
|
+
Array.isArray(payload)
|
|
147
|
+
) {
|
|
148
|
+
throw new Error("parent payload bridge produced an unsafe payload");
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export class ParentPayloadBridge {
|
|
153
|
+
private nextGeneration = 0;
|
|
154
|
+
private state: GenerationState | undefined;
|
|
155
|
+
private readonly captureStates = new WeakMap<
|
|
156
|
+
ParentPayloadCapture,
|
|
157
|
+
GenerationState
|
|
158
|
+
>();
|
|
159
|
+
|
|
160
|
+
beginSession(sessionId: string): number {
|
|
161
|
+
this.invalidate();
|
|
162
|
+
const state: GenerationState = {
|
|
163
|
+
generation: ++this.nextGeneration,
|
|
164
|
+
sessionId,
|
|
165
|
+
active: true,
|
|
166
|
+
ready: new Map(),
|
|
167
|
+
installed: new Map(),
|
|
168
|
+
tail: Promise.resolve(),
|
|
169
|
+
};
|
|
170
|
+
this.state = state;
|
|
171
|
+
return state.generation;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
clear(): void {
|
|
175
|
+
this.invalidate();
|
|
176
|
+
this.nextGeneration++;
|
|
177
|
+
this.state = undefined;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
install(
|
|
181
|
+
model: Model<string>,
|
|
182
|
+
registry: ParentProviderRegistry,
|
|
183
|
+
): Provider | undefined {
|
|
184
|
+
const current = registry.getProvider(model.provider);
|
|
185
|
+
if (!current) return undefined;
|
|
186
|
+
const delegate = this.unwrapProvider(current);
|
|
187
|
+
const state = this.state;
|
|
188
|
+
if (state?.active) {
|
|
189
|
+
const key = modelKeyId(modelKeyFor(model));
|
|
190
|
+
state.installed.set(key, delegate);
|
|
191
|
+
const ready = state.ready.get(key);
|
|
192
|
+
if (ready && ready.delegate !== delegate) {
|
|
193
|
+
state.ready.delete(key);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const wrapped = this.wrapProvider(delegate);
|
|
197
|
+
registry.registerProvider(wrapped);
|
|
198
|
+
return wrapped;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
capture(
|
|
202
|
+
modelKey: ParentModelKey,
|
|
203
|
+
registry?: Pick<ParentProviderRegistry, "getProvider">,
|
|
204
|
+
): ParentPayloadCapture | undefined {
|
|
205
|
+
const state = this.state;
|
|
206
|
+
if (!state?.active) return undefined;
|
|
207
|
+
const capture = state.ready.get(modelKeyId(modelKey));
|
|
208
|
+
if (!capture || !sameModelKey(capture.modelKey, modelKey)) {
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
if (registry) {
|
|
212
|
+
const current = registry.getProvider(modelKey.provider);
|
|
213
|
+
if (!current || this.unwrapProvider(current) !== capture.delegate) {
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return capture;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
wrapProvider(delegate: Provider): Provider {
|
|
221
|
+
const original = this.unwrapProvider(delegate);
|
|
222
|
+
const stream = (
|
|
223
|
+
model: Model<string>,
|
|
224
|
+
context: Context,
|
|
225
|
+
options?: unknown,
|
|
226
|
+
) => this.forwardProvider(original, "stream", model, context, options);
|
|
227
|
+
const streamSimple = (
|
|
228
|
+
model: Model<string>,
|
|
229
|
+
context: Context,
|
|
230
|
+
options?: unknown,
|
|
231
|
+
) =>
|
|
232
|
+
this.forwardProvider(original, "streamSimple", model, context, options);
|
|
233
|
+
const boundMethods = new Map<
|
|
234
|
+
PropertyKey,
|
|
235
|
+
{
|
|
236
|
+
source: (...args: never[]) => unknown;
|
|
237
|
+
bound: (...args: never[]) => unknown;
|
|
238
|
+
}
|
|
239
|
+
>();
|
|
240
|
+
|
|
241
|
+
return new Proxy(original, {
|
|
242
|
+
get(target, property) {
|
|
243
|
+
if (property === PROVIDER_WRAPPER) return original;
|
|
244
|
+
if (property === "stream") return stream;
|
|
245
|
+
if (property === "streamSimple") return streamSimple;
|
|
246
|
+
const value = Reflect.get(target, property, target) as unknown;
|
|
247
|
+
if (typeof value !== "function") return value;
|
|
248
|
+
const current = boundMethods.get(property);
|
|
249
|
+
if (current?.source === value) return current.bound;
|
|
250
|
+
const source = value as (...args: never[]) => unknown;
|
|
251
|
+
const bound = source.bind(target);
|
|
252
|
+
boundMethods.set(property, { source, bound });
|
|
253
|
+
return bound;
|
|
254
|
+
},
|
|
255
|
+
set(target, property, value) {
|
|
256
|
+
return Reflect.set(target, property, value, target);
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async composePayload(
|
|
262
|
+
capture: ParentPayloadCapture | undefined,
|
|
263
|
+
payload: unknown,
|
|
264
|
+
model: Model<string>,
|
|
265
|
+
childOnPayload?: ParentPayloadCallback,
|
|
266
|
+
signal?: AbortSignal,
|
|
267
|
+
): Promise<Record<string, unknown>> {
|
|
268
|
+
if (!capture) throw new Error("parent payload bridge is unavailable");
|
|
269
|
+
const requestedKey = modelKeyFor(model);
|
|
270
|
+
const state = this.requireCurrentCapture(capture, requestedKey);
|
|
271
|
+
signal?.throwIfAborted();
|
|
272
|
+
|
|
273
|
+
let current = payload;
|
|
274
|
+
if (childOnPayload) {
|
|
275
|
+
const childResult = await abortable(
|
|
276
|
+
childOnPayload(current, model),
|
|
277
|
+
signal,
|
|
278
|
+
);
|
|
279
|
+
if (childResult !== undefined) current = childResult;
|
|
280
|
+
}
|
|
281
|
+
requireSafePayload(current);
|
|
282
|
+
signal?.throwIfAborted();
|
|
283
|
+
this.requireCurrentCapture(capture, requestedKey);
|
|
284
|
+
|
|
285
|
+
const predecessor = state.tail;
|
|
286
|
+
let release = () => {};
|
|
287
|
+
const completed = new Promise<void>((resolve) => {
|
|
288
|
+
release = () => resolve();
|
|
289
|
+
});
|
|
290
|
+
state.tail = predecessor.then(
|
|
291
|
+
() => completed,
|
|
292
|
+
() => completed,
|
|
293
|
+
);
|
|
294
|
+
let parentSettlement: Promise<void> | undefined;
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
await abortable(predecessor, signal);
|
|
298
|
+
signal?.throwIfAborted();
|
|
299
|
+
this.requireCurrentCapture(capture, requestedKey);
|
|
300
|
+
|
|
301
|
+
const parentCall = Promise.resolve(capture.onPayload(current, model));
|
|
302
|
+
parentSettlement = parentCall.then(
|
|
303
|
+
() => undefined,
|
|
304
|
+
() => undefined,
|
|
305
|
+
);
|
|
306
|
+
const parentResult = await abortable(parentCall, signal);
|
|
307
|
+
if (parentResult !== undefined) current = parentResult;
|
|
308
|
+
requireSafePayload(current);
|
|
309
|
+
|
|
310
|
+
let finalPayload: Record<string, unknown> = current;
|
|
311
|
+
if (model.api === "openai-responses") {
|
|
312
|
+
finalPayload = { ...current };
|
|
313
|
+
delete finalPayload.max_output_tokens;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
signal?.throwIfAborted();
|
|
317
|
+
this.requireCurrentCapture(capture, requestedKey);
|
|
318
|
+
return finalPayload;
|
|
319
|
+
} finally {
|
|
320
|
+
if (parentSettlement) void parentSettlement.then(release);
|
|
321
|
+
else release();
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private forwardProvider(
|
|
326
|
+
delegate: Provider,
|
|
327
|
+
method: "stream" | "streamSimple",
|
|
328
|
+
model: Model<string>,
|
|
329
|
+
context: Context,
|
|
330
|
+
options?: unknown,
|
|
331
|
+
): AssistantMessageEventStream {
|
|
332
|
+
const state = this.state;
|
|
333
|
+
const candidate = (options as { onPayload?: unknown } | null | undefined)
|
|
334
|
+
?.onPayload;
|
|
335
|
+
const onPayload =
|
|
336
|
+
typeof candidate === "function"
|
|
337
|
+
? (candidate as ParentPayloadCallback)
|
|
338
|
+
: undefined;
|
|
339
|
+
const provisional: ProvisionalCapture | undefined =
|
|
340
|
+
state?.active && onPayload
|
|
341
|
+
? {
|
|
342
|
+
state,
|
|
343
|
+
modelKey: modelKeyFor(model),
|
|
344
|
+
delegate,
|
|
345
|
+
onPayload,
|
|
346
|
+
}
|
|
347
|
+
: undefined;
|
|
348
|
+
const source =
|
|
349
|
+
method === "stream"
|
|
350
|
+
? delegate.stream(model, context, options as never)
|
|
351
|
+
: delegate.streamSimple(model, context, options as never);
|
|
352
|
+
const output = createAssistantMessageEventStream();
|
|
353
|
+
void this.mirrorStream(source, output, model, provisional);
|
|
354
|
+
return output;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
private async mirrorStream(
|
|
358
|
+
source: AssistantMessageEventStream,
|
|
359
|
+
output: AssistantMessageEventStream,
|
|
360
|
+
model: Model<string>,
|
|
361
|
+
provisional: ProvisionalCapture | undefined,
|
|
362
|
+
): Promise<void> {
|
|
363
|
+
let successful = false;
|
|
364
|
+
try {
|
|
365
|
+
for await (const event of source) {
|
|
366
|
+
if (event.type === "done") successful = true;
|
|
367
|
+
output.push(event);
|
|
368
|
+
}
|
|
369
|
+
if (successful && provisional) this.commitCapture(provisional);
|
|
370
|
+
} catch {
|
|
371
|
+
const error = streamFailure(model);
|
|
372
|
+
output.push({ type: "error", reason: "error", error });
|
|
373
|
+
} finally {
|
|
374
|
+
output.end();
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
private commitCapture(provisional: ProvisionalCapture): void {
|
|
379
|
+
const { state } = provisional;
|
|
380
|
+
if (!state.active || this.state !== state) return;
|
|
381
|
+
const key = modelKeyId(provisional.modelKey);
|
|
382
|
+
if (
|
|
383
|
+
state.installed.has(key) &&
|
|
384
|
+
state.installed.get(key) !== provisional.delegate
|
|
385
|
+
) {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
const capture: ParentPayloadCapture = Object.freeze({
|
|
389
|
+
generation: state.generation,
|
|
390
|
+
sessionId: state.sessionId,
|
|
391
|
+
modelKey: Object.freeze({ ...provisional.modelKey }),
|
|
392
|
+
delegate: provisional.delegate,
|
|
393
|
+
onPayload: provisional.onPayload,
|
|
394
|
+
});
|
|
395
|
+
state.ready.set(key, capture);
|
|
396
|
+
this.captureStates.set(capture, state);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
private requireCurrentCapture(
|
|
400
|
+
capture: ParentPayloadCapture | undefined,
|
|
401
|
+
modelKey: ParentModelKey,
|
|
402
|
+
): GenerationState {
|
|
403
|
+
const state = this.state;
|
|
404
|
+
if (
|
|
405
|
+
!capture ||
|
|
406
|
+
!state?.active ||
|
|
407
|
+
this.captureStates.get(capture) !== state ||
|
|
408
|
+
capture.generation !== state.generation ||
|
|
409
|
+
capture.sessionId !== state.sessionId ||
|
|
410
|
+
!sameModelKey(capture.modelKey, modelKey) ||
|
|
411
|
+
state.ready.get(modelKeyId(modelKey)) !== capture
|
|
412
|
+
) {
|
|
413
|
+
throw new Error("parent payload bridge is unavailable");
|
|
414
|
+
}
|
|
415
|
+
return state;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
private unwrapProvider(provider: Provider): Provider {
|
|
419
|
+
let current = provider;
|
|
420
|
+
const seen = new Set<Provider>();
|
|
421
|
+
while (!seen.has(current)) {
|
|
422
|
+
seen.add(current);
|
|
423
|
+
const candidate = Reflect.get(current, PROVIDER_WRAPPER) as unknown;
|
|
424
|
+
if (
|
|
425
|
+
typeof candidate !== "object" ||
|
|
426
|
+
candidate === null ||
|
|
427
|
+
seen.has(candidate as Provider)
|
|
428
|
+
) {
|
|
429
|
+
return current;
|
|
430
|
+
}
|
|
431
|
+
current = candidate as Provider;
|
|
432
|
+
}
|
|
433
|
+
return current;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
private invalidate(): void {
|
|
437
|
+
if (!this.state) return;
|
|
438
|
+
this.state.active = false;
|
|
439
|
+
this.state.ready.clear();
|
|
440
|
+
this.state.installed.clear();
|
|
441
|
+
}
|
|
442
|
+
}
|
package/src/parent-provider.ts
CHANGED
|
@@ -7,6 +7,59 @@ import {
|
|
|
7
7
|
type ExtensionContext,
|
|
8
8
|
ModelRuntime,
|
|
9
9
|
} from "@earendil-works/pi-coding-agent";
|
|
10
|
+
import type { ChildFailure } from "./contracts.ts";
|
|
11
|
+
import type {
|
|
12
|
+
ParentPayloadBridge,
|
|
13
|
+
ParentPayloadCallback,
|
|
14
|
+
ParentPayloadCapture,
|
|
15
|
+
} from "./parent-payload-bridge.ts";
|
|
16
|
+
|
|
17
|
+
export interface PhasePayloadBridge {
|
|
18
|
+
readonly bridge: ParentPayloadBridge;
|
|
19
|
+
readonly capture: ParentPayloadCapture;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type PhaseRuntimeFailure = Extract<
|
|
23
|
+
ChildFailure,
|
|
24
|
+
{ kind: "cancelled" | "environment" | "transport" }
|
|
25
|
+
>;
|
|
26
|
+
|
|
27
|
+
export type PhaseRuntimeResult =
|
|
28
|
+
| { ok: true; modelRuntime: ModelRuntime; model: Model<string> }
|
|
29
|
+
| { ok: false; error: string; failure: PhaseRuntimeFailure };
|
|
30
|
+
|
|
31
|
+
function phaseRuntimeFailure(
|
|
32
|
+
failure: PhaseRuntimeFailure,
|
|
33
|
+
error: string,
|
|
34
|
+
): Extract<PhaseRuntimeResult, { ok: false }> {
|
|
35
|
+
return { ok: false, error, failure };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function cancelledPhaseRuntime(): Extract<PhaseRuntimeResult, { ok: false }> {
|
|
39
|
+
return phaseRuntimeFailure(
|
|
40
|
+
{ kind: "cancelled", code: "cancelled" },
|
|
41
|
+
"child phase cancelled",
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isCancellation(error: unknown, signal?: AbortSignal): boolean {
|
|
46
|
+
return signal?.aborted === true && error === signal.reason;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function phasePayloadCallback(
|
|
50
|
+
payloadBridge: PhasePayloadBridge,
|
|
51
|
+
childOnPayload: ParentPayloadCallback | undefined,
|
|
52
|
+
signal: AbortSignal | undefined,
|
|
53
|
+
): ParentPayloadCallback {
|
|
54
|
+
return (payload, model) =>
|
|
55
|
+
payloadBridge.bridge.composePayload(
|
|
56
|
+
payloadBridge.capture,
|
|
57
|
+
payload,
|
|
58
|
+
model,
|
|
59
|
+
childOnPayload,
|
|
60
|
+
signal,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
10
63
|
|
|
11
64
|
export async function runtimeForProvider(
|
|
12
65
|
provider: Provider,
|
|
@@ -35,6 +88,7 @@ export function phaseProvider(
|
|
|
35
88
|
baseUrl?: string;
|
|
36
89
|
env?: Record<string, string>;
|
|
37
90
|
},
|
|
91
|
+
payloadBridge: PhasePayloadBridge,
|
|
38
92
|
): Provider {
|
|
39
93
|
return {
|
|
40
94
|
...parent,
|
|
@@ -54,48 +108,139 @@ export function phaseProvider(
|
|
|
54
108
|
},
|
|
55
109
|
},
|
|
56
110
|
stream(model, context, options) {
|
|
111
|
+
const onPayload = phasePayloadCallback(
|
|
112
|
+
payloadBridge,
|
|
113
|
+
options?.onPayload as ParentPayloadCallback | undefined,
|
|
114
|
+
options?.signal,
|
|
115
|
+
);
|
|
57
116
|
return parent.stream(model, context, {
|
|
58
117
|
...options,
|
|
59
118
|
apiKey: auth.apiKey,
|
|
60
|
-
headers: { ...
|
|
61
|
-
env: { ...
|
|
119
|
+
headers: { ...options?.headers, ...auth.headers },
|
|
120
|
+
env: { ...options?.env, ...auth.env },
|
|
62
121
|
maxRetries: 0,
|
|
122
|
+
onPayload,
|
|
63
123
|
} as never);
|
|
64
124
|
},
|
|
65
125
|
streamSimple(model, context, options) {
|
|
126
|
+
const onPayload = phasePayloadCallback(
|
|
127
|
+
payloadBridge,
|
|
128
|
+
options?.onPayload as ParentPayloadCallback | undefined,
|
|
129
|
+
options?.signal,
|
|
130
|
+
);
|
|
66
131
|
return parent.streamSimple(model, context, {
|
|
67
132
|
...options,
|
|
68
133
|
apiKey: auth.apiKey,
|
|
69
|
-
headers: { ...
|
|
70
|
-
env: { ...
|
|
134
|
+
headers: { ...options?.headers, ...auth.headers },
|
|
135
|
+
env: { ...options?.env, ...auth.env },
|
|
71
136
|
maxRetries: 0,
|
|
137
|
+
onPayload,
|
|
72
138
|
});
|
|
73
139
|
},
|
|
74
140
|
};
|
|
75
141
|
}
|
|
76
142
|
|
|
143
|
+
function modelKeyFor(model: Model<string>) {
|
|
144
|
+
return {
|
|
145
|
+
provider: model.provider,
|
|
146
|
+
id: model.id,
|
|
147
|
+
api: model.api,
|
|
148
|
+
baseUrl: model.baseUrl,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function sameSelectedModel(
|
|
153
|
+
left: Model<string>,
|
|
154
|
+
right: ReturnType<typeof modelKeyFor>,
|
|
155
|
+
): boolean {
|
|
156
|
+
return (
|
|
157
|
+
left.provider === right.provider &&
|
|
158
|
+
left.id === right.id &&
|
|
159
|
+
left.api === right.api &&
|
|
160
|
+
left.baseUrl === right.baseUrl
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
77
164
|
export async function runtimeFromContext(
|
|
78
165
|
ctx: Pick<ExtensionContext, "model" | "modelRegistry">,
|
|
166
|
+
payloadBridge: ParentPayloadBridge,
|
|
79
167
|
signal?: AbortSignal,
|
|
80
|
-
): Promise<
|
|
81
|
-
signal?.
|
|
82
|
-
if (!ctx.model)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
168
|
+
): Promise<PhaseRuntimeResult> {
|
|
169
|
+
if (signal?.aborted) return cancelledPhaseRuntime();
|
|
170
|
+
if (!ctx.model) {
|
|
171
|
+
return phaseRuntimeFailure(
|
|
172
|
+
{ kind: "environment", code: "sandbox-runtime-unavailable" },
|
|
173
|
+
"parent model is unavailable",
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
const selectedModel = ctx.model;
|
|
177
|
+
const selectedModelKey = modelKeyFor(selectedModel);
|
|
178
|
+
let resolved: Awaited<
|
|
179
|
+
ReturnType<typeof ctx.modelRegistry.getApiKeyAndHeaders>
|
|
180
|
+
>;
|
|
181
|
+
try {
|
|
182
|
+
resolved = await abortable(
|
|
183
|
+
ctx.modelRegistry.getApiKeyAndHeaders(selectedModel),
|
|
184
|
+
signal,
|
|
185
|
+
);
|
|
186
|
+
} catch (error) {
|
|
187
|
+
if (isCancellation(error, signal)) return cancelledPhaseRuntime();
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
190
|
+
if (!resolved.ok) {
|
|
191
|
+
return phaseRuntimeFailure(
|
|
192
|
+
{ kind: "environment", code: "sandbox-runtime-unavailable" },
|
|
193
|
+
"phase authentication is unavailable",
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
if (signal?.aborted) return cancelledPhaseRuntime();
|
|
94
197
|
const model = {
|
|
95
|
-
...
|
|
96
|
-
baseUrl: resolved.baseUrl ??
|
|
198
|
+
...selectedModel,
|
|
199
|
+
baseUrl: resolved.baseUrl ?? selectedModel.baseUrl,
|
|
97
200
|
} as Model<string>;
|
|
98
|
-
|
|
201
|
+
const effectiveModelKey = modelKeyFor(model);
|
|
202
|
+
const capture = payloadBridge.capture(effectiveModelKey, ctx.modelRegistry);
|
|
203
|
+
if (!capture) {
|
|
204
|
+
return phaseRuntimeFailure(
|
|
205
|
+
{ kind: "transport", code: "transport-failure" },
|
|
206
|
+
"parent payload bridge is unavailable",
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
const captureIsReady = () => {
|
|
210
|
+
const currentModel = ctx.model;
|
|
211
|
+
return (
|
|
212
|
+
currentModel !== undefined &&
|
|
213
|
+
sameSelectedModel(currentModel, selectedModelKey) &&
|
|
214
|
+
payloadBridge.capture(effectiveModelKey, ctx.modelRegistry) === capture
|
|
215
|
+
);
|
|
216
|
+
};
|
|
217
|
+
if (!captureIsReady()) {
|
|
218
|
+
return phaseRuntimeFailure(
|
|
219
|
+
{ kind: "transport", code: "transport-failure" },
|
|
220
|
+
"parent payload bridge is unavailable",
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
const delegate = capture.delegate;
|
|
224
|
+
if (!delegate) throw new Error("parent Provider is unavailable");
|
|
225
|
+
const provider = phaseProvider(delegate, resolved, {
|
|
226
|
+
bridge: payloadBridge,
|
|
227
|
+
capture,
|
|
228
|
+
});
|
|
229
|
+
let modelRuntime: ModelRuntime;
|
|
230
|
+
try {
|
|
231
|
+
modelRuntime = await runtimeForProvider(provider, signal);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
if (isCancellation(error, signal)) return cancelledPhaseRuntime();
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
if (signal?.aborted) return cancelledPhaseRuntime();
|
|
237
|
+
if (!captureIsReady()) {
|
|
238
|
+
return phaseRuntimeFailure(
|
|
239
|
+
{ kind: "transport", code: "transport-failure" },
|
|
240
|
+
"parent payload bridge is unavailable",
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
return { ok: true, modelRuntime, model };
|
|
99
244
|
}
|
|
100
245
|
|
|
101
246
|
function abortable<T>(promise: Promise<T>, signal?: AbortSignal): Promise<T> {
|