@owlmeans/agent 0.1.18-rc.16 → 0.1.18-rc.18
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 +18 -4
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/agent/SKILL.md +109 -15
- package/build/checkpoint/index.d.ts +2 -0
- package/build/checkpoint/index.d.ts.map +1 -0
- package/build/checkpoint/index.js +2 -0
- package/build/checkpoint/index.js.map +1 -0
- package/build/checkpoint/saver.d.ts +7 -0
- package/build/checkpoint/saver.d.ts.map +1 -0
- package/build/checkpoint/saver.js +135 -0
- package/build/checkpoint/saver.js.map +1 -0
- package/build/helpers/tools.d.ts +7 -1
- package/build/helpers/tools.d.ts.map +1 -1
- package/build/helpers/tools.js +10 -1
- package/build/helpers/tools.js.map +1 -1
- package/build/index.d.ts +2 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2 -1
- package/build/index.js.map +1 -1
- package/build/model.d.ts +11 -6
- package/build/model.d.ts.map +1 -1
- package/build/model.js +35 -20
- package/build/model.js.map +1 -1
- package/build/pipeline/index.d.ts +3 -0
- package/build/pipeline/index.d.ts.map +1 -0
- package/build/pipeline/index.js +2 -0
- package/build/pipeline/index.js.map +1 -0
- package/build/pipeline/runner.d.ts +22 -0
- package/build/pipeline/runner.d.ts.map +1 -0
- package/build/pipeline/runner.js +566 -0
- package/build/pipeline/runner.js.map +1 -0
- package/build/pipeline/types.d.ts +190 -0
- package/build/pipeline/types.d.ts.map +1 -0
- package/build/pipeline/types.js +2 -0
- package/build/pipeline/types.js.map +1 -0
- package/build/plugins/export.d.ts +2 -0
- package/build/plugins/export.d.ts.map +1 -1
- package/build/plugins/export.js +1 -0
- package/build/plugins/export.js.map +1 -1
- package/build/plugins/inquiry.d.ts +36 -0
- package/build/plugins/inquiry.d.ts.map +1 -0
- package/build/plugins/inquiry.js +126 -0
- package/build/plugins/inquiry.js.map +1 -0
- package/build/service.d.ts +1 -1
- package/build/service.d.ts.map +1 -1
- package/build/service.js +30 -0
- package/build/service.js.map +1 -1
- package/build/stores/memory.d.ts +3 -2
- package/build/stores/memory.d.ts.map +1 -1
- package/build/stores/memory.js +0 -0
- package/build/stores/memory.js.map +1 -1
- package/build/stores/types.d.ts +74 -4
- package/build/stores/types.d.ts.map +1 -1
- package/build/types.d.ts +36 -1
- package/build/types.d.ts.map +1 -1
- package/package.json +26 -10
- package/src/checkpoint/index.ts +1 -0
- package/src/checkpoint/saver.ts +178 -0
- package/src/helpers/tools.ts +12 -1
- package/src/index.ts +2 -1
- package/src/model.ts +39 -19
- package/src/pipeline/index.ts +2 -0
- package/src/pipeline/runner.ts +724 -0
- package/src/pipeline/types.ts +202 -0
- package/src/plugins/export.ts +3 -0
- package/src/plugins/inquiry.ts +169 -0
- package/src/service.ts +31 -1
- package/src/stores/memory.ts +0 -0
- package/src/stores/types.ts +80 -5
- package/src/types.ts +40 -1
- package/tests/agent.spec.ts +22 -0
- package/tests/checkpoint.spec.ts +158 -0
- package/tests/inquiry.spec.ts +121 -0
- package/tests/pipeline-inquiry.spec.ts +299 -0
- package/tests/pipeline-resume.spec.ts +320 -0
- package/tests/pipeline.spec.ts +297 -0
- package/tests/runtime.spec.ts +80 -41
- package/tests/tools.spec.ts +23 -0
- package/src/runtime/checkpoint.ts +0 -89
package/README.md
CHANGED
|
@@ -39,6 +39,7 @@ const agent = context.agents().agent({ exec, tools })
|
|
|
39
39
|
| `summarizePlugin` | Compacts each finished run into `summary` + `advice`, and replays the last few on the way in |
|
|
40
40
|
| `memoryGraphPlugin` | Durable notes filed by subsystem, with links — index injected, content pulled by tool |
|
|
41
41
|
| `memoryEventsPlugin` | A bounded, ordered record of what happened |
|
|
42
|
+
| `inquiryPlugin` | The `ask_user` tool — offered only when a channel to a person is wired |
|
|
42
43
|
|
|
43
44
|
Both memory plugins also export a plain API (`memoryGraph`, `memoryEvents`) usable with no agent at
|
|
44
45
|
all, so a pipeline helper writes to the same store an agent reads.
|
|
@@ -72,9 +73,22 @@ implements. An unbound port is not an error — the plugin that needs it becomes
|
|
|
72
73
|
purpose is streamed to the client, so without it the summary of a run types itself out in the
|
|
73
74
|
user's view of that run, right after it finished.
|
|
74
75
|
|
|
75
|
-
**
|
|
76
|
-
|
|
77
|
-
of `@owlmeans/llm`'s
|
|
76
|
+
**An agent run is not resumable; a PIPELINE is.** A turn's tool calls are effects already applied
|
|
77
|
+
to the world, so re-entering one re-applies them — `makeAgentModel` therefore keeps no checkpoint,
|
|
78
|
+
and `makeAgentExecutionPlugin` is the first real implementation of `@owlmeans/llm`'s
|
|
79
|
+
`ExecutionPlugin` seam. What resumes is `makePipeline`, a state machine over named steps whose
|
|
80
|
+
position is written to a run row at every boundary; an agent run belongs inside one of its steps.
|
|
81
|
+
The row is the authority on where a run stands — never the LangGraph checkpoint, which is
|
|
82
|
+
size-guarded and expires. A checkpointer is optional and buys replay, never correctness.
|
|
83
|
+
|
|
84
|
+
**A step asks a person with `ctx.ask(inquiry)`, and a run nobody can answer parks `Waiting`.** An
|
|
85
|
+
answer already in the state comes straight back, so a question is never asked twice; that only
|
|
86
|
+
holds if `Inquiry.id` is DERIVED from the step and the thing being decided rather than minted per
|
|
87
|
+
call. Answers are merged by the runner, in `invoke` and in `resume({ answers })`, never by a
|
|
88
|
+
caller's mapping. An agent that installs `inquiryPlugin` must pass
|
|
89
|
+
`fatal: e => isFatalError(e) != null` into its tool invocation: the tool rethrows
|
|
90
|
+
`InquiryUnavailable` alone — a channel that has gone is terminal, and without the predicate the
|
|
91
|
+
loop spends its whole turn budget on it.
|
|
78
92
|
|
|
79
93
|
<!-- owlmeans:agent-guidance:start -->
|
|
80
94
|
## Agent guidance
|
|
@@ -84,7 +98,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
84
98
|
your project's skill store (`.agents/skills/`):
|
|
85
99
|
|
|
86
100
|
```sh
|
|
87
|
-
npx @owlmeans/agent-skills@^0.1.18-rc.
|
|
101
|
+
npx @owlmeans/agent-skills@^0.1.18-rc.14
|
|
88
102
|
```
|
|
89
103
|
|
|
90
104
|
The embedded files are version-matched to this package release. Do not edit them
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@owlmeans/agent",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-09-
|
|
4
|
+
"version": "0.1.18-rc.18",
|
|
5
|
+
"generatedAt": "2026-09-10T23:10:57.043Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: agent
|
|
3
|
-
description: How to use @owlmeans/agent — context-aware LLM agents
|
|
3
|
+
description: How to use @owlmeans/agent — context-aware LLM agents over the LangGraph functional API, and resumable PIPELINES over a checkpointed StateGraph, with the AgentPlugin seam, conversation-summarization and memory plugins, and storage-independent ports. Auto-invoked when importing makeAgentModel, makePipeline, makeCheckpointSaver, appendAgentsService, an agent plugin, safeInvokeTool, or an agent store.
|
|
4
4
|
user-invocable: false
|
|
5
5
|
---
|
|
6
6
|
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
@@ -8,7 +8,7 @@ user-invocable: false
|
|
|
8
8
|
# @owlmeans/agent
|
|
9
9
|
|
|
10
10
|
**Layer:** Cross-cutting domain
|
|
11
|
-
**Install:** `"@owlmeans/agent": "^0.1.18-rc.
|
|
11
|
+
**Install:** `"@owlmeans/agent": "^0.1.18-rc.18"` in `dependencies`, plus the `@langchain/core` and
|
|
12
12
|
`@langchain/langgraph` **peers**
|
|
13
13
|
|
|
14
14
|
The agent runtime. Contracts live in `@owlmeans/agent-common`.
|
|
@@ -18,20 +18,58 @@ The agent runtime. Contracts live in `@owlmeans/agent-common`.
|
|
|
18
18
|
| Export | Description |
|
|
19
19
|
|---|---|
|
|
20
20
|
| `makeAgentModel(options)` | An agent over the LangGraph functional API: `invoke`, `use`, `conversation`. |
|
|
21
|
+
| `makePipeline(spec, options)` | A resumable state machine over a checkpointed LangGraph `StateGraph`: `invoke`, `resume`, `snapshot`, `asStep`. |
|
|
22
|
+
| `makeCheckpointSaver(store)` | A `BaseCheckpointSaver` over the `CheckpointStore` port. |
|
|
21
23
|
| `makeAgentsService(options?, alias?)` · `appendAgentsService(ctx, options?, alias?)` · `agentServiceApi(options, self)` | The service, and its half without `createService` for composition. |
|
|
22
24
|
| `summarizePlugin(options?)` | Compacts each finished run into `summary` + `advice`; replays the last few. |
|
|
23
25
|
| `memoryGraphPlugin(options?)` · `memoryGraph(store, options?)` | Durable notes filed by subsystem, with links. Plugin **and** plain API. |
|
|
24
26
|
| `memoryEventsPlugin(options?)` · `memoryEvents(store, options?)` | A bounded, ordered record of what happened. |
|
|
27
|
+
| `inquiryPlugin(options?)` · `INQUIRY_PLUGIN` · `ASK_USER_TOOL` | The `ask_user` tool and its one Context paragraph — offered only when a channel is wired. |
|
|
25
28
|
| `safeInvokeTool`, `toErrorResponse`, `isToolError` | The never-throwing tool contract. |
|
|
26
29
|
| `composeCompaction`, `composeRollingSummary`, `renderTranscript`, `messageText` | Summary primitives; both composers are total. |
|
|
27
|
-
| `makeAgentExecutionPlugin(options)` | The first real implementation of `@owlmeans/llm`'s `ExecutionPlugin`. |
|
|
28
30
|
| `makeStaticFlowProvider(flows)` | The server-side `FlowProvider` `@owlmeans/flow` does not ship. |
|
|
29
31
|
| `inProcessTransport()`, `AgentTransport` | The scaling seam; default carries messages by direct call. |
|
|
30
|
-
| `createMemory*Store()` | In-memory reference implementations of every port
|
|
32
|
+
| `createMemory*Store()` | In-memory reference implementations of every port, including `createMemoryPipelineRunStore` and `createMemoryCheckpointStore`. |
|
|
31
33
|
| `AgentError` · `AgentMissconfiguredError` · `AgentLoopExhaustedError` | The `ResilientError` family. `AgentMissconfiguredError` is a model or tool set the agent was built without; `AgentLoopExhaustedError` is the tool loop hitting its ceiling. |
|
|
32
34
|
| `DEFAULT_MAX_TURNS` (64) · `DEFAULT_PLUGIN_ORDER` · `DEFAULT_ACTION` · `DEFAULT_ENTRYPOINT` | The loop and plugin defaults. |
|
|
33
35
|
|
|
34
|
-
Subpath exports: `./plugins`, `./helpers`, `./stores`.
|
|
36
|
+
Subpath exports: `./plugins`, `./helpers`, `./stores`, `./pipeline`, `./checkpoint`.
|
|
37
|
+
|
|
38
|
+
## Pipelines
|
|
39
|
+
|
|
40
|
+
A pipeline is a **state machine over named steps** whose position is persisted at every step
|
|
41
|
+
boundary, so a run killed anywhere continues from the step it stopped at rather than from zero.
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
const spec: PipelineSpec = {
|
|
45
|
+
alias: 'vib:story:design', version: 1,
|
|
46
|
+
steps: [
|
|
47
|
+
{ step: 'screens' },
|
|
48
|
+
{ step: 'ux', after: ['screens'] },
|
|
49
|
+
{ step: 'data', after: ['screens'] }, // runs BESIDE `ux`
|
|
50
|
+
{ step: 'persist', after: ['ux', 'data'] }, // joins BOTH
|
|
51
|
+
],
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const pipeline = makePipeline<State, Deps>(spec, { steps, runs, checkpointer, fatal })
|
|
55
|
+
await pipeline.invoke(seed, { runId, deps, scope: projectId })
|
|
56
|
+
await pipeline.resume(runId, { deps, from: 'data' })
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**`StateGraph`, not the functional API.** A resume must be able to name the step it starts at, and
|
|
60
|
+
only a graph has named nodes; `entrypoint`/`task` identifies a task by its positional call ordinal,
|
|
61
|
+
which renumbers on any edit to the function — under a file watcher that is the common case, not the
|
|
62
|
+
corner case. The functional API stays exactly where it is, inside `makeAgentModel`'s ReAct loop.
|
|
63
|
+
|
|
64
|
+
**Edges are `after: string[]`.** Several steps naming one predecessor fan out; one step naming
|
|
65
|
+
several joins them, and the join waits for ALL of them (the array form of `addEdge`, which is a
|
|
66
|
+
barrier — separate single edges would fire on the first). Branching is `skipWhen`, never a
|
|
67
|
+
conditional edge: a branch expressed as an edge is invisible to a resume, while a branch expressed
|
|
68
|
+
as a guard is the same mechanism that makes a resume correct.
|
|
69
|
+
|
|
70
|
+
**The graph is compiled per run and the steps close over their dependencies.** Passing collaborators
|
|
71
|
+
through the engine's config would make a run depend on which config keys a given LangGraph minor
|
|
72
|
+
propagates into a node body. A closure cannot be lost.
|
|
35
73
|
|
|
36
74
|
## The plugin seam
|
|
37
75
|
|
|
@@ -101,8 +139,8 @@ a prompt is a request. Both composers (`composeCompaction`, `composeRollingSumma
|
|
|
101
139
|
no model, a failing model or an empty answer they fall back deterministically, so a caller can record
|
|
102
140
|
history unconditionally. A failed fold costs detail, never the event.
|
|
103
141
|
|
|
104
|
-
**Ports, not resources.** `ConversationStore`, `MemoryGraphStore`, `MemoryEventStore
|
|
105
|
-
`
|
|
142
|
+
**Ports, not resources.** `ConversationStore`, `MemoryGraphStore`, `MemoryEventStore`,
|
|
143
|
+
`PipelineRunStore` and `CheckpointStore` are narrow interfaces a consumer implements. A port names exactly what the
|
|
106
144
|
plugin needs, which is a far smaller surface than CRUD, and anything can satisfy it — a `Resource`,
|
|
107
145
|
or a file on disk, which is what the project-history equivalent is. An unbound port is a no-op, not
|
|
108
146
|
an error.
|
|
@@ -115,13 +153,67 @@ forgetting, which is not a decision one caller has the standing to take. A node
|
|
|
115
153
|
the context window on knowledge the run cannot tell apart from what it needs; the agent pulls what it
|
|
116
154
|
wants by name.
|
|
117
155
|
|
|
118
|
-
**
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
156
|
+
**The run ROW is the authority on where a run stands — never the LangGraph checkpoint.** The
|
|
157
|
+
checkpoint is size-guarded and expires, so a design that reads a run's position out of it has a
|
|
158
|
+
silent hole exactly where a crashed run needs an answer. Every reader of a position — a resume, a
|
|
159
|
+
reconciler, an operator, a status endpoint — reads the row. With no checkpointer bound at all, a
|
|
160
|
+
resume is still correct; the checkpointer buys replay, never correctness.
|
|
161
|
+
|
|
162
|
+
**Every step is guarded twice.** The runner refuses to re-enter a step its row calls complete, and
|
|
163
|
+
the application's `skipWhen` reads a durable marker the step itself wrote. The first covers a clean
|
|
164
|
+
crash; the second covers a crash BETWEEN the side effect and the row write, which the first cannot
|
|
165
|
+
see. A step that does N undoable things in a loop marks a cursor with `ctx.mark`.
|
|
166
|
+
|
|
167
|
+
**A pipeline state is scalars and KEYS.** Ids, revisions, markers, lists of codes — never an
|
|
168
|
+
artifact. The state is serialized into the row at every step boundary, so a field carrying a
|
|
169
|
+
document writes one copy of it per step. Over `maxStateChars` the step FAILS: refused, never
|
|
170
|
+
truncated, because a resume replaying from a cut-down state replays from a state that never existed.
|
|
171
|
+
|
|
172
|
+
**A step failure is an OUTCOME, not a throw.** `invoke`/`resume` return
|
|
173
|
+
`{ status: Failed, failedAt, error }`, because every caller has a status, a warning or a record to
|
|
174
|
+
write before it decides anything, and a throwing runner puts that in a `catch` where it gets
|
|
175
|
+
forgotten. The one exception is `options.fatal`: those are written to the row Failed FIRST and then
|
|
176
|
+
rethrown, so an exhausted budget cannot become a result a caller carries on from.
|
|
177
|
+
|
|
178
|
+
**No default retry.** `attempts` defaults to 1, because retry budgets in this family MULTIPLY — an
|
|
179
|
+
outer retry around a model's own inner retry is their product. `attempts > 1` on a `nonIdempotent`
|
|
180
|
+
step is refused at build time: an automatic retry is exactly what that flag says must not happen.
|
|
181
|
+
|
|
182
|
+
**`nonIdempotent` marks a step whose side effect cannot be undone** — a wipe, a purge, a claim
|
|
183
|
+
against a rate-limited authority, a model call whose output is already on disk. `resume({ from })`
|
|
184
|
+
refuses to re-enter a completed one without `force`.
|
|
185
|
+
|
|
186
|
+
**A ReAct run is not a resumable unit, and that is why `makeAgentModel` still builds its
|
|
187
|
+
`entrypoint` inside `invoke()`.** Its state is an unbounded message list whose tool results are side
|
|
188
|
+
effects already applied to the world: re-entering a turn re-applies them. What IS resumable is a
|
|
189
|
+
pipeline — and an agent run belongs inside one of its steps.
|
|
190
|
+
|
|
191
|
+
**A step asks with `ctx.ask(inquiry)`, and a run nobody can answer parks `Waiting`.** Three
|
|
192
|
+
outcomes in order: an answer already in the state comes straight back (a question is never asked
|
|
193
|
+
twice); a live `options.inquiry.ask` answer is RECORDED in the state — `stateAnswerOf(capAnswer(…))`
|
|
194
|
+
under `state[INQUIRY_ANSWERS_KEY][id]`, the decision whole and the prose cut — and the FULL answer
|
|
195
|
+
returned to the step; otherwise the run stops `Waiting` with the inquiry on its row, and with no run
|
|
196
|
+
store it throws `PipelineNotResumableError` instead. A throwing channel is not a park: it fails the
|
|
197
|
+
step as an ordinary outcome, because the asking failed rather than the answer being no. `Inquiry.id`
|
|
198
|
+
is the only thing an answer is matched by, so DERIVE it from the step and the thing being decided —
|
|
199
|
+
an id minted per call never matches the state, and the run re-asks and parks forever.
|
|
200
|
+
|
|
201
|
+
**Answers are merged by the RUNNER, in `invoke` and in `resume({ answers })`, never by a mapping.**
|
|
202
|
+
`invoke` builds `{ ...restored, ...seed }`, so a mapping that forwarded the answers map would
|
|
203
|
+
replace the child's own recorded answers wholesale — and write `undefined` over them when the parent
|
|
204
|
+
had none, which re-asks question 1 on every resume. When a composed child parks, `asStep` relays its
|
|
205
|
+
question to the parent's `ctx.ask` and re-invokes the child with the answer (bounded at 8 questions
|
|
206
|
+
per composing step); the parent parks only when its own `ask` parks, and never throws a plain error
|
|
207
|
+
in that path.
|
|
208
|
+
|
|
209
|
+
**An agent that installs `inquiryPlugin` must pass `fatal: e => isFatalError(e) != null`.** The tool
|
|
210
|
+
rethrows `InquiryUnavailable` alone — no channel is an answerable situation, a channel that has GONE
|
|
211
|
+
is terminal — and `safeInvokeTool` contains everything else by default, so without that predicate
|
|
212
|
+
the loop spends its whole turn budget on a dead channel. See [[inquiry]] for the whole primitive.
|
|
213
|
+
|
|
214
|
+
**`safeInvokeTool(tools, call, fatal?)` takes a fatal predicate.** Containment is right for a bad
|
|
215
|
+
argument and wrong for an exhausted budget: a tool may be a whole pipeline behind one call, and
|
|
216
|
+
handing the model a readable "out of tokens" is an invitation to pick another tool and spend again.
|
|
125
217
|
|
|
126
218
|
**`makeStaticFlowProvider` must throw on an unknown flow.** `makeFlowModel` reads a string as a flow
|
|
127
219
|
name first and only re-reads it as a serialized token once the provider throws — returning null
|
|
@@ -136,7 +228,9 @@ boundary — never for an `@owlmeans/*` package.
|
|
|
136
228
|
|
|
137
229
|
## Related
|
|
138
230
|
|
|
231
|
+
- [[inquiry]] — the human-in-the-loop primitive `ctx.ask`, `Waiting` and `ask_user` belong to
|
|
139
232
|
- [[agent-common]] — the serializable records and the run lifecycle flow
|
|
140
|
-
- [[llm]] — `Execution`, the model contract and `ExecutionPlugin`
|
|
233
|
+
- [[llm]] — `Execution`, the model contract and the `advise`-only `ExecutionPlugin`
|
|
234
|
+
- [[agent-checkpoint]] — the durable Mongo implementation of both storage ports (internal)
|
|
141
235
|
- [[llm-prompt-caching]] — which block contributed context lands in, and why
|
|
142
236
|
- [[agent-skills]] — `projectSkillsAgentPlugin` and the `read_skill` tool
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/checkpoint/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/checkpoint/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { BaseCheckpointSaver } from '@langchain/langgraph';
|
|
2
|
+
import type { SerializerProtocol } from '@langchain/langgraph-checkpoint';
|
|
3
|
+
import type { CheckpointStore } from '../stores/types.js';
|
|
4
|
+
export declare const makeCheckpointSaver: (store: CheckpointStore, options?: {
|
|
5
|
+
serde?: SerializerProtocol;
|
|
6
|
+
}) => BaseCheckpointSaver;
|
|
7
|
+
//# sourceMappingURL=saver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"saver.d.ts","sourceRoot":"","sources":["../../src/checkpoint/saver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAE1D,OAAO,KAAK,EAEqB,kBAAkB,EAClD,MAAM,iCAAiC,CAAA;AAExC,OAAO,KAAK,EAAE,eAAe,EAAyB,MAAM,oBAAoB,CAAA;AAwKhF,eAAO,MAAM,mBAAmB,UACvB,eAAe,YAAW;IAAE,KAAK,CAAC,EAAE,kBAAkB,CAAA;CAAE,KAC9D,mBAAoE,CAAA"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { BaseCheckpointSaver } from '@langchain/langgraph';
|
|
2
|
+
import { WRITES_IDX_MAP } from '@langchain/langgraph-checkpoint';
|
|
3
|
+
/**
|
|
4
|
+
* Where each half of LangGraph's checkpoint API comes from, and why it is two imports.
|
|
5
|
+
*
|
|
6
|
+
* The CLASS is imported from `@langchain/langgraph`'s root, because a checkpointer crosses a
|
|
7
|
+
* package boundary and a class with protected members is nominally distinct per installed copy —
|
|
8
|
+
* two copies and `entrypoint({ checkpointer })` stops type-checking, or worse, stops recognising
|
|
9
|
+
* what it was handed. The protocol VALUES (`WRITES_IDX_MAP`, and the types beside it) are not in
|
|
10
|
+
* langgraph's root export list at all, so they come from `@langchain/langgraph-checkpoint` — which
|
|
11
|
+
* is a hard dependency of langgraph, so there is exactly one of it either way.
|
|
12
|
+
*/
|
|
13
|
+
const encode = (data) => Buffer.from(data).toString('base64');
|
|
14
|
+
const decode = (data) => new Uint8Array(Buffer.from(data, 'base64'));
|
|
15
|
+
const threadOf = (config) => ({
|
|
16
|
+
thread: String(config.configurable?.thread_id ?? ''),
|
|
17
|
+
ns: String(config.configurable?.checkpoint_ns ?? ''),
|
|
18
|
+
id: config.configurable?.checkpoint_id != null
|
|
19
|
+
? String(config.configurable.checkpoint_id)
|
|
20
|
+
: undefined,
|
|
21
|
+
});
|
|
22
|
+
const configFor = (thread, ns, id) => ({
|
|
23
|
+
configurable: { thread_id: thread, checkpoint_ns: ns, checkpoint_id: id },
|
|
24
|
+
});
|
|
25
|
+
/**
|
|
26
|
+
* A {@link BaseCheckpointSaver} over the package's storage-agnostic {@link CheckpointStore} port.
|
|
27
|
+
*
|
|
28
|
+
* Everything protocol-shaped lives here; everything storage-shaped lives behind the port. That
|
|
29
|
+
* split is what lets an application bind Mongo, Redis or nothing at all without this file — or
|
|
30
|
+
* LangGraph's version of it — being a consideration.
|
|
31
|
+
*
|
|
32
|
+
* Payloads are base64 of whatever the serde produced. Never `JSON.stringify`: the checkpoint holds
|
|
33
|
+
* `BaseMessage` instances, and a plain-object round trip returns something no `instanceof` in a
|
|
34
|
+
* provider adapter recognises again — a resumed run would silently drop its tool calls.
|
|
35
|
+
*/
|
|
36
|
+
class PortCheckpointSaver extends BaseCheckpointSaver {
|
|
37
|
+
store;
|
|
38
|
+
constructor(store, serde) {
|
|
39
|
+
super(serde);
|
|
40
|
+
this.store = store;
|
|
41
|
+
}
|
|
42
|
+
async toTuple(thread, ns, record) {
|
|
43
|
+
const checkpoint = await this.serde.loadsTyped(record.type, decode(record.checkpoint));
|
|
44
|
+
const metadata = await this.serde.loadsTyped(record.type, decode(record.metadata));
|
|
45
|
+
const writes = await this.store.writesFor(thread, ns, [record.checkpointId]);
|
|
46
|
+
const pendingWrites = await Promise.all([...writes].sort((a, b) => a.idx - b.idx).map(async (write) => [
|
|
47
|
+
write.taskId, write.channel, await this.serde.loadsTyped(write.type, decode(write.value)),
|
|
48
|
+
]));
|
|
49
|
+
return {
|
|
50
|
+
config: configFor(thread, ns, record.checkpointId),
|
|
51
|
+
checkpoint,
|
|
52
|
+
metadata,
|
|
53
|
+
...(record.parentCheckpointId != null
|
|
54
|
+
? { parentConfig: configFor(thread, ns, record.parentCheckpointId) }
|
|
55
|
+
: {}),
|
|
56
|
+
pendingWrites,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
async getTuple(config) {
|
|
60
|
+
const { thread, ns, id } = threadOf(config);
|
|
61
|
+
if (thread === '') {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
const record = await this.store.one(thread, ns, id);
|
|
65
|
+
return record != null ? await this.toTuple(thread, ns, record) : undefined;
|
|
66
|
+
}
|
|
67
|
+
async *list(config, options) {
|
|
68
|
+
const { thread, ns } = threadOf(config);
|
|
69
|
+
if (thread === '') {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const before = options?.before?.configurable?.checkpoint_id;
|
|
73
|
+
// `limit` is applied by the STORE, not after decoding: a caller asking for the last three
|
|
74
|
+
// checkpoints must not pay for deserializing a thread's whole history to get them.
|
|
75
|
+
const records = await this.store.page({
|
|
76
|
+
threadId: thread,
|
|
77
|
+
...(config.configurable?.checkpoint_ns != null ? { ns } : {}),
|
|
78
|
+
...(before != null ? { beforeId: String(before) } : {}),
|
|
79
|
+
...(options?.limit != null ? { limit: options.limit } : {}),
|
|
80
|
+
});
|
|
81
|
+
for (const record of records) {
|
|
82
|
+
yield await this.toTuple(thread, record.ns, record);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async put(config, checkpoint, metadata, _newVersions) {
|
|
86
|
+
const { thread, ns, id } = threadOf(config);
|
|
87
|
+
const [type, serialized] = await this.serde.dumpsTyped(checkpoint);
|
|
88
|
+
const [metadataType, serializedMetadata] = await this.serde.dumpsTyped(metadata);
|
|
89
|
+
if (type !== metadataType) {
|
|
90
|
+
throw new Error(`Mismatched checkpoint serialization types: ${type} vs ${metadataType}`);
|
|
91
|
+
}
|
|
92
|
+
await this.store.put({
|
|
93
|
+
threadId: thread,
|
|
94
|
+
ns,
|
|
95
|
+
checkpointId: checkpoint.id,
|
|
96
|
+
...(id != null ? { parentCheckpointId: id } : {}),
|
|
97
|
+
type,
|
|
98
|
+
checkpoint: encode(serialized),
|
|
99
|
+
metadata: encode(serializedMetadata),
|
|
100
|
+
createdAt: new Date().toISOString(),
|
|
101
|
+
});
|
|
102
|
+
return configFor(thread, ns, checkpoint.id);
|
|
103
|
+
}
|
|
104
|
+
async putWrites(config, writes, taskId) {
|
|
105
|
+
const { thread, ns, id } = threadOf(config);
|
|
106
|
+
if (id == null) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const taskPath = config.configurable?.checkpoint_task_path;
|
|
110
|
+
const records = await Promise.all(writes.map(async ([channel, value], position) => {
|
|
111
|
+
const [type, serialized] = await this.serde.dumpsTyped(value);
|
|
112
|
+
return {
|
|
113
|
+
threadId: thread,
|
|
114
|
+
ns,
|
|
115
|
+
checkpointId: id,
|
|
116
|
+
taskId,
|
|
117
|
+
// A control channel has a well-known NEGATIVE index; a task's own output keeps its
|
|
118
|
+
// position. The sign is the write rule, and the store reads it: positive is first-wins,
|
|
119
|
+
// so a retried task cannot overwrite the answer its first attempt committed; negative is
|
|
120
|
+
// last-wins, because that is what an error or an interrupt is.
|
|
121
|
+
idx: WRITES_IDX_MAP[channel] ?? position,
|
|
122
|
+
channel,
|
|
123
|
+
type,
|
|
124
|
+
value: encode(serialized),
|
|
125
|
+
...(taskPath != null ? { taskPath: String(taskPath) } : {}),
|
|
126
|
+
};
|
|
127
|
+
}));
|
|
128
|
+
await this.store.putWrites(records);
|
|
129
|
+
}
|
|
130
|
+
async deleteThread(threadId) {
|
|
131
|
+
await this.store.dropThread(threadId);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
export const makeCheckpointSaver = (store, options = {}) => new PortCheckpointSaver(store, options.serde);
|
|
135
|
+
//# sourceMappingURL=saver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"saver.js","sourceRoot":"","sources":["../../src/checkpoint/saver.ts"],"names":[],"mappings":"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"}
|
package/build/helpers/tools.d.ts
CHANGED
|
@@ -24,6 +24,12 @@ export declare const isToolError: (result: unknown) => result is ToolErrorRespon
|
|
|
24
24
|
* that is what the model calls — a map keyed by a local variable silently loses any tool whose two
|
|
25
25
|
* names drifted apart, leaving it advertised, callable, and permanently "not found". The key stays
|
|
26
26
|
* as a fallback so a caller may still address a tool by it.
|
|
27
|
+
*
|
|
28
|
+
* `fatal` is the ONE exception to "never throws", and it exists because containment has a cost the
|
|
29
|
+
* containment itself cannot see. A tool may be a whole pipeline behind a single call; when the
|
|
30
|
+
* thing that stopped it is an exhausted budget or a refusal, handing the model a readable error
|
|
31
|
+
* message is an invitation to pick another tool and spend again. A caller that knows which errors
|
|
32
|
+
* mean "stop" says so here, and those alone travel out.
|
|
27
33
|
*/
|
|
28
|
-
export declare const safeInvokeTool: (tools: AgentToolSet, toolCall: ToolCall) => Promise<unknown>;
|
|
34
|
+
export declare const safeInvokeTool: (tools: AgentToolSet, toolCall: ToolCall, fatal?: (e: unknown) => boolean) => Promise<unknown>;
|
|
29
35
|
//# sourceMappingURL=tools.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/helpers/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C,oGAAoG;AACpG,MAAM,WAAW,iBAAiB;IAAG,KAAK,EAAE,MAAM,CAAA;CAAE;AAEpD,eAAO,MAAM,eAAe,MAAO,OAAO,KAAG,iBAC2C,CAAA;AAExF,eAAO,MAAM,WAAW,WAAY,OAAO,KAAG,MAAM,IAAI,iBACW,CAAA;AAEnE
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/helpers/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C,oGAAoG;AACpG,MAAM,WAAW,iBAAiB;IAAG,KAAK,EAAE,MAAM,CAAA;CAAE;AAEpD,eAAO,MAAM,eAAe,MAAO,OAAO,KAAG,iBAC2C,CAAA;AAExF,eAAO,MAAM,WAAW,WAAY,OAAO,KAAG,MAAM,IAAI,iBACW,CAAA;AAEnE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,cAAc,UAClB,YAAY,YAAY,QAAQ,UAAU,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,KACvE,OAAO,CAAC,OAAO,CAiBjB,CAAA"}
|
package/build/helpers/tools.js
CHANGED
|
@@ -18,8 +18,14 @@ export const isToolError = (result) => typeof result === 'object' && result != n
|
|
|
18
18
|
* that is what the model calls — a map keyed by a local variable silently loses any tool whose two
|
|
19
19
|
* names drifted apart, leaving it advertised, callable, and permanently "not found". The key stays
|
|
20
20
|
* as a fallback so a caller may still address a tool by it.
|
|
21
|
+
*
|
|
22
|
+
* `fatal` is the ONE exception to "never throws", and it exists because containment has a cost the
|
|
23
|
+
* containment itself cannot see. A tool may be a whole pipeline behind a single call; when the
|
|
24
|
+
* thing that stopped it is an exhausted budget or a refusal, handing the model a readable error
|
|
25
|
+
* message is an invitation to pick another tool and spend again. A caller that knows which errors
|
|
26
|
+
* mean "stop" says so here, and those alone travel out.
|
|
21
27
|
*/
|
|
22
|
-
export const safeInvokeTool = async (tools, toolCall) => {
|
|
28
|
+
export const safeInvokeTool = async (tools, toolCall, fatal) => {
|
|
23
29
|
const tool = tools[toolCall.name]
|
|
24
30
|
?? Object.values(tools).find(entry => entry.name === toolCall.name);
|
|
25
31
|
if (tool == null) {
|
|
@@ -29,6 +35,9 @@ export const safeInvokeTool = async (tools, toolCall) => {
|
|
|
29
35
|
return await tool.invoke(toolCall.args);
|
|
30
36
|
}
|
|
31
37
|
catch (e) {
|
|
38
|
+
if (fatal?.(e) === true) {
|
|
39
|
+
throw e;
|
|
40
|
+
}
|
|
32
41
|
console.warn(`Error during tool call ${toolCall.name}:`, e);
|
|
33
42
|
return toErrorResponse(e);
|
|
34
43
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/helpers/tools.ts"],"names":[],"mappings":"AAMA,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAU,EAAqB,EAAE,CAC/D,CAAC,EAAE,KAAK,EAAE,2BAA2B,CAAC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;AAExF,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,MAAe,EAA+B,EAAE,CAC1E,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,IAAI,IAAI,IAAI,OAAO,IAAI,MAAM,CAAA;AAEnE
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/helpers/tools.ts"],"names":[],"mappings":"AAMA,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAU,EAAqB,EAAE,CAC/D,CAAC,EAAE,KAAK,EAAE,2BAA2B,CAAC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;AAExF,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,MAAe,EAA+B,EAAE,CAC1E,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,IAAI,IAAI,IAAI,OAAO,IAAI,MAAM,CAAA;AAEnE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,KAAK,EACjC,KAAmB,EAAE,QAAkB,EAAE,KAA+B,EACtD,EAAE;IACpB,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;WAC5B,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,IAAI,CAAC,CAAA;IAErE,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;QACjB,OAAO,eAAe,CAAC,IAAI,KAAK,CAAC,QAAQ,QAAQ,CAAC,IAAI,YAAY,CAAC,CAAC,CAAA;IACtE,CAAC;IAED,IAAI,CAAC;QACH,OAAO,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;IACzC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YACxB,MAAM,CAAC,CAAA;QACT,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,0BAA0B,QAAQ,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC,CAAA;QAC3D,OAAO,eAAe,CAAC,CAAC,CAAC,CAAA;IAC3B,CAAC;AACH,CAAC,CAAA"}
|
package/build/index.d.ts
CHANGED
|
@@ -5,8 +5,9 @@ export * from './model.js';
|
|
|
5
5
|
export * from './service.js';
|
|
6
6
|
export * from './helpers/index.js';
|
|
7
7
|
export * from './stores/index.js';
|
|
8
|
+
export * from './pipeline/index.js';
|
|
9
|
+
export * from './checkpoint/index.js';
|
|
8
10
|
export * from './runtime/provider.js';
|
|
9
11
|
export * from './runtime/transport.js';
|
|
10
|
-
export * from './runtime/checkpoint.js';
|
|
11
12
|
export * from './plugins/export.js';
|
|
12
13
|
//# sourceMappingURL=index.d.ts.map
|
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,mBAAmB,YAAY,CAAA;AAC/B,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,oBAAoB,CAAA;AAClC,cAAc,mBAAmB,CAAA;AACjC,cAAc,uBAAuB,CAAA;AACrC,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,mBAAmB,YAAY,CAAA;AAC/B,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,oBAAoB,CAAA;AAClC,cAAc,mBAAmB,CAAA;AACjC,cAAc,qBAAqB,CAAA;AACnC,cAAc,uBAAuB,CAAA;AACrC,cAAc,uBAAuB,CAAA;AACrC,cAAc,wBAAwB,CAAA;AACtC,cAAc,qBAAqB,CAAA"}
|
package/build/index.js
CHANGED
|
@@ -4,8 +4,9 @@ export * from './model.js';
|
|
|
4
4
|
export * from './service.js';
|
|
5
5
|
export * from './helpers/index.js';
|
|
6
6
|
export * from './stores/index.js';
|
|
7
|
+
export * from './pipeline/index.js';
|
|
8
|
+
export * from './checkpoint/index.js';
|
|
7
9
|
export * from './runtime/provider.js';
|
|
8
10
|
export * from './runtime/transport.js';
|
|
9
|
-
export * from './runtime/checkpoint.js';
|
|
10
11
|
export * from './plugins/export.js';
|
|
11
12
|
//# sourceMappingURL=index.js.map
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAE3B,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,oBAAoB,CAAA;AAClC,cAAc,mBAAmB,CAAA;AACjC,cAAc,uBAAuB,CAAA;AACrC,cAAc,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAE3B,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,oBAAoB,CAAA;AAClC,cAAc,mBAAmB,CAAA;AACjC,cAAc,qBAAqB,CAAA;AACnC,cAAc,uBAAuB,CAAA;AACrC,cAAc,uBAAuB,CAAA;AACrC,cAAc,wBAAwB,CAAA;AACtC,cAAc,qBAAqB,CAAA"}
|
package/build/model.d.ts
CHANGED
|
@@ -3,13 +3,18 @@ import type { AgentModel, AgentOptions } from './types.js';
|
|
|
3
3
|
* An OwlMeans agent over the LangGraph functional API.
|
|
4
4
|
*
|
|
5
5
|
* The loop is deliberately the plain one: ask the model, run whatever tools it asked for, feed the
|
|
6
|
-
* results back, repeat until it stops asking.
|
|
7
|
-
* family's recoverability lives in the OwlMeans execution and flow layers, which already own a
|
|
8
|
-
* serializable state model, and adopting a second one would leave two half-truths about where a
|
|
9
|
-
* crashed run stands.
|
|
6
|
+
* results back, repeat until it stops asking.
|
|
10
7
|
*
|
|
11
|
-
* The `entrypoint` is created INSIDE `invoke()
|
|
12
|
-
*
|
|
8
|
+
* The `entrypoint` is created INSIDE `invoke()` and takes no checkpointer, and that is not for want
|
|
9
|
+
* of one — `makePipeline` in this same package is a checkpointed `StateGraph`. **A ReAct run is not
|
|
10
|
+
* a resumable unit.** Its state is an unbounded message list whose tool results are side effects
|
|
11
|
+
* already applied to the world: re-entering a turn re-applies them, and the identity a functional
|
|
12
|
+
* replay would key on is the positional call ordinal, which renumbers on any edit to the loop. What
|
|
13
|
+
* IS resumable is a pipeline, whose steps are named, whose state is scalars, and whose side effects
|
|
14
|
+
* are guarded per step — and an agent run belongs INSIDE one of those steps.
|
|
15
|
+
*
|
|
16
|
+
* So nothing survives a call here. What continuity a conversation has comes from plugins putting it
|
|
17
|
+
* back into the prompt, not from the graph.
|
|
13
18
|
*/
|
|
14
19
|
export declare const makeAgentModel: (options: AgentOptions) => AgentModel;
|
|
15
20
|
//# sourceMappingURL=model.d.ts.map
|
package/build/model.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EACV,UAAU,EAAE,YAAY,EACzB,MAAM,YAAY,CAAA;AAEnB
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EACV,UAAU,EAAE,YAAY,EACzB,MAAM,YAAY,CAAA;AAEnB;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,cAAc,YAAa,YAAY,KAAG,UA6PtD,CAAA"}
|
package/build/model.js
CHANGED
|
@@ -11,13 +11,18 @@ import { safeInvokeTool } from './helpers/tools.js';
|
|
|
11
11
|
* An OwlMeans agent over the LangGraph functional API.
|
|
12
12
|
*
|
|
13
13
|
* The loop is deliberately the plain one: ask the model, run whatever tools it asked for, feed the
|
|
14
|
-
* results back, repeat until it stops asking.
|
|
15
|
-
* family's recoverability lives in the OwlMeans execution and flow layers, which already own a
|
|
16
|
-
* serializable state model, and adopting a second one would leave two half-truths about where a
|
|
17
|
-
* crashed run stands.
|
|
14
|
+
* results back, repeat until it stops asking.
|
|
18
15
|
*
|
|
19
|
-
* The `entrypoint` is created INSIDE `invoke()
|
|
20
|
-
*
|
|
16
|
+
* The `entrypoint` is created INSIDE `invoke()` and takes no checkpointer, and that is not for want
|
|
17
|
+
* of one — `makePipeline` in this same package is a checkpointed `StateGraph`. **A ReAct run is not
|
|
18
|
+
* a resumable unit.** Its state is an unbounded message list whose tool results are side effects
|
|
19
|
+
* already applied to the world: re-entering a turn re-applies them, and the identity a functional
|
|
20
|
+
* replay would key on is the positional call ordinal, which renumbers on any edit to the loop. What
|
|
21
|
+
* IS resumable is a pipeline, whose steps are named, whose state is scalars, and whose side effects
|
|
22
|
+
* are guarded per step — and an agent run belongs INSIDE one of those steps.
|
|
23
|
+
*
|
|
24
|
+
* So nothing survives a call here. What continuity a conversation has comes from plugins putting it
|
|
25
|
+
* back into the prompt, not from the graph.
|
|
21
26
|
*/
|
|
22
27
|
export const makeAgentModel = (options) => {
|
|
23
28
|
const { exec, tools, entrypoint: entrypointName = DEFAULT_ENTRYPOINT, maxTurns = DEFAULT_MAX_TURNS, autoFinish = true, spectate, } = options;
|
|
@@ -29,22 +34,32 @@ export const makeAgentModel = (options) => {
|
|
|
29
34
|
const prompts = options.prompts ?? exec.prompts;
|
|
30
35
|
const provider = options.provider ?? pluginFor(agentModel);
|
|
31
36
|
const purpose = exec.purpose;
|
|
32
|
-
const registry = [
|
|
37
|
+
const registry = [];
|
|
33
38
|
const ordered = () => [...registry].sort((a, b) => (a.order ?? DEFAULT_PLUGIN_ORDER) - (b.order ?? DEFAULT_PLUGIN_ORDER));
|
|
39
|
+
/**
|
|
40
|
+
* Seat by alias, keeping the original position.
|
|
41
|
+
*
|
|
42
|
+
* Registering the same plugin twice is a wiring accident, and the failure it causes — every
|
|
43
|
+
* context block emitted twice — is silent and expensive rather than loud. The options list goes
|
|
44
|
+
* through this same path, which is the whole point: a service composes its plugins ahead of an
|
|
45
|
+
* agent's own precisely so the agent's can REPLACE one by alias, and a constructor that simply
|
|
46
|
+
* spread the array made "replace" mean "run both".
|
|
47
|
+
*/
|
|
48
|
+
const seat = (plugin) => {
|
|
49
|
+
const at = registry.findIndex(entry => entry.alias === plugin.alias);
|
|
50
|
+
if (at < 0) {
|
|
51
|
+
registry.push(plugin);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
registry[at] = plugin;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
for (const plugin of options.plugins ?? []) {
|
|
58
|
+
seat(plugin);
|
|
59
|
+
}
|
|
34
60
|
const model = {
|
|
35
61
|
conversation: () => conversation,
|
|
36
|
-
use:
|
|
37
|
-
// Seat by alias, keeping the original position: registering the same plugin twice is a wiring
|
|
38
|
-
// accident, and the failure it would otherwise cause — every context block emitted twice — is
|
|
39
|
-
// silent and expensive rather than loud.
|
|
40
|
-
const at = registry.findIndex(entry => entry.alias === plugin.alias);
|
|
41
|
-
if (at < 0) {
|
|
42
|
-
registry.push(plugin);
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
registry[at] = plugin;
|
|
46
|
-
}
|
|
47
|
-
},
|
|
62
|
+
use: seat,
|
|
48
63
|
invoke: async (input, args = {}) => {
|
|
49
64
|
const action = args.action ?? DEFAULT_ACTION;
|
|
50
65
|
const opening = typeof input === 'string' ? new HumanMessage({ content: input }) : input;
|
|
@@ -104,7 +119,7 @@ export const makeAgentModel = (options) => {
|
|
|
104
119
|
});
|
|
105
120
|
// A rejected task aborts the whole superstep, killing every sibling call in the same batch —
|
|
106
121
|
// `safeInvokeTool` is what keeps a bad argument from costing the work the others finished.
|
|
107
|
-
const call = task('call-tool', async (toolCall) => safeInvokeTool(toolSet, toolCall));
|
|
122
|
+
const call = task('call-tool', async (toolCall) => safeInvokeTool(toolSet, toolCall, options.fatal));
|
|
108
123
|
const agent = entrypoint(entrypointName, async (messages) => {
|
|
109
124
|
let response = await ask(messages);
|
|
110
125
|
let turn = 0;
|