@noetaris/harness 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -7
- package/dist/index.d.ts +61 -16
- package/dist/index.js +88 -25
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -140,6 +140,53 @@ h.store({
|
|
|
140
140
|
|
|
141
141
|
The framework injects `ctx.sessionId` automatically on every run — no declaration in `Ctx` needed.
|
|
142
142
|
|
|
143
|
+
### Turns and `requestId`
|
|
144
|
+
|
|
145
|
+
With a session store, a session is a conversation thread. Each `agent.run()` on it is one
|
|
146
|
+
**turn** (a request). A turn may span several runs — `run()`, then a `resume()` for each
|
|
147
|
+
interrupt — and every run of a turn is saved with the turn's `requestId`.
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
// one turn per user message, on one thread
|
|
151
|
+
await agent.run({ messages: [msg1] }, { sessionId: 'chat-1', requestId: 'msg-1' })
|
|
152
|
+
await agent.run({ messages: [msg2] }, { sessionId: 'chat-1', requestId: 'msg-2' })
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
A new turn continues from the state the last turn left: each input field is merged with its
|
|
156
|
+
`reduce` (or replaced when it has none), and the run starts again from the entry step.
|
|
157
|
+
`$error`, `$interrupt` and `$interruptResponses` start fresh for each new turn.
|
|
158
|
+
|
|
159
|
+
`requestId` is optional. It is the turn's idempotency key: a call with a `requestId` the
|
|
160
|
+
session already ran does **not** run it again. What `agent.run(input, { sessionId, requestId })`
|
|
161
|
+
does depends on the session's latest run:
|
|
162
|
+
|
|
163
|
+
| Latest run | No `requestId`, or a new one | The latest run's `requestId` (a retry) |
|
|
164
|
+
|---|---|---|
|
|
165
|
+
| none | runs from the entry step | – |
|
|
166
|
+
| completed | a new turn | returns its outcome, runs nothing |
|
|
167
|
+
| paused on an interrupt | resolves `$error` with `SessionPendingInterruptError` | returns its outcome (`signal: '$interrupt'`), runs nothing |
|
|
168
|
+
| paused by a stop or an unhandled error | merges the input and continues from where it paused | continues from where it paused, **without** merging the input again |
|
|
169
|
+
|
|
170
|
+
A retry of an earlier request (not the latest) is found with the store's optional
|
|
171
|
+
`findRequest` and its outcome is returned; it never runs again, since later turns have
|
|
172
|
+
moved on from it. A store without `findRequest` only recognises the latest run's
|
|
173
|
+
`requestId`, so a late retry of an earlier request runs as a new turn.
|
|
174
|
+
|
|
175
|
+
Notes:
|
|
176
|
+
- A retry returns the request's **current** outcome, not its first response: if the turn
|
|
177
|
+
paused and was then resumed to completion, the retry returns the completed outcome. A
|
|
178
|
+
retry that returns `signal: '$interrupt'` can be resumed from its handle.
|
|
179
|
+
- A returned (not re-run) outcome fires no observer events and saves nothing; its
|
|
180
|
+
`handle.runId` has no stored run and no trace.
|
|
181
|
+
- Without a `requestId`, every call is a new turn. For a session that serves one request,
|
|
182
|
+
pass `requestId: sessionId` to get "run once, then return the result".
|
|
183
|
+
- Duplicate calls at the same time are safe only with a store that supports claims: the
|
|
184
|
+
second call resolves `$error` with `SessionBusyError` and its retry then returns the
|
|
185
|
+
outcome. Without claims, both calls may run.
|
|
186
|
+
- Without a session store, `requestId` is ignored.
|
|
187
|
+
- `sessionId` and `requestId` must be non-empty strings when given; otherwise `agent.run()`
|
|
188
|
+
throws `InvalidRunResourceError`.
|
|
189
|
+
|
|
143
190
|
### Interrupts
|
|
144
191
|
|
|
145
192
|
A run can be stopped or resumed:
|
|
@@ -331,9 +378,13 @@ Some runs continue an earlier one. Their `RunContext` says which, so an observer
|
|
|
331
378
|
|---|---|
|
|
332
379
|
| the next run on a session that paused — on an interrupt, a stop, or an unhandled error — whether started by `agent.resume()`, `run.resume()` (with or without a store), or `agent.run()` on that session | `resumedFrom: { runId, carrier? }` — the run that paused |
|
|
333
380
|
| the first run of a session created by `SessionStore.branch()` | `branchedFrom: { sessionId, runId, carrier? }` — the run it was branched from, in the source session |
|
|
381
|
+
| a new turn on a session whose last run completed (see [Turns and `requestId`](#turns-and-requestid)) | `continuedFrom: { runId, carrier? }` — the previous turn's last run |
|
|
382
|
+
|
|
383
|
+
A run has at most one of the three, only on its top-level context (never on a fork branch's). A
|
|
384
|
+
call that returns a request's outcome without running has none: it has no run.
|
|
334
385
|
|
|
335
|
-
|
|
336
|
-
|
|
386
|
+
Every context of a turn's runs (fork branches included) also carries `requestId` when the turn
|
|
387
|
+
has one.
|
|
337
388
|
|
|
338
389
|
`carrier` is what the earlier run's observer returned from `carry(ctx)`: core calls `carry` once,
|
|
339
390
|
right after `onRunStart`, keeps its string values, and saves them as `StoredRun.carrier` (or on the
|
|
@@ -416,9 +467,9 @@ The harness never trusts a wrapper. `fn` always runs exactly once, and the calle
|
|
|
416
467
|
| `required()` | Marks a provider slot as required at `createAgent()`. |
|
|
417
468
|
| `runtime()` | Marks a provider slot as required at `agent.run()`. |
|
|
418
469
|
| `composeObservers([a, b], onObserverError?)` | Merges multiple `Observer` instances into one fan-out observer; a throwing observer is isolated and reported to `onObserverError`. |
|
|
419
|
-
| `SessionStore` | Interface for session persistence backends. |
|
|
420
|
-
| `StoredRun` | Type for a persisted run snapshot. Includes `agentId`, `runId`, `sessionId`, `phase`, state, and optionally `carrier` (the run's `carry` result) and `branchedFrom` (on a branch seed). |
|
|
421
|
-
| `RunRef` | `{ runId, carrier? }` — points at an earlier run; used by `RunContext.resumedFrom` and `branchedFrom` (which adds `sessionId`). |
|
|
470
|
+
| `SessionStore` | Interface for session persistence backends. Optional extensions: `loadHistory`, `branch`, `claim` / `release` / `extendClaim`, and `findRequest` (the newest run of a request, for retries of earlier requests). |
|
|
471
|
+
| `StoredRun` | Type for a persisted run snapshot. Includes `agentId`, `runId`, `sessionId`, `phase`, state, and optionally `requestId` (the run's turn), `carrier` (the run's `carry` result) and `branchedFrom` (on a branch seed). |
|
|
472
|
+
| `RunRef` | `{ runId, carrier? }` — points at an earlier run; used by `RunContext.resumedFrom`, `continuedFrom` and `branchedFrom` (which adds `sessionId`). |
|
|
422
473
|
| `RunCarrier` | `Readonly<Record<string, string>>` — the opaque map an observer's `carry` returns. |
|
|
423
474
|
| `Observer` | Interface for telemetry hooks on run and step lifecycle events (see [Observers](#observers)). |
|
|
424
475
|
| `StepSettledEvent` | Payload of `Observer.onStepSettled` — outcome, duration, update, signal, next, error, interrupt, fork. |
|
|
@@ -426,11 +477,12 @@ The harness never trusts a wrapper. `fn` always runs exactly once, and the calle
|
|
|
426
477
|
| `ObserverAware` | Interface for provider objects that want per-step telemetry: the harness calls `withTelemetry(telemetry)` once per step and installs the returned view on `ctx`. |
|
|
427
478
|
| `TelemetryContext` | What `withTelemetry` receives: `observer` (the run's observer, fault-isolated), `stepContext`, and `within(type, payload, fn)` bound to that step. |
|
|
428
479
|
| `WrapScope` | What `Observer.wrap` is asked to enclose: `{ kind: 'step' }` or `{ kind: 'event', type, payload }`. |
|
|
429
|
-
| `RunContext` | Context passed to run-level observer hooks — `agentId`, `sessionId`, `runId`, `parentRunId?`, `instanceId?`, `branchPath?`, `resumedFrom?`, `branchedFrom?` (see [Linking runs](#linking-runs)). |
|
|
480
|
+
| `RunContext` | Context passed to run-level observer hooks — `agentId`, `sessionId`, `runId`, `parentRunId?`, `instanceId?`, `branchPath?`, `resumedFrom?`, `branchedFrom?`, `continuedFrom?`, `requestId?` (see [Linking runs](#linking-runs)). |
|
|
430
481
|
| `StepContext` | Context passed to step-level observer hooks — `agentId`, `sessionId`, `runId`, `stepName`, `branchPath?`. |
|
|
482
|
+
| `InvalidRunResourceError` | Thrown by `agent.run()` when `sessionId` or `requestId` is given but is not a non-empty string; `key` and `value` name it. |
|
|
431
483
|
| `NoInterruptError` | Thrown when `resume()` is called but the session is not paused on a matching interrupt. |
|
|
432
484
|
| `SessionInFlightError` | Thrown when a session is already running. |
|
|
433
|
-
| `SessionPendingInterruptError` |
|
|
485
|
+
| `SessionPendingInterruptError` | `agent.run()` on a session paused on a pending interrupt — use `agent.resume()` instead. With a session store, the run resolves with `signal: '$error'` and this error in `state.$error`; without one, `agent.run()` throws it. |
|
|
434
486
|
| `StoreLoadError` | Thrown when the session store fails to load state. |
|
|
435
487
|
|
|
436
488
|
## Design Principles
|
package/dist/index.d.ts
CHANGED
|
@@ -133,7 +133,8 @@ interface RunContext {
|
|
|
133
133
|
/**
|
|
134
134
|
* The run this one continues: the run that paused the session (on an interrupt, a stop or
|
|
135
135
|
* an unhandled error), with the carrier its observer produced. Present on a resumed
|
|
136
|
-
* top-level run only — never on a fork branch's context, never together with `branchedFrom
|
|
136
|
+
* top-level run only — never on a fork branch's context, never together with `branchedFrom` or
|
|
137
|
+
* `continuedFrom`.
|
|
137
138
|
*/
|
|
138
139
|
readonly resumedFrom?: RunRef;
|
|
139
140
|
/**
|
|
@@ -143,6 +144,18 @@ interface RunContext {
|
|
|
143
144
|
readonly branchedFrom?: RunRef & {
|
|
144
145
|
readonly sessionId: string;
|
|
145
146
|
};
|
|
147
|
+
/**
|
|
148
|
+
* The previous turn's run, when this run starts a new turn on a session whose last run
|
|
149
|
+
* completed. Present on the top-level run only — never together with `resumedFrom` or
|
|
150
|
+
* `branchedFrom`.
|
|
151
|
+
*/
|
|
152
|
+
readonly continuedFrom?: RunRef;
|
|
153
|
+
/**
|
|
154
|
+
* The request (turn) this run belongs to — the `requestId` passed to `agent.run()`, kept by
|
|
155
|
+
* every resume of that turn. Present on every context of the run, fork branches included.
|
|
156
|
+
* Absent for keyless turns and when the agent has no session store.
|
|
157
|
+
*/
|
|
158
|
+
readonly requestId?: string;
|
|
146
159
|
}
|
|
147
160
|
/**
|
|
148
161
|
* Identifies the agent, session, and current step for step-level observer callbacks.
|
|
@@ -321,7 +334,7 @@ interface Observer {
|
|
|
321
334
|
* Called once per top-level run, right after `onRunStart` (never for a fork branch). Return
|
|
322
335
|
* an opaque string map that lets a later run refer back to this one — e.g. the run's trace
|
|
323
336
|
* context. Core saves it on the run's record and hands it back as `RunContext.resumedFrom.carrier`
|
|
324
|
-
* (or `branchedFrom.carrier`) to the run that continues or branches from it. Return
|
|
337
|
+
* (or `branchedFrom.carrier`, `continuedFrom.carrier`) to the run that continues or branches from it. Return
|
|
325
338
|
* `undefined` for nothing. Only string values are kept; a throw is reported via
|
|
326
339
|
* `onObserverError` (hook name `'carry'`) and counts as `undefined`.
|
|
327
340
|
*/
|
|
@@ -537,7 +550,7 @@ interface SessionStore {
|
|
|
537
550
|
* no `step`, both states equal to the source run's `finalState`, and `branchedFrom` set to the
|
|
538
551
|
* source session, `runId` and the source run's `carrier` (when it has one). When the source is
|
|
539
552
|
* itself such a seed, copy its `branchedFrom` instead (or leave it out if it has none): a seed's
|
|
540
|
-
* own `runId` never ran. The seed has no `carrier` of its own.
|
|
553
|
+
* own `runId` never ran. The seed has no `carrier` and no `requestId` of its own.
|
|
541
554
|
*
|
|
542
555
|
* @throws {@link BranchNotFoundError} when `runId` is not found in history.
|
|
543
556
|
*/
|
|
@@ -574,6 +587,16 @@ interface SessionStore {
|
|
|
574
587
|
* Optional — omit if your store does not support claim/release.
|
|
575
588
|
*/
|
|
576
589
|
extendClaim?(lease: Lease, options: ClaimOptions): Promise<Lease>;
|
|
590
|
+
/**
|
|
591
|
+
* The newest run of the session whose `requestId` equals `requestId`, or `null` when there is none.
|
|
592
|
+
*
|
|
593
|
+
* Core calls it only from `agent.run()` with a `requestId` that the latest run does not carry,
|
|
594
|
+
* so a late retry of an earlier request is replayed instead of run again. Without it, core
|
|
595
|
+
* dedupes against the latest run only.
|
|
596
|
+
*
|
|
597
|
+
* Optional — omit if your store keeps no history.
|
|
598
|
+
*/
|
|
599
|
+
findRequest?(agentId: string, sessionId: string, requestId: string): Promise<StoredRun | null>;
|
|
577
600
|
}
|
|
578
601
|
/**
|
|
579
602
|
* Serializable snapshot of a single agent run, written to the store after
|
|
@@ -620,6 +643,12 @@ interface StoredRun {
|
|
|
620
643
|
readonly branchedFrom?: RunRef & {
|
|
621
644
|
readonly sessionId: string;
|
|
622
645
|
};
|
|
646
|
+
/**
|
|
647
|
+
* The request (turn) this run belongs to: the `requestId` passed to `agent.run()`, inherited by
|
|
648
|
+
* every resume of that turn. Absent for keyless turns, branch seeds and runs saved before this
|
|
649
|
+
* field was added.
|
|
650
|
+
*/
|
|
651
|
+
readonly requestId?: string;
|
|
623
652
|
}
|
|
624
653
|
/**
|
|
625
654
|
* Discriminated union returned by {@link Agent.status} describing the lifecycle
|
|
@@ -1056,7 +1085,15 @@ interface Agent {
|
|
|
1056
1085
|
/** The agent's unique identifier, as provided to createAgent(). */
|
|
1057
1086
|
readonly id: string;
|
|
1058
1087
|
/**
|
|
1059
|
-
* Start a
|
|
1088
|
+
* Start a run. Returns a RunHandle synchronously before execution begins.
|
|
1089
|
+
*
|
|
1090
|
+
* Reserved `resources` keys that shape the session:
|
|
1091
|
+
* - `sessionId?: string` — the conversation thread; a random UUID when absent
|
|
1092
|
+
* - `requestId?: string` — this turn and its idempotency key. With a session store, a call whose
|
|
1093
|
+
* `requestId` the session already ran returns that request's current outcome without running
|
|
1094
|
+
* it again; without one, every call is a new turn. Ignored when the agent has no session store
|
|
1095
|
+
*
|
|
1096
|
+
* Both must be non-empty strings when given; otherwise `run` throws `InvalidRunResourceError`.
|
|
1060
1097
|
*/
|
|
1061
1098
|
run(initialState: Record<string, unknown>, resources: Record<string, unknown>): RunHandle;
|
|
1062
1099
|
/**
|
|
@@ -1078,6 +1115,14 @@ interface Agent {
|
|
|
1078
1115
|
*/
|
|
1079
1116
|
status(sessionId: string): Promise<SessionPhase>;
|
|
1080
1117
|
}
|
|
1118
|
+
/** Thrown by `agent.run()` when `sessionId` or `requestId` is given but is not a non-empty string. */
|
|
1119
|
+
declare class InvalidRunResourceError extends Error {
|
|
1120
|
+
/** The resource key that was rejected. */
|
|
1121
|
+
readonly key: 'sessionId' | 'requestId';
|
|
1122
|
+
/** The rejected value, as given. */
|
|
1123
|
+
readonly value: unknown;
|
|
1124
|
+
constructor(key: 'sessionId' | 'requestId', value: unknown);
|
|
1125
|
+
}
|
|
1081
1126
|
/**
|
|
1082
1127
|
* Instantiate an agent from a fully-configured {@link Harness}.
|
|
1083
1128
|
*
|
|
@@ -1104,17 +1149,6 @@ interface Agent {
|
|
|
1104
1149
|
*/
|
|
1105
1150
|
declare function createAgent<Ctx, State, Req extends keyof Ctx, Run extends keyof Ctx>(id: string, h: Harness<Ctx, State, Req, Run>, slots: Pick<Ctx, Req>, agentOptions?: AgentOptions): Agent;
|
|
1106
1151
|
|
|
1107
|
-
/**
|
|
1108
|
-
* Thrown by {@link RunHandle.resume} or {@link Agent.resume} when the session
|
|
1109
|
-
* is not currently paused on a matching interrupt.
|
|
1110
|
-
*
|
|
1111
|
-
* Common causes: the run has not yet settled, the session does not exist,
|
|
1112
|
-
* or the supplied `interruptId` does not match the pending interrupt.
|
|
1113
|
-
*/
|
|
1114
|
-
declare class NoInterruptError extends Error {
|
|
1115
|
-
constructor();
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
1152
|
/**
|
|
1119
1153
|
* Thrown by {@link Agent.run} or {@link Agent.resume} when the given session
|
|
1120
1154
|
* is already executing a run in the same process.
|
|
@@ -1174,4 +1208,15 @@ declare class LeaseExpiredError extends Error {
|
|
|
1174
1208
|
constructor(sessionId: string);
|
|
1175
1209
|
}
|
|
1176
1210
|
|
|
1177
|
-
|
|
1211
|
+
/**
|
|
1212
|
+
* Thrown by {@link RunHandle.resume} or {@link Agent.resume} when the session
|
|
1213
|
+
* is not currently paused on a matching interrupt.
|
|
1214
|
+
*
|
|
1215
|
+
* Common causes: the run has not yet settled, the session does not exist,
|
|
1216
|
+
* or the supplied `interruptId` does not match the pending interrupt.
|
|
1217
|
+
*/
|
|
1218
|
+
declare class NoInterruptError extends Error {
|
|
1219
|
+
constructor();
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
export { type Agent, type AgentOptions, type BranchCursor, type BranchDef, type ClaimOptions, type Cursor, type DeepWithMarkers, type FieldDefinition, type ForkCursor, type ForkDef, type FrameworkState, type Harness, InvalidRunResourceError, type Lease, LeaseExpiredError, type LoopDefinition, type LoopNode, LoopNotDefinedError, NoInterruptError, type Observer, type ObserverAware, type ObserverErrorContext, type ObserverErrorSink, REQUIRED_TAG, RUNTIME_TAG, type RequiredMarker, type RouteFn, type RunCarrier, type RunContext, type RunFn, type RunRef, type RuntimeMarker, SessionBusyError, SessionInFlightError, SessionPendingInterruptError, type SessionStore, type SignalTransition, type StateFromSchema, type StepContext, type StepDef, type StepSettledEvent, type StepSettledNext, type StepState, StoreLoadError, type StoredRun, type StoredRunMetadata, type TelemetryContext, type TransitionTarget, type WrapScope, composeObservers, createAgent, createHarness, field, isForkCursor, isForkDef, isRequiredMarker, isRuntimeMarker, required, runtime };
|
package/dist/index.js
CHANGED
|
@@ -745,11 +745,13 @@ async function runLoop(graph, state, ctx, schema, shouldStop, startCursor, callb
|
|
|
745
745
|
sessionId: ctx.sessionId,
|
|
746
746
|
runId: typeof callbacks?.runId === "string" ? callbacks.runId : "",
|
|
747
747
|
...typeof callbacks?.parentRunId === "string" ? { parentRunId: callbacks.parentRunId } : {},
|
|
748
|
+
...typeof callbacks?.requestId === "string" ? { requestId: callbacks.requestId } : {},
|
|
748
749
|
...typeof ctx.instanceId === "string" ? { instanceId: ctx.instanceId } : {},
|
|
749
750
|
...isBranch ? { branchPath } : {},
|
|
750
751
|
// Branch runLoops receive the outer callbacks by spread, so the refs are dropped here.
|
|
751
752
|
...!isBranch && callbacks?.resumedFrom !== void 0 ? { resumedFrom: callbacks.resumedFrom } : {},
|
|
752
|
-
...!isBranch && callbacks?.branchedFrom !== void 0 ? { branchedFrom: callbacks.branchedFrom } : {}
|
|
753
|
+
...!isBranch && callbacks?.branchedFrom !== void 0 ? { branchedFrom: callbacks.branchedFrom } : {},
|
|
754
|
+
...!isBranch && callbacks?.continuedFrom !== void 0 ? { continuedFrom: callbacks.continuedFrom } : {}
|
|
753
755
|
};
|
|
754
756
|
safeInvoke("onRunStart", () => obs?.onRunStart?.(runCtx), onObserverError);
|
|
755
757
|
const carrier = isBranch ? void 0 : normalizeCarrier(safeCall("carry", () => obs?.carry?.(runCtx), onObserverError));
|
|
@@ -1287,12 +1289,32 @@ async function querySessionPhase(store, agentId, sessionId) {
|
|
|
1287
1289
|
return storedSessionToPhase(loaded);
|
|
1288
1290
|
}
|
|
1289
1291
|
function runRefsOf(loaded) {
|
|
1290
|
-
if (loaded === null
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
}
|
|
1292
|
+
if (loaded === null) return {};
|
|
1293
|
+
const ref = { runId: loaded.runId, ...loaded.carrier !== void 0 ? { carrier: loaded.carrier } : {} };
|
|
1294
|
+
if (loaded.phase === "completed") return { continuedFrom: ref };
|
|
1295
|
+
if (loaded.step !== void 0) return { resumedFrom: ref };
|
|
1294
1296
|
return loaded.branchedFrom !== void 0 ? { branchedFrom: loaded.branchedFrom } : {};
|
|
1295
1297
|
}
|
|
1298
|
+
function decideRun(args) {
|
|
1299
|
+
const { loaded, older, mode, requestId, sessionId } = args;
|
|
1300
|
+
if (loaded === null) return { action: "start" };
|
|
1301
|
+
const pending = loaded.phase === "paused" && collectPendingInterrupts(loaded.step, loaded.finalState).length > 0;
|
|
1302
|
+
if (mode === "resume") {
|
|
1303
|
+
return loaded.phase === "completed" ? { action: "replay", row: loaded } : { action: "continue", mergeInput: false };
|
|
1304
|
+
}
|
|
1305
|
+
if (requestId !== void 0 && loaded.requestId === requestId) {
|
|
1306
|
+
if (loaded.phase === "completed" || pending) return { action: "replay", row: loaded };
|
|
1307
|
+
return { action: "continue", mergeInput: false };
|
|
1308
|
+
}
|
|
1309
|
+
if (older !== null) return { action: "replay", row: older };
|
|
1310
|
+
if (loaded.phase === "completed") return { action: "new-turn" };
|
|
1311
|
+
if (pending) return { action: "reject", error: new SessionPendingInterruptError(sessionId) };
|
|
1312
|
+
return { action: "continue", mergeInput: true };
|
|
1313
|
+
}
|
|
1314
|
+
function withoutSystemFields(loaded) {
|
|
1315
|
+
const { $error: _error, $interrupt: _interrupt, $interruptResponses: _responses, ...finalState } = loaded.finalState;
|
|
1316
|
+
return { ...loaded, finalState };
|
|
1317
|
+
}
|
|
1296
1318
|
async function runWithSession(store, agentId, sessionId, runId, graph, initialStateArg, schema, ctx, options) {
|
|
1297
1319
|
const leaseRef = options?.leaseRef ?? { current: null };
|
|
1298
1320
|
const keepAlive = createKeepAliveFn(leaseRef, store, options?.claimTtlMs ?? defaultClaimTtlMs());
|
|
@@ -1306,10 +1328,11 @@ async function runWithSession(store, agentId, sessionId, runId, graph, initialSt
|
|
|
1306
1328
|
}
|
|
1307
1329
|
return false;
|
|
1308
1330
|
};
|
|
1331
|
+
const { mode = "run", requestId: callerRequestId, ...loopOptions } = options ?? {};
|
|
1309
1332
|
if (store === void 0) {
|
|
1310
1333
|
const state2 = initializeState(null, initialStateArg, schema);
|
|
1311
1334
|
const result2 = await runLoop(graph, state2, ctx, schema, composedShouldStop, void 0, {
|
|
1312
|
-
...
|
|
1335
|
+
...loopOptions,
|
|
1313
1336
|
runId,
|
|
1314
1337
|
...options?.parentRunId !== void 0 ? { parentRunId: options.parentRunId } : {}
|
|
1315
1338
|
});
|
|
@@ -1320,31 +1343,46 @@ async function runWithSession(store, agentId, sessionId, runId, graph, initialSt
|
|
|
1320
1343
|
}
|
|
1321
1344
|
return result2;
|
|
1322
1345
|
}
|
|
1346
|
+
const requestId = mode === "run" ? callerRequestId : void 0;
|
|
1347
|
+
const fail = (error) => {
|
|
1348
|
+
const failState = initializeState(null, initialStateArg, schema);
|
|
1349
|
+
failState["$error"] = error;
|
|
1350
|
+
return { state: failState, signal: "$error", paused: false, cursor: null };
|
|
1351
|
+
};
|
|
1323
1352
|
let loaded;
|
|
1353
|
+
let older = null;
|
|
1324
1354
|
try {
|
|
1325
1355
|
loaded = await store.load(agentId, sessionId);
|
|
1356
|
+
if (requestId !== void 0 && loaded !== null && loaded.requestId !== requestId && typeof store.findRequest === "function") {
|
|
1357
|
+
older = await store.findRequest(agentId, sessionId, requestId);
|
|
1358
|
+
}
|
|
1326
1359
|
} catch (error) {
|
|
1327
1360
|
const storeError = new StoreLoadError(error);
|
|
1328
1361
|
options?.onStoreError?.(storeError, "load");
|
|
1329
|
-
|
|
1330
|
-
failState["$error"] = storeError;
|
|
1331
|
-
return { state: failState, signal: "$error", paused: false, cursor: null };
|
|
1362
|
+
return fail(storeError);
|
|
1332
1363
|
}
|
|
1333
|
-
|
|
1364
|
+
const decision = decideRun({ loaded, older, mode, requestId, sessionId });
|
|
1365
|
+
if (decision.action === "reject") return fail(decision.error);
|
|
1366
|
+
if (decision.action === "replay") {
|
|
1367
|
+
const { row } = decision;
|
|
1334
1368
|
return {
|
|
1335
|
-
state:
|
|
1336
|
-
signal:
|
|
1337
|
-
cursor: null,
|
|
1338
|
-
paused:
|
|
1369
|
+
state: row.finalState,
|
|
1370
|
+
signal: row.signal ?? null,
|
|
1371
|
+
cursor: row.step ?? null,
|
|
1372
|
+
paused: row.phase === "paused"
|
|
1339
1373
|
};
|
|
1340
1374
|
}
|
|
1341
|
-
const
|
|
1375
|
+
const segmentRequestId = mode === "run" ? requestId : loaded?.requestId;
|
|
1376
|
+
const base = decision.action === "new-turn" && loaded !== null ? withoutSystemFields(loaded) : loaded;
|
|
1377
|
+
const input = decision.action === "continue" && !decision.mergeInput ? {} : initialStateArg;
|
|
1378
|
+
const state = initializeState(base, input, schema);
|
|
1342
1379
|
const initialStateSnapshot = { ...state };
|
|
1343
1380
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1344
1381
|
const result = await runLoop(graph, state, ctx, schema, composedShouldStop, loaded?.step, {
|
|
1345
|
-
...
|
|
1382
|
+
...loopOptions,
|
|
1346
1383
|
runId,
|
|
1347
1384
|
...options?.parentRunId !== void 0 ? { parentRunId: options.parentRunId } : {},
|
|
1385
|
+
...segmentRequestId !== void 0 ? { requestId: segmentRequestId } : {},
|
|
1348
1386
|
...runRefsOf(loaded)
|
|
1349
1387
|
});
|
|
1350
1388
|
if (leaseExpired) {
|
|
@@ -1370,7 +1408,8 @@ async function runWithSession(store, agentId, sessionId, runId, graph, initialSt
|
|
|
1370
1408
|
step: result.cursor,
|
|
1371
1409
|
...result.signal !== null ? { signal: result.signal } : {},
|
|
1372
1410
|
...metadata !== void 0 ? { metadata } : {},
|
|
1373
|
-
...result.carrier !== void 0 ? { carrier: result.carrier } : {}
|
|
1411
|
+
...result.carrier !== void 0 ? { carrier: result.carrier } : {},
|
|
1412
|
+
...segmentRequestId !== void 0 ? { requestId: segmentRequestId } : {}
|
|
1374
1413
|
};
|
|
1375
1414
|
await store.save(agentId, sessionId, saved);
|
|
1376
1415
|
} else {
|
|
@@ -1386,7 +1425,8 @@ async function runWithSession(store, agentId, sessionId, runId, graph, initialSt
|
|
|
1386
1425
|
finalState: result.state,
|
|
1387
1426
|
...result.signal !== null ? { signal: result.signal } : {},
|
|
1388
1427
|
...metadata !== void 0 ? { metadata } : {},
|
|
1389
|
-
...result.carrier !== void 0 ? { carrier: result.carrier } : {}
|
|
1428
|
+
...result.carrier !== void 0 ? { carrier: result.carrier } : {},
|
|
1429
|
+
...segmentRequestId !== void 0 ? { requestId: segmentRequestId } : {}
|
|
1390
1430
|
};
|
|
1391
1431
|
await store.save(agentId, sessionId, saved);
|
|
1392
1432
|
}
|
|
@@ -1510,7 +1550,8 @@ async function injectInterruptResponse(store, agentId, sessionId, interruptId, r
|
|
|
1510
1550
|
...loaded.signal !== void 0 ? { signal: loaded.signal } : {},
|
|
1511
1551
|
...loaded.metadata !== void 0 ? { metadata: loaded.metadata } : {},
|
|
1512
1552
|
...loaded.carrier !== void 0 ? { carrier: loaded.carrier } : {},
|
|
1513
|
-
...loaded.branchedFrom !== void 0 ? { branchedFrom: loaded.branchedFrom } : {}
|
|
1553
|
+
...loaded.branchedFrom !== void 0 ? { branchedFrom: loaded.branchedFrom } : {},
|
|
1554
|
+
...loaded.requestId !== void 0 ? { requestId: loaded.requestId } : {}
|
|
1514
1555
|
};
|
|
1515
1556
|
await store.save(agentId, sessionId, updated);
|
|
1516
1557
|
}
|
|
@@ -1697,6 +1738,24 @@ var InvalidClaimOptionsError = class extends Error {
|
|
|
1697
1738
|
this.ttlMs = ttlMs;
|
|
1698
1739
|
}
|
|
1699
1740
|
};
|
|
1741
|
+
var InvalidRunResourceError = class extends Error {
|
|
1742
|
+
/** The resource key that was rejected. */
|
|
1743
|
+
key;
|
|
1744
|
+
/** The rejected value, as given. */
|
|
1745
|
+
value;
|
|
1746
|
+
constructor(key, value) {
|
|
1747
|
+
super(`${key} must be a non-empty string \u2014 got ${describeValue(value)} in agent.run() resources`);
|
|
1748
|
+
this.name = "InvalidRunResourceError";
|
|
1749
|
+
this.key = key;
|
|
1750
|
+
this.value = value;
|
|
1751
|
+
}
|
|
1752
|
+
};
|
|
1753
|
+
function readIdResource(resources, key) {
|
|
1754
|
+
const value = resources[key];
|
|
1755
|
+
if (value === void 0) return void 0;
|
|
1756
|
+
if (typeof value !== "string" || value.length === 0) throw new InvalidRunResourceError(key, value);
|
|
1757
|
+
return value;
|
|
1758
|
+
}
|
|
1700
1759
|
var _agentInternals = /* @__PURE__ */ Symbol("_agentInternals");
|
|
1701
1760
|
function createAgent(id, h, slots, agentOptions) {
|
|
1702
1761
|
const internals = getInternals(h);
|
|
@@ -1783,7 +1842,7 @@ function createAgent(id, h, slots, agentOptions) {
|
|
|
1783
1842
|
runtimeKeys,
|
|
1784
1843
|
requiredKeys
|
|
1785
1844
|
};
|
|
1786
|
-
const reservedRunKeys = /* @__PURE__ */ new Set(["sessionId", "signal", "events", "listeners", "observer", "onObserverError", "claimOptions", "parentRunId"]);
|
|
1845
|
+
const reservedRunKeys = /* @__PURE__ */ new Set(["sessionId", "requestId", "signal", "events", "listeners", "observer", "onObserverError", "claimOptions", "parentRunId"]);
|
|
1787
1846
|
const inFlightSessions = /* @__PURE__ */ new Set();
|
|
1788
1847
|
const interruptPendingSessions = /* @__PURE__ */ new Set();
|
|
1789
1848
|
const resumeFailState = () => initializeState(null, {}, agentInternals.stateSchema);
|
|
@@ -1842,6 +1901,7 @@ function createAgent(id, h, slots, agentOptions) {
|
|
|
1842
1901
|
agentInternals.stateSchema,
|
|
1843
1902
|
agentCtx,
|
|
1844
1903
|
{
|
|
1904
|
+
mode: "resume",
|
|
1845
1905
|
shouldStop: () => flag.stopped,
|
|
1846
1906
|
onBeforeStep: (n) => {
|
|
1847
1907
|
ref.current = n;
|
|
@@ -1855,7 +1915,6 @@ function createAgent(id, h, slots, agentOptions) {
|
|
|
1855
1915
|
...parentRunId !== void 0 ? { parentRunId } : {}
|
|
1856
1916
|
}
|
|
1857
1917
|
);
|
|
1858
|
-
if (r.signal === "$interrupt") interruptPendingSessions.add(sId);
|
|
1859
1918
|
return { state: r.state, signal: r.signal };
|
|
1860
1919
|
}
|
|
1861
1920
|
});
|
|
@@ -1894,10 +1953,11 @@ function createAgent(id, h, slots, agentOptions) {
|
|
|
1894
1953
|
throw new MissingRuntimeSlotError(key);
|
|
1895
1954
|
}
|
|
1896
1955
|
}
|
|
1897
|
-
const sessionId =
|
|
1956
|
+
const sessionId = readIdResource(resources, "sessionId") ?? randomUUID();
|
|
1957
|
+
const requestId = readIdResource(resources, "requestId");
|
|
1898
1958
|
const runId = randomUUID();
|
|
1899
1959
|
if (inFlightSessions.has(sessionId)) throw new SessionInFlightError(sessionId);
|
|
1900
|
-
if (interruptPendingSessions.has(sessionId)) throw new SessionPendingInterruptError(sessionId);
|
|
1960
|
+
if (capturedStore === void 0 && interruptPendingSessions.has(sessionId)) throw new SessionPendingInterruptError(sessionId);
|
|
1901
1961
|
inFlightSessions.add(sessionId);
|
|
1902
1962
|
let _stopped = false;
|
|
1903
1963
|
const abortController = new AbortController();
|
|
@@ -1946,6 +2006,8 @@ function createAgent(id, h, slots, agentOptions) {
|
|
|
1946
2006
|
const parentRunId = typeof resources["parentRunId"] === "string" ? resources["parentRunId"] : void 0;
|
|
1947
2007
|
const claimOptions = parseClaimOptions(resources["claimOptions"], fallbackClaimOptions());
|
|
1948
2008
|
const options = {
|
|
2009
|
+
mode: "run",
|
|
2010
|
+
...requestId !== void 0 ? { requestId } : {},
|
|
1949
2011
|
shouldStop: () => stopFlag.stopped,
|
|
1950
2012
|
onBeforeStep: (name, state) => {
|
|
1951
2013
|
stepRef.current = name;
|
|
@@ -1987,7 +2049,7 @@ function createAgent(id, h, slots, agentOptions) {
|
|
|
1987
2049
|
ctx,
|
|
1988
2050
|
{ ...options, ...lease }
|
|
1989
2051
|
);
|
|
1990
|
-
if (r.signal === "$interrupt") interruptPendingSessions.add(sessionId);
|
|
2052
|
+
if (capturedStore === void 0 && r.signal === "$interrupt") interruptPendingSessions.add(sessionId);
|
|
1991
2053
|
lastResult = { state: r.state, cursor: r.cursor, runId, ...r.carrier !== void 0 ? { carrier: r.carrier } : {} };
|
|
1992
2054
|
return { state: r.state, signal: r.signal };
|
|
1993
2055
|
}
|
|
@@ -2051,6 +2113,7 @@ function createAgent(id, h, slots, agentOptions) {
|
|
|
2051
2113
|
agentInternals.stateSchema,
|
|
2052
2114
|
resumeCtx,
|
|
2053
2115
|
{
|
|
2116
|
+
mode: "resume",
|
|
2054
2117
|
shouldStop: () => resumeStopFlag.stopped,
|
|
2055
2118
|
onBeforeStep: (n, s) => {
|
|
2056
2119
|
resumeStepRef.current = n;
|
|
@@ -2070,7 +2133,6 @@ function createAgent(id, h, slots, agentOptions) {
|
|
|
2070
2133
|
...lease
|
|
2071
2134
|
}
|
|
2072
2135
|
);
|
|
2073
|
-
if (r2.signal === "$interrupt") interruptPendingSessions.add(sessionId);
|
|
2074
2136
|
lastResult = { state: r2.state, cursor: r2.cursor, runId: resumeRunId, ...r2.carrier !== void 0 ? { carrier: r2.carrier } : {} };
|
|
2075
2137
|
return { state: r2.state, signal: r2.signal };
|
|
2076
2138
|
}
|
|
@@ -2144,6 +2206,7 @@ function createAgent(id, h, slots, agentOptions) {
|
|
|
2144
2206
|
return agent;
|
|
2145
2207
|
}
|
|
2146
2208
|
export {
|
|
2209
|
+
InvalidRunResourceError,
|
|
2147
2210
|
LeaseExpiredError,
|
|
2148
2211
|
LoopNotDefinedError,
|
|
2149
2212
|
NoInterruptError,
|