@hunterzhu/pulse-runtime 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/dist/context/builder.d.ts +68 -0
- package/dist/context/builder.js +127 -0
- package/dist/context/index.d.ts +2 -0
- package/dist/context/index.js +2 -0
- package/dist/context/merger.d.ts +25 -0
- package/dist/context/merger.js +125 -0
- package/dist/core/actions.d.ts +1 -0
- package/dist/core/actions.js +1 -0
- package/dist/core/errors.d.ts +8 -0
- package/dist/core/errors.js +36 -0
- package/dist/core/events.d.ts +10 -0
- package/dist/core/events.js +24 -0
- package/dist/core/factory.d.ts +35 -0
- package/dist/core/factory.js +27 -0
- package/dist/core/inbox.d.ts +119 -0
- package/dist/core/inbox.js +217 -0
- package/dist/core/mutations.d.ts +80 -0
- package/dist/core/mutations.js +127 -0
- package/dist/core/records.d.ts +1 -0
- package/dist/core/records.js +1 -0
- package/dist/core/types.d.ts +615 -0
- package/dist/core/types.js +109 -0
- package/dist/dependencies/graph.d.ts +25 -0
- package/dist/dependencies/graph.js +92 -0
- package/dist/dependencies/index.d.ts +1 -0
- package/dist/dependencies/index.js +1 -0
- package/dist/dsl/context-proxy.d.ts +20 -0
- package/dist/dsl/context-proxy.js +64 -0
- package/dist/dsl/index.d.ts +4 -0
- package/dist/dsl/index.js +4 -0
- package/dist/dsl/program.d.ts +314 -0
- package/dist/dsl/program.js +756 -0
- package/dist/dsl/session.d.ts +45 -0
- package/dist/dsl/session.js +93 -0
- package/dist/dsl/templates-index.d.ts +1 -0
- package/dist/dsl/templates-index.js +1 -0
- package/dist/dsl/templates.d.ts +85 -0
- package/dist/dsl/templates.js +110 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +15 -0
- package/dist/lifecycle/index.d.ts +2 -0
- package/dist/lifecycle/index.js +2 -0
- package/dist/lifecycle/scopes.d.ts +38 -0
- package/dist/lifecycle/scopes.js +50 -0
- package/dist/lifecycle/watchdog.d.ts +16 -0
- package/dist/lifecycle/watchdog.js +66 -0
- package/dist/models/actions.d.ts +10 -0
- package/dist/models/actions.js +68 -0
- package/dist/models/index.d.ts +2 -0
- package/dist/models/index.js +2 -0
- package/dist/models/router.d.ts +187 -0
- package/dist/models/router.js +353 -0
- package/dist/scheduler/clock.d.ts +45 -0
- package/dist/scheduler/clock.js +92 -0
- package/dist/scheduler/decision.d.ts +72 -0
- package/dist/scheduler/decision.js +63 -0
- package/dist/scheduler/index.d.ts +6 -0
- package/dist/scheduler/index.js +6 -0
- package/dist/scheduler/locks.d.ts +18 -0
- package/dist/scheduler/locks.js +106 -0
- package/dist/scheduler/ready-queue.d.ts +32 -0
- package/dist/scheduler/ready-queue.js +40 -0
- package/dist/scheduler/runtime.d.ts +486 -0
- package/dist/scheduler/runtime.js +3445 -0
- package/dist/scheduler/telemetry.d.ts +111 -0
- package/dist/scheduler/telemetry.js +177 -0
- package/dist/scheduler/worker.d.ts +158 -0
- package/dist/scheduler/worker.js +744 -0
- package/dist/storage/artifacts.d.ts +17 -0
- package/dist/storage/artifacts.js +90 -0
- package/dist/storage/findings.d.ts +12 -0
- package/dist/storage/findings.js +70 -0
- package/dist/storage/index.d.ts +8 -0
- package/dist/storage/index.js +8 -0
- package/dist/storage/memory.d.ts +11 -0
- package/dist/storage/memory.js +21 -0
- package/dist/storage/mutation-log.d.ts +41 -0
- package/dist/storage/mutation-log.js +140 -0
- package/dist/storage/outbox.d.ts +30 -0
- package/dist/storage/outbox.js +59 -0
- package/dist/storage/persistence.d.ts +183 -0
- package/dist/storage/persistence.js +999 -0
- package/dist/storage/policy.d.ts +80 -0
- package/dist/storage/policy.js +268 -0
- package/dist/storage/session.d.ts +140 -0
- package/dist/storage/session.js +447 -0
- package/dist/tools/registry.d.ts +125 -0
- package/dist/tools/registry.js +308 -0
- package/dist/transitions/index.d.ts +2 -0
- package/dist/transitions/index.js +1 -0
- package/dist/transitions/validate.d.ts +4 -0
- package/dist/transitions/validate.js +1118 -0
- package/package.json +21 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { PulseRuntime } from '../scheduler/runtime.js';
|
|
2
|
+
import type { JsonValue, Outcome, RuntimeEvent } from '../core/types.js';
|
|
3
|
+
export type SessionEventKind = 'fact' | 'observation' | 'gap' | 'snapshot';
|
|
4
|
+
/**
|
|
5
|
+
* Session events use the DSL's `kind` field. `type` remains as a compatibility
|
|
6
|
+
* alias for the initial runtime API and is emitted with the same value.
|
|
7
|
+
*/
|
|
8
|
+
export interface SessionEvent {
|
|
9
|
+
kind: SessionEventKind;
|
|
10
|
+
type: SessionEventKind;
|
|
11
|
+
seq: number;
|
|
12
|
+
event?: RuntimeEvent;
|
|
13
|
+
observation?: JsonValue;
|
|
14
|
+
fromSeq?: number;
|
|
15
|
+
toSeq?: number;
|
|
16
|
+
snapshot?: JsonValue;
|
|
17
|
+
}
|
|
18
|
+
export interface PulseSessionSnapshot {
|
|
19
|
+
schemaVersion: 1;
|
|
20
|
+
agentId: string;
|
|
21
|
+
now: number;
|
|
22
|
+
eventSeq: number;
|
|
23
|
+
agent: JsonValue;
|
|
24
|
+
lanes: unknown[];
|
|
25
|
+
effects: unknown[];
|
|
26
|
+
waits: unknown[];
|
|
27
|
+
results: unknown[];
|
|
28
|
+
mergeProposals: unknown[];
|
|
29
|
+
quarantine: unknown[];
|
|
30
|
+
observationsPending: number;
|
|
31
|
+
}
|
|
32
|
+
export declare class PulseSession {
|
|
33
|
+
private readonly runtime;
|
|
34
|
+
readonly agentId: string;
|
|
35
|
+
private readonly execution;
|
|
36
|
+
/** Stable session handle used by the explicit warm-start API. */
|
|
37
|
+
readonly sessionId: string;
|
|
38
|
+
constructor(runtime: PulseRuntime, agentId: string);
|
|
39
|
+
private ownsEvent;
|
|
40
|
+
stream(fromSeq?: number): AsyncIterable<SessionEvent>;
|
|
41
|
+
snapshot(): Promise<PulseSessionSnapshot>;
|
|
42
|
+
outcome(): Promise<Outcome>;
|
|
43
|
+
reply(effectId: string, value: JsonValue): Promise<void>;
|
|
44
|
+
cancel(reason: string): Promise<void>;
|
|
45
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
export class PulseSession {
|
|
2
|
+
runtime;
|
|
3
|
+
agentId;
|
|
4
|
+
execution;
|
|
5
|
+
/** Stable session handle used by the explicit warm-start API. */
|
|
6
|
+
sessionId;
|
|
7
|
+
constructor(runtime, agentId) {
|
|
8
|
+
this.runtime = runtime;
|
|
9
|
+
this.agentId = agentId;
|
|
10
|
+
this.sessionId = agentId;
|
|
11
|
+
this.execution = runtime.runAgent(agentId).then((result) => {
|
|
12
|
+
const root = runtime.state.lanes.get(runtime.state.agents.get(agentId)?.rootLaneId ?? '');
|
|
13
|
+
return {
|
|
14
|
+
status: result.status,
|
|
15
|
+
...(root?.resultRef === undefined ? {} : { resultRef: root.resultRef }),
|
|
16
|
+
...(root?.failure === undefined ? {} : { error: root.failure.error }),
|
|
17
|
+
...(result.status === 'cancelled' && root?.cancelReason !== undefined ? { reason: root.cancelReason } : {}),
|
|
18
|
+
...((result.unresolvedEffectIds ?? []).length ? { unresolvedEffectIds: [...(result.unresolvedEffectIds ?? [])] } : {}),
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
ownsEvent(event) {
|
|
23
|
+
if (event.agentId === this.agentId)
|
|
24
|
+
return true;
|
|
25
|
+
if (event.laneId !== undefined)
|
|
26
|
+
return this.runtime.state.lanes.get(event.laneId)?.agentId === this.agentId;
|
|
27
|
+
if (event.effectId !== undefined)
|
|
28
|
+
return this.runtime.state.effects.get(event.effectId)?.agentId === this.agentId;
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
async *stream(fromSeq = 0) {
|
|
32
|
+
let cursor = fromSeq;
|
|
33
|
+
let observationCursor = 0;
|
|
34
|
+
while (true) {
|
|
35
|
+
const oldest = this.runtime.state.events[0]?.seq;
|
|
36
|
+
const compactedThrough = this.runtime.state.eventsCompactedThrough ?? 0;
|
|
37
|
+
const gapEnd = oldest === undefined ? compactedThrough : oldest - 1;
|
|
38
|
+
if (cursor < gapEnd) {
|
|
39
|
+
yield { kind: 'gap', type: 'gap', seq: gapEnd, fromSeq: cursor + 1, toSeq: gapEnd };
|
|
40
|
+
cursor = gapEnd;
|
|
41
|
+
}
|
|
42
|
+
const events = this.runtime.state.events.filter((event) => event.seq > cursor);
|
|
43
|
+
for (const event of events) {
|
|
44
|
+
cursor = event.seq;
|
|
45
|
+
if (this.ownsEvent(event))
|
|
46
|
+
yield { kind: 'fact', type: 'fact', seq: event.seq, event: structuredClone(event) };
|
|
47
|
+
}
|
|
48
|
+
const observationGapEnd = this.runtime.observationInbox.droppedThrough(this.agentId);
|
|
49
|
+
if (observationCursor < observationGapEnd) {
|
|
50
|
+
yield { kind: 'gap', type: 'gap', seq: observationGapEnd, fromSeq: observationCursor + 1, toSeq: observationGapEnd };
|
|
51
|
+
observationCursor = observationGapEnd;
|
|
52
|
+
}
|
|
53
|
+
for (const observation of this.runtime.observationInbox.drain(this.agentId)) {
|
|
54
|
+
observationCursor = Math.max(observationCursor, observation.seq);
|
|
55
|
+
yield { kind: 'observation', type: 'observation', seq: observation.seq, observation: observation };
|
|
56
|
+
}
|
|
57
|
+
const root = [...this.runtime.state.lanes.values()].find((lane) => lane.agentId === this.agentId && lane.ownerLaneId === undefined);
|
|
58
|
+
if (root && ['succeeded', 'failed', 'cancelled'].includes(root.status) && (this.runtime.state.events.at(-1)?.seq ?? compactedThrough) === cursor)
|
|
59
|
+
return;
|
|
60
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async snapshot() {
|
|
64
|
+
const agent = this.runtime.state.agents.get(this.agentId);
|
|
65
|
+
const laneIds = new Set([...this.runtime.state.lanes.values()].filter((lane) => lane.agentId === this.agentId).map((lane) => lane.id));
|
|
66
|
+
const effects = [...this.runtime.state.effects.values()].filter((effect) => effect.agentId === this.agentId);
|
|
67
|
+
const effectIds = new Set(effects.map((effect) => effect.id));
|
|
68
|
+
return {
|
|
69
|
+
schemaVersion: 1,
|
|
70
|
+
agentId: this.agentId,
|
|
71
|
+
now: this.runtime.state.now,
|
|
72
|
+
eventSeq: this.runtime.state.events.at(-1)?.seq ?? 0,
|
|
73
|
+
agent: agent ? structuredClone({ id: agent.id, goal: agent.goal ?? null, state: agent.state ?? null, latestGlobalVersion: agent.latestGlobalVersion, globalVersions: [...agent.globalVersions.entries()].map(([version, value]) => ({ version, value, ...(agent.globalPrivacy?.get(version) === undefined ? {} : { privacy: agent.globalPrivacy.get(version) }) })) }) : null,
|
|
74
|
+
lanes: [...this.runtime.state.lanes.values()].filter((lane) => laneIds.has(lane.id)).map((lane) => structuredClone({ id: lane.id, agentId: lane.agentId, ownerLaneId: lane.ownerLaneId ?? null, status: lane.status, cancelReason: lane.cancelReason ?? null, failure: lane.failure ?? null, version: lane.version, goal: lane.goal, priority: lane.priority, inheritedFloor: lane.inheritedFloor ?? null, readySince: lane.readySince, resume: lane.resume, pendingResumeInput: lane.pendingResumeInput ?? null, contextSnapshotVersion: lane.contextSnapshotVersion, context: lane.context, visibleResultRefs: lane.visibleResultRefs ? [...lane.visibleResultRefs] : [], historyPressure: lane.historyPressure ?? null, activeWaitId: lane.activeWaitId ?? null, children: [...lane.children], ownedEffectIds: [...lane.ownedEffectIds], resultRef: lane.resultRef ?? null, closingResult: lane.closingResult ?? null, consecutiveControlErrors: lane.consecutiveControlErrors ?? 0, unresolvedEffectIds: lane.unresolvedEffectIds ?? [], progressWatchdog: lane.progressWatchdog ?? null })),
|
|
75
|
+
effects: effects.map((effect) => structuredClone(effect)),
|
|
76
|
+
waits: [...this.runtime.state.waits.values()].filter((wait) => laneIds.has(wait.laneId)).map((wait) => structuredClone(wait)),
|
|
77
|
+
results: [...this.runtime.state.results.values()].filter((result) => (result.effectId !== undefined && effectIds.has(result.effectId)) || [...this.runtime.state.lanes.values()].some((lane) => laneIds.has(lane.id) && lane.resultRef === result.id)).map((result) => structuredClone(result)),
|
|
78
|
+
mergeProposals: [...this.runtime.state.mergeProposals.values()].filter((proposal) => proposal.agentId === this.agentId).map((proposal) => structuredClone(proposal)),
|
|
79
|
+
quarantine: structuredClone(this.runtime.quarantine.snapshot().filter((entry) => effectIds.has(entry.effectId))),
|
|
80
|
+
observationsPending: this.runtime.observationInbox.snapshot().filter((observation) => observation.agentId === this.agentId).length,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
async outcome() { return this.execution; }
|
|
84
|
+
async reply(effectId, value) {
|
|
85
|
+
const effect = this.runtime.state.effects.get(effectId);
|
|
86
|
+
if (!effect || effect.agentId !== this.agentId)
|
|
87
|
+
throw new Error('EFFECT_NOT_OWNED');
|
|
88
|
+
if (effect.kind !== 'human' || effect.outcome)
|
|
89
|
+
throw new Error('EFFECT_NOT_REPLYABLE');
|
|
90
|
+
this.runtime.enqueueHostCommand({ type: 'reply', agentId: this.agentId, effectId, value });
|
|
91
|
+
}
|
|
92
|
+
async cancel(reason) { this.runtime.requestCancel(this.agentId, reason); }
|
|
93
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './templates.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './templates.js';
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { type LaneProgramDefinition, type StepContext, type InstructionView, type NextStepTarget, type StepInputs } from './program.js';
|
|
2
|
+
import type { LaneProgram } from '../scheduler/runtime.js';
|
|
3
|
+
import type { Outcome, JsonValue } from '../core/types.js';
|
|
4
|
+
import type { ZodTypeAny } from 'zod';
|
|
5
|
+
export interface ProgramRef {
|
|
6
|
+
programId: string;
|
|
7
|
+
programVersion: string;
|
|
8
|
+
step?: string;
|
|
9
|
+
locals?: JsonValue;
|
|
10
|
+
}
|
|
11
|
+
export declare function defineReActLane(config: {
|
|
12
|
+
id: string;
|
|
13
|
+
version?: string;
|
|
14
|
+
system?: string;
|
|
15
|
+
toolSet?: string;
|
|
16
|
+
task?: string;
|
|
17
|
+
instruction: string | ((view: InstructionView<JsonValue>) => string);
|
|
18
|
+
inputs?: (ctx: StepContext<JsonValue>) => StepInputs;
|
|
19
|
+
toolAllow?: string[];
|
|
20
|
+
maxTurns?: number;
|
|
21
|
+
outputSchema?: ZodTypeAny;
|
|
22
|
+
requirements?: Record<string, JsonValue>;
|
|
23
|
+
toolApproval?: {
|
|
24
|
+
prompt: string | ((calls: JsonValue, ctx: StepContext<JsonValue>) => string);
|
|
25
|
+
onDenied?: (reason: string, ctx: StepContext<JsonValue>) => NextStepTarget<JsonValue>;
|
|
26
|
+
};
|
|
27
|
+
historyCompaction?: {
|
|
28
|
+
summarizeTask: string;
|
|
29
|
+
keepRecentRounds: number;
|
|
30
|
+
};
|
|
31
|
+
}): LaneProgramDefinition;
|
|
32
|
+
export declare function defineSeriesLane(config: {
|
|
33
|
+
id: string;
|
|
34
|
+
version?: string;
|
|
35
|
+
steps: string[];
|
|
36
|
+
} | {
|
|
37
|
+
id: string;
|
|
38
|
+
version?: string;
|
|
39
|
+
member: LaneProgram | ProgramRef;
|
|
40
|
+
keys?: string[];
|
|
41
|
+
onMemberFailure?: 'continue' | 'abort';
|
|
42
|
+
}): LaneProgramDefinition;
|
|
43
|
+
export interface PlanWorker extends ProgramRef {
|
|
44
|
+
goal?: string;
|
|
45
|
+
}
|
|
46
|
+
export interface PlanTask {
|
|
47
|
+
key: string;
|
|
48
|
+
goal: string;
|
|
49
|
+
affinityKey?: string;
|
|
50
|
+
}
|
|
51
|
+
export interface PlanDocument {
|
|
52
|
+
tasks: PlanTask[];
|
|
53
|
+
}
|
|
54
|
+
export interface PlanAndExecuteConfig {
|
|
55
|
+
id: string;
|
|
56
|
+
version?: string;
|
|
57
|
+
system?: string;
|
|
58
|
+
toolSet?: string;
|
|
59
|
+
planInstruction?: string;
|
|
60
|
+
planner?: {
|
|
61
|
+
task?: string;
|
|
62
|
+
instruction: string;
|
|
63
|
+
schema?: ZodTypeAny;
|
|
64
|
+
};
|
|
65
|
+
workers: Record<string, PlanWorker | {
|
|
66
|
+
goal: string;
|
|
67
|
+
programId: string;
|
|
68
|
+
programVersion?: string;
|
|
69
|
+
}>;
|
|
70
|
+
affinity?: 'collapse' | 'ack';
|
|
71
|
+
synthesizer?: {
|
|
72
|
+
task?: string;
|
|
73
|
+
instruction: string | ((ctx: StepContext) => string);
|
|
74
|
+
schema?: ZodTypeAny;
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export declare function definePlanAndExecuteLane(config: PlanAndExecuteConfig): LaneProgramDefinition;
|
|
78
|
+
export declare function defineScatterGatherLane<TItem>(config: {
|
|
79
|
+
id: string;
|
|
80
|
+
version?: string;
|
|
81
|
+
items: (ctx: StepContext) => TItem[];
|
|
82
|
+
worker: ProgramRef;
|
|
83
|
+
batch?: number;
|
|
84
|
+
reducer: (outcomes: Outcome[], ctx: StepContext) => NextStepTarget;
|
|
85
|
+
}): LaneProgramDefinition;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { defineLaneProgram } from './program.js';
|
|
2
|
+
export function defineReActLane(config) {
|
|
3
|
+
return defineLaneProgram({ id: config.id, version: config.version ?? '1', ...(config.system === undefined ? {} : { system: config.system }), ...(config.toolSet === undefined ? {} : { toolSet: config.toolSet }), ...(config.historyCompaction === undefined ? {} : { historyCompaction: config.historyCompaction }) }, (builder) => {
|
|
4
|
+
const onFinish = config.outputSchema === undefined
|
|
5
|
+
? { text: (resultRef) => ({ complete: { value: { textRef: resultRef } } }) }
|
|
6
|
+
: { text: (resultRef) => ({ complete: { value: { textRef: resultRef } } }), structured: { schema: config.outputSchema, onParsed: (value) => ({ complete: { value: value } }) } };
|
|
7
|
+
builder.addReActLoopStep('react', { ...(config.task === undefined ? {} : { task: config.task }), instruction: config.instruction, ...(config.inputs === undefined ? {} : { inputs: config.inputs }), ...(config.toolAllow === undefined ? {} : { toolAllow: config.toolAllow }), ...(config.maxTurns === undefined ? {} : { maxTurns: config.maxTurns }), ...(config.outputSchema === undefined ? {} : { outputSchema: config.outputSchema }), ...(config.requirements === undefined ? {} : { requirements: config.requirements }), ...(config.toolApproval === undefined ? {} : { toolApproval: config.toolApproval }), onFinish });
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
export function defineSeriesLane(config) {
|
|
11
|
+
if ('steps' in config)
|
|
12
|
+
return defineLaneProgram({ id: config.id, version: config.version ?? '1' }, (builder) => { config.steps.forEach((step, index) => builder.addStep(step, () => ({ actions: [{ type: 'complete', result: { step } }], next: config.steps[index + 1] ?? 'finish' }))); builder.addStep('finish', () => ({ actions: [{ type: 'complete', result: { ok: true } }], next: 'finish' })); });
|
|
13
|
+
const member = config.member;
|
|
14
|
+
const ref = 'id' in member ? { programId: member.id, programVersion: member.version, step: member.entry ?? 'start', locals: {} } : { programId: member.programId, programVersion: member.programVersion, step: member.step ?? 'start', locals: member.locals ?? {} };
|
|
15
|
+
const wrapper = defineLaneProgram({ id: config.id, version: config.version ?? '1' }, (builder) => { builder.addStep('start', () => ({ actions: [{ type: 'complete', result: { results: {} } }], next: 'start' })); });
|
|
16
|
+
wrapper.entry = 'start';
|
|
17
|
+
wrapper.seriesMember = ref;
|
|
18
|
+
if ('id' in member && 'step' in member)
|
|
19
|
+
wrapper.seriesMemberProgram = member;
|
|
20
|
+
wrapper.seriesKeys = [...(config.keys ?? ['member'])];
|
|
21
|
+
wrapper.seriesOnMemberFailure = config.onMemberFailure ?? 'continue';
|
|
22
|
+
return wrapper;
|
|
23
|
+
}
|
|
24
|
+
const permissiveSchema = { safeParse: (value) => ({ success: true, data: value }) };
|
|
25
|
+
function workerProgram(worker) {
|
|
26
|
+
return { programId: worker.programId, programVersion: worker.programVersion ?? '1', ...(!('step' in worker) || worker.step === undefined ? {} : { step: worker.step }), ...(!('locals' in worker) || worker.locals === undefined ? {} : { locals: worker.locals }) };
|
|
27
|
+
}
|
|
28
|
+
function plannedWorkers(config, ctx) {
|
|
29
|
+
const laneState = ctx.laneState;
|
|
30
|
+
const plan = laneState && typeof laneState === 'object' && !Array.isArray(laneState)
|
|
31
|
+
? laneState.plan
|
|
32
|
+
: undefined;
|
|
33
|
+
const tasks = plan && typeof plan === 'object' && !Array.isArray(plan)
|
|
34
|
+
? plan.tasks
|
|
35
|
+
: undefined;
|
|
36
|
+
// Keep the legacy permissive-planner behavior when no task list was declared.
|
|
37
|
+
// A typed planner schema produces `tasks` and therefore takes the dynamic path.
|
|
38
|
+
if (!Array.isArray(tasks))
|
|
39
|
+
return Object.fromEntries(Object.entries(config.workers).map(([key, worker]) => [key, { goal: 'goal' in worker && worker.goal !== undefined ? worker.goal : key, program: workerProgram(worker) }]));
|
|
40
|
+
const lanes = {};
|
|
41
|
+
for (const task of tasks) {
|
|
42
|
+
if (!task || typeof task !== 'object' || Array.isArray(task))
|
|
43
|
+
throw Object.assign(new Error('Planner task must be an object.'), { code: 'INVALID_PLAN' });
|
|
44
|
+
const entry = task;
|
|
45
|
+
const key = entry.key;
|
|
46
|
+
const goal = entry.goal;
|
|
47
|
+
if (typeof key !== 'string' || key.length === 0 || typeof goal !== 'string' || goal.length === 0)
|
|
48
|
+
throw Object.assign(new Error('Planner task requires a worker key and goal.'), { code: 'INVALID_PLAN' });
|
|
49
|
+
if (lanes[key] !== undefined || config.workers[key] === undefined)
|
|
50
|
+
throw Object.assign(new Error(`Planner task references an unavailable or duplicate worker: ${key}`), { code: 'INVALID_PLAN' });
|
|
51
|
+
const affinityKey = entry.affinityKey;
|
|
52
|
+
if (affinityKey !== undefined && (typeof affinityKey !== 'string' || affinityKey.length === 0))
|
|
53
|
+
throw Object.assign(new Error(`Planner task affinityKey must be a non-empty string: ${key}`), { code: 'INVALID_PLAN' });
|
|
54
|
+
lanes[key] = { goal, program: workerProgram(config.workers[key]), ...(affinityKey === undefined ? {} : { affinityKey }) };
|
|
55
|
+
}
|
|
56
|
+
return lanes;
|
|
57
|
+
}
|
|
58
|
+
export function definePlanAndExecuteLane(config) {
|
|
59
|
+
const planner = config.planner ?? { task: 'plan', instruction: config.planInstruction ?? 'Create an executable plan for the goal.' };
|
|
60
|
+
const synthesizer = config.synthesizer ?? { task: 'merge', instruction: 'Synthesize the joined worker outcomes into a concise final report.' };
|
|
61
|
+
return defineLaneProgram({ id: config.id, version: config.version ?? '1', ...(config.system === undefined ? {} : { system: config.system }), ...(config.toolSet === undefined ? {} : { toolSet: config.toolSet }) }, (builder) => {
|
|
62
|
+
builder.addStructuredLLMStep('plan', {
|
|
63
|
+
task: planner.task ?? 'plan',
|
|
64
|
+
instruction: planner.instruction,
|
|
65
|
+
schema: planner.schema ?? permissiveSchema,
|
|
66
|
+
onSuccess: (plan, ctx) => {
|
|
67
|
+
ctx.mutateLane((draft) => {
|
|
68
|
+
if (draft && typeof draft === 'object' && !Array.isArray(draft))
|
|
69
|
+
draft.plan = plan;
|
|
70
|
+
});
|
|
71
|
+
return 'dispatch';
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
builder.addDynamicForkStep('dispatch', {
|
|
75
|
+
lanes: (ctx) => plannedWorkers(config, ctx),
|
|
76
|
+
affinity: config.affinity === 'ack' ? 'ack' : 'collapse',
|
|
77
|
+
next: 'synthesize',
|
|
78
|
+
});
|
|
79
|
+
builder.addMergeStep('synthesize', {
|
|
80
|
+
task: synthesizer.task ?? 'merge',
|
|
81
|
+
instruction: synthesizer.instruction,
|
|
82
|
+
...(synthesizer.schema === undefined ? {} : { schema: synthesizer.schema }),
|
|
83
|
+
sources: { proposals: 'joined', outcomes: 'joined' },
|
|
84
|
+
onSynthesized: (report, ctx) => {
|
|
85
|
+
ctx.commitGlobal({ ops: [{ op: 'set', path: ['synthesis'], value: report }], adoptImmediately: true });
|
|
86
|
+
return 'finish';
|
|
87
|
+
},
|
|
88
|
+
next: 'finish',
|
|
89
|
+
});
|
|
90
|
+
builder.addStep('finish', (ctx) => ({ actions: [{ type: 'complete', result: { ok: true, globalVersion: ctx.globalVersion } }], next: 'finish' }));
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
export function defineScatterGatherLane(config) {
|
|
94
|
+
const batch = Math.max(1, Math.floor(config.batch ?? 1));
|
|
95
|
+
return defineLaneProgram({ id: config.id, version: config.version ?? '1' }, (builder) => {
|
|
96
|
+
builder.addDynamicForkStep('scatter', {
|
|
97
|
+
lanes: (ctx) => {
|
|
98
|
+
const items = config.items(ctx);
|
|
99
|
+
const lanes = {};
|
|
100
|
+
for (let index = 0; index < items.length; index += batch) {
|
|
101
|
+
const group = items.slice(index, index + batch);
|
|
102
|
+
const key = `item-${index}`;
|
|
103
|
+
lanes[key] = { goal: JSON.stringify(group), program: { ...config.worker, locals: { items: group } } };
|
|
104
|
+
}
|
|
105
|
+
return lanes;
|
|
106
|
+
}, affinity: 'ack', onJoin: (outcomes, ctx) => config.reducer([...outcomes.values()], ctx), next: 'finish',
|
|
107
|
+
});
|
|
108
|
+
builder.addStep('finish', (ctx) => ({ actions: [{ type: 'complete', result: { gathered: true, outcomes: (ctx.resumeInput?.type === 'wait' ? Object.fromEntries(Object.entries(ctx.resumeInput.resolution.dependencies).map(([key, value]) => [key, value.state === 'pending' ? null : value.outcome])) : {}) } }], next: 'finish' }));
|
|
109
|
+
});
|
|
110
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export * from './core/types.js';
|
|
2
|
+
export * from './core/errors.js';
|
|
3
|
+
export * from './core/mutations.js';
|
|
4
|
+
export * from './core/inbox.js';
|
|
5
|
+
export * from './core/factory.js';
|
|
6
|
+
export * from './transitions/index.js';
|
|
7
|
+
export * from './dependencies/index.js';
|
|
8
|
+
export * from './scheduler/index.js';
|
|
9
|
+
export * from './scheduler/runtime.js';
|
|
10
|
+
export * from './lifecycle/index.js';
|
|
11
|
+
export * from './context/index.js';
|
|
12
|
+
export * from './models/index.js';
|
|
13
|
+
export * from './tools/registry.js';
|
|
14
|
+
export * from './storage/index.js';
|
|
15
|
+
export * from './dsl/index.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export * from './core/types.js';
|
|
2
|
+
export * from './core/errors.js';
|
|
3
|
+
export * from './core/mutations.js';
|
|
4
|
+
export * from './core/inbox.js';
|
|
5
|
+
export * from './core/factory.js';
|
|
6
|
+
export * from './transitions/index.js';
|
|
7
|
+
export * from './dependencies/index.js';
|
|
8
|
+
export * from './scheduler/index.js';
|
|
9
|
+
export * from './scheduler/runtime.js';
|
|
10
|
+
export * from './lifecycle/index.js';
|
|
11
|
+
export * from './context/index.js';
|
|
12
|
+
export * from './models/index.js';
|
|
13
|
+
export * from './tools/registry.js';
|
|
14
|
+
export * from './storage/index.js';
|
|
15
|
+
export * from './dsl/index.js';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export declare class CancellationScope {
|
|
2
|
+
readonly ownerId: string;
|
|
3
|
+
readonly parent?: CancellationScope | undefined;
|
|
4
|
+
readonly children: Set<CancellationScope>;
|
|
5
|
+
cancelled: boolean;
|
|
6
|
+
reason?: string;
|
|
7
|
+
constructor(ownerId: string, parent?: CancellationScope | undefined);
|
|
8
|
+
cancel(reason?: string): void;
|
|
9
|
+
canCancel(target: CancellationScope): boolean;
|
|
10
|
+
dispose(): void;
|
|
11
|
+
}
|
|
12
|
+
export interface QuarantineEntry {
|
|
13
|
+
effectId: string;
|
|
14
|
+
unresolvedAt: number;
|
|
15
|
+
reason: string;
|
|
16
|
+
}
|
|
17
|
+
export declare class QuarantineScope {
|
|
18
|
+
private readonly entries;
|
|
19
|
+
add(effectId: string, unresolvedAt: number, reason?: string): void;
|
|
20
|
+
reconcile(effectId: string): boolean;
|
|
21
|
+
abandon(effectId: string): boolean;
|
|
22
|
+
has(effectId: string): boolean;
|
|
23
|
+
get unresolvedEffectIds(): string[];
|
|
24
|
+
snapshot(): QuarantineEntry[];
|
|
25
|
+
restore(entries: QuarantineEntry[]): void;
|
|
26
|
+
run<T>(work: () => T): {
|
|
27
|
+
value: T;
|
|
28
|
+
unresolvedEffectIds: string[];
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export declare class HostCommandQueue {
|
|
32
|
+
private draining;
|
|
33
|
+
private readonly pending;
|
|
34
|
+
enqueue(command: () => void): void;
|
|
35
|
+
beginDrain(): void;
|
|
36
|
+
finishDrain(): void;
|
|
37
|
+
get size(): number;
|
|
38
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export class CancellationScope {
|
|
2
|
+
ownerId;
|
|
3
|
+
parent;
|
|
4
|
+
children = new Set();
|
|
5
|
+
cancelled = false;
|
|
6
|
+
reason;
|
|
7
|
+
constructor(ownerId, parent) {
|
|
8
|
+
this.ownerId = ownerId;
|
|
9
|
+
this.parent = parent;
|
|
10
|
+
parent?.children.add(this);
|
|
11
|
+
}
|
|
12
|
+
cancel(reason = 'USER_REQUESTED') { if (this.cancelled)
|
|
13
|
+
return; this.cancelled = true; this.reason = reason; for (const child of this.children)
|
|
14
|
+
child.cancel(reason); }
|
|
15
|
+
canCancel(target) { let current = target; while (current) {
|
|
16
|
+
if (current === this)
|
|
17
|
+
return true;
|
|
18
|
+
current = current.parent;
|
|
19
|
+
} return false; }
|
|
20
|
+
dispose() { this.parent?.children.delete(this); for (const child of this.children)
|
|
21
|
+
child.dispose(); this.children.clear(); }
|
|
22
|
+
}
|
|
23
|
+
export class QuarantineScope {
|
|
24
|
+
entries = new Map();
|
|
25
|
+
add(effectId, unresolvedAt, reason = 'cancel_grace_elapsed') { this.entries.set(effectId, { effectId, unresolvedAt, reason }); }
|
|
26
|
+
reconcile(effectId) { return this.entries.delete(effectId); }
|
|
27
|
+
abandon(effectId) { return this.entries.delete(effectId); }
|
|
28
|
+
has(effectId) { return this.entries.has(effectId); }
|
|
29
|
+
get unresolvedEffectIds() { return [...this.entries.keys()]; }
|
|
30
|
+
snapshot() { return [...this.entries.values()].map((entry) => ({ ...entry })); }
|
|
31
|
+
restore(entries) { for (const entry of entries)
|
|
32
|
+
this.entries.set(entry.effectId, { ...entry }); }
|
|
33
|
+
run(work) { return { value: work(), unresolvedEffectIds: this.unresolvedEffectIds }; }
|
|
34
|
+
}
|
|
35
|
+
export class HostCommandQueue {
|
|
36
|
+
draining = false;
|
|
37
|
+
pending = [];
|
|
38
|
+
enqueue(command) { if (this.draining)
|
|
39
|
+
this.pending.push(command);
|
|
40
|
+
else
|
|
41
|
+
command(); }
|
|
42
|
+
beginDrain() { this.draining = true; }
|
|
43
|
+
finishDrain() {
|
|
44
|
+
this.draining = false;
|
|
45
|
+
const commands = this.pending.splice(0);
|
|
46
|
+
for (const command of commands)
|
|
47
|
+
command();
|
|
48
|
+
}
|
|
49
|
+
get size() { return this.pending.length; }
|
|
50
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { LaneRecord, LaneStepOutput, ProgressWatchdogState, RuntimeError, RuntimeState } from '../core/types.js';
|
|
2
|
+
export interface ProgressWatchdogOptions {
|
|
3
|
+
windowSize?: number;
|
|
4
|
+
noProgressThreshold?: number;
|
|
5
|
+
repeatedActionThreshold?: number;
|
|
6
|
+
admission?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface ProgressObservation {
|
|
9
|
+
state: ProgressWatchdogState;
|
|
10
|
+
fingerprint: string;
|
|
11
|
+
progressed: boolean;
|
|
12
|
+
repeatedActionCount: number;
|
|
13
|
+
rejected?: RuntimeError;
|
|
14
|
+
}
|
|
15
|
+
export declare function progressFingerprint(lane: LaneRecord, output: LaneStepOutput, state: RuntimeState): string;
|
|
16
|
+
export declare function observeProgress(lane: LaneRecord, output: LaneStepOutput, state: RuntimeState, previous?: ProgressWatchdogState, options?: ProgressWatchdogOptions): ProgressObservation;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { contentHash } from '../context/builder.js';
|
|
2
|
+
import { provenanceRefId } from '../core/types.js';
|
|
3
|
+
function withoutSdk(value) {
|
|
4
|
+
if (Array.isArray(value))
|
|
5
|
+
return value.map(withoutSdk);
|
|
6
|
+
if (!value || typeof value !== 'object')
|
|
7
|
+
return value;
|
|
8
|
+
return Object.fromEntries(Object.entries(value).filter(([key]) => key !== '$sdk').map(([key, item]) => [key, withoutSdk(item)]));
|
|
9
|
+
}
|
|
10
|
+
function canonical(value, key) {
|
|
11
|
+
if (key === 'id' || key === 'effectId' || key === 'attemptId' || key === 'toolCallId' || key === 'requestId' || key === 'providerId' || key === 'timestamp' || key === 'seq' || key === 'telemetry')
|
|
12
|
+
return null;
|
|
13
|
+
if (Array.isArray(value))
|
|
14
|
+
return value.map((item) => canonical(item));
|
|
15
|
+
if (!value || typeof value !== 'object')
|
|
16
|
+
return value;
|
|
17
|
+
return Object.fromEntries(Object.entries(value).filter(([entryKey]) => entryKey !== 'derivedFrom').sort(([left], [right]) => left.localeCompare(right)).map(([entryKey, item]) => [entryKey, canonical(item, entryKey)]));
|
|
18
|
+
}
|
|
19
|
+
function actionSignature(output) {
|
|
20
|
+
return contentHash(output.actions.map((action) => canonical(action)));
|
|
21
|
+
}
|
|
22
|
+
function resultSignature(output, state) {
|
|
23
|
+
const refs = output.actions.flatMap((action) => 'derivedFrom' in action && action.derivedFrom ? action.derivedFrom : []);
|
|
24
|
+
const results = refs.map((ref) => {
|
|
25
|
+
const result = state.results.get(provenanceRefId(ref));
|
|
26
|
+
return result?.normalized ?? result?.value;
|
|
27
|
+
}).filter((value) => value !== undefined);
|
|
28
|
+
const terminalResults = output.actions.filter((action) => action.type === 'complete').map((action) => action.result);
|
|
29
|
+
const all = [...results, ...terminalResults];
|
|
30
|
+
return all.length ? contentHash(all.map((value) => canonical(value))) : undefined;
|
|
31
|
+
}
|
|
32
|
+
function progressKey(lane, output) {
|
|
33
|
+
return contentHash({ goal: canonical(lane.goal), contextVersion: lane.context.version, context: canonical(lane.context.state), resumeStep: output.next.step, localsHash: contentHash(withoutSdk(output.next.locals)) });
|
|
34
|
+
}
|
|
35
|
+
export function progressFingerprint(lane, output, state) {
|
|
36
|
+
const action = actionSignature(output);
|
|
37
|
+
const result = resultSignature(output, state);
|
|
38
|
+
return contentHash({ goalStateHash: contentHash({ goal: canonical(lane.goal), context: canonical(lane.context.state) }), contextVersion: lane.context.version, actionSignature: action, ...(result === undefined ? {} : { resultSignature: result }), resumeStep: output.next.step, localsHash: contentHash(withoutSdk(output.next.locals)) });
|
|
39
|
+
}
|
|
40
|
+
export function observeProgress(lane, output, state, previous, options = {}) {
|
|
41
|
+
const fingerprint = progressFingerprint(lane, output, state);
|
|
42
|
+
const windowSize = options.windowSize ?? 8;
|
|
43
|
+
const threshold = Math.max(1, options.noProgressThreshold ?? 3);
|
|
44
|
+
const repeatedThreshold = Math.max(1, options.repeatedActionThreshold ?? 3);
|
|
45
|
+
const prior = previous ?? { window: [], noProgressCount: 0, interventionLevel: 0 };
|
|
46
|
+
const action = actionSignature(output);
|
|
47
|
+
const progress = progressKey(lane, output);
|
|
48
|
+
const priorActions = prior.actionSignatures ?? [];
|
|
49
|
+
const priorProgress = prior.progressKeys ?? [];
|
|
50
|
+
const repeatedActionCount = priorActions.filter((candidate) => candidate === action).length + 1;
|
|
51
|
+
const sameProgress = priorProgress.at(-1) === progress;
|
|
52
|
+
const progressed = prior.lastFingerprint === undefined || !sameProgress;
|
|
53
|
+
const qualifies = sameProgress && priorActions.includes(action) && repeatedActionCount >= repeatedThreshold;
|
|
54
|
+
const noProgressCount = qualifies ? prior.noProgressCount + 1 : sameProgress ? prior.noProgressCount : 0;
|
|
55
|
+
const reached = qualifies && noProgressCount >= threshold;
|
|
56
|
+
if (reached && options.admission) {
|
|
57
|
+
const interventionLevel = Math.min(3, prior.interventionLevel + 1);
|
|
58
|
+
const rejection = { code: 'NO_PROGRESS_DETECTED', message: interventionLevel >= 3 ? 'Lane made no observable progress within the watchdog threshold.' : 'Lane repeated an action without material progress; change strategy before retrying.', details: { repeatedActionCount, noProgressCount, interventionLevel } };
|
|
59
|
+
return { fingerprint, progressed: false, repeatedActionCount, rejected: rejection, state: { ...prior, noProgressCount: 0, interventionLevel, lastReason: rejection.code } };
|
|
60
|
+
}
|
|
61
|
+
const interventionLevel = progressed ? 0 : Math.max(prior.interventionLevel, Math.min(3, Math.floor(noProgressCount / threshold)));
|
|
62
|
+
const window = [...prior.window, fingerprint].slice(-windowSize);
|
|
63
|
+
const actionSignatures = [...priorActions, action].slice(-windowSize);
|
|
64
|
+
const progressKeys = [...priorProgress, progress].slice(-windowSize);
|
|
65
|
+
return { fingerprint, progressed, repeatedActionCount, state: { window, actionSignatures, progressKeys, noProgressCount, interventionLevel, lastFingerprint: fingerprint, ...(interventionLevel === 0 ? {} : { lastReason: 'NO_PROGRESS_DETECTED' }) } };
|
|
66
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PrivacyLabel, ProvenanceRef, RuntimeAction } from '../core/types.js';
|
|
2
|
+
import type { LLMResult } from './router.js';
|
|
3
|
+
export interface ActionDecoderOptions {
|
|
4
|
+
allowedTools: ReadonlySet<string>;
|
|
5
|
+
wait?: boolean;
|
|
6
|
+
llmEffectId?: string;
|
|
7
|
+
privacy?: PrivacyLabel;
|
|
8
|
+
derivedFrom?: ProvenanceRef[];
|
|
9
|
+
}
|
|
10
|
+
export declare function decodeLLMActions(result: LLMResult, options: ActionDecoderOptions): RuntimeAction[];
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { validateActionToolCalls, validateAdapterResult } from './router.js';
|
|
2
|
+
function toJsonValue(value, seen = new Set()) {
|
|
3
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean')
|
|
4
|
+
return value;
|
|
5
|
+
if (typeof value === 'number') {
|
|
6
|
+
if (!Number.isFinite(value))
|
|
7
|
+
throw new Error('ACTION_INPUT_NOT_SERIALIZABLE');
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
if (Array.isArray(value)) {
|
|
11
|
+
if (seen.has(value))
|
|
12
|
+
throw new Error('ACTION_INPUT_NOT_SERIALIZABLE');
|
|
13
|
+
seen.add(value);
|
|
14
|
+
try {
|
|
15
|
+
return value.map((item) => toJsonValue(item, seen));
|
|
16
|
+
}
|
|
17
|
+
finally {
|
|
18
|
+
seen.delete(value);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (typeof value === 'object') {
|
|
22
|
+
if (value instanceof Uint8Array || value instanceof ArrayBuffer || value instanceof Date || Object.getPrototypeOf(value) !== Object.prototype || seen.has(value))
|
|
23
|
+
throw new Error('ACTION_INPUT_NOT_SERIALIZABLE');
|
|
24
|
+
seen.add(value);
|
|
25
|
+
try {
|
|
26
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, toJsonValue(item, seen)]));
|
|
27
|
+
}
|
|
28
|
+
finally {
|
|
29
|
+
seen.delete(value);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
throw new Error('ACTION_INPUT_NOT_SERIALIZABLE');
|
|
33
|
+
}
|
|
34
|
+
function cloneProvenanceRefs(refs) {
|
|
35
|
+
return refs === undefined ? undefined : refs.map((ref) => typeof ref === 'string' ? ref : { ...ref });
|
|
36
|
+
}
|
|
37
|
+
export function decodeLLMActions(result, options) {
|
|
38
|
+
validateAdapterResult(result);
|
|
39
|
+
validateActionToolCalls(result, options.allowedTools);
|
|
40
|
+
if (result.finishReason !== 'tool_calls')
|
|
41
|
+
return [];
|
|
42
|
+
if (result.toolCalls.length === 0)
|
|
43
|
+
throw new Error('INVALID_TOOL_CALL_FINISH_REASON');
|
|
44
|
+
const privacy = options.privacy ?? result.privacy;
|
|
45
|
+
const inheritedDerivedFrom = cloneProvenanceRefs(options.derivedFrom ?? result.derivedFrom);
|
|
46
|
+
return [{
|
|
47
|
+
type: 'submit_effects',
|
|
48
|
+
effects: result.toolCalls.map((call) => {
|
|
49
|
+
const derivedFrom = inheritedDerivedFrom === undefined ? undefined : cloneProvenanceRefs(inheritedDerivedFrom);
|
|
50
|
+
const input = { toolCallId: call.toolCallId, name: call.name, arguments: toJsonValue(call.input) };
|
|
51
|
+
if (privacy !== undefined)
|
|
52
|
+
input.privacy = privacy;
|
|
53
|
+
if (derivedFrom !== undefined)
|
|
54
|
+
input.derivedFrom = derivedFrom;
|
|
55
|
+
return {
|
|
56
|
+
key: `tool:${call.toolCallId}`,
|
|
57
|
+
toolCallId: call.toolCallId,
|
|
58
|
+
...(options.llmEffectId === undefined ? {} : { llmEffectId: options.llmEffectId }),
|
|
59
|
+
...(privacy === undefined ? {} : { privacy }),
|
|
60
|
+
...(derivedFrom === undefined ? {} : { derivedFrom }),
|
|
61
|
+
kind: 'tool',
|
|
62
|
+
concurrencyClass: 'tool',
|
|
63
|
+
input,
|
|
64
|
+
};
|
|
65
|
+
}),
|
|
66
|
+
...(options.wait === false ? {} : { wait: { onUnsatisfied: 'resume_with_error', reason: 'effect' } }),
|
|
67
|
+
}];
|
|
68
|
+
}
|