@yolk-sdk/harness 0.1.0-canary.77
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/LICENSE +21 -0
- package/README.md +134 -0
- package/dist/coordinator.d.mts +62 -0
- package/dist/coordinator.d.mts.map +1 -0
- package/dist/coordinator.mjs +129 -0
- package/dist/coordinator.mjs.map +1 -0
- package/dist/driver/durable-object.d.mts +20 -0
- package/dist/driver/durable-object.d.mts.map +1 -0
- package/dist/driver/durable-object.mjs +12 -0
- package/dist/driver/durable-object.mjs.map +1 -0
- package/dist/driver/memory.d.mts +21 -0
- package/dist/driver/memory.d.mts.map +1 -0
- package/dist/driver/memory.mjs +15 -0
- package/dist/driver/memory.mjs.map +1 -0
- package/dist/driver.d.mts +67 -0
- package/dist/driver.d.mts.map +1 -0
- package/dist/driver.mjs +95 -0
- package/dist/driver.mjs.map +1 -0
- package/dist/inbox.d.mts +91 -0
- package/dist/inbox.d.mts.map +1 -0
- package/dist/inbox.mjs +224 -0
- package/dist/inbox.mjs.map +1 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.mjs +1 -0
- package/dist/outcome.d.mts +61 -0
- package/dist/outcome.d.mts.map +1 -0
- package/dist/outcome.mjs +159 -0
- package/dist/outcome.mjs.map +1 -0
- package/dist/store.d.mts +25 -0
- package/dist/store.d.mts.map +1 -0
- package/dist/store.mjs +86 -0
- package/dist/store.mjs.map +1 -0
- package/package.json +90 -0
- package/src/coordinator.ts +254 -0
- package/src/driver/durable-object.ts +34 -0
- package/src/driver/memory.ts +38 -0
- package/src/driver.ts +250 -0
- package/src/inbox.ts +454 -0
- package/src/index.ts +2 -0
- package/src/outcome.ts +316 -0
- package/src/store.ts +137 -0
package/dist/driver.mjs
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { makeCoordinator } from "./coordinator.mjs";
|
|
2
|
+
import { Inbox } from "./inbox.mjs";
|
|
3
|
+
import { RunStore } from "./store.mjs";
|
|
4
|
+
import { Cause, Context, Data, Effect, Exit, Layer, Semaphore } from "effect";
|
|
5
|
+
//#region src/driver.ts
|
|
6
|
+
const defaultMaxResumeAttempts = 10;
|
|
7
|
+
var InvalidMaxResumeAttempts = class extends Data.TaggedError("InvalidMaxResumeAttempts") {};
|
|
8
|
+
const isValidMaxResumeAttempts = (value) => Number.isSafeInteger(value) && value >= 0;
|
|
9
|
+
var Driver = class extends Context.Service()("@yolk-sdk/harness/Driver") {};
|
|
10
|
+
const makeHarness = (input) => Layer.mergeAll(input.driver, input.store, input.inbox);
|
|
11
|
+
const admit = (item) => Effect.flatMap(Driver, (driver) => driver.admit(item));
|
|
12
|
+
const makeValidatedDriverLayer = (maxResumeAttempts, hostDrain) => Layer.effect(Driver, Effect.gen(function* () {
|
|
13
|
+
const store = yield* RunStore;
|
|
14
|
+
const inbox = yield* Inbox;
|
|
15
|
+
const sweep = yield* Semaphore.make(1);
|
|
16
|
+
const acquired = /* @__PURE__ */ new Set();
|
|
17
|
+
const coordinator = yield* makeCoordinator({
|
|
18
|
+
drain: (runId, force, scope) => inbox.beginDrain(runId, scope).pipe(Effect.flatMap((begun) => {
|
|
19
|
+
if (begun._tag === "Skip") return Effect.void;
|
|
20
|
+
return Effect.suspend(() => hostDrain(runId, force, scope, {
|
|
21
|
+
drainToken: begun.drainToken,
|
|
22
|
+
readyResponses: begun.readyResponses
|
|
23
|
+
})).pipe(Effect.provideService(Inbox, inbox), Effect.provideService(RunStore, store), Effect.onExit((exit) => inbox.endDrain(runId, begun.drainToken, Exit.isSuccess(exit))));
|
|
24
|
+
})),
|
|
25
|
+
started: (runId) => store.claim(runId).pipe(Effect.tap(() => Effect.sync(() => {
|
|
26
|
+
acquired.add(runId);
|
|
27
|
+
}))),
|
|
28
|
+
settled: (runId, exit, reason) => Effect.suspend(() => {
|
|
29
|
+
const owned = acquired.delete(runId);
|
|
30
|
+
if (owned && (reason === "user" || reason === void 0 && !Exit.hasInterrupts(exit))) return store.release(runId);
|
|
31
|
+
if (reason === "user" && !owned) return store.isClaimed(runId).pipe(Effect.flatMap((claimed) => claimed ? store.release(runId) : Effect.void));
|
|
32
|
+
return Effect.void;
|
|
33
|
+
}).pipe(Effect.exit, Effect.flatMap((settledExit) => {
|
|
34
|
+
if (Exit.isSuccess(settledExit)) return Effect.void;
|
|
35
|
+
return Effect.failCause(Exit.isFailure(exit) ? Cause.combine(exit.cause, settledExit.cause) : settledExit.cause);
|
|
36
|
+
}))
|
|
37
|
+
});
|
|
38
|
+
const resumeSuspended = sweep.withPermits(1)(Effect.gen(function* () {
|
|
39
|
+
const claimed = yield* store.claimed;
|
|
40
|
+
const resumed = [];
|
|
41
|
+
const exhausted = [];
|
|
42
|
+
for (const runId of claimed) {
|
|
43
|
+
const admission = yield* inbox.admitRecovery(runId, Effect.gen(function* () {
|
|
44
|
+
if (yield* coordinator.isActive(runId)) return { _tag: "Skip" };
|
|
45
|
+
if (!(yield* store.isClaimed(runId))) return { _tag: "Skip" };
|
|
46
|
+
if ((yield* store.resumeCount(runId)) >= maxResumeAttempts) {
|
|
47
|
+
yield* store.release(runId);
|
|
48
|
+
return { _tag: "Exhausted" };
|
|
49
|
+
}
|
|
50
|
+
yield* store.incrementResumeCount(runId);
|
|
51
|
+
return { _tag: "Resume" };
|
|
52
|
+
}), coordinator.wake(runId, "input"));
|
|
53
|
+
if (admission._tag === "Resumed") resumed.push(runId);
|
|
54
|
+
else if (admission._tag === "Exhausted") exhausted.push(runId);
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
resumed,
|
|
58
|
+
exhausted
|
|
59
|
+
};
|
|
60
|
+
}));
|
|
61
|
+
return Driver.of({
|
|
62
|
+
active: coordinator.active,
|
|
63
|
+
isActive: coordinator.isActive,
|
|
64
|
+
run: (runId) => {
|
|
65
|
+
const continueRun = () => inbox.startIfUnblocked(runId, coordinator.captureRun(runId)).pipe(Effect.flatMap((ticket) => {
|
|
66
|
+
if (ticket === void 0) return Effect.void;
|
|
67
|
+
if (ticket._tag === "Stopping") return ticket.awaitSettlement.pipe(Effect.andThen(Effect.suspend(continueRun)));
|
|
68
|
+
return ticket.join;
|
|
69
|
+
}));
|
|
70
|
+
return continueRun();
|
|
71
|
+
},
|
|
72
|
+
wake: (runId, scope = "input") => inbox.wakeIfUnblocked(runId, scope, coordinator.wake(runId, scope)).pipe(Effect.asVoid),
|
|
73
|
+
interrupt: (runId, interruptOptions) => coordinator.interrupt(runId, interruptOptions?.reason ?? "user", interruptOptions),
|
|
74
|
+
awaitIdle: coordinator.awaitIdle,
|
|
75
|
+
resumeSuspended,
|
|
76
|
+
admit: (item) => inbox.enqueueAndWake(item, coordinator.wake(item.runId, item.delivery === "steer" ? "steer" : "input")),
|
|
77
|
+
pause: (runId, requestIds, drainToken) => inbox.park(runId, requestIds, drainToken),
|
|
78
|
+
resumeHitl: (runId, admission) => inbox.acceptHitl(runId, admission, coordinator.wake(runId, "input")),
|
|
79
|
+
stop: (runId) => inbox.invalidate(runId, coordinator.terminalStop(runId, "user").pipe(Effect.map((receipt) => receipt._tag === "Interrupted" || receipt._tag === "LiveStopping")), store.isClaimed(runId).pipe(Effect.flatMap((claimed) => claimed ? store.release(runId) : Effect.void))).pipe(Effect.map((result) => {
|
|
80
|
+
if (result.interrupted) return { _tag: "Interrupted" };
|
|
81
|
+
if (result.hadPark) return { _tag: "ParkCleared" };
|
|
82
|
+
return { _tag: "Idle" };
|
|
83
|
+
}))
|
|
84
|
+
});
|
|
85
|
+
}));
|
|
86
|
+
function makeDriverLayer(options) {
|
|
87
|
+
const hostDrain = options?.drain ?? ((_runId, _force, _scope, _context) => Effect.void);
|
|
88
|
+
const maxResumeAttempts = options?.maxResumeAttempts ?? 10;
|
|
89
|
+
if (!isValidMaxResumeAttempts(maxResumeAttempts)) return Layer.effect(Driver, Effect.fail(new InvalidMaxResumeAttempts({ maxResumeAttempts })));
|
|
90
|
+
return makeValidatedDriverLayer(maxResumeAttempts, hostDrain);
|
|
91
|
+
}
|
|
92
|
+
//#endregion
|
|
93
|
+
export { Driver, InvalidMaxResumeAttempts, admit, defaultMaxResumeAttempts, makeDriverLayer, makeHarness };
|
|
94
|
+
|
|
95
|
+
//# sourceMappingURL=driver.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"driver.mjs","names":[],"sources":["../src/driver.ts"],"sourcesContent":["import { Cause, Context, Data, Effect, Exit, Layer, Semaphore } from 'effect'\nimport { makeCoordinator, type Promotable } from './coordinator.ts'\nimport {\n Inbox,\n type HitlAdmission,\n type HitlDecision,\n type InboxItem,\n type ParkedResponse,\n type PauseDecision\n} from './inbox.ts'\nimport { RunStore } from './store.ts'\n\nexport type InterruptReason = 'user' | 'shutdown'\n\nexport const defaultMaxResumeAttempts = 10\n\nexport class InvalidMaxResumeAttempts extends Data.TaggedError('InvalidMaxResumeAttempts')<{\n readonly maxResumeAttempts: number\n}> {}\n\nconst isValidMaxResumeAttempts = (value: number) => Number.isSafeInteger(value) && value >= 0\n\nexport type ResumeSuspendedResult = {\n readonly resumed: ReadonlyArray<string>\n readonly exhausted: ReadonlyArray<string>\n}\n\nexport type DrainContext = {\n readonly drainToken: string\n readonly readyResponses: ReadonlyArray<ParkedResponse>\n}\n\nexport type Drain = (\n runId: string,\n force: boolean,\n scope: Promotable,\n context: DrainContext\n) => Effect.Effect<void, never, Inbox | RunStore>\n\nexport type DriverLayerOptions = {\n readonly drain?: Drain\n readonly maxResumeAttempts?: number\n}\n\nexport type StopDecision =\n | { readonly _tag: 'Interrupted' }\n | { readonly _tag: 'ParkCleared' }\n | { readonly _tag: 'Idle' }\n\nexport type DriverShape = {\n readonly active: Effect.Effect<ReadonlySet<string>>\n readonly isActive: (runId: string) => Effect.Effect<boolean>\n readonly run: (runId: string) => Effect.Effect<void>\n readonly wake: (runId: string, scope?: Promotable) => Effect.Effect<void>\n readonly interrupt: (\n runId: string,\n options?: { readonly reason?: InterruptReason; readonly awaitSettlement?: boolean }\n ) => Effect.Effect<boolean>\n readonly awaitIdle: (runId: string) => Effect.Effect<void>\n readonly resumeSuspended: Effect.Effect<ResumeSuspendedResult>\n readonly admit: (item: InboxItem) => Effect.Effect<void>\n readonly pause: (\n runId: string,\n requestIds: ReadonlyArray<string>,\n drainToken: string\n ) => Effect.Effect<PauseDecision>\n readonly resumeHitl: (runId: string, admission: HitlAdmission) => Effect.Effect<HitlDecision>\n readonly stop: (runId: string) => Effect.Effect<StopDecision>\n}\n\nexport class Driver extends Context.Service<Driver, DriverShape>()('@yolk-sdk/harness/Driver') {}\n\nexport const makeHarness = <DE, DR, SE, SR, IE, IR>(input: {\n readonly driver: Layer.Layer<Driver, DE, DR>\n readonly store: Layer.Layer<RunStore, SE, SR>\n readonly inbox: Layer.Layer<Inbox, IE, IR>\n}): Layer.Layer<Driver | RunStore | Inbox, DE | SE | IE, DR | SR | IR> =>\n Layer.mergeAll(input.driver, input.store, input.inbox)\n\nexport const admit = (item: InboxItem) => Effect.flatMap(Driver, driver => driver.admit(item))\n\nconst makeValidatedDriverLayer = (\n maxResumeAttempts: number,\n hostDrain: Drain\n): Layer.Layer<Driver, never, RunStore | Inbox> =>\n Layer.effect(\n Driver,\n Effect.gen(function* () {\n const store = yield* RunStore\n const inbox = yield* Inbox\n const sweep = yield* Semaphore.make(1)\n const acquired = new Set<string>()\n const coordinator = yield* makeCoordinator<string, never, InterruptReason>({\n drain: (runId, force, scope) =>\n inbox.beginDrain(runId, scope).pipe(\n Effect.flatMap(begun => {\n if (begun._tag === 'Skip') return Effect.void\n return Effect.suspend(() =>\n hostDrain(runId, force, scope, {\n drainToken: begun.drainToken,\n readyResponses: begun.readyResponses\n })\n ).pipe(\n Effect.provideService(Inbox, inbox),\n Effect.provideService(RunStore, store),\n Effect.onExit(exit => inbox.endDrain(runId, begun.drainToken, Exit.isSuccess(exit)))\n )\n })\n ),\n started: runId =>\n store.claim(runId).pipe(\n Effect.tap(() =>\n Effect.sync(() => {\n acquired.add(runId)\n })\n )\n ),\n settled: (runId, exit, reason) =>\n Effect.suspend(() => {\n const owned = acquired.delete(runId)\n if (\n owned &&\n (reason === 'user' || (reason === undefined && !Exit.hasInterrupts(exit)))\n ) {\n return store.release(runId)\n }\n // Acquisition receipt guards automatic failed-start settlement only.\n // Explicit user-terminal authority still releases a leftover claim\n // after this owner has actually settled, matching Idle/Settling stop.\n if (reason === 'user' && !owned) {\n return store\n .isClaimed(runId)\n .pipe(Effect.flatMap(claimed => (claimed ? store.release(runId) : Effect.void)))\n }\n return Effect.void\n }).pipe(\n Effect.exit,\n Effect.flatMap(settledExit => {\n if (Exit.isSuccess(settledExit)) return Effect.void\n return Effect.failCause(\n Exit.isFailure(exit)\n ? Cause.combine(exit.cause, settledExit.cause)\n : settledExit.cause\n )\n })\n )\n })\n\n const resumeSuspended = sweep.withPermits(1)(\n Effect.gen(function* () {\n const claimed = yield* store.claimed\n const resumed: Array<string> = []\n const exhausted: Array<string> = []\n\n for (const runId of claimed) {\n const admission = yield* inbox.admitRecovery(\n runId,\n Effect.gen(function* () {\n if (yield* coordinator.isActive(runId)) return { _tag: 'Skip' } as const\n if (!(yield* store.isClaimed(runId))) return { _tag: 'Skip' } as const\n const count = yield* store.resumeCount(runId)\n if (count >= maxResumeAttempts) {\n yield* store.release(runId)\n return { _tag: 'Exhausted' } as const\n }\n yield* store.incrementResumeCount(runId)\n return { _tag: 'Resume' } as const\n }),\n coordinator.wake(runId, 'input')\n )\n if (admission._tag === 'Resumed') resumed.push(runId)\n else if (admission._tag === 'Exhausted') exhausted.push(runId)\n }\n\n return { resumed, exhausted }\n })\n )\n\n return Driver.of({\n active: coordinator.active,\n isActive: coordinator.isActive,\n run: runId => {\n const continueRun = (): Effect.Effect<void> =>\n inbox.startIfUnblocked(runId, coordinator.captureRun(runId)).pipe(\n Effect.flatMap(ticket => {\n if (ticket === undefined) return Effect.void\n if (ticket._tag === 'Stopping') {\n return ticket.awaitSettlement.pipe(Effect.andThen(Effect.suspend(continueRun)))\n }\n return ticket.join\n })\n )\n return continueRun()\n },\n wake: (runId, scope = 'input') =>\n inbox.wakeIfUnblocked(runId, scope, coordinator.wake(runId, scope)).pipe(Effect.asVoid),\n interrupt: (runId, interruptOptions) =>\n coordinator.interrupt(runId, interruptOptions?.reason ?? 'user', interruptOptions),\n awaitIdle: coordinator.awaitIdle,\n resumeSuspended,\n admit: item =>\n inbox.enqueueAndWake(\n item,\n coordinator.wake(item.runId, item.delivery === 'steer' ? 'steer' : 'input')\n ),\n pause: (runId, requestIds, drainToken) => inbox.park(runId, requestIds, drainToken),\n resumeHitl: (runId, admission) =>\n inbox.acceptHitl(runId, admission, coordinator.wake(runId, 'input')),\n stop: runId =>\n inbox\n .invalidate(\n runId,\n coordinator\n .terminalStop(runId, 'user')\n .pipe(\n Effect.map(\n receipt => receipt._tag === 'Interrupted' || receipt._tag === 'LiveStopping'\n )\n ),\n store\n .isClaimed(runId)\n .pipe(Effect.flatMap(claimed => (claimed ? store.release(runId) : Effect.void)))\n )\n .pipe(\n Effect.map(result => {\n if (result.interrupted) return { _tag: 'Interrupted' } as const\n if (result.hadPark) return { _tag: 'ParkCleared' } as const\n return { _tag: 'Idle' } as const\n })\n )\n })\n })\n )\n\nexport function makeDriverLayer(): Layer.Layer<Driver, never, RunStore | Inbox>\nexport function makeDriverLayer(options?: {\n readonly drain?: Drain\n readonly maxResumeAttempts?: undefined\n}): Layer.Layer<Driver, never, RunStore | Inbox>\nexport function makeDriverLayer(\n options?: DriverLayerOptions\n): Layer.Layer<Driver, InvalidMaxResumeAttempts, RunStore | Inbox>\nexport function makeDriverLayer(options?: DriverLayerOptions) {\n const hostDrain = options?.drain ?? ((_runId, _force, _scope, _context) => Effect.void)\n const maxResumeAttempts = options?.maxResumeAttempts ?? defaultMaxResumeAttempts\n if (!isValidMaxResumeAttempts(maxResumeAttempts)) {\n return Layer.effect(Driver, Effect.fail(new InvalidMaxResumeAttempts({ maxResumeAttempts })))\n }\n return makeValidatedDriverLayer(maxResumeAttempts, hostDrain)\n}\n"],"mappings":"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"}
|
package/dist/inbox.d.mts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { CapturedRun, Promotable } from "./coordinator.mjs";
|
|
2
|
+
import { Context, Effect, Layer } from "effect";
|
|
3
|
+
|
|
4
|
+
//#region src/inbox.d.ts
|
|
5
|
+
type InboxKind = 'input' | 'hitl';
|
|
6
|
+
type InboxItem = {
|
|
7
|
+
readonly id: string;
|
|
8
|
+
readonly runId: string;
|
|
9
|
+
readonly delivery: Promotable | 'queue';
|
|
10
|
+
readonly kind: InboxKind;
|
|
11
|
+
};
|
|
12
|
+
type ParkedResponse = {
|
|
13
|
+
readonly itemId: string;
|
|
14
|
+
readonly requestId: string;
|
|
15
|
+
};
|
|
16
|
+
type ParkedState = {
|
|
17
|
+
readonly generation: string;
|
|
18
|
+
readonly requestIds: ReadonlyArray<string>;
|
|
19
|
+
readonly responses: ReadonlyArray<ParkedResponse>;
|
|
20
|
+
readonly ready: boolean;
|
|
21
|
+
};
|
|
22
|
+
type HitlAdmission = {
|
|
23
|
+
readonly itemId: string;
|
|
24
|
+
readonly requestId: string;
|
|
25
|
+
readonly generation: string;
|
|
26
|
+
};
|
|
27
|
+
type HitlDecision = {
|
|
28
|
+
readonly _tag: 'Accepted';
|
|
29
|
+
} | {
|
|
30
|
+
readonly _tag: 'Ready';
|
|
31
|
+
} | {
|
|
32
|
+
readonly _tag: 'Duplicate';
|
|
33
|
+
} | {
|
|
34
|
+
readonly _tag: 'Stale';
|
|
35
|
+
} | {
|
|
36
|
+
readonly _tag: 'UnknownRequest';
|
|
37
|
+
} | {
|
|
38
|
+
readonly _tag: 'NotParked';
|
|
39
|
+
};
|
|
40
|
+
type PauseDecision = {
|
|
41
|
+
readonly _tag: 'Parked';
|
|
42
|
+
readonly generation: string;
|
|
43
|
+
} | {
|
|
44
|
+
readonly _tag: 'Stale';
|
|
45
|
+
};
|
|
46
|
+
type RecoveryAttempt = {
|
|
47
|
+
readonly _tag: 'Skip';
|
|
48
|
+
} | {
|
|
49
|
+
readonly _tag: 'Exhausted';
|
|
50
|
+
} | {
|
|
51
|
+
readonly _tag: 'Resume';
|
|
52
|
+
};
|
|
53
|
+
type RecoveryAdmission = {
|
|
54
|
+
readonly _tag: 'Skip';
|
|
55
|
+
} | {
|
|
56
|
+
readonly _tag: 'Exhausted';
|
|
57
|
+
} | {
|
|
58
|
+
readonly _tag: 'Resumed';
|
|
59
|
+
};
|
|
60
|
+
type DrainBegin = {
|
|
61
|
+
readonly _tag: 'Skip';
|
|
62
|
+
} | {
|
|
63
|
+
readonly _tag: 'Run';
|
|
64
|
+
readonly drainToken: string;
|
|
65
|
+
readonly readyResponses: ReadonlyArray<ParkedResponse>;
|
|
66
|
+
};
|
|
67
|
+
type InboxShape = {
|
|
68
|
+
readonly enqueue: (item: InboxItem) => Effect.Effect<void>;
|
|
69
|
+
readonly takePromotable: (runId: string, scope: Promotable, drainToken: string) => Effect.Effect<InboxItem | undefined>;
|
|
70
|
+
readonly pending: (runId: string) => Effect.Effect<ReadonlyArray<InboxItem>>;
|
|
71
|
+
readonly parked: (runId: string) => Effect.Effect<ParkedState | undefined>;
|
|
72
|
+
readonly park: (runId: string, requestIds: ReadonlyArray<string>, drainToken: string) => Effect.Effect<PauseDecision>;
|
|
73
|
+
readonly acceptHitl: (runId: string, admission: HitlAdmission, onReady: Effect.Effect<void>) => Effect.Effect<HitlDecision>;
|
|
74
|
+
readonly clearPark: (runId: string, generation: string) => Effect.Effect<boolean>;
|
|
75
|
+
readonly beginDrain: (runId: string, scope: Promotable) => Effect.Effect<DrainBegin>;
|
|
76
|
+
readonly endDrain: (runId: string, drainToken: string, acknowledged: boolean) => Effect.Effect<void>;
|
|
77
|
+
readonly invalidate: (runId: string, interrupt: Effect.Effect<boolean>, releaseIdleClaim: Effect.Effect<void>) => Effect.Effect<{
|
|
78
|
+
readonly hadPark: boolean;
|
|
79
|
+
readonly interrupted: boolean;
|
|
80
|
+
}>;
|
|
81
|
+
readonly enqueueAndWake: (item: InboxItem, wake: Effect.Effect<void>) => Effect.Effect<void>;
|
|
82
|
+
readonly wakeIfUnblocked: (runId: string, scope: Promotable, wake: Effect.Effect<void>) => Effect.Effect<boolean>;
|
|
83
|
+
readonly startIfUnblocked: <E, R>(runId: string, start: Effect.Effect<CapturedRun<E>, E, R>) => Effect.Effect<CapturedRun<E> | undefined, E, R>;
|
|
84
|
+
readonly admitRecovery: <E, R>(runId: string, attempt: Effect.Effect<RecoveryAttempt, E, R>, wake: Effect.Effect<void>) => Effect.Effect<RecoveryAdmission, E, R>;
|
|
85
|
+
};
|
|
86
|
+
declare const Inbox_base: Context.ServiceClass<Inbox, "@yolk-sdk/harness/Inbox", InboxShape>;
|
|
87
|
+
declare class Inbox extends Inbox_base {}
|
|
88
|
+
declare const makeInMemoryInboxLayer: () => Layer.Layer<Inbox>;
|
|
89
|
+
//#endregion
|
|
90
|
+
export { DrainBegin, HitlAdmission, HitlDecision, Inbox, InboxItem, InboxKind, InboxShape, ParkedResponse, ParkedState, PauseDecision, RecoveryAdmission, RecoveryAttempt, makeInMemoryInboxLayer };
|
|
91
|
+
//# sourceMappingURL=inbox.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inbox.d.mts","names":[],"sources":["../src/inbox.ts"],"mappings":"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"}
|
package/dist/inbox.mjs
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { Context, Effect, Layer, Ref, Semaphore } from "effect";
|
|
2
|
+
//#region src/inbox.ts
|
|
3
|
+
var Inbox = class extends Context.Service()("@yolk-sdk/harness/Inbox") {};
|
|
4
|
+
const isPromotableAt = (item, scope) => {
|
|
5
|
+
if (scope === "steer") return item.delivery === "steer";
|
|
6
|
+
return item.delivery === "steer" || item.delivery === "input" || item.delivery === "queue";
|
|
7
|
+
};
|
|
8
|
+
const emptyControl = {
|
|
9
|
+
pending: void 0,
|
|
10
|
+
liveToken: void 0,
|
|
11
|
+
park: void 0,
|
|
12
|
+
leasedGeneration: void 0
|
|
13
|
+
};
|
|
14
|
+
const widerPending = (current, next) => current === "input" || next === "input" ? "input" : "steer";
|
|
15
|
+
const remainingPending = (pending, scope) => scope === "steer" && pending === "input" ? "input" : void 0;
|
|
16
|
+
const itemScope = (item) => item.delivery === "steer" ? "steer" : "input";
|
|
17
|
+
const parkComplete = (park) => park.requestIds.every((requestId) => park.responses.some((response) => response.requestId === requestId));
|
|
18
|
+
const parkBlocked = (park) => park !== void 0 && !parkComplete(park);
|
|
19
|
+
const parkedState = (park) => ({
|
|
20
|
+
generation: park.generation,
|
|
21
|
+
requestIds: park.requestIds,
|
|
22
|
+
responses: park.responses,
|
|
23
|
+
ready: parkComplete(park)
|
|
24
|
+
});
|
|
25
|
+
const isPrunable = (control) => control.pending === void 0 && control.liveToken === void 0 && control.park === void 0 && control.leasedGeneration === void 0;
|
|
26
|
+
const makeInMemoryInboxLayer = () => Layer.effect(Inbox, Effect.gen(function* () {
|
|
27
|
+
const items = yield* Ref.make([]);
|
|
28
|
+
const controls = yield* Ref.make(/* @__PURE__ */ new Map());
|
|
29
|
+
const gate = yield* Semaphore.make(1);
|
|
30
|
+
let generationSeq = 0;
|
|
31
|
+
let drainSeq = 0;
|
|
32
|
+
const withGate = (body) => gate.withPermits(1)(body);
|
|
33
|
+
const getControl = (runId) => Ref.get(controls).pipe(Effect.map((current) => current.get(runId) ?? emptyControl));
|
|
34
|
+
const writeControl = (runId, next) => Ref.update(controls, (current) => {
|
|
35
|
+
const copy = new Map(current);
|
|
36
|
+
if (isPrunable(next)) copy.delete(runId);
|
|
37
|
+
else copy.set(runId, next);
|
|
38
|
+
return copy;
|
|
39
|
+
});
|
|
40
|
+
const dropQueued = (runId) => Ref.update(items, (current) => current.filter((item) => item.runId !== runId));
|
|
41
|
+
const acceptHitlPure = (control, admission) => {
|
|
42
|
+
const park = control.park;
|
|
43
|
+
if (park === void 0) return { _tag: "NotParked" };
|
|
44
|
+
if (park.generation !== admission.generation) return { _tag: "Stale" };
|
|
45
|
+
if (!park.requestIds.includes(admission.requestId)) return { _tag: "UnknownRequest" };
|
|
46
|
+
if (park.responses.some((response) => response.itemId === admission.itemId || response.requestId === admission.requestId)) return { _tag: "Duplicate" };
|
|
47
|
+
if (park.readyWoken) return { _tag: "Duplicate" };
|
|
48
|
+
const responses = [...park.responses, {
|
|
49
|
+
itemId: admission.itemId,
|
|
50
|
+
requestId: admission.requestId
|
|
51
|
+
}];
|
|
52
|
+
return parkComplete({
|
|
53
|
+
...park,
|
|
54
|
+
responses
|
|
55
|
+
}) ? { _tag: "Ready" } : { _tag: "Accepted" };
|
|
56
|
+
};
|
|
57
|
+
return Inbox.of({
|
|
58
|
+
enqueue: (item) => Ref.update(items, (current) => [...current, item]),
|
|
59
|
+
takePromotable: (runId, scope, drainToken) => withGate(Effect.uninterruptible(Effect.gen(function* () {
|
|
60
|
+
const control = yield* getControl(runId);
|
|
61
|
+
if (control.liveToken === void 0 || control.liveToken !== drainToken) return;
|
|
62
|
+
return yield* Ref.modify(items, (current) => {
|
|
63
|
+
const index = current.findIndex((item) => item.runId === runId && isPromotableAt(item, scope));
|
|
64
|
+
if (index < 0) return [void 0, current];
|
|
65
|
+
return [current[index], current.filter((_, itemIndex) => itemIndex !== index)];
|
|
66
|
+
});
|
|
67
|
+
}))),
|
|
68
|
+
pending: (runId) => Ref.get(items).pipe(Effect.map((current) => current.filter((item) => item.runId === runId))),
|
|
69
|
+
parked: (runId) => getControl(runId).pipe(Effect.map((control) => control.park === void 0 ? void 0 : parkedState(control.park))),
|
|
70
|
+
park: (runId, requestIds, drainToken) => withGate(Effect.uninterruptible(Effect.gen(function* () {
|
|
71
|
+
const control = yield* getControl(runId);
|
|
72
|
+
if (control.liveToken !== drainToken) return { _tag: "Stale" };
|
|
73
|
+
generationSeq += 1;
|
|
74
|
+
const generation = String(generationSeq);
|
|
75
|
+
yield* writeControl(runId, {
|
|
76
|
+
pending: control.pending,
|
|
77
|
+
liveToken: control.liveToken,
|
|
78
|
+
leasedGeneration: control.leasedGeneration,
|
|
79
|
+
park: {
|
|
80
|
+
generation,
|
|
81
|
+
requestIds: [...requestIds],
|
|
82
|
+
responses: [],
|
|
83
|
+
readyWoken: false
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
return {
|
|
87
|
+
_tag: "Parked",
|
|
88
|
+
generation
|
|
89
|
+
};
|
|
90
|
+
}))),
|
|
91
|
+
acceptHitl: (runId, admission, onReady) => withGate(Effect.uninterruptible(Effect.gen(function* () {
|
|
92
|
+
const control = yield* getControl(runId);
|
|
93
|
+
const decision = acceptHitlPure(control, admission);
|
|
94
|
+
if (decision._tag !== "Accepted" && decision._tag !== "Ready") return decision;
|
|
95
|
+
const park = control.park;
|
|
96
|
+
if (park === void 0) return { _tag: "NotParked" };
|
|
97
|
+
const nextPark = {
|
|
98
|
+
...park,
|
|
99
|
+
responses: [...park.responses, {
|
|
100
|
+
itemId: admission.itemId,
|
|
101
|
+
requestId: admission.requestId
|
|
102
|
+
}],
|
|
103
|
+
readyWoken: decision._tag === "Ready"
|
|
104
|
+
};
|
|
105
|
+
yield* writeControl(runId, {
|
|
106
|
+
pending: decision._tag === "Ready" ? widerPending(control.pending, "input") : control.pending,
|
|
107
|
+
liveToken: control.liveToken,
|
|
108
|
+
leasedGeneration: control.leasedGeneration,
|
|
109
|
+
park: nextPark
|
|
110
|
+
});
|
|
111
|
+
if (decision._tag === "Ready") yield* onReady;
|
|
112
|
+
return decision;
|
|
113
|
+
}))),
|
|
114
|
+
clearPark: (runId, generation) => withGate(Effect.uninterruptible(Effect.gen(function* () {
|
|
115
|
+
const control = yield* getControl(runId);
|
|
116
|
+
if (control.park === void 0 || control.park.generation !== generation) return false;
|
|
117
|
+
yield* writeControl(runId, {
|
|
118
|
+
pending: control.pending,
|
|
119
|
+
liveToken: control.liveToken,
|
|
120
|
+
leasedGeneration: control.leasedGeneration,
|
|
121
|
+
park: void 0
|
|
122
|
+
});
|
|
123
|
+
return true;
|
|
124
|
+
}))),
|
|
125
|
+
beginDrain: (runId, scope) => withGate(Effect.uninterruptible(Effect.gen(function* () {
|
|
126
|
+
const control = yield* getControl(runId);
|
|
127
|
+
const pending = control.pending;
|
|
128
|
+
if (parkBlocked(control.park) || pending === void 0) return { _tag: "Skip" };
|
|
129
|
+
drainSeq += 1;
|
|
130
|
+
const drainToken = `d${drainSeq}`;
|
|
131
|
+
const park = control.park;
|
|
132
|
+
const ready = park !== void 0 && parkComplete(park) ? park : void 0;
|
|
133
|
+
yield* writeControl(runId, {
|
|
134
|
+
pending: remainingPending(pending, scope),
|
|
135
|
+
liveToken: drainToken,
|
|
136
|
+
park,
|
|
137
|
+
leasedGeneration: ready?.generation
|
|
138
|
+
});
|
|
139
|
+
return {
|
|
140
|
+
_tag: "Run",
|
|
141
|
+
drainToken,
|
|
142
|
+
readyResponses: ready === void 0 ? [] : ready.responses
|
|
143
|
+
};
|
|
144
|
+
}))),
|
|
145
|
+
endDrain: (runId, drainToken, acknowledged) => withGate(Effect.uninterruptible(Effect.gen(function* () {
|
|
146
|
+
const control = yield* getControl(runId);
|
|
147
|
+
if (control.liveToken !== drainToken) return;
|
|
148
|
+
const leased = control.leasedGeneration;
|
|
149
|
+
const park = control.park;
|
|
150
|
+
const clearPark = acknowledged && leased !== void 0 && park !== void 0 && park.generation === leased;
|
|
151
|
+
yield* writeControl(runId, {
|
|
152
|
+
pending: control.pending,
|
|
153
|
+
liveToken: void 0,
|
|
154
|
+
leasedGeneration: void 0,
|
|
155
|
+
park: clearPark ? void 0 : park
|
|
156
|
+
});
|
|
157
|
+
}))),
|
|
158
|
+
invalidate: (runId, interrupt, releaseIdleClaim) => withGate(Effect.uninterruptible(Effect.gen(function* () {
|
|
159
|
+
const hadPark = (yield* getControl(runId)).park !== void 0;
|
|
160
|
+
yield* dropQueued(runId);
|
|
161
|
+
yield* writeControl(runId, emptyControl);
|
|
162
|
+
const interrupted = yield* interrupt;
|
|
163
|
+
if (!interrupted) yield* releaseIdleClaim;
|
|
164
|
+
return {
|
|
165
|
+
hadPark,
|
|
166
|
+
interrupted
|
|
167
|
+
};
|
|
168
|
+
}))),
|
|
169
|
+
enqueueAndWake: (item, wake) => withGate(Effect.uninterruptible(Effect.gen(function* () {
|
|
170
|
+
yield* Ref.update(items, (current) => [...current, item]);
|
|
171
|
+
const control = yield* getControl(item.runId);
|
|
172
|
+
if (parkBlocked(control.park)) return;
|
|
173
|
+
yield* writeControl(item.runId, {
|
|
174
|
+
pending: widerPending(control.pending, itemScope(item)),
|
|
175
|
+
liveToken: control.liveToken,
|
|
176
|
+
leasedGeneration: control.leasedGeneration,
|
|
177
|
+
park: control.park
|
|
178
|
+
});
|
|
179
|
+
yield* wake;
|
|
180
|
+
}))),
|
|
181
|
+
wakeIfUnblocked: (runId, scope, wake) => withGate(Effect.uninterruptible(Effect.gen(function* () {
|
|
182
|
+
const control = yield* getControl(runId);
|
|
183
|
+
if (parkBlocked(control.park)) return false;
|
|
184
|
+
yield* writeControl(runId, {
|
|
185
|
+
pending: widerPending(control.pending, scope),
|
|
186
|
+
liveToken: control.liveToken,
|
|
187
|
+
leasedGeneration: control.leasedGeneration,
|
|
188
|
+
park: control.park
|
|
189
|
+
});
|
|
190
|
+
yield* wake;
|
|
191
|
+
return true;
|
|
192
|
+
}))),
|
|
193
|
+
startIfUnblocked: (runId, start) => withGate(Effect.uninterruptible(Effect.gen(function* () {
|
|
194
|
+
const control = yield* getControl(runId);
|
|
195
|
+
if (parkBlocked(control.park)) return void 0;
|
|
196
|
+
const ticket = yield* start;
|
|
197
|
+
if (ticket._tag === "Started") yield* writeControl(runId, {
|
|
198
|
+
pending: widerPending(control.pending, "input"),
|
|
199
|
+
liveToken: control.liveToken,
|
|
200
|
+
leasedGeneration: control.leasedGeneration,
|
|
201
|
+
park: control.park
|
|
202
|
+
});
|
|
203
|
+
return ticket;
|
|
204
|
+
}))),
|
|
205
|
+
admitRecovery: (runId, attempt, wake) => Effect.uninterruptibleMask((restore) => restore(gate.take(1)).pipe(Effect.flatMap(() => Effect.gen(function* () {
|
|
206
|
+
const control = yield* getControl(runId);
|
|
207
|
+
if (parkBlocked(control.park)) return { _tag: "Skip" };
|
|
208
|
+
const decision = yield* attempt;
|
|
209
|
+
if (decision._tag !== "Resume") return decision;
|
|
210
|
+
yield* writeControl(runId, {
|
|
211
|
+
pending: widerPending(control.pending, "input"),
|
|
212
|
+
liveToken: control.liveToken,
|
|
213
|
+
leasedGeneration: control.leasedGeneration,
|
|
214
|
+
park: control.park
|
|
215
|
+
});
|
|
216
|
+
yield* wake;
|
|
217
|
+
return { _tag: "Resumed" };
|
|
218
|
+
}).pipe(Effect.ensuring(gate.release(1))))))
|
|
219
|
+
});
|
|
220
|
+
}));
|
|
221
|
+
//#endregion
|
|
222
|
+
export { Inbox, makeInMemoryInboxLayer };
|
|
223
|
+
|
|
224
|
+
//# sourceMappingURL=inbox.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inbox.mjs","names":[],"sources":["../src/inbox.ts"],"sourcesContent":["import { Context, Effect, Layer, Ref, Semaphore } from 'effect'\nimport type { CapturedRun, Promotable } from './coordinator.ts'\n\nexport type InboxKind = 'input' | 'hitl'\n\nexport type InboxItem = {\n readonly id: string\n readonly runId: string\n readonly delivery: Promotable | 'queue'\n readonly kind: InboxKind\n}\n\nexport type ParkedResponse = {\n readonly itemId: string\n readonly requestId: string\n}\n\nexport type ParkedState = {\n readonly generation: string\n readonly requestIds: ReadonlyArray<string>\n readonly responses: ReadonlyArray<ParkedResponse>\n readonly ready: boolean\n}\n\nexport type HitlAdmission = {\n readonly itemId: string\n readonly requestId: string\n readonly generation: string\n}\n\nexport type HitlDecision =\n | { readonly _tag: 'Accepted' }\n | { readonly _tag: 'Ready' }\n | { readonly _tag: 'Duplicate' }\n | { readonly _tag: 'Stale' }\n | { readonly _tag: 'UnknownRequest' }\n | { readonly _tag: 'NotParked' }\n\nexport type PauseDecision =\n | { readonly _tag: 'Parked'; readonly generation: string }\n | { readonly _tag: 'Stale' }\n\nexport type RecoveryAttempt =\n | { readonly _tag: 'Skip' }\n | { readonly _tag: 'Exhausted' }\n | { readonly _tag: 'Resume' }\n\nexport type RecoveryAdmission =\n | { readonly _tag: 'Skip' }\n | { readonly _tag: 'Exhausted' }\n | { readonly _tag: 'Resumed' }\n\nexport type DrainBegin =\n | { readonly _tag: 'Skip' }\n | {\n readonly _tag: 'Run'\n readonly drainToken: string\n readonly readyResponses: ReadonlyArray<ParkedResponse>\n }\n\nexport type InboxShape = {\n readonly enqueue: (item: InboxItem) => Effect.Effect<void>\n readonly takePromotable: (\n runId: string,\n scope: Promotable,\n drainToken: string\n ) => Effect.Effect<InboxItem | undefined>\n readonly pending: (runId: string) => Effect.Effect<ReadonlyArray<InboxItem>>\n readonly parked: (runId: string) => Effect.Effect<ParkedState | undefined>\n readonly park: (\n runId: string,\n requestIds: ReadonlyArray<string>,\n drainToken: string\n ) => Effect.Effect<PauseDecision>\n readonly acceptHitl: (\n runId: string,\n admission: HitlAdmission,\n onReady: Effect.Effect<void>\n ) => Effect.Effect<HitlDecision>\n readonly clearPark: (runId: string, generation: string) => Effect.Effect<boolean>\n readonly beginDrain: (runId: string, scope: Promotable) => Effect.Effect<DrainBegin>\n readonly endDrain: (\n runId: string,\n drainToken: string,\n acknowledged: boolean\n ) => Effect.Effect<void>\n readonly invalidate: (\n runId: string,\n interrupt: Effect.Effect<boolean>,\n releaseIdleClaim: Effect.Effect<void>\n ) => Effect.Effect<{ readonly hadPark: boolean; readonly interrupted: boolean }>\n readonly enqueueAndWake: (item: InboxItem, wake: Effect.Effect<void>) => Effect.Effect<void>\n readonly wakeIfUnblocked: (\n runId: string,\n scope: Promotable,\n wake: Effect.Effect<void>\n ) => Effect.Effect<boolean>\n readonly startIfUnblocked: <E, R>(\n runId: string,\n start: Effect.Effect<CapturedRun<E>, E, R>\n ) => Effect.Effect<CapturedRun<E> | undefined, E, R>\n readonly admitRecovery: <E, R>(\n runId: string,\n attempt: Effect.Effect<RecoveryAttempt, E, R>,\n wake: Effect.Effect<void>\n ) => Effect.Effect<RecoveryAdmission, E, R>\n}\n\nexport class Inbox extends Context.Service<Inbox, InboxShape>()('@yolk-sdk/harness/Inbox') {}\n\nconst isPromotableAt = (item: InboxItem, scope: Promotable) => {\n if (scope === 'steer') return item.delivery === 'steer'\n return item.delivery === 'steer' || item.delivery === 'input' || item.delivery === 'queue'\n}\n\ntype Park = {\n readonly generation: string\n readonly requestIds: ReadonlyArray<string>\n readonly responses: ReadonlyArray<ParkedResponse>\n readonly readyWoken: boolean\n}\n\ntype RunControl = {\n readonly pending: Promotable | undefined\n readonly liveToken: string | undefined\n readonly park: Park | undefined\n readonly leasedGeneration: string | undefined\n}\n\nconst emptyControl: RunControl = {\n pending: undefined,\n liveToken: undefined,\n park: undefined,\n leasedGeneration: undefined\n}\n\nconst widerPending = (current: Promotable | undefined, next: Promotable): Promotable =>\n current === 'input' || next === 'input' ? 'input' : 'steer'\n\nconst remainingPending = (pending: Promotable, scope: Promotable): Promotable | undefined =>\n scope === 'steer' && pending === 'input' ? 'input' : undefined\n\nconst itemScope = (item: InboxItem): Promotable => (item.delivery === 'steer' ? 'steer' : 'input')\n\nconst parkComplete = (park: Park) =>\n park.requestIds.every(requestId =>\n park.responses.some(response => response.requestId === requestId)\n )\n\nconst parkBlocked = (park: Park | undefined) => park !== undefined && !parkComplete(park)\n\nconst parkedState = (park: Park): ParkedState => ({\n generation: park.generation,\n requestIds: park.requestIds,\n responses: park.responses,\n ready: parkComplete(park)\n})\n\nconst isPrunable = (control: RunControl) =>\n control.pending === undefined &&\n control.liveToken === undefined &&\n control.park === undefined &&\n control.leasedGeneration === undefined\n\nexport const makeInMemoryInboxLayer = (): Layer.Layer<Inbox> =>\n Layer.effect(\n Inbox,\n Effect.gen(function* () {\n const items = yield* Ref.make<ReadonlyArray<InboxItem>>([])\n const controls = yield* Ref.make<ReadonlyMap<string, RunControl>>(new Map())\n const gate = yield* Semaphore.make(1)\n let generationSeq = 0\n let drainSeq = 0\n\n const withGate = <A, E, R>(body: Effect.Effect<A, E, R>) => gate.withPermits(1)(body)\n\n const getControl = (runId: string) =>\n Ref.get(controls).pipe(Effect.map(current => current.get(runId) ?? emptyControl))\n\n const writeControl = (runId: string, next: RunControl) =>\n Ref.update(controls, current => {\n const copy = new Map(current)\n if (isPrunable(next)) copy.delete(runId)\n else copy.set(runId, next)\n return copy\n })\n\n const dropQueued = (runId: string) =>\n Ref.update(items, current => current.filter(item => item.runId !== runId))\n\n const acceptHitlPure = (control: RunControl, admission: HitlAdmission): HitlDecision => {\n const park = control.park\n if (park === undefined) return { _tag: 'NotParked' }\n if (park.generation !== admission.generation) return { _tag: 'Stale' }\n if (!park.requestIds.includes(admission.requestId)) return { _tag: 'UnknownRequest' }\n if (\n park.responses.some(\n response =>\n response.itemId === admission.itemId || response.requestId === admission.requestId\n )\n ) {\n return { _tag: 'Duplicate' }\n }\n if (park.readyWoken) return { _tag: 'Duplicate' }\n const responses = [\n ...park.responses,\n { itemId: admission.itemId, requestId: admission.requestId }\n ]\n return parkComplete({ ...park, responses }) ? { _tag: 'Ready' } : { _tag: 'Accepted' }\n }\n\n return Inbox.of({\n enqueue: item => Ref.update(items, current => [...current, item]),\n takePromotable: (runId, scope, drainToken) =>\n withGate(\n Effect.uninterruptible(\n Effect.gen(function* () {\n const control = yield* getControl(runId)\n if (control.liveToken === undefined || control.liveToken !== drainToken) {\n return undefined\n }\n return yield* Ref.modify(items, current => {\n const index = current.findIndex(\n item => item.runId === runId && isPromotableAt(item, scope)\n )\n if (index < 0) return [undefined, current] as const\n const taken = current[index]\n return [taken, current.filter((_, itemIndex) => itemIndex !== index)] as const\n })\n })\n )\n ),\n pending: runId =>\n Ref.get(items).pipe(Effect.map(current => current.filter(item => item.runId === runId))),\n parked: runId =>\n getControl(runId).pipe(\n Effect.map(control =>\n control.park === undefined ? undefined : parkedState(control.park)\n )\n ),\n park: (runId, requestIds, drainToken) =>\n withGate(\n Effect.uninterruptible(\n Effect.gen(function* () {\n const control = yield* getControl(runId)\n if (control.liveToken !== drainToken) return { _tag: 'Stale' } as const\n generationSeq += 1\n const generation = String(generationSeq)\n yield* writeControl(runId, {\n pending: control.pending,\n liveToken: control.liveToken,\n leasedGeneration: control.leasedGeneration,\n park: {\n generation,\n requestIds: [...requestIds],\n responses: [],\n readyWoken: false\n }\n })\n return { _tag: 'Parked', generation } as const\n })\n )\n ),\n acceptHitl: (runId, admission, onReady) =>\n withGate(\n Effect.uninterruptible(\n Effect.gen(function* () {\n const control = yield* getControl(runId)\n const decision = acceptHitlPure(control, admission)\n if (decision._tag !== 'Accepted' && decision._tag !== 'Ready') {\n return decision\n }\n const park = control.park\n if (park === undefined) return { _tag: 'NotParked' } as const\n const nextPark: Park = {\n ...park,\n responses: [\n ...park.responses,\n { itemId: admission.itemId, requestId: admission.requestId }\n ],\n readyWoken: decision._tag === 'Ready'\n }\n yield* writeControl(runId, {\n pending:\n decision._tag === 'Ready'\n ? widerPending(control.pending, 'input')\n : control.pending,\n liveToken: control.liveToken,\n leasedGeneration: control.leasedGeneration,\n park: nextPark\n })\n if (decision._tag === 'Ready') yield* onReady\n return decision\n })\n )\n ),\n clearPark: (runId, generation) =>\n withGate(\n Effect.uninterruptible(\n Effect.gen(function* () {\n const control = yield* getControl(runId)\n if (control.park === undefined || control.park.generation !== generation) {\n return false\n }\n yield* writeControl(runId, {\n pending: control.pending,\n liveToken: control.liveToken,\n leasedGeneration: control.leasedGeneration,\n park: undefined\n })\n return true\n })\n )\n ),\n beginDrain: (runId, scope) =>\n withGate(\n Effect.uninterruptible(\n Effect.gen(function* () {\n const control = yield* getControl(runId)\n const pending = control.pending\n if (parkBlocked(control.park) || pending === undefined) {\n return { _tag: 'Skip' } as const\n }\n drainSeq += 1\n const drainToken = `d${drainSeq}`\n const park = control.park\n const ready = park !== undefined && parkComplete(park) ? park : undefined\n yield* writeControl(runId, {\n pending: remainingPending(pending, scope),\n liveToken: drainToken,\n park,\n leasedGeneration: ready?.generation\n })\n return {\n _tag: 'Run',\n drainToken,\n readyResponses: ready === undefined ? [] : ready.responses\n } as const\n })\n )\n ),\n endDrain: (runId, drainToken, acknowledged) =>\n withGate(\n Effect.uninterruptible(\n Effect.gen(function* () {\n const control = yield* getControl(runId)\n if (control.liveToken !== drainToken) return\n const leased = control.leasedGeneration\n const park = control.park\n const clearPark =\n acknowledged &&\n leased !== undefined &&\n park !== undefined &&\n park.generation === leased\n yield* writeControl(runId, {\n pending: control.pending,\n liveToken: undefined,\n leasedGeneration: undefined,\n park: clearPark ? undefined : park\n })\n })\n )\n ),\n invalidate: (runId, interrupt, releaseIdleClaim) =>\n withGate(\n Effect.uninterruptible(\n Effect.gen(function* () {\n const control = yield* getControl(runId)\n const hadPark = control.park !== undefined\n yield* dropQueued(runId)\n yield* writeControl(runId, emptyControl)\n const interrupted = yield* interrupt\n if (!interrupted) yield* releaseIdleClaim\n return { hadPark, interrupted }\n })\n )\n ),\n enqueueAndWake: (item, wake) =>\n withGate(\n Effect.uninterruptible(\n Effect.gen(function* () {\n yield* Ref.update(items, current => [...current, item])\n const control = yield* getControl(item.runId)\n if (parkBlocked(control.park)) return\n yield* writeControl(item.runId, {\n pending: widerPending(control.pending, itemScope(item)),\n liveToken: control.liveToken,\n leasedGeneration: control.leasedGeneration,\n park: control.park\n })\n yield* wake\n })\n )\n ),\n wakeIfUnblocked: (runId, scope, wake) =>\n withGate(\n Effect.uninterruptible(\n Effect.gen(function* () {\n const control = yield* getControl(runId)\n if (parkBlocked(control.park)) return false\n yield* writeControl(runId, {\n pending: widerPending(control.pending, scope),\n liveToken: control.liveToken,\n leasedGeneration: control.leasedGeneration,\n park: control.park\n })\n yield* wake\n return true\n })\n )\n ),\n startIfUnblocked: (runId, start) =>\n withGate(\n Effect.uninterruptible(\n Effect.gen(function* () {\n const control = yield* getControl(runId)\n if (parkBlocked(control.park)) return undefined\n const ticket = yield* start\n if (ticket._tag === 'Started') {\n yield* writeControl(runId, {\n pending: widerPending(control.pending, 'input'),\n liveToken: control.liveToken,\n leasedGeneration: control.leasedGeneration,\n park: control.park\n })\n }\n return ticket\n })\n )\n ),\n admitRecovery: (runId, attempt, wake) =>\n Effect.uninterruptibleMask(restore =>\n restore(gate.take(1)).pipe(\n Effect.flatMap(() =>\n Effect.gen(function* () {\n const control = yield* getControl(runId)\n if (parkBlocked(control.park)) return { _tag: 'Skip' } as const\n const decision = yield* attempt\n if (decision._tag !== 'Resume') return decision\n yield* writeControl(runId, {\n pending: widerPending(control.pending, 'input'),\n liveToken: control.liveToken,\n leasedGeneration: control.leasedGeneration,\n park: control.park\n })\n yield* wake\n return { _tag: 'Resumed' } as const\n }).pipe(Effect.ensuring(gate.release(1)))\n )\n )\n )\n })\n })\n )\n"],"mappings":"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"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { AgentEvent, AgentMessage, AgentUsage, HitlRequest, HitlResponse } from "@yolk-sdk/agent/protocol";
|
|
3
|
+
import { AgentLoopError, ContextTransformer, LLMProvider, LoopConfig, ModelTurnConfig, ModelTurnResult, ToolBatchConfig, ToolExecutor } from "@yolk-sdk/agent/loop";
|
|
4
|
+
|
|
5
|
+
//#region src/outcome.d.ts
|
|
6
|
+
type OverflowCompactionResult = {
|
|
7
|
+
readonly _tag: 'Compacted';
|
|
8
|
+
readonly messages: ReadonlyArray<AgentMessage>;
|
|
9
|
+
} | {
|
|
10
|
+
readonly _tag: 'Skipped';
|
|
11
|
+
};
|
|
12
|
+
type CompletedTurn = {
|
|
13
|
+
readonly _tag: 'Completed';
|
|
14
|
+
readonly needsContinuation: boolean;
|
|
15
|
+
} & ModelTurnResult;
|
|
16
|
+
type ModelTurnOutcome = CompletedTurn | {
|
|
17
|
+
readonly _tag: 'Retry';
|
|
18
|
+
readonly error: AgentLoopError;
|
|
19
|
+
} | ({
|
|
20
|
+
readonly _tag: 'Continue';
|
|
21
|
+
readonly error: AgentLoopError;
|
|
22
|
+
} & ModelTurnResult) | {
|
|
23
|
+
readonly _tag: 'RecoverFull';
|
|
24
|
+
readonly error: AgentLoopError;
|
|
25
|
+
} | {
|
|
26
|
+
readonly _tag: 'Compacted';
|
|
27
|
+
readonly messages: ReadonlyArray<AgentMessage>;
|
|
28
|
+
readonly overflowCompactionAttempt: number;
|
|
29
|
+
};
|
|
30
|
+
type ToolBatchOutcome = CompletedTurn | {
|
|
31
|
+
readonly _tag: 'AwaitingInput';
|
|
32
|
+
readonly requests: ReadonlyArray<HitlRequest>;
|
|
33
|
+
readonly usage: AgentUsage;
|
|
34
|
+
};
|
|
35
|
+
type StepOutcome = ModelTurnOutcome | ToolBatchOutcome;
|
|
36
|
+
declare const attemptModelTurn: <E2 = never, R2 = never>(config: ModelTurnConfig, options?: {
|
|
37
|
+
readonly onEvent?: (event: AgentEvent) => Effect.Effect<void, E2, R2>;
|
|
38
|
+
readonly initialUsage?: AgentUsage;
|
|
39
|
+
readonly overflowCompactionAttempt?: number;
|
|
40
|
+
readonly compact?: (messages: ReadonlyArray<AgentMessage>) => Effect.Effect<OverflowCompactionResult, E2, R2>;
|
|
41
|
+
}) => Effect.Effect<ModelTurnOutcome, AgentLoopError | E2, ContextTransformer | LLMProvider | LoopConfig | R2>;
|
|
42
|
+
declare const attemptToolBatch: <E2 = never, R2 = never>(config: ToolBatchConfig, options?: {
|
|
43
|
+
readonly onEvent?: (event: AgentEvent) => Effect.Effect<void, E2, R2>;
|
|
44
|
+
}) => Effect.Effect<ToolBatchOutcome, AgentLoopError | E2, LoopConfig | ToolExecutor | R2>;
|
|
45
|
+
type HitlMatch = {
|
|
46
|
+
readonly _tag: 'Match';
|
|
47
|
+
readonly requestId: string;
|
|
48
|
+
} | {
|
|
49
|
+
readonly _tag: 'Mismatch';
|
|
50
|
+
};
|
|
51
|
+
declare const matchHitlResponse: (pending: ReadonlyArray<HitlRequest>, response: HitlResponse) => HitlMatch;
|
|
52
|
+
declare const resumeHitlIfMatched: <A, E, R>(input: {
|
|
53
|
+
readonly pending: ReadonlyArray<HitlRequest>;
|
|
54
|
+
readonly response: HitlResponse;
|
|
55
|
+
readonly resume: (requestId: string) => Effect.Effect<A, E, R>;
|
|
56
|
+
}) => Effect.Effect<A | {
|
|
57
|
+
readonly _tag: "Mismatch";
|
|
58
|
+
}, E, R>;
|
|
59
|
+
//#endregion
|
|
60
|
+
export { CompletedTurn, HitlMatch, ModelTurnOutcome, OverflowCompactionResult, StepOutcome, ToolBatchOutcome, attemptModelTurn, attemptToolBatch, matchHitlResponse, resumeHitlIfMatched };
|
|
61
|
+
//# sourceMappingURL=outcome.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outcome.d.mts","names":[],"sources":["../src/outcome.ts"],"mappings":";;;;;KA6BY,wBAAA;EAAA,SAEG,IAAA;EAAA,SACA,QAAA,EAAU,aAAa,CAAC,YAAA;AAAA;EAAA,SAGxB,IAAA;AAAA;AAAA,KAGH,aAAA;EAAA,SACD,IAAA;EAAA,SACA,iBAAA;AAAA,IACP,eAAe;AAAA,KAEP,gBAAA,GACR,aAAA;EAAA,SACW,IAAA;EAAA,SAAwB,KAAA,EAAO,cAAA;AAAA;EAAA,SAC9B,IAAA;EAAA,SAA2B,KAAA,EAAO,cAAA;AAAA,IAAmB,eAAA;EAAA,SACtD,IAAA;EAAA,SAA8B,KAAA,EAAO,cAAA;AAAA;EAAA,SAErC,IAAA;EAAA,SACA,QAAA,EAAU,aAAA,CAAc,YAAA;EAAA,SACxB,yBAAA;AAAA;AAAA,KAGH,gBAAA,GACR,aAAA;EAAA,SAEW,IAAA;EAAA,SACA,QAAA,EAAU,aAAA,CAAc,WAAA;EAAA,SACxB,KAAA,EAAO,UAAA;AAAA;AAAA,KAGV,WAAA,GAAc,gBAAA,GAAmB,gBAAgB;AAAA,cAkHhD,gBAAA,2BACX,MAAA,EAAQ,eAAA,EACR,OAAA;EAAA,SACW,OAAA,IAAW,KAAA,EAAO,UAAA,KAAe,MAAA,CAAO,MAAA,OAAa,EAAA,EAAI,EAAA;EAAA,SACzD,YAAA,GAAe,UAAA;EAAA,SACf,yBAAA;EAAA,SACA,OAAA,IACP,QAAA,EAAU,aAAA,CAAc,YAAA,MACrB,MAAA,CAAO,MAAA,CAAO,wBAAA,EAA0B,EAAA,EAAI,EAAA;AAAA,MAElD,MAAA,CAAO,MAAA,CACR,gBAAA,EACA,cAAA,GAAiB,EAAA,EACjB,kBAAA,GAAqB,WAAA,GAAc,UAAA,GAAa,EAAA;AAAA,cAuCrC,gBAAA,2BACX,MAAA,EAAQ,eAAA,EACR,OAAA;EAAA,SACW,OAAA,IAAW,KAAA,EAAO,UAAA,KAAe,MAAA,CAAO,MAAA,OAAa,EAAA,EAAI,EAAA;AAAA,MAEnE,MAAA,CAAO,MAAA,CAAO,gBAAA,EAAkB,cAAA,GAAiB,EAAA,EAAI,UAAA,GAAa,YAAA,GAAe,EAAA;AAAA,KA2CxE,SAAA;EAAA,SACG,IAAA;EAAA,SAAwB,SAAA;AAAA;EAAA,SACxB,IAAA;AAAA;AAAA,cAmBF,iBAAA,GACX,OAAA,EAAS,aAAA,CAAc,WAAA,GACvB,QAAA,EAAU,YAAA,KACT,SAAA;AAAA,cAOU,mBAAA,YAAgC,KAAA;EAAA,SAClC,OAAA,EAAS,aAAA,CAAc,WAAA;EAAA,SACvB,QAAA,EAAU,YAAA;EAAA,SACV,MAAA,GAAS,SAAA,aAAsB,MAAA,CAAO,MAAA,CAAO,CAAA,EAAG,CAAA,EAAG,CAAA;AAAA,MAC1D,MAAA,CAAO,MAAA,CAAO,CAAA;EAAA,SAAe,IAAA;AAAA,GAAoB,CAAA,EAAG,CAAA"}
|