@loopingai/core 0.3.0 → 0.4.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 +83 -28
- package/dist/a2a/caller.d.ts +24 -0
- package/dist/a2a/caller.d.ts.map +1 -0
- package/dist/a2a/caller.js +34 -0
- package/dist/a2a/caller.js.map +1 -0
- package/dist/a2a/card.d.ts +7 -2
- package/dist/a2a/card.d.ts.map +1 -1
- package/dist/a2a/card.js +16 -7
- package/dist/a2a/card.js.map +1 -1
- package/dist/a2a/index.d.ts +15 -0
- package/dist/a2a/index.d.ts.map +1 -1
- package/dist/a2a/index.js +15 -0
- package/dist/a2a/index.js.map +1 -1
- package/dist/a2a/notify.d.ts +27 -5
- package/dist/a2a/notify.d.ts.map +1 -1
- package/dist/a2a/notify.js +18 -8
- package/dist/a2a/notify.js.map +1 -1
- package/dist/a2a/push.d.ts +71 -0
- package/dist/a2a/push.d.ts.map +1 -0
- package/dist/a2a/push.js +54 -0
- package/dist/a2a/push.js.map +1 -0
- package/dist/a2a/verify.d.ts +28 -36
- package/dist/a2a/verify.d.ts.map +1 -1
- package/dist/a2a/verify.js +20 -25
- package/dist/a2a/verify.js.map +1 -1
- package/dist/agent/index.d.ts +16 -5
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +16 -5
- package/dist/agent/index.js.map +1 -1
- package/dist/config.d.ts +49 -7
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +25 -3
- package/dist/config.js.map +1 -1
- package/dist/db/models/tasks.d.ts +19 -5
- package/dist/db/models/tasks.d.ts.map +1 -1
- package/dist/db/models/tasks.js +38 -8
- package/dist/db/models/tasks.js.map +1 -1
- package/dist/host/agent.d.ts +217 -0
- package/dist/host/agent.d.ts.map +1 -0
- package/dist/host/agent.js +306 -0
- package/dist/host/agent.js.map +1 -0
- package/dist/host/index.d.ts +21 -0
- package/dist/host/index.d.ts.map +1 -0
- package/dist/host/index.js +20 -0
- package/dist/host/index.js.map +1 -0
- package/dist/host/plugin-host.d.ts +47 -0
- package/dist/host/plugin-host.d.ts.map +1 -0
- package/dist/host/plugin-host.js +2 -0
- package/dist/host/plugin-host.js.map +1 -0
- package/dist/round/agent.d.ts +267 -0
- package/dist/round/agent.d.ts.map +1 -0
- package/dist/round/agent.js +691 -0
- package/dist/round/agent.js.map +1 -0
- package/dist/round/index.d.ts +25 -0
- package/dist/round/index.d.ts.map +1 -0
- package/dist/round/index.js +24 -0
- package/dist/round/index.js.map +1 -0
- package/dist/round/policy.d.ts +99 -0
- package/dist/round/policy.d.ts.map +1 -0
- package/dist/round/policy.js +2 -0
- package/dist/round/policy.js.map +1 -0
- package/dist/round/subagent.d.ts +67 -0
- package/dist/round/subagent.d.ts.map +1 -0
- package/dist/round/subagent.js +91 -0
- package/dist/round/subagent.js.map +1 -0
- package/dist/round/turn.d.ts +231 -0
- package/dist/round/turn.d.ts.map +1 -0
- package/dist/round/turn.js +497 -0
- package/dist/round/turn.js.map +1 -0
- package/dist/round/workflow.d.ts +109 -0
- package/dist/round/workflow.d.ts.map +1 -0
- package/dist/round/workflow.js +316 -0
- package/dist/round/workflow.js.map +1 -0
- package/dist/runtime/index.d.ts +5 -1
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js.map +1 -1
- package/dist/testing/auth.d.ts.map +1 -1
- package/dist/testing/auth.js +10 -7
- package/dist/testing/auth.js.map +1 -1
- package/dist/testing/fixtures.d.ts +17 -0
- package/dist/testing/fixtures.d.ts.map +1 -1
- package/dist/testing/fixtures.js +17 -0
- package/dist/testing/fixtures.js.map +1 -1
- package/dist/testing/harness.d.ts +98 -0
- package/dist/testing/harness.d.ts.map +1 -0
- package/dist/testing/harness.js +139 -0
- package/dist/testing/harness.js.map +1 -0
- package/dist/testing/index.d.ts +2 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +2 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/vcr-store.d.ts +14 -18
- package/dist/testing/vcr-store.d.ts.map +1 -1
- package/dist/testing/vcr-store.js +30 -30
- package/dist/testing/vcr-store.js.map +1 -1
- package/dist/worker/define-agent.d.ts +124 -0
- package/dist/worker/define-agent.d.ts.map +1 -0
- package/dist/worker/define-agent.js +21 -0
- package/dist/worker/define-agent.js.map +1 -0
- package/dist/worker/index.d.ts +29 -4
- package/dist/worker/index.d.ts.map +1 -1
- package/dist/worker/index.js +70 -12
- package/dist/worker/index.js.map +1 -1
- package/package.json +20 -6
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { Agent, type Schedule } from "agents";
|
|
2
|
+
import { type Task } from "@a2a-js/sdk";
|
|
3
|
+
import { type AgentRuntime } from "../runtime/index.js";
|
|
4
|
+
import type { AgentPlugin } from "../contract/plugin.js";
|
|
5
|
+
import { type CoreConfig, type CoreConfigOverrides } from "../config.js";
|
|
6
|
+
import type { A2ASecretsEnv, AiEnv } from "../env.js";
|
|
7
|
+
import { AgentDB } from "../db/index.js";
|
|
8
|
+
import type { GatewayIdentity } from "../a2a/verify.js";
|
|
9
|
+
import type { PlainTask } from "../a2a/task.js";
|
|
10
|
+
import type { TaskListQuery } from "../a2a/agent-stub.js";
|
|
11
|
+
import { type PushChannel, type TurnPushContext } from "../a2a/push.js";
|
|
12
|
+
import { type SessionLike } from "../agent/session.js";
|
|
13
|
+
import { type GatewayMetadata, type ModelPair, type ModelRuntime } from "../agent/model.js";
|
|
14
|
+
import type { PluginHost } from "./plugin-host.js";
|
|
15
|
+
/**
|
|
16
|
+
* The Durable Object body every Looping agent has, whatever loop it runs.
|
|
17
|
+
*
|
|
18
|
+
* ## Why this is core's and not the app's
|
|
19
|
+
*
|
|
20
|
+
* It was the app's, in the starter, and it was written twice — once for the
|
|
21
|
+
* delegating round agent, once for the single-turn proactive one. The two copies
|
|
22
|
+
* were identical for ~180 lines: the memoized runtime/db/models getters, the
|
|
23
|
+
* `onStart` that must await migrations before the SDK dispatches any RPC, the
|
|
24
|
+
* cron registration guard, the session with its displacement fan-out, the
|
|
25
|
+
* `identityKey` timing, and the task RPC surface.
|
|
26
|
+
*
|
|
27
|
+
* They did not stay identical. The second copy dropped `markWorking`'s
|
|
28
|
+
* cancellation verdict on the floor and probed with a separate `getTask` before
|
|
29
|
+
* writing a terminal Task — so a canceled task still burned a model call, and the
|
|
30
|
+
* gateway could still receive a `completed` callback for a task the caller had
|
|
31
|
+
* abandoned. Both are lifecycle invariants, both were documented in the first
|
|
32
|
+
* copy, and neither is visible to a type checker or a linter.
|
|
33
|
+
*
|
|
34
|
+
* That is the argument for this class. **None of it is policy.** How a turn is
|
|
35
|
+
* shaped, what ends it, what the model is told — all of that stays with the
|
|
36
|
+
* agent, and an agent that wants a different loop simply does not extend
|
|
37
|
+
* {@link file://../round/agent.ts RoundAgentBase}. What is here is the part where
|
|
38
|
+
* being different is only ever a bug.
|
|
39
|
+
*
|
|
40
|
+
* ## The three seams
|
|
41
|
+
*
|
|
42
|
+
* ```ts
|
|
43
|
+
* export class MyAgent extends LoopingAgent<Env> {
|
|
44
|
+
* protected agentConfig() { return MY_CONFIG; }
|
|
45
|
+
* protected agentPlugins(host: PluginHost<Env>) { return plugins(host); }
|
|
46
|
+
* protected agentSoul(capabilities: string) { return soulPrompt(capabilities); }
|
|
47
|
+
* }
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* One Durable Object instance per verified caller (keyed by the gateway JWT's
|
|
51
|
+
* `identity.key`), each owning **one continuous Session** — durable history plus
|
|
52
|
+
* a self-edited `memory` block, backed by `this.sql`. All of a caller's turns, in
|
|
53
|
+
* any channel or thread, accumulate into that one conversation.
|
|
54
|
+
*/
|
|
55
|
+
export declare abstract class LoopingAgent<TEnv extends Cloudflare.Env & AiEnv & A2ASecretsEnv = Cloudflare.Env & AiEnv & A2ASecretsEnv> extends Agent<TEnv> {
|
|
56
|
+
private session?;
|
|
57
|
+
private _runtime?;
|
|
58
|
+
private _models?;
|
|
59
|
+
private _pair?;
|
|
60
|
+
private _db?;
|
|
61
|
+
/**
|
|
62
|
+
* The verified caller this instance belongs to, set on the first turn.
|
|
63
|
+
*
|
|
64
|
+
* `onStart` runs before any request, so it is not known when `agentPlugins()`
|
|
65
|
+
* is built — which is why anything per-caller takes a thunk. The DO is keyed
|
|
66
|
+
* 1:1 by this value, so it is constant once set.
|
|
67
|
+
*/
|
|
68
|
+
private identityKey?;
|
|
69
|
+
/**
|
|
70
|
+
* Test-only model injection. A **field**, not a constructor argument or an RPC
|
|
71
|
+
* parameter, so it never appears on the generated DO stub: production callers
|
|
72
|
+
* cannot reach it, and no model configuration crosses the RPC boundary.
|
|
73
|
+
*/
|
|
74
|
+
modelsOverride?: ModelPair;
|
|
75
|
+
/** This agent's config overrides. Merged onto core's baseline once, at start. */
|
|
76
|
+
protected abstract agentConfig(): CoreConfigOverrides;
|
|
77
|
+
/** This agent's installed capabilities. Conventionally its `./plugins.ts`. */
|
|
78
|
+
protected abstract agentPlugins(host: PluginHost<TEnv>): AgentPlugin[];
|
|
79
|
+
/**
|
|
80
|
+
* This agent's identity, with the installed plugins' capability blocks already
|
|
81
|
+
* rendered in. Core ships no prompt copy — this is yours to write.
|
|
82
|
+
*/
|
|
83
|
+
protected abstract agentSoul(capabilities: string): string;
|
|
84
|
+
/**
|
|
85
|
+
* Everything that would otherwise be a module-level constant, resolved once
|
|
86
|
+
* per DO instance from this agent's config and its installed plugins.
|
|
87
|
+
*
|
|
88
|
+
* Resolving a registry at *import* time is the one thing the package split
|
|
89
|
+
* exists to prevent: it freezes the registry before `env` exists (which on
|
|
90
|
+
* Workers is always), defeats tree-shaking, and makes per-agent plugin
|
|
91
|
+
* selection impossible.
|
|
92
|
+
*/
|
|
93
|
+
protected get runtime(): AgentRuntime;
|
|
94
|
+
/** The resolved config. */
|
|
95
|
+
protected get config(): CoreConfig;
|
|
96
|
+
/** The agent's database (drizzle + migrations), built once per DO instance. */
|
|
97
|
+
protected get db(): AgentDB;
|
|
98
|
+
/** The model runtime for this instance, built lazily over the `AI` binding. */
|
|
99
|
+
protected get models(): ModelRuntime;
|
|
100
|
+
/**
|
|
101
|
+
* What this agent's plugins are handed. Built from
|
|
102
|
+
* {@link resolvedModelIds} rather than `this.config`, which would be a cycle —
|
|
103
|
+
* building the runtime is what needs these.
|
|
104
|
+
*/
|
|
105
|
+
protected pluginHost(): PluginHost<TEnv>;
|
|
106
|
+
/**
|
|
107
|
+
* The model settings a locally-declared recipe runs on, resolved *before* the
|
|
108
|
+
* runtime exists.
|
|
109
|
+
*
|
|
110
|
+
* Deliberately not `this.config` — that would be a cycle. `resolveConfig` is
|
|
111
|
+
* cheap and pure and fills in core's defaults, so this is the same result the
|
|
112
|
+
* runtime lands on; that matters because a recipe's models are checked against
|
|
113
|
+
* `policy.modelAllowlist`, which is built from these very values.
|
|
114
|
+
*/
|
|
115
|
+
private resolvedModelIds;
|
|
116
|
+
onStart(): Promise<void>;
|
|
117
|
+
/**
|
|
118
|
+
* Cron handler: delete task rows older than 30 days. Runs Sunday 01:00 UTC.
|
|
119
|
+
*
|
|
120
|
+
* A plugin's own tables are its business — core's journal does not reach them,
|
|
121
|
+
* and neither does this sweep. A subclass with more durable state of its own
|
|
122
|
+
* overrides {@link cleanupAgentState}.
|
|
123
|
+
*/
|
|
124
|
+
cleanupOldTasks(_payload: Record<string, never>, _schedule: Schedule): Promise<void>;
|
|
125
|
+
/** Extra durable state to age out alongside the task rows. Default: none. */
|
|
126
|
+
protected cleanupAgentState(): void;
|
|
127
|
+
/**
|
|
128
|
+
* The main agent's primary/fallback pair. With `metadata` it builds a fresh
|
|
129
|
+
* pair carrying that AI Gateway correlation tag (so a gateway log ties the call
|
|
130
|
+
* to its task and round); without it — the Session's own compaction model — it
|
|
131
|
+
* reuses a memoized default. A test `modelsOverride` always wins.
|
|
132
|
+
*/
|
|
133
|
+
protected modelPair(metadata?: GatewayMetadata): ModelPair;
|
|
134
|
+
/**
|
|
135
|
+
* The one continuous Session for this caller (rebuilt from `this.sql` after
|
|
136
|
+
* eviction). Memoized — `identity` is constant for the DO's life, since the DO
|
|
137
|
+
* is keyed 1:1 by `identity.key`.
|
|
138
|
+
*
|
|
139
|
+
* `onMessagesDisplaced` is the whole integration for anything that wants the
|
|
140
|
+
* messages a compaction folds away: core performs the compaction, so core
|
|
141
|
+
* announces the loss, and the runtime fans it out to every plugin that asked.
|
|
142
|
+
*/
|
|
143
|
+
getSession(identity: GatewayIdentity): SessionLike;
|
|
144
|
+
/** The caller key, which is present on every path that can reach a plugin. */
|
|
145
|
+
protected requireIdentityKey(): string;
|
|
146
|
+
/** The gateway callback channel for one turn. See {@link PushChannel}. */
|
|
147
|
+
protected push(context: TurnPushContext): PushChannel;
|
|
148
|
+
/**
|
|
149
|
+
* The per-request system-prompt suffix describing the verified caller.
|
|
150
|
+
*
|
|
151
|
+
* A rendering of a protocol fact rather than prompt copy, so core supplies one
|
|
152
|
+
* — see {@link callerContext}. Override it to name what a workspace id means in
|
|
153
|
+
* your deployment; do not use it to say who the *user* is, which this is not.
|
|
154
|
+
*/
|
|
155
|
+
protected callerContext(identity: GatewayIdentity): string;
|
|
156
|
+
beginTask(input: {
|
|
157
|
+
messageId: string;
|
|
158
|
+
taskId: string;
|
|
159
|
+
contextId: string;
|
|
160
|
+
}): Promise<PlainTask>;
|
|
161
|
+
getTask(taskId: string): Promise<PlainTask | null>;
|
|
162
|
+
listTasks(query: TaskListQuery): Promise<{
|
|
163
|
+
tasks: PlainTask[];
|
|
164
|
+
totalSize: number;
|
|
165
|
+
}>;
|
|
166
|
+
/**
|
|
167
|
+
* Persist a Task, returning **whether the guarded write applied**.
|
|
168
|
+
*
|
|
169
|
+
* That boolean is the cancellation check, and a caller must key its callback on
|
|
170
|
+
* it: `AgentDB` refuses to write a terminal state over a `canceled` row and
|
|
171
|
+
* does that read and write in one synchronous pass inside the DO. Probing with
|
|
172
|
+
* {@link getTask} first and saving second leaves a window — between the two
|
|
173
|
+
* calls, and again between the save and the notify — in which a cancel lands
|
|
174
|
+
* and the gateway still receives a `completed` callback.
|
|
175
|
+
*
|
|
176
|
+
* A `canceled` state routes to {@link markCanceled} instead of a plain write,
|
|
177
|
+
* so a `tasks/cancel` arriving through the a2a-js TaskStore and one arriving
|
|
178
|
+
* through {@link cancelTask} converge on the same interruption path.
|
|
179
|
+
*/
|
|
180
|
+
saveTask(task: Task): Promise<boolean>;
|
|
181
|
+
/**
|
|
182
|
+
* Move the Task to `working`. Returns `"canceled"` when the caller cancelled
|
|
183
|
+
* first — read it and stop, rather than probing with a separate
|
|
184
|
+
* {@link getTask}, which reopens the gap between asking and acting.
|
|
185
|
+
*
|
|
186
|
+
* Anything else is `"ok"`, including an unknown row and a row already `working`
|
|
187
|
+
* (a replayed step): only an actual cancellation stops the pipeline.
|
|
188
|
+
*/
|
|
189
|
+
markWorking(taskId: string): Promise<"ok" | "canceled">;
|
|
190
|
+
cancelTask(taskId: string): Promise<PlainTask | null>;
|
|
191
|
+
/**
|
|
192
|
+
* The one place a Task becomes canceled: flip the row — terminal, so every
|
|
193
|
+
* non-canceled write is refused afterwards — then interrupt whatever is still
|
|
194
|
+
* running for it.
|
|
195
|
+
*
|
|
196
|
+
* `task` is supplied when the caller already built the canceled Task (the
|
|
197
|
+
* a2a-js cancel branch attaches its own status message); otherwise the row's
|
|
198
|
+
* own guarded flip produces it. Both paths are guarded against the same race:
|
|
199
|
+
* a task that already reached `completed`/`failed` refuses the write, and its
|
|
200
|
+
* verdict — not a `get` read straight after, which would return that
|
|
201
|
+
* unchanged terminal row and be mistaken for a successful cancellation — is
|
|
202
|
+
* what decides whether {@link onTaskCanceled} runs at all.
|
|
203
|
+
*/
|
|
204
|
+
private markCanceled;
|
|
205
|
+
/**
|
|
206
|
+
* Interrupt work still in flight for a task that has just been canceled.
|
|
207
|
+
*
|
|
208
|
+
* Default: nothing, which is right for an agent whose turn is a single
|
|
209
|
+
* inference — the row is terminal and the next guarded write refuses. An agent
|
|
210
|
+
* with children overrides this to abort them.
|
|
211
|
+
*
|
|
212
|
+
* **Must be best-effort.** Cancellation has already been recorded by the time
|
|
213
|
+
* this runs, and it must not fail because cleanup did.
|
|
214
|
+
*/
|
|
215
|
+
protected onTaskCanceled(_taskId: string): Promise<void>;
|
|
216
|
+
}
|
|
217
|
+
//# sourceMappingURL=agent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/host/agent.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { Agent } from "agents";
|
|
2
|
+
import { TaskState } from "@a2a-js/sdk";
|
|
3
|
+
import { createAgentRuntime } from "../runtime/index.js";
|
|
4
|
+
import { resolveConfig } from "../config.js";
|
|
5
|
+
import { AgentDB, stateOf } from "../db/index.js";
|
|
6
|
+
import { callerContext } from "../a2a/caller.js";
|
|
7
|
+
import { createPushChannel } from "../a2a/push.js";
|
|
8
|
+
import { buildAgentSession } from "../agent/session.js";
|
|
9
|
+
import { createModelRuntime } from "../agent/model.js";
|
|
10
|
+
/**
|
|
11
|
+
* The Durable Object body every Looping agent has, whatever loop it runs.
|
|
12
|
+
*
|
|
13
|
+
* ## Why this is core's and not the app's
|
|
14
|
+
*
|
|
15
|
+
* It was the app's, in the starter, and it was written twice — once for the
|
|
16
|
+
* delegating round agent, once for the single-turn proactive one. The two copies
|
|
17
|
+
* were identical for ~180 lines: the memoized runtime/db/models getters, the
|
|
18
|
+
* `onStart` that must await migrations before the SDK dispatches any RPC, the
|
|
19
|
+
* cron registration guard, the session with its displacement fan-out, the
|
|
20
|
+
* `identityKey` timing, and the task RPC surface.
|
|
21
|
+
*
|
|
22
|
+
* They did not stay identical. The second copy dropped `markWorking`'s
|
|
23
|
+
* cancellation verdict on the floor and probed with a separate `getTask` before
|
|
24
|
+
* writing a terminal Task — so a canceled task still burned a model call, and the
|
|
25
|
+
* gateway could still receive a `completed` callback for a task the caller had
|
|
26
|
+
* abandoned. Both are lifecycle invariants, both were documented in the first
|
|
27
|
+
* copy, and neither is visible to a type checker or a linter.
|
|
28
|
+
*
|
|
29
|
+
* That is the argument for this class. **None of it is policy.** How a turn is
|
|
30
|
+
* shaped, what ends it, what the model is told — all of that stays with the
|
|
31
|
+
* agent, and an agent that wants a different loop simply does not extend
|
|
32
|
+
* {@link file://../round/agent.ts RoundAgentBase}. What is here is the part where
|
|
33
|
+
* being different is only ever a bug.
|
|
34
|
+
*
|
|
35
|
+
* ## The three seams
|
|
36
|
+
*
|
|
37
|
+
* ```ts
|
|
38
|
+
* export class MyAgent extends LoopingAgent<Env> {
|
|
39
|
+
* protected agentConfig() { return MY_CONFIG; }
|
|
40
|
+
* protected agentPlugins(host: PluginHost<Env>) { return plugins(host); }
|
|
41
|
+
* protected agentSoul(capabilities: string) { return soulPrompt(capabilities); }
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* One Durable Object instance per verified caller (keyed by the gateway JWT's
|
|
46
|
+
* `identity.key`), each owning **one continuous Session** — durable history plus
|
|
47
|
+
* a self-edited `memory` block, backed by `this.sql`. All of a caller's turns, in
|
|
48
|
+
* any channel or thread, accumulate into that one conversation.
|
|
49
|
+
*/
|
|
50
|
+
export class LoopingAgent extends Agent {
|
|
51
|
+
session;
|
|
52
|
+
_runtime;
|
|
53
|
+
_models;
|
|
54
|
+
_pair;
|
|
55
|
+
_db;
|
|
56
|
+
/**
|
|
57
|
+
* The verified caller this instance belongs to, set on the first turn.
|
|
58
|
+
*
|
|
59
|
+
* `onStart` runs before any request, so it is not known when `agentPlugins()`
|
|
60
|
+
* is built — which is why anything per-caller takes a thunk. The DO is keyed
|
|
61
|
+
* 1:1 by this value, so it is constant once set.
|
|
62
|
+
*/
|
|
63
|
+
identityKey;
|
|
64
|
+
/**
|
|
65
|
+
* Test-only model injection. A **field**, not a constructor argument or an RPC
|
|
66
|
+
* parameter, so it never appears on the generated DO stub: production callers
|
|
67
|
+
* cannot reach it, and no model configuration crosses the RPC boundary.
|
|
68
|
+
*/
|
|
69
|
+
modelsOverride;
|
|
70
|
+
// --- assembly -------------------------------------------------------------
|
|
71
|
+
/**
|
|
72
|
+
* Everything that would otherwise be a module-level constant, resolved once
|
|
73
|
+
* per DO instance from this agent's config and its installed plugins.
|
|
74
|
+
*
|
|
75
|
+
* Resolving a registry at *import* time is the one thing the package split
|
|
76
|
+
* exists to prevent: it freezes the registry before `env` exists (which on
|
|
77
|
+
* Workers is always), defeats tree-shaking, and makes per-agent plugin
|
|
78
|
+
* selection impossible.
|
|
79
|
+
*/
|
|
80
|
+
get runtime() {
|
|
81
|
+
return (this._runtime ??= createAgentRuntime({
|
|
82
|
+
config: this.agentConfig(),
|
|
83
|
+
plugins: this.agentPlugins(this.pluginHost()),
|
|
84
|
+
// Opt in to verifying every plugin's declared bindings exist. Fails at DO
|
|
85
|
+
// start with a sentence naming the plugin, rather than at the first tool
|
|
86
|
+
// call inside a request someone is waiting on.
|
|
87
|
+
env: this.env
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
90
|
+
/** The resolved config. */
|
|
91
|
+
get config() {
|
|
92
|
+
return this.runtime.config;
|
|
93
|
+
}
|
|
94
|
+
/** The agent's database (drizzle + migrations), built once per DO instance. */
|
|
95
|
+
get db() {
|
|
96
|
+
return (this._db ??= new AgentDB(this.ctx.storage, {
|
|
97
|
+
maxSubtasks: this.config.maxSubtasks,
|
|
98
|
+
// Plugin-owned tables, applied after core's own migrations. A store that
|
|
99
|
+
// throws fails DO start rather than being skipped — a plugin whose tables
|
|
100
|
+
// are missing would otherwise fail at its first tool call.
|
|
101
|
+
stores: this.runtime.stores
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
/** The model runtime for this instance, built lazily over the `AI` binding. */
|
|
105
|
+
get models() {
|
|
106
|
+
return (this._models ??= createModelRuntime({
|
|
107
|
+
ai: this.env.AI,
|
|
108
|
+
config: this.config.model
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* What this agent's plugins are handed. Built from
|
|
113
|
+
* {@link resolvedModelIds} rather than `this.config`, which would be a cycle —
|
|
114
|
+
* building the runtime is what needs these.
|
|
115
|
+
*/
|
|
116
|
+
pluginHost() {
|
|
117
|
+
const model = this.resolvedModelIds();
|
|
118
|
+
return {
|
|
119
|
+
env: this.env,
|
|
120
|
+
storage: this.ctx.storage,
|
|
121
|
+
// A thunk, not a value — see `identityKey`.
|
|
122
|
+
callerKey: () => this.requireIdentityKey(),
|
|
123
|
+
primaryModelId: model.chatModelId,
|
|
124
|
+
fallbackModelId: model.fallbackChatModelId,
|
|
125
|
+
aiGatewayId: model.aiGatewayId
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* The model settings a locally-declared recipe runs on, resolved *before* the
|
|
130
|
+
* runtime exists.
|
|
131
|
+
*
|
|
132
|
+
* Deliberately not `this.config` — that would be a cycle. `resolveConfig` is
|
|
133
|
+
* cheap and pure and fills in core's defaults, so this is the same result the
|
|
134
|
+
* runtime lands on; that matters because a recipe's models are checked against
|
|
135
|
+
* `policy.modelAllowlist`, which is built from these very values.
|
|
136
|
+
*/
|
|
137
|
+
resolvedModelIds() {
|
|
138
|
+
return resolveConfig(this.agentConfig()).model;
|
|
139
|
+
}
|
|
140
|
+
async onStart() {
|
|
141
|
+
// Await migrations before the SDK dispatches any RPC — eliminates the race
|
|
142
|
+
// between schema creation and first query on cold start / hibernation wake-up.
|
|
143
|
+
await this.db.ensureReady();
|
|
144
|
+
// Register the weekly cleanup cron once per DO instance (idempotent guard).
|
|
145
|
+
const existing = await this.listSchedules({ type: "cron" });
|
|
146
|
+
if (!existing.some((s) => s.callback === "cleanupOldTasks")) {
|
|
147
|
+
await this.schedule("0 1 * * 0", "cleanupOldTasks", {});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Cron handler: delete task rows older than 30 days. Runs Sunday 01:00 UTC.
|
|
152
|
+
*
|
|
153
|
+
* A plugin's own tables are its business — core's journal does not reach them,
|
|
154
|
+
* and neither does this sweep. A subclass with more durable state of its own
|
|
155
|
+
* overrides {@link cleanupAgentState}.
|
|
156
|
+
*/
|
|
157
|
+
async cleanupOldTasks(_payload, _schedule) {
|
|
158
|
+
this.db.tasks.cleanup();
|
|
159
|
+
this.cleanupAgentState();
|
|
160
|
+
}
|
|
161
|
+
/** Extra durable state to age out alongside the task rows. Default: none. */
|
|
162
|
+
cleanupAgentState() { }
|
|
163
|
+
/**
|
|
164
|
+
* The main agent's primary/fallback pair. With `metadata` it builds a fresh
|
|
165
|
+
* pair carrying that AI Gateway correlation tag (so a gateway log ties the call
|
|
166
|
+
* to its task and round); without it — the Session's own compaction model — it
|
|
167
|
+
* reuses a memoized default. A test `modelsOverride` always wins.
|
|
168
|
+
*/
|
|
169
|
+
modelPair(metadata) {
|
|
170
|
+
if (this.modelsOverride)
|
|
171
|
+
return this.modelsOverride;
|
|
172
|
+
if (!metadata)
|
|
173
|
+
return (this._pair ??= this.models.createModelPair());
|
|
174
|
+
return this.models.createModelPair({ metadata });
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* The one continuous Session for this caller (rebuilt from `this.sql` after
|
|
178
|
+
* eviction). Memoized — `identity` is constant for the DO's life, since the DO
|
|
179
|
+
* is keyed 1:1 by `identity.key`.
|
|
180
|
+
*
|
|
181
|
+
* `onMessagesDisplaced` is the whole integration for anything that wants the
|
|
182
|
+
* messages a compaction folds away: core performs the compaction, so core
|
|
183
|
+
* announces the loss, and the runtime fans it out to every plugin that asked.
|
|
184
|
+
*/
|
|
185
|
+
getSession(identity) {
|
|
186
|
+
this.identityKey ??= identity.key ?? undefined;
|
|
187
|
+
const { session, model } = this.config;
|
|
188
|
+
return (this.session ??= buildAgentSession(this, this.modelPair().primary(), {
|
|
189
|
+
soul: () => this.agentSoul(this.runtime.renderCapabilities()),
|
|
190
|
+
memoryDescription: session.memoryDescription,
|
|
191
|
+
memoryMaxTokens: session.memoryMaxTokens,
|
|
192
|
+
compactAfterTokens: session.compactAfterTokens,
|
|
193
|
+
compactTailTokens: session.compactTailTokens,
|
|
194
|
+
maxOutputTokens: model.maxOutputTokens,
|
|
195
|
+
onMessagesDisplaced: this.runtime.onMessagesDisplaced
|
|
196
|
+
}));
|
|
197
|
+
}
|
|
198
|
+
/** The caller key, which is present on every path that can reach a plugin. */
|
|
199
|
+
requireIdentityKey() {
|
|
200
|
+
if (!this.identityKey) {
|
|
201
|
+
throw new Error("identity.key is required for per-caller isolation");
|
|
202
|
+
}
|
|
203
|
+
return this.identityKey;
|
|
204
|
+
}
|
|
205
|
+
/** The gateway callback channel for one turn. See {@link PushChannel}. */
|
|
206
|
+
push(context) {
|
|
207
|
+
return createPushChannel(this.env.A2A_SIGNING_KEY, context);
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* The per-request system-prompt suffix describing the verified caller.
|
|
211
|
+
*
|
|
212
|
+
* A rendering of a protocol fact rather than prompt copy, so core supplies one
|
|
213
|
+
* — see {@link callerContext}. Override it to name what a workspace id means in
|
|
214
|
+
* your deployment; do not use it to say who the *user* is, which this is not.
|
|
215
|
+
*/
|
|
216
|
+
callerContext(identity) {
|
|
217
|
+
return callerContext(identity);
|
|
218
|
+
}
|
|
219
|
+
// --- Async task state (accept + notify) ----------------------------------
|
|
220
|
+
//
|
|
221
|
+
// A thin RPC surface over `AgentDB`'s `tasks` table. Native RPC methods — the
|
|
222
|
+
// DO is never a network-reachable server — called by the Workflow, which
|
|
223
|
+
// cannot touch this SQLite directly.
|
|
224
|
+
//
|
|
225
|
+
// The Task-returning methods return `PlainTask`: the SDK `Task` narrowed to
|
|
226
|
+
// what survives Cloudflare's RPC types. Returning the raw SDK `Task` breaks
|
|
227
|
+
// the generated DO-stub types (under v1.0 it blows past TypeScript's
|
|
228
|
+
// instantiation-depth limit).
|
|
229
|
+
async beginTask(input) {
|
|
230
|
+
return this.db.tasks.begin(input);
|
|
231
|
+
}
|
|
232
|
+
async getTask(taskId) {
|
|
233
|
+
return this.db.tasks.get(taskId);
|
|
234
|
+
}
|
|
235
|
+
async listTasks(query) {
|
|
236
|
+
return this.db.tasks.list(query);
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Persist a Task, returning **whether the guarded write applied**.
|
|
240
|
+
*
|
|
241
|
+
* That boolean is the cancellation check, and a caller must key its callback on
|
|
242
|
+
* it: `AgentDB` refuses to write a terminal state over a `canceled` row and
|
|
243
|
+
* does that read and write in one synchronous pass inside the DO. Probing with
|
|
244
|
+
* {@link getTask} first and saving second leaves a window — between the two
|
|
245
|
+
* calls, and again between the save and the notify — in which a cancel lands
|
|
246
|
+
* and the gateway still receives a `completed` callback.
|
|
247
|
+
*
|
|
248
|
+
* A `canceled` state routes to {@link markCanceled} instead of a plain write,
|
|
249
|
+
* so a `tasks/cancel` arriving through the a2a-js TaskStore and one arriving
|
|
250
|
+
* through {@link cancelTask} converge on the same interruption path.
|
|
251
|
+
*/
|
|
252
|
+
async saveTask(task) {
|
|
253
|
+
if (stateOf(task) === TaskState.TASK_STATE_CANCELED) {
|
|
254
|
+
return (await this.markCanceled(task.id, task)) !== null;
|
|
255
|
+
}
|
|
256
|
+
return this.db.tasks.save(task);
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Move the Task to `working`. Returns `"canceled"` when the caller cancelled
|
|
260
|
+
* first — read it and stop, rather than probing with a separate
|
|
261
|
+
* {@link getTask}, which reopens the gap between asking and acting.
|
|
262
|
+
*
|
|
263
|
+
* Anything else is `"ok"`, including an unknown row and a row already `working`
|
|
264
|
+
* (a replayed step): only an actual cancellation stops the pipeline.
|
|
265
|
+
*/
|
|
266
|
+
async markWorking(taskId) {
|
|
267
|
+
return this.db.tasks.markWorking(taskId);
|
|
268
|
+
}
|
|
269
|
+
async cancelTask(taskId) {
|
|
270
|
+
return this.markCanceled(taskId);
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* The one place a Task becomes canceled: flip the row — terminal, so every
|
|
274
|
+
* non-canceled write is refused afterwards — then interrupt whatever is still
|
|
275
|
+
* running for it.
|
|
276
|
+
*
|
|
277
|
+
* `task` is supplied when the caller already built the canceled Task (the
|
|
278
|
+
* a2a-js cancel branch attaches its own status message); otherwise the row's
|
|
279
|
+
* own guarded flip produces it. Both paths are guarded against the same race:
|
|
280
|
+
* a task that already reached `completed`/`failed` refuses the write, and its
|
|
281
|
+
* verdict — not a `get` read straight after, which would return that
|
|
282
|
+
* unchanged terminal row and be mistaken for a successful cancellation — is
|
|
283
|
+
* what decides whether {@link onTaskCanceled} runs at all.
|
|
284
|
+
*/
|
|
285
|
+
async markCanceled(taskId, task) {
|
|
286
|
+
const canceled = task
|
|
287
|
+
? this.db.tasks.save(task) && this.db.tasks.get(taskId)
|
|
288
|
+
: this.db.tasks.cancel(taskId);
|
|
289
|
+
if (!canceled)
|
|
290
|
+
return null;
|
|
291
|
+
await this.onTaskCanceled(taskId);
|
|
292
|
+
return canceled;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Interrupt work still in flight for a task that has just been canceled.
|
|
296
|
+
*
|
|
297
|
+
* Default: nothing, which is right for an agent whose turn is a single
|
|
298
|
+
* inference — the row is terminal and the next guarded write refuses. An agent
|
|
299
|
+
* with children overrides this to abort them.
|
|
300
|
+
*
|
|
301
|
+
* **Must be best-effort.** Cancellation has already been recorded by the time
|
|
302
|
+
* this runs, and it must not fail because cleanup did.
|
|
303
|
+
*/
|
|
304
|
+
async onTaskCanceled(_taskId) { }
|
|
305
|
+
}
|
|
306
|
+
//# sourceMappingURL=agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../../src/host/agent.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@loopingai/core/host` — the Durable Object half of an agent.
|
|
3
|
+
*
|
|
4
|
+
* {@link LoopingAgent} is the DO body every Looping agent has whatever loop it
|
|
5
|
+
* runs: the runtime and database built once per instance, the one continuous
|
|
6
|
+
* Session per verified caller, the gateway callback channel, and the task
|
|
7
|
+
* lifecycle RPC surface a Workflow drives. {@link PluginHost} is what it hands
|
|
8
|
+
* the plugins.
|
|
9
|
+
*
|
|
10
|
+
* Its own subpath rather than part of `/agent` on purpose. `/agent` is the
|
|
11
|
+
* primitives a *loop* is built from — session, model pair, budget, control tools
|
|
12
|
+
* — and a loop module should be able to import those without pulling a Durable
|
|
13
|
+
* Object base class and drizzle into its graph.
|
|
14
|
+
*
|
|
15
|
+
* Core still ships **no loop and no prompt copy**. Nothing here decides how a
|
|
16
|
+
* turn is shaped or what ends it; see `@loopingai/core/round` for the delegating
|
|
17
|
+
* round loop, which is opt-in and takes its prompt copy from the agent.
|
|
18
|
+
*/
|
|
19
|
+
export { LoopingAgent } from "./agent.js";
|
|
20
|
+
export type { PluginHost } from "./plugin-host.js";
|
|
21
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/host/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@loopingai/core/host` — the Durable Object half of an agent.
|
|
3
|
+
*
|
|
4
|
+
* {@link LoopingAgent} is the DO body every Looping agent has whatever loop it
|
|
5
|
+
* runs: the runtime and database built once per instance, the one continuous
|
|
6
|
+
* Session per verified caller, the gateway callback channel, and the task
|
|
7
|
+
* lifecycle RPC surface a Workflow drives. {@link PluginHost} is what it hands
|
|
8
|
+
* the plugins.
|
|
9
|
+
*
|
|
10
|
+
* Its own subpath rather than part of `/agent` on purpose. `/agent` is the
|
|
11
|
+
* primitives a *loop* is built from — session, model pair, budget, control tools
|
|
12
|
+
* — and a loop module should be able to import those without pulling a Durable
|
|
13
|
+
* Object base class and drizzle into its graph.
|
|
14
|
+
*
|
|
15
|
+
* Core still ships **no loop and no prompt copy**. Nothing here decides how a
|
|
16
|
+
* turn is shaped or what ends it; see `@loopingai/core/round` for the delegating
|
|
17
|
+
* round loop, which is opt-in and takes its prompt copy from the agent.
|
|
18
|
+
*/
|
|
19
|
+
export { LoopingAgent } from "./agent.js";
|
|
20
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/host/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Everything a plugin may need from its host, resolved once per Durable Object
|
|
3
|
+
* instance.
|
|
4
|
+
*
|
|
5
|
+
* This is **contract, not application code**, which is why it lives in core. A
|
|
6
|
+
* published plugin's config is written against this shape — `arcAgi` takes
|
|
7
|
+
* `storage`, `recall` takes `callerKey` — so a plugin that could not name the
|
|
8
|
+
* type was writing its signature against a structural guess at an interface
|
|
9
|
+
* declared in an app it has never seen.
|
|
10
|
+
*
|
|
11
|
+
* Not just `env`, and each field is load-bearing:
|
|
12
|
+
*
|
|
13
|
+
* - `env` — bindings and secrets. Typed `object` for the same reason
|
|
14
|
+
* {@link file://../runtime/index.ts CreateAgentRuntimeOptions.env} is: `Env` is
|
|
15
|
+
* the ambient interface `wrangler types` generates into a consumer's
|
|
16
|
+
* `worker-configuration.d.ts`, it has no index signature, and requiring a cast
|
|
17
|
+
* to pass one's own `this.env` is how a seam goes unused. A host narrows it.
|
|
18
|
+
* - `storage` — a plugin that owns tables needs the DO's storage to build a query
|
|
19
|
+
* handle over. `this.ctx.storage`.
|
|
20
|
+
* - `callerKey` — **a thunk, deliberately.** It derives from the verified
|
|
21
|
+
* caller's identity, which does not exist yet when `plugins()` runs at DO
|
|
22
|
+
* start. The DO is keyed 1:1 by that caller, so the value is constant once
|
|
23
|
+
* known; a thunk is what lets the host supply it late while every hook reads
|
|
24
|
+
* the same one. On a subagent facet there is no caller at all, and the honest
|
|
25
|
+
* encoding is a thunk that throws.
|
|
26
|
+
* - the model ids — what a locally-declared recipe runs on. A recipe may only
|
|
27
|
+
* name models in the host's allowlist, so a plugin that ships one is handed the
|
|
28
|
+
* host's pair rather than guessing.
|
|
29
|
+
* - `aiGatewayId` — the **resolved** AI Gateway slug, so a plugin making its own
|
|
30
|
+
* model calls is correlated with the agent's. Resolved, not read off the
|
|
31
|
+
* overrides object: reaching into `MY_CONFIG.model?.aiGatewayId` at a call site
|
|
32
|
+
* silently yields `undefined` the moment that override is dropped in favour of
|
|
33
|
+
* core's default, and the plugin's calls quietly stop being correlated.
|
|
34
|
+
*/
|
|
35
|
+
export interface PluginHost<TEnv extends object = object> {
|
|
36
|
+
env: TEnv;
|
|
37
|
+
storage: DurableObjectStorage;
|
|
38
|
+
/** The verified caller. A thunk — it does not exist when `onStart` runs. */
|
|
39
|
+
callerKey: () => string;
|
|
40
|
+
/** `config.model.chatModelId` — what a locally-declared recipe runs on. */
|
|
41
|
+
primaryModelId: string;
|
|
42
|
+
/** `config.model.fallbackChatModelId`. */
|
|
43
|
+
fallbackModelId: string;
|
|
44
|
+
/** `config.model.aiGatewayId`, already resolved over core's defaults. */
|
|
45
|
+
aiGatewayId: string;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=plugin-host.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-host.d.ts","sourceRoot":"","sources":["../../src/host/plugin-host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,WAAW,UAAU,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM;IACtD,GAAG,EAAE,IAAI,CAAC;IACV,OAAO,EAAE,oBAAoB,CAAC;IAC9B,4EAA4E;IAC5E,SAAS,EAAE,MAAM,MAAM,CAAC;IACxB,2EAA2E;IAC3E,cAAc,EAAE,MAAM,CAAC;IACvB,0CAA0C;IAC1C,eAAe,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,WAAW,EAAE,MAAM,CAAC;CACrB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-host.js","sourceRoot":"","sources":["../../src/host/plugin-host.ts"],"names":[],"mappings":""}
|