@dynamicagents/core 0.9.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/LICENSE +201 -0
- package/README.md +522 -0
- package/dist/a2a/agent-stub.d.ts +60 -0
- package/dist/a2a/agent-stub.js +1 -0
- package/dist/a2a/caller-token.d.ts +44 -0
- package/dist/a2a/caller-token.js +61 -0
- package/dist/a2a/caller.d.ts +23 -0
- package/dist/a2a/caller.js +33 -0
- package/dist/a2a/card.d.ts +158 -0
- package/dist/a2a/card.js +163 -0
- package/dist/a2a/context.d.ts +19 -0
- package/dist/a2a/context.js +61 -0
- package/dist/a2a/deliver.d.ts +159 -0
- package/dist/a2a/deliver.js +185 -0
- package/dist/a2a/executor.d.ts +84 -0
- package/dist/a2a/executor.js +106 -0
- package/dist/a2a/index.d.ts +35 -0
- package/dist/a2a/index.js +34 -0
- package/dist/a2a/notify.d.ts +106 -0
- package/dist/a2a/notify.js +180 -0
- package/dist/a2a/parts.d.ts +45 -0
- package/dist/a2a/parts.js +94 -0
- package/dist/a2a/push.d.ts +70 -0
- package/dist/a2a/push.js +53 -0
- package/dist/a2a/self-origin.d.ts +91 -0
- package/dist/a2a/self-origin.js +114 -0
- package/dist/a2a/task-store.d.ts +44 -0
- package/dist/a2a/task-store.js +99 -0
- package/dist/a2a/task.d.ts +85 -0
- package/dist/a2a/task.js +15 -0
- package/dist/a2a/verify.d.ts +80 -0
- package/dist/a2a/verify.js +143 -0
- package/dist/agent/budget.d.ts +46 -0
- package/dist/agent/budget.js +42 -0
- package/dist/agent/control.d.ts +109 -0
- package/dist/agent/control.js +115 -0
- package/dist/agent/errors.d.ts +85 -0
- package/dist/agent/errors.js +64 -0
- package/dist/agent/final-reply.d.ts +49 -0
- package/dist/agent/final-reply.js +68 -0
- package/dist/agent/history.d.ts +97 -0
- package/dist/agent/history.js +133 -0
- package/dist/agent/index.d.ts +29 -0
- package/dist/agent/index.js +29 -0
- package/dist/agent/inference.d.ts +110 -0
- package/dist/agent/inference.js +120 -0
- package/dist/agent/model.d.ts +90 -0
- package/dist/agent/model.js +1 -0
- package/dist/agent/session.d.ts +100 -0
- package/dist/agent/session.js +82 -0
- package/dist/agent/workers-ai/index.d.ts +23 -0
- package/dist/agent/workers-ai/index.js +23 -0
- package/dist/agent/workers-ai/runtime.d.ts +42 -0
- package/dist/agent/workers-ai/runtime.js +63 -0
- package/dist/alarm/index.d.ts +77 -0
- package/dist/alarm/index.js +116 -0
- package/dist/config.d.ts +202 -0
- package/dist/config.js +135 -0
- package/dist/contract/index.d.ts +9 -0
- package/dist/contract/index.js +8 -0
- package/dist/contract/plugin.d.ts +324 -0
- package/dist/contract/plugin.js +114 -0
- package/dist/contract/recipe.d.ts +180 -0
- package/dist/contract/recipe.js +1 -0
- package/dist/contract/validation.d.ts +91 -0
- package/dist/contract/validation.js +84 -0
- package/dist/db/db.d.ts +147 -0
- package/dist/db/db.js +90 -0
- package/dist/db/index.d.ts +8 -0
- package/dist/db/index.js +8 -0
- package/dist/db/migrations/index.d.ts +20 -0
- package/dist/db/migrations/index.js +60 -0
- package/dist/db/models/subtasks.d.ts +100 -0
- package/dist/db/models/subtasks.js +241 -0
- package/dist/db/models/tasks.d.ts +118 -0
- package/dist/db/models/tasks.js +274 -0
- package/dist/db/schema.d.ts +468 -0
- package/dist/db/schema.js +88 -0
- package/dist/env.d.ts +53 -0
- package/dist/env.js +47 -0
- package/dist/host/agent.d.ts +305 -0
- package/dist/host/agent.js +400 -0
- package/dist/host/index.d.ts +20 -0
- package/dist/host/index.js +19 -0
- package/dist/host/plugin-host.d.ts +42 -0
- package/dist/host/plugin-host.js +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +17 -0
- package/dist/job/index.d.ts +21 -0
- package/dist/job/index.js +21 -0
- package/dist/job/lifecycle.d.ts +176 -0
- package/dist/job/lifecycle.js +230 -0
- package/dist/job/state.d.ts +92 -0
- package/dist/job/state.js +40 -0
- package/dist/platform.d.ts +138 -0
- package/dist/platform.js +140 -0
- package/dist/round/agent.d.ts +271 -0
- package/dist/round/agent.js +678 -0
- package/dist/round/index.d.ts +25 -0
- package/dist/round/index.js +23 -0
- package/dist/round/policy.d.ts +98 -0
- package/dist/round/policy.js +1 -0
- package/dist/round/subagent.d.ts +87 -0
- package/dist/round/subagent.js +108 -0
- package/dist/round/turn.d.ts +249 -0
- package/dist/round/turn.js +564 -0
- package/dist/round/workflow.d.ts +147 -0
- package/dist/round/workflow.js +387 -0
- package/dist/runtime/index.d.ts +127 -0
- package/dist/runtime/index.js +186 -0
- package/dist/runtime/tool-families.d.ts +32 -0
- package/dist/runtime/tool-families.js +61 -0
- package/dist/subagent/fingerprint.d.ts +37 -0
- package/dist/subagent/fingerprint.js +92 -0
- package/dist/subagent/index.d.ts +169 -0
- package/dist/subagent/index.js +330 -0
- package/dist/subagent/prompt.d.ts +32 -0
- package/dist/subagent/prompt.js +37 -0
- package/dist/subagent/run.d.ts +157 -0
- package/dist/subagent/run.js +540 -0
- package/dist/subagent/workspace.d.ts +85 -0
- package/dist/subagent/workspace.js +127 -0
- package/dist/subtasks/catalog.d.ts +40 -0
- package/dist/subtasks/catalog.js +36 -0
- package/dist/subtasks/decomposition.d.ts +85 -0
- package/dist/subtasks/decomposition.js +156 -0
- package/dist/subtasks/delegate.d.ts +120 -0
- package/dist/subtasks/delegate.js +131 -0
- package/dist/subtasks/index.d.ts +9 -0
- package/dist/subtasks/index.js +9 -0
- package/dist/subtasks/subtask-types.d.ts +91 -0
- package/dist/subtasks/subtask-types.js +103 -0
- package/dist/subtasks/types.d.ts +295 -0
- package/dist/subtasks/types.js +15 -0
- package/dist/testing/auth.d.ts +34 -0
- package/dist/testing/auth.js +35 -0
- package/dist/testing/do.d.ts +29 -0
- package/dist/testing/do.js +25 -0
- package/dist/testing/fake-session.d.ts +26 -0
- package/dist/testing/fake-session.js +37 -0
- package/dist/testing/fixtures.d.ts +64 -0
- package/dist/testing/fixtures.js +104 -0
- package/dist/testing/harness.d.ts +97 -0
- package/dist/testing/harness.js +138 -0
- package/dist/testing/index.d.ts +31 -0
- package/dist/testing/index.js +35 -0
- package/dist/testing/mock-model.d.ts +77 -0
- package/dist/testing/mock-model.js +136 -0
- package/dist/testing/node.d.ts +56 -0
- package/dist/testing/node.js +56 -0
- package/dist/testing/vcr-global-setup.d.ts +12 -0
- package/dist/testing/vcr-global-setup.js +15 -0
- package/dist/testing/vcr-shared.d.ts +38 -0
- package/dist/testing/vcr-shared.js +33 -0
- package/dist/testing/vcr-spec.d.ts +25 -0
- package/dist/testing/vcr-spec.js +124 -0
- package/dist/testing/vcr-store.d.ts +86 -0
- package/dist/testing/vcr-store.js +191 -0
- package/dist/testing/vcr.d.ts +117 -0
- package/dist/testing/vcr.js +275 -0
- package/dist/worker/define-agent.d.ts +123 -0
- package/dist/worker/define-agent.js +20 -0
- package/dist/worker/index.d.ts +218 -0
- package/dist/worker/index.js +369 -0
- package/eslint-rules/index.js +31 -0
- package/eslint-rules/no-deprecated-object-properties.js +81 -0
- package/package.json +178 -0
- package/scripts/generate-keys.mjs +48 -0
|
@@ -0,0 +1,400 @@
|
|
|
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 { SelfOrigin } from "../a2a/self-origin.js";
|
|
9
|
+
import { buildAgentSession } from "../agent/session.js";
|
|
10
|
+
import { workersAIModels } from "../agent/workers-ai/index.js";
|
|
11
|
+
/**
|
|
12
|
+
* The Durable Object body every Dynamic Agents agent has, whatever loop it runs.
|
|
13
|
+
*
|
|
14
|
+
* ## Why this is core's and not the app's
|
|
15
|
+
*
|
|
16
|
+
* It was the app's, in the starter, and it was written twice — once for the
|
|
17
|
+
* delegating round agent, once for the single-turn proactive one. The two copies
|
|
18
|
+
* were identical for ~180 lines: the memoized runtime/db/models getters, the
|
|
19
|
+
* `onStart` that must await migrations before the SDK dispatches any RPC, the
|
|
20
|
+
* cron registration guard, the session with its displacement fan-out, the
|
|
21
|
+
* `identityKey` timing, and the task RPC surface.
|
|
22
|
+
*
|
|
23
|
+
* They did not stay identical. The second copy dropped `markWorking`'s
|
|
24
|
+
* cancellation verdict on the floor and probed with a separate `getTask` before
|
|
25
|
+
* writing a terminal Task — so a canceled task still burned a model call, and the
|
|
26
|
+
* gatekeeper could still receive a `completed` callback for a task the caller had
|
|
27
|
+
* abandoned. Both are lifecycle invariants, both were documented in the first
|
|
28
|
+
* copy, and neither is visible to a type checker or a linter.
|
|
29
|
+
*
|
|
30
|
+
* That is the argument for this class. **None of it is policy.** How a turn is
|
|
31
|
+
* shaped, what ends it, what the model is told — all of that stays with the
|
|
32
|
+
* agent, and an agent that wants a different loop simply does not extend
|
|
33
|
+
* {@link file://../round/agent.ts RoundAgentBase}. What is here is the part where
|
|
34
|
+
* being different is only ever a bug.
|
|
35
|
+
*
|
|
36
|
+
* ## The three seams
|
|
37
|
+
*
|
|
38
|
+
* ```ts
|
|
39
|
+
* export class MyAgent extends DynamicAgent<Env> {
|
|
40
|
+
* protected agentConfig() { return MY_CONFIG; }
|
|
41
|
+
* protected agentPlugins(host: PluginHost<Env>) { return plugins(host); }
|
|
42
|
+
* protected agentSoul(capabilities: string) { return soulPrompt(capabilities); }
|
|
43
|
+
* }
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* One Durable Object instance per verified caller (keyed by the gatekeeper JWT's
|
|
47
|
+
* `identity.key`), each owning **one continuous Session** — durable history plus
|
|
48
|
+
* a self-edited `memory` block, backed by `this.sql`. All of a caller's turns, in
|
|
49
|
+
* any channel or thread, accumulate into that one conversation.
|
|
50
|
+
*/
|
|
51
|
+
export class DynamicAgent extends Agent {
|
|
52
|
+
session;
|
|
53
|
+
_runtime;
|
|
54
|
+
_models;
|
|
55
|
+
_pair;
|
|
56
|
+
_db;
|
|
57
|
+
/**
|
|
58
|
+
* The verified caller this instance belongs to, set on the first turn.
|
|
59
|
+
*
|
|
60
|
+
* `onStart` runs before any request, so it is not known when `agentPlugins()`
|
|
61
|
+
* is built — which is why anything per-caller takes a thunk. The DO is keyed
|
|
62
|
+
* 1:1 by this value, so it is constant once set.
|
|
63
|
+
*
|
|
64
|
+
* In-memory, and deliberately so — see {@link requireIdentityKey}, which does
|
|
65
|
+
* not depend on it surviving.
|
|
66
|
+
*/
|
|
67
|
+
identityKey;
|
|
68
|
+
/**
|
|
69
|
+
* This deployment's own public origin, learned from the `jku` every turn
|
|
70
|
+
* carries and **pinned on the first one** this instance serves.
|
|
71
|
+
*
|
|
72
|
+
* Unlike {@link identityKey} this is shared by concurrent turns — the object
|
|
73
|
+
* is keyed by caller, not by origin — so it is pinned rather than
|
|
74
|
+
* last-write-wins: an immutable field cannot change under a credential thunk
|
|
75
|
+
* that reads it while a turn awaits a model call. See {@link SelfOrigin} for
|
|
76
|
+
* the full argument, and for why nothing is persisted.
|
|
77
|
+
*/
|
|
78
|
+
selfOriginMemo = new SelfOrigin();
|
|
79
|
+
/**
|
|
80
|
+
* Test-only model injection. A **field**, not a constructor argument or an RPC
|
|
81
|
+
* parameter, so it never appears on the generated DO stub: production callers
|
|
82
|
+
* cannot reach it, and no model configuration crosses the RPC boundary.
|
|
83
|
+
*/
|
|
84
|
+
modelsOverride;
|
|
85
|
+
// --- assembly -------------------------------------------------------------
|
|
86
|
+
/**
|
|
87
|
+
* Everything that would otherwise be a module-level constant, resolved once
|
|
88
|
+
* per DO instance from this agent's config and its installed plugins.
|
|
89
|
+
*
|
|
90
|
+
* Resolving a registry at *import* time is the one thing the package split
|
|
91
|
+
* exists to prevent: it freezes the registry before `env` exists (which on
|
|
92
|
+
* Workers is always), defeats tree-shaking, and makes per-agent plugin
|
|
93
|
+
* selection impossible.
|
|
94
|
+
*/
|
|
95
|
+
get runtime() {
|
|
96
|
+
return (this._runtime ??= createAgentRuntime({
|
|
97
|
+
config: this.agentConfig(),
|
|
98
|
+
plugins: this.agentPlugins(this.pluginHost()),
|
|
99
|
+
// Opt in to verifying every plugin's declared bindings exist. Fails at DO
|
|
100
|
+
// start with a sentence naming the plugin, rather than at the first tool
|
|
101
|
+
// call inside a request someone is waiting on.
|
|
102
|
+
env: this.env
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
/** The resolved config. */
|
|
106
|
+
get config() {
|
|
107
|
+
return this.runtime.config;
|
|
108
|
+
}
|
|
109
|
+
/** The agent's database (drizzle + migrations), built once per DO instance. */
|
|
110
|
+
get db() {
|
|
111
|
+
return (this._db ??= new AgentDB(this.ctx.storage, {
|
|
112
|
+
maxSubtasks: this.config.maxSubtasks,
|
|
113
|
+
// Plugin-owned tables, applied after core's own migrations. A store that
|
|
114
|
+
// throws fails DO start rather than being skipped — a plugin whose tables
|
|
115
|
+
// are missing would otherwise fail at its first tool call.
|
|
116
|
+
stores: this.runtime.stores
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Which provider this agent's loops run on. Defaults to Workers AI; override
|
|
121
|
+
* to run on something else.
|
|
122
|
+
*
|
|
123
|
+
* The seam is here rather than in `models` because `models` memoizes, and a
|
|
124
|
+
* subclass overriding a memoized getter has to remember to keep the caching —
|
|
125
|
+
* a trap that only shows up as a performance bug. This is called once.
|
|
126
|
+
*
|
|
127
|
+
* `ModelRuntime` is the whole contract: return anything satisfying it and
|
|
128
|
+
* every loop in core keeps working unchanged. Core ships one implementation,
|
|
129
|
+
* {@link file://../agent/workers-ai/index.ts `agent/workers-ai`} (the default
|
|
130
|
+
* below); a second provider is one more
|
|
131
|
+
* {@link file://../agent/model.ts ModelRuntimeFactory}, defined here or in the
|
|
132
|
+
* consumer, not a change to anything on this path.
|
|
133
|
+
*
|
|
134
|
+
* Takes the resolved {@link ModelConfig} rather than reading `this.config`, so
|
|
135
|
+
* that this signature matches
|
|
136
|
+
* {@link file://../round/subagent.ts RecipeSubagentHost.modelRuntime} — an
|
|
137
|
+
* agent and its facet **must** run the same provider, and identical seams are
|
|
138
|
+
* what let one factory serve both instead of two hand-copied bodies.
|
|
139
|
+
*/
|
|
140
|
+
modelRuntime(model) {
|
|
141
|
+
return workersAIModels(this.env, model);
|
|
142
|
+
}
|
|
143
|
+
/** The model runtime for this instance, built lazily and memoized. */
|
|
144
|
+
get models() {
|
|
145
|
+
return (this._models ??= this.modelRuntime(this.config.model));
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* What this agent's plugins are handed. Built from
|
|
149
|
+
* {@link resolvedModelIds} rather than `this.config`, which would be a cycle —
|
|
150
|
+
* building the runtime is what needs these.
|
|
151
|
+
*/
|
|
152
|
+
pluginHost() {
|
|
153
|
+
const model = this.resolvedModelIds();
|
|
154
|
+
return {
|
|
155
|
+
env: this.env,
|
|
156
|
+
storage: this.ctx.storage,
|
|
157
|
+
// A thunk, not a value — see `identityKey`.
|
|
158
|
+
callerKey: () => this.requireIdentityKey(),
|
|
159
|
+
aiGatewayId: model.aiGatewayId
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* The model settings a locally-declared recipe runs on, resolved *before* the
|
|
164
|
+
* runtime exists.
|
|
165
|
+
*
|
|
166
|
+
* Deliberately not `this.config` — that would be a cycle. `resolveConfig` is
|
|
167
|
+
* cheap and pure and fills in core's baseline, so this is the same result the
|
|
168
|
+
* runtime lands on; that matters because every recipe runs on exactly this
|
|
169
|
+
* pair — `RecipePolicy` carries it and `validateRecipe` stamps it on.
|
|
170
|
+
*/
|
|
171
|
+
resolvedModelIds() {
|
|
172
|
+
return resolveConfig(this.agentConfig()).model;
|
|
173
|
+
}
|
|
174
|
+
async onStart() {
|
|
175
|
+
// Await migrations before the SDK dispatches any RPC — eliminates the race
|
|
176
|
+
// between schema creation and first query on cold start / hibernation wake-up.
|
|
177
|
+
await this.db.ensureReady();
|
|
178
|
+
// Register the weekly cleanup cron once per DO instance (idempotent guard).
|
|
179
|
+
const existing = await this.listSchedules({ type: "cron" });
|
|
180
|
+
if (!existing.some((s) => s.callback === "cleanupOldTasks")) {
|
|
181
|
+
await this.schedule("0 1 * * 0", "cleanupOldTasks", {});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Cron handler: delete task rows older than 30 days. Runs Sunday 01:00 UTC.
|
|
186
|
+
*
|
|
187
|
+
* A plugin's own tables are its business — core's journal does not reach them,
|
|
188
|
+
* and neither does this sweep. A subclass with more durable state of its own
|
|
189
|
+
* overrides {@link cleanupAgentState}.
|
|
190
|
+
*/
|
|
191
|
+
async cleanupOldTasks(_payload, _schedule) {
|
|
192
|
+
this.db.tasks.cleanup();
|
|
193
|
+
this.cleanupAgentState();
|
|
194
|
+
}
|
|
195
|
+
/** Extra durable state to age out alongside the task rows. Default: none. */
|
|
196
|
+
cleanupAgentState() { }
|
|
197
|
+
/**
|
|
198
|
+
* The main agent's primary/fallback pair. With `metadata` it builds a fresh
|
|
199
|
+
* pair carrying that AI Gateway correlation tag (so an AI Gateway log ties the call
|
|
200
|
+
* to its task and round); without it — the Session's own compaction model — it
|
|
201
|
+
* reuses a memoized default. A test `modelsOverride` always wins.
|
|
202
|
+
*/
|
|
203
|
+
modelPair(metadata) {
|
|
204
|
+
if (this.modelsOverride)
|
|
205
|
+
return this.modelsOverride;
|
|
206
|
+
if (!metadata)
|
|
207
|
+
return (this._pair ??= this.models.createModelPair());
|
|
208
|
+
return this.models.createModelPair({ metadata });
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* The one continuous Session for this caller (rebuilt from `this.sql` after
|
|
212
|
+
* eviction). Memoized — `identity` is constant for the DO's life, since the DO
|
|
213
|
+
* is keyed 1:1 by `identity.key`.
|
|
214
|
+
*
|
|
215
|
+
* `onMessagesDisplaced` is the whole integration for anything that wants the
|
|
216
|
+
* messages a compaction folds away: core performs the compaction, so core
|
|
217
|
+
* announces the loss, and the runtime fans it out to every plugin that asked.
|
|
218
|
+
*/
|
|
219
|
+
getSession(identity) {
|
|
220
|
+
this.identityKey ??= identity.key ?? undefined;
|
|
221
|
+
const { session, model } = this.config;
|
|
222
|
+
return (this.session ??= buildAgentSession(this, this.modelPair().primary(), {
|
|
223
|
+
soul: () => this.agentSoul(this.runtime.renderCapabilities()),
|
|
224
|
+
memoryDescription: session.memoryDescription,
|
|
225
|
+
memoryMaxTokens: session.memoryMaxTokens,
|
|
226
|
+
compactAfterTokens: session.compactAfterTokens,
|
|
227
|
+
compactTailTokens: session.compactTailTokens,
|
|
228
|
+
maxOutputTokens: model.maxOutputTokens,
|
|
229
|
+
onMessagesDisplaced: this.runtime.onMessagesDisplaced
|
|
230
|
+
}));
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* The caller key, which is present on every path that can reach a plugin.
|
|
234
|
+
*
|
|
235
|
+
* ## Why this does not just read the field
|
|
236
|
+
*
|
|
237
|
+
* `identityKey` is set on the first turn and lives in the isolate. An isolate
|
|
238
|
+
* does not live as long as the work does: it can be evicted between two rounds
|
|
239
|
+
* of the same task, and it can be reset outright — "Durable Object connection
|
|
240
|
+
* closed because the object was reset" — while a Workflow step is mid-flight.
|
|
241
|
+
* The next call arrives on a fresh instance where the field is empty, and
|
|
242
|
+
* every per-caller thunk built off it throws.
|
|
243
|
+
*
|
|
244
|
+
* That failure is disproportionate to its cause. A plugin asking which caller
|
|
245
|
+
* it is serving gets an exception, mid-task, on an object whose entire purpose
|
|
246
|
+
* is to be that caller's — and because the throw happens inside a tool or a
|
|
247
|
+
* runtime resolution rather than at the edge, it surfaces as a failed branch
|
|
248
|
+
* rather than as anything an operator can read.
|
|
249
|
+
*
|
|
250
|
+
* So the object answers from itself. `define-agent` routes with
|
|
251
|
+
* `ns.get(ns.idFromName(identity.key))`, which means the caller key *is* this
|
|
252
|
+
* object's name and the platform hands it back on `ctx.id.name` — durable, free
|
|
253
|
+
* and correct by construction: an object cannot disagree with the name it was
|
|
254
|
+
* addressed by.
|
|
255
|
+
*
|
|
256
|
+
* The field still wins when it is set. `id.name` is undefined for an object
|
|
257
|
+
* addressed by `newUniqueId()` or a raw id string, so it is a fallback rather
|
|
258
|
+
* than the source of truth, and the throw is kept for the case where neither
|
|
259
|
+
* exists.
|
|
260
|
+
*/
|
|
261
|
+
requireIdentityKey() {
|
|
262
|
+
const key = this.identityKey ?? this.ctx.id.name;
|
|
263
|
+
if (!key) {
|
|
264
|
+
throw new Error("identity.key is required for per-caller isolation");
|
|
265
|
+
}
|
|
266
|
+
return (this.identityKey = key);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Offer this deployment's own origin from a value that carries it. The first
|
|
270
|
+
* usable one is kept for the life of the instance.
|
|
271
|
+
*
|
|
272
|
+
* Called wherever a {@link TurnPushContext} arrives — here for every agent
|
|
273
|
+
* shape, and at the entry of `RoundAgentBase`'s two RPCs, where the origin is
|
|
274
|
+
* needed *before* this channel would be built. All three matter because any of
|
|
275
|
+
* them can be the call that wakes a fresh isolate. Cheap and unfailing: past
|
|
276
|
+
* the first turn it is one truthiness check, and an unusable value is ignored
|
|
277
|
+
* rather than thrown, because a turn must not fail over this.
|
|
278
|
+
*/
|
|
279
|
+
noteSelfOrigin(url) {
|
|
280
|
+
this.selfOriginMemo.note(url);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* This deployment's own public origin, if a turn has carried it to this
|
|
284
|
+
* instance yet. Constant once set, so it reads the same from any turn running
|
|
285
|
+
* on this object. See {@link SelfOrigin}.
|
|
286
|
+
*/
|
|
287
|
+
selfOrigin() {
|
|
288
|
+
return this.selfOriginMemo.peek();
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* The same, for a caller that cannot proceed without it — signing a caller
|
|
292
|
+
* token with {@link file://../a2a/caller-token.ts signCallerToken} above all,
|
|
293
|
+
* whose `iss` this is. Throws naming the timing rather than producing a token
|
|
294
|
+
* with a nonsense issuer.
|
|
295
|
+
*/
|
|
296
|
+
requireSelfOrigin() {
|
|
297
|
+
return this.selfOriginMemo.require();
|
|
298
|
+
}
|
|
299
|
+
/** The gatekeeper callback channel for one turn. See {@link PushChannel}. */
|
|
300
|
+
push(context) {
|
|
301
|
+
this.noteSelfOrigin(context.jku);
|
|
302
|
+
return createPushChannel(this.env.A2A_SIGNING_KEY, context);
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* The per-request system-prompt suffix describing the verified caller.
|
|
306
|
+
*
|
|
307
|
+
* A rendering of a protocol fact rather than prompt copy, so core supplies one
|
|
308
|
+
* — see {@link callerContext}. Override it to name what a workspace id means in
|
|
309
|
+
* your deployment; do not use it to say who the *user* is, which this is not.
|
|
310
|
+
*/
|
|
311
|
+
callerContext(identity) {
|
|
312
|
+
return callerContext(identity);
|
|
313
|
+
}
|
|
314
|
+
// --- Async task state (accept + notify) ----------------------------------
|
|
315
|
+
//
|
|
316
|
+
// A thin RPC surface over `AgentDB`'s `tasks` table. Native RPC methods — the
|
|
317
|
+
// DO is never a network-reachable server — called by the Workflow, which
|
|
318
|
+
// cannot touch this SQLite directly.
|
|
319
|
+
//
|
|
320
|
+
// The Task-returning methods return `PlainTask`: the SDK `Task` narrowed to
|
|
321
|
+
// what survives Cloudflare's RPC types. Returning the raw SDK `Task` breaks
|
|
322
|
+
// the generated DO-stub types (under v1.0 it blows past TypeScript's
|
|
323
|
+
// instantiation-depth limit).
|
|
324
|
+
async beginTask(input) {
|
|
325
|
+
return this.db.tasks.begin(input);
|
|
326
|
+
}
|
|
327
|
+
async getTask(taskId) {
|
|
328
|
+
return this.db.tasks.get(taskId);
|
|
329
|
+
}
|
|
330
|
+
async listTasks(query) {
|
|
331
|
+
return this.db.tasks.list(query);
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Persist a Task, returning **whether the guarded write applied**.
|
|
335
|
+
*
|
|
336
|
+
* That boolean is the cancellation check, and a caller must key its callback on
|
|
337
|
+
* it: `AgentDB` refuses to write a terminal state over a `canceled` row and
|
|
338
|
+
* does that read and write in one synchronous pass inside the DO. Probing with
|
|
339
|
+
* {@link getTask} first and saving second leaves a window — between the two
|
|
340
|
+
* calls, and again between the save and the notify — in which a cancel lands
|
|
341
|
+
* and the gatekeeper still receives a `completed` callback.
|
|
342
|
+
*
|
|
343
|
+
* A `canceled` state routes to {@link markCanceled} instead of a plain write,
|
|
344
|
+
* so a `tasks/cancel` arriving through the a2a-js TaskStore and one arriving
|
|
345
|
+
* through {@link cancelTask} converge on the same interruption path.
|
|
346
|
+
*/
|
|
347
|
+
async saveTask(task) {
|
|
348
|
+
if (stateOf(task) === TaskState.TASK_STATE_CANCELED) {
|
|
349
|
+
return (await this.markCanceled(task.id, task)) !== null;
|
|
350
|
+
}
|
|
351
|
+
return this.db.tasks.save(task);
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Move the Task to `working`. Returns `"canceled"` when the caller cancelled
|
|
355
|
+
* first — read it and stop, rather than probing with a separate
|
|
356
|
+
* {@link getTask}, which reopens the gap between asking and acting.
|
|
357
|
+
*
|
|
358
|
+
* Anything else is `"ok"`, including an unknown row and a row already `working`
|
|
359
|
+
* (a replayed step): only an actual cancellation stops the pipeline.
|
|
360
|
+
*/
|
|
361
|
+
async markWorking(taskId) {
|
|
362
|
+
return this.db.tasks.markWorking(taskId);
|
|
363
|
+
}
|
|
364
|
+
async cancelTask(taskId) {
|
|
365
|
+
return this.markCanceled(taskId);
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* The one place a Task becomes canceled: flip the row — terminal, so every
|
|
369
|
+
* non-canceled write is refused afterwards — then interrupt whatever is still
|
|
370
|
+
* running for it.
|
|
371
|
+
*
|
|
372
|
+
* `task` is supplied when the caller already built the canceled Task (the
|
|
373
|
+
* a2a-js cancel branch attaches its own status message); otherwise the row's
|
|
374
|
+
* own guarded flip produces it. Both paths are guarded against the same race:
|
|
375
|
+
* a task that already reached `completed`/`failed` refuses the write, and its
|
|
376
|
+
* verdict — not a `get` read straight after, which would return that
|
|
377
|
+
* unchanged terminal row and be mistaken for a successful cancellation — is
|
|
378
|
+
* what decides whether {@link onTaskCanceled} runs at all.
|
|
379
|
+
*/
|
|
380
|
+
async markCanceled(taskId, task) {
|
|
381
|
+
const canceled = task
|
|
382
|
+
? this.db.tasks.save(task) && this.db.tasks.get(taskId)
|
|
383
|
+
: this.db.tasks.cancel(taskId);
|
|
384
|
+
if (!canceled)
|
|
385
|
+
return null;
|
|
386
|
+
await this.onTaskCanceled(taskId);
|
|
387
|
+
return canceled;
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Interrupt work still in flight for a task that has just been canceled.
|
|
391
|
+
*
|
|
392
|
+
* Default: nothing, which is right for an agent whose turn is a single
|
|
393
|
+
* inference — the row is terminal and the next guarded write refuses. An agent
|
|
394
|
+
* with children overrides this to abort them.
|
|
395
|
+
*
|
|
396
|
+
* **Must be best-effort.** Cancellation has already been recorded by the time
|
|
397
|
+
* this runs, and it must not fail because cleanup did.
|
|
398
|
+
*/
|
|
399
|
+
async onTaskCanceled(_taskId) { }
|
|
400
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@dynamicagents/core/host` — the Durable Object half of an agent.
|
|
3
|
+
*
|
|
4
|
+
* {@link DynamicAgent} is the DO body every Dynamic Agents agent has whatever loop it
|
|
5
|
+
* runs: the runtime and database built once per instance, the one continuous
|
|
6
|
+
* Session per verified caller, the gatekeeper 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 `@dynamicagents/core/round` for the delegating
|
|
17
|
+
* round loop, which is opt-in and takes its prompt copy from the agent.
|
|
18
|
+
*/
|
|
19
|
+
export { DynamicAgent } from "./agent.js";
|
|
20
|
+
export type { PluginHost } from "./plugin-host.js";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@dynamicagents/core/host` — the Durable Object half of an agent.
|
|
3
|
+
*
|
|
4
|
+
* {@link DynamicAgent} is the DO body every Dynamic Agents agent has whatever loop it
|
|
5
|
+
* runs: the runtime and database built once per instance, the one continuous
|
|
6
|
+
* Session per verified caller, the gatekeeper 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 `@dynamicagents/core/round` for the delegating
|
|
17
|
+
* round loop, which is opt-in and takes its prompt copy from the agent.
|
|
18
|
+
*/
|
|
19
|
+
export { DynamicAgent } from "./agent.js";
|
|
@@ -0,0 +1,42 @@
|
|
|
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
|
+
* - `aiGatewayId` — the **resolved** AI Gateway slug, so a plugin making its own
|
|
27
|
+
* model calls is correlated with the agent's. Resolved, not read off the
|
|
28
|
+
* overrides object: reaching into `MY_CONFIG.model?.aiGatewayId` at a call site
|
|
29
|
+
* silently yields `undefined` the moment that override is dropped in favour of
|
|
30
|
+
* core's baseline, and the plugin's calls quietly stop being correlated.
|
|
31
|
+
*
|
|
32
|
+
* Deliberately **not** here: the agent's model pair. See the note on the
|
|
33
|
+
* interface below.
|
|
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.aiGatewayId`, already resolved over core's baseline. */
|
|
41
|
+
aiGatewayId: string;
|
|
42
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@dynamicagents/core` — the mandatory foundation for a Dynamic Agents agent.
|
|
3
|
+
*
|
|
4
|
+
* This root entry carries only what nearly every consumer touches: the plugin
|
|
5
|
+
* contract, the config shape, the runtime factory, and the platform facts.
|
|
6
|
+
* Everything else lives behind a subpath (`/a2a`, `/agent`, `/db`, `/subtasks`,
|
|
7
|
+
* `/subagent`, `/worker`, `/testing`), so importing the delegation layer does not
|
|
8
|
+
* pull in the A2A adapter and importing the test harness never reaches a
|
|
9
|
+
* production bundle.
|
|
10
|
+
*/
|
|
11
|
+
export { createAgentRuntime, RuntimeSetupError, buildRecipeTools, collectToolFamilies, type AgentRuntime, type CreateAgentRuntimeOptions } from "./runtime/index.js";
|
|
12
|
+
export { PLUGIN_CONTRACT_VERSION, definePlugin, restrictMainAgentTools, type AgentPlugin, type RestrictMainAgentToolsOptions, type EmitProgress, type EnrichResultContext, type MainAgentToolContext, type PluginRequirements, type RecipeToolSet, type ResolveRuntimeContext, type ToolFamilyBuilder, type ToolFamilyContext, type TurnGateContext } from "./contract/plugin.js";
|
|
13
|
+
export type { DelegationNames, RecipeLimits, ResolvedRecipe, SubtaskParams, SubtaskParamsSchema, SubtaskParamsShape, SubtaskTypeSpec, ValidatedRecipe } from "./contract/recipe.js";
|
|
14
|
+
export { RecipeValidationError, resolveLimits, validateRecipe, type RecipePolicy } from "./contract/validation.js";
|
|
15
|
+
export { ConfigError, DEFAULT_CORE_CONFIG, resolveConfig, type AgentLimits, type CoreConfig, type CoreConfigOverrides, type ModelConfig, type SessionConfig } from "./config.js";
|
|
16
|
+
export { parseGatekeeperOrigins, type A2ASecretsEnv, type AiEnv, type CoreEnv } from "./env.js";
|
|
17
|
+
export { CHUNK_SOFT_MS, CHUNK_STEP, MAX_CHUNKS_PER_BRANCH, MAX_TOOL_CALL_MS, STEP_TIMEOUT_MS, STEPS_PER_INSTANCE } from "./platform.js";
|
|
18
|
+
export type { PluginStore } from "./db/db.js";
|
|
19
|
+
export { makeWorkspaceHandle, memoryWorkspaceBacking, WorkspaceLimitError, WORKSPACE_MAX_FILES, WORKSPACE_MAX_FILE_BYTES, type WorkspaceBacking, type WorkspaceEntry, type WorkspaceHandle } from "./subagent/workspace.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@dynamicagents/core` — the mandatory foundation for a Dynamic Agents agent.
|
|
3
|
+
*
|
|
4
|
+
* This root entry carries only what nearly every consumer touches: the plugin
|
|
5
|
+
* contract, the config shape, the runtime factory, and the platform facts.
|
|
6
|
+
* Everything else lives behind a subpath (`/a2a`, `/agent`, `/db`, `/subtasks`,
|
|
7
|
+
* `/subagent`, `/worker`, `/testing`), so importing the delegation layer does not
|
|
8
|
+
* pull in the A2A adapter and importing the test harness never reaches a
|
|
9
|
+
* production bundle.
|
|
10
|
+
*/
|
|
11
|
+
export { createAgentRuntime, RuntimeSetupError, buildRecipeTools, collectToolFamilies } from "./runtime/index.js";
|
|
12
|
+
export { PLUGIN_CONTRACT_VERSION, definePlugin, restrictMainAgentTools } from "./contract/plugin.js";
|
|
13
|
+
export { RecipeValidationError, resolveLimits, validateRecipe } from "./contract/validation.js";
|
|
14
|
+
export { ConfigError, DEFAULT_CORE_CONFIG, resolveConfig } from "./config.js";
|
|
15
|
+
export { parseGatekeeperOrigins } from "./env.js";
|
|
16
|
+
export { CHUNK_SOFT_MS, CHUNK_STEP, MAX_CHUNKS_PER_BRANCH, MAX_TOOL_CALL_MS, STEP_TIMEOUT_MS, STEPS_PER_INSTANCE } from "./platform.js";
|
|
17
|
+
export { makeWorkspaceHandle, memoryWorkspaceBacking, WorkspaceLimitError, WORKSPACE_MAX_FILES, WORKSPACE_MAX_FILE_BYTES } from "./subagent/workspace.js";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@dynamicagents/core/job` — a long job a Durable Object owns through its alarm.
|
|
3
|
+
*
|
|
4
|
+
* **The sibling of `@dynamicagents/core/alarm`, and the pairing is the point.**
|
|
5
|
+
* `WakeMap` owns *when* an object wakes; this owns *what a job owes on waking*.
|
|
6
|
+
* Neither depends on the other's reason for existing, and both are useful to a
|
|
7
|
+
* plain `DurableObject` rather than only to a `DynamicAgent` — which is why they
|
|
8
|
+
* are subpaths and not part of the agent machinery.
|
|
9
|
+
*
|
|
10
|
+
* **Mechanism only.** Nothing here knows what a job *does*: no command, no
|
|
11
|
+
* container, no filesystem, no vendor library. A consumer supplies the handle
|
|
12
|
+
* and the meaning; this supplies the four rules that are wrong in the same way
|
|
13
|
+
* every time — arming before the work starts, one job at a time, a drain that
|
|
14
|
+
* can outlive its job, and a job nobody is draining. See {@link JobLifecycle}.
|
|
15
|
+
*
|
|
16
|
+
* Deliberately **not** called `task`. Core already has a `Task` — the A2A one,
|
|
17
|
+
* with its own lifecycle, its own guarded writes and its own table — and two
|
|
18
|
+
* unrelated meanings in one namespace is a cost paid forever by every reader.
|
|
19
|
+
*/
|
|
20
|
+
export { isRearmable, isRunning, type DoneJob, type FailedJob, type IdleJob, type JobState, type RunningJob, type SkippedJob } from "./state.js";
|
|
21
|
+
export { JobLifecycle, type JobContext, type JobHandle, type JobLifecycleOptions, type JobResult } from "./lifecycle.js";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@dynamicagents/core/job` — a long job a Durable Object owns through its alarm.
|
|
3
|
+
*
|
|
4
|
+
* **The sibling of `@dynamicagents/core/alarm`, and the pairing is the point.**
|
|
5
|
+
* `WakeMap` owns *when* an object wakes; this owns *what a job owes on waking*.
|
|
6
|
+
* Neither depends on the other's reason for existing, and both are useful to a
|
|
7
|
+
* plain `DurableObject` rather than only to a `DynamicAgent` — which is why they
|
|
8
|
+
* are subpaths and not part of the agent machinery.
|
|
9
|
+
*
|
|
10
|
+
* **Mechanism only.** Nothing here knows what a job *does*: no command, no
|
|
11
|
+
* container, no filesystem, no vendor library. A consumer supplies the handle
|
|
12
|
+
* and the meaning; this supplies the four rules that are wrong in the same way
|
|
13
|
+
* every time — arming before the work starts, one job at a time, a drain that
|
|
14
|
+
* can outlive its job, and a job nobody is draining. See {@link JobLifecycle}.
|
|
15
|
+
*
|
|
16
|
+
* Deliberately **not** called `task`. Core already has a `Task` — the A2A one,
|
|
17
|
+
* with its own lifecycle, its own guarded writes and its own table — and two
|
|
18
|
+
* unrelated meanings in one namespace is a cost paid forever by every reader.
|
|
19
|
+
*/
|
|
20
|
+
export { isRearmable, isRunning } from "./state.js";
|
|
21
|
+
export { JobLifecycle } from "./lifecycle.js";
|