@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,123 @@
|
|
|
1
|
+
import type { AgentManifest } from "../a2a/card.js";
|
|
2
|
+
import type { GatekeeperIdentity } from "../a2a/verify.js";
|
|
3
|
+
import type { TaskAgent } from "../a2a/agent-stub.js";
|
|
4
|
+
import { type AcceptedTurn } from "../a2a/executor.js";
|
|
5
|
+
/**
|
|
6
|
+
* One agent's wiring, declared once.
|
|
7
|
+
*
|
|
8
|
+
* ## What this replaces
|
|
9
|
+
*
|
|
10
|
+
* Mounting an agent used to mean writing the same four things by hand and keeping
|
|
11
|
+
* them in agreement: a `getAgent(identity)` that guards `identity.key` and reads a
|
|
12
|
+
* Durable Object binding, a `startTurn` that derives an idempotent workflow id and
|
|
13
|
+
* swallows the already-exists race, an entry in the `tenants` map, and — in the
|
|
14
|
+
* agent's own Workflow entrypoint — a second reference to the same DO binding.
|
|
15
|
+
*
|
|
16
|
+
* Four copies of one fact, and only the first two were even adjacent. The wiring
|
|
17
|
+
* also type-checked when it was **wrong**: naming a sibling's workflow binding on
|
|
18
|
+
* a tenant compiles perfectly and fails at runtime, after auth, after the turn was
|
|
19
|
+
* accepted, as a task that simply never calls back.
|
|
20
|
+
*
|
|
21
|
+
* ```ts
|
|
22
|
+
* export const reactive = defineAgent({
|
|
23
|
+
* tenant: "reactive",
|
|
24
|
+
* manifest,
|
|
25
|
+
* agent: (env: Env) => env.ReactiveAgent,
|
|
26
|
+
* workflow: (env: Env) => env.HANDLE_TASK_WORKFLOW
|
|
27
|
+
* });
|
|
28
|
+
*
|
|
29
|
+
* // the Worker entry
|
|
30
|
+
* export default {
|
|
31
|
+
* fetch: createA2AWorker<Env>({ manifest: hostManifest, agents: [reactive] })
|
|
32
|
+
* };
|
|
33
|
+
*
|
|
34
|
+
* // the agent's Workflow entrypoint
|
|
35
|
+
* await runHandleTask(event.payload, step, {
|
|
36
|
+
* resolveAgent: (identity) => reactive.resolveAgent(this.env, identity),
|
|
37
|
+
* …
|
|
38
|
+
* });
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* ## Why accessors rather than binding names
|
|
42
|
+
*
|
|
43
|
+
* `agent` and `workflow` are functions of `env`, not strings. A string would have
|
|
44
|
+
* to be checked against `keyof Env` and then widened back to a namespace, which
|
|
45
|
+
* loses the Durable Object's own class — so the Workflow could not see the very
|
|
46
|
+
* methods it exists to drive. An accessor infers both: the tenant is mounted and
|
|
47
|
+
* the Workflow is driven from one declaration, `env.RactiveAgent` is a compile
|
|
48
|
+
* error, and `resolveAgent` comes back typed as the agent itself.
|
|
49
|
+
*
|
|
50
|
+
* `env` stays a parameter throughout, for the reason `src/env.ts` gives: core
|
|
51
|
+
* never reaches for a consumer's ambient bindings, and on Workers `env` does not
|
|
52
|
+
* exist at module scope anyway.
|
|
53
|
+
*/
|
|
54
|
+
/**
|
|
55
|
+
* What `createA2AWorker` needs in order to mount an agent. The Durable Object's
|
|
56
|
+
* own class is irrelevant to routing, so this shape forgets it —
|
|
57
|
+
* {@link AgentDefinition} keeps it for the agent's Workflow.
|
|
58
|
+
*/
|
|
59
|
+
export interface MountedAgent<TEnv> {
|
|
60
|
+
/** The tenant id a caller addresses this agent with. */
|
|
61
|
+
tenant: string;
|
|
62
|
+
/** The transport-independent half of this agent's card. */
|
|
63
|
+
manifest: AgentManifest;
|
|
64
|
+
resolveAgent(env: TEnv, identity: GatekeeperIdentity): TaskAgent;
|
|
65
|
+
startTurn(env: TEnv, turn: AcceptedTurn): Promise<void>;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* {@link AgentDefinition} deliberately does **not** declare `extends
|
|
69
|
+
* MountedAgent`, even though every one satisfies it.
|
|
70
|
+
*
|
|
71
|
+
* A generic `DurableObjectStub<TAgent>` cannot be proven assignable to
|
|
72
|
+
* {@link TaskAgent} while `TAgent` is unresolved: the RPC stub type widens an
|
|
73
|
+
* *optional* method (`listTasks?`) to `Promise<undefined> | (…) => …`, and
|
|
74
|
+
* forcing the comparison also trips TypeScript's instantiation-depth limit — the
|
|
75
|
+
* same wall that made `PlainTask` necessary. At a concrete call site, where
|
|
76
|
+
* `TAgent` is a real agent class, the assignment resolves normally. So the check
|
|
77
|
+
* happens where it can actually succeed: passing `agents: [reactive]` to
|
|
78
|
+
* `createA2AWorker`.
|
|
79
|
+
*/
|
|
80
|
+
export interface DefineAgentOptions<TEnv, TAgent extends Rpc.DurableObjectBranded> {
|
|
81
|
+
/**
|
|
82
|
+
* The tenant id a caller addresses this agent with, and what a gatekeeper
|
|
83
|
+
* registers against. Renaming one is a re-registration, not a refactor.
|
|
84
|
+
*/
|
|
85
|
+
tenant: string;
|
|
86
|
+
/** The transport-independent half of this agent's card. */
|
|
87
|
+
manifest: AgentManifest;
|
|
88
|
+
/**
|
|
89
|
+
* This agent's Durable Object namespace. One instance per verified caller —
|
|
90
|
+
* the tenant picks the agent, `identity.key` picks which instance of it.
|
|
91
|
+
*/
|
|
92
|
+
agent: (env: TEnv) => DurableObjectNamespace<TAgent>;
|
|
93
|
+
/** The Workflow this agent's turns run on. */
|
|
94
|
+
workflow: (env: TEnv) => Workflow<AcceptedTurn>;
|
|
95
|
+
}
|
|
96
|
+
export interface AgentDefinition<TEnv, TAgent extends Rpc.DurableObjectBranded> {
|
|
97
|
+
/** The tenant id a caller addresses this agent with. */
|
|
98
|
+
tenant: string;
|
|
99
|
+
/** The transport-independent half of this agent's card. */
|
|
100
|
+
manifest: AgentManifest;
|
|
101
|
+
/**
|
|
102
|
+
* Resolve the per-caller agent DO stub, keyed by the verified `identity.key`.
|
|
103
|
+
*
|
|
104
|
+
* Typed as the agent's **own** class, so the Workflow that drives its round
|
|
105
|
+
* methods reaches them through the same declaration the tenant is mounted with
|
|
106
|
+
* — the two cannot address different Durable Objects.
|
|
107
|
+
*
|
|
108
|
+
* Refuses a caller with no key rather than falling back to a shared instance:
|
|
109
|
+
* that key is what makes one caller's tasks unreachable from another's, so a
|
|
110
|
+
* missing one is a routing failure, not a default.
|
|
111
|
+
*/
|
|
112
|
+
resolveAgent(env: TEnv, identity: GatekeeperIdentity): DurableObjectStub<TAgent>;
|
|
113
|
+
/**
|
|
114
|
+
* Start this agent's durable turn, idempotently.
|
|
115
|
+
*
|
|
116
|
+
* The instance id is derived from the gatekeeper's `messageId`, which is stable
|
|
117
|
+
* across dispatch retries — so a retry finding its instance already running is
|
|
118
|
+
* the idempotency working, not a failure. {@link ignoreAlreadyExists} swallows
|
|
119
|
+
* exactly that race and rethrows everything else.
|
|
120
|
+
*/
|
|
121
|
+
startTurn(env: TEnv, turn: AcceptedTurn): Promise<void>;
|
|
122
|
+
}
|
|
123
|
+
export declare function defineAgent<TEnv, TAgent extends Rpc.DurableObjectBranded>(options: DefineAgentOptions<TEnv, TAgent>): AgentDefinition<TEnv, TAgent>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ignoreAlreadyExists, workflowIdForMessage } from "../a2a/executor.js";
|
|
2
|
+
export function defineAgent(options) {
|
|
3
|
+
return {
|
|
4
|
+
tenant: options.tenant,
|
|
5
|
+
manifest: options.manifest,
|
|
6
|
+
resolveAgent(env, identity) {
|
|
7
|
+
if (!identity.key) {
|
|
8
|
+
throw new Error("identity.key is required to route to the agent DO");
|
|
9
|
+
}
|
|
10
|
+
const ns = options.agent(env);
|
|
11
|
+
return ns.get(ns.idFromName(identity.key));
|
|
12
|
+
},
|
|
13
|
+
startTurn(env, turn) {
|
|
14
|
+
return ignoreAlreadyExists(() => options.workflow(env).create({
|
|
15
|
+
id: workflowIdForMessage(turn.messageId),
|
|
16
|
+
params: { ...turn }
|
|
17
|
+
}));
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { type AgentManifest } from "../a2a/card.js";
|
|
2
|
+
import { type TurnStarter } from "../a2a/executor.js";
|
|
3
|
+
import type { AgentResolver } from "../a2a/agent-stub.js";
|
|
4
|
+
import { type A2ASecretsEnv } from "../env.js";
|
|
5
|
+
import type { MountedAgent } from "./define-agent.js";
|
|
6
|
+
/**
|
|
7
|
+
* The A2A Worker: the zero-trust, no-shared-secrets edge every Dynamic Agents agent
|
|
8
|
+
* puts in front of its Durable Object.
|
|
9
|
+
*
|
|
10
|
+
* Trust flows entirely on domains and asymmetric (Ed25519) signatures over
|
|
11
|
+
* public JWKS: a gatekeeper verifies and pins this agent's identity from its signed
|
|
12
|
+
* card at registration ("G knows R"), and this Worker verifies the gatekeeper's
|
|
13
|
+
* identity JWT on every JSON-RPC call ("R knows G"). No secret is ever shared in
|
|
14
|
+
* either direction.
|
|
15
|
+
*
|
|
16
|
+
* **Several agents share one Worker, addressed by `tenant`.** They share one
|
|
17
|
+
* origin, one endpoint, one signing key and one card at the well-known path;
|
|
18
|
+
* what differs is the tenant id on every request. That is the A2A mechanism for
|
|
19
|
+
* exactly this — `AgentInterface.tenant` is "an opaque string used for routing
|
|
20
|
+
* requests to a specific agent or tenant when multiple agents are served behind
|
|
21
|
+
* a single A2A endpoint", and §8.3.2 requires a client to send the value the
|
|
22
|
+
* selected interface declared.
|
|
23
|
+
*
|
|
24
|
+
* A tenant is **required on every request**. There is no default and no
|
|
25
|
+
* implicit routing: an absent tenant is an error, because guessing one would
|
|
26
|
+
* mean picking an agent the caller never named.
|
|
27
|
+
*
|
|
28
|
+
* This replaces mounting a handler per path prefix. That could not work: the
|
|
29
|
+
* card is a **well-known URI**, which RFC 8615 defines per-authority, so only
|
|
30
|
+
* one card per origin is discoverable at the registered path — a gatekeeper
|
|
31
|
+
* resolving `…/.well-known/agent-card.json` against the origin found whichever
|
|
32
|
+
* agent owned the bare path and pinned *its* key for all of them.
|
|
33
|
+
*
|
|
34
|
+
* Three routes:
|
|
35
|
+
*
|
|
36
|
+
* 1. `GET jwksPath` — the card-signing public JWKS, resolving every card's
|
|
37
|
+
* `jku`.
|
|
38
|
+
* 2. `GET …/.well-known/agent-card.json` — the signed **stub** card for the
|
|
39
|
+
* origin. Matched by suffix, since that path is fixed by the spec.
|
|
40
|
+
* 3. `POST rpcPath` — gatekeeper-authenticated JSON-RPC, routed to the named
|
|
41
|
+
* tenant. `GetExtendedAgentCard` returns that tenant's own signed card;
|
|
42
|
+
* everything else runs a turn against its Durable Object.
|
|
43
|
+
*
|
|
44
|
+
* Two independent checks keep one tenant's traffic out of another's:
|
|
45
|
+
* `audience` proves the token was minted for this deployment, and
|
|
46
|
+
* {@link file://../a2a/verify.ts TENANT_CLAIM} proves it was minted for *this
|
|
47
|
+
* agent on it*. The second is what makes `tenant` more than an unauthenticated
|
|
48
|
+
* routing hint in the request body.
|
|
49
|
+
*/
|
|
50
|
+
export { defineAgent, type AgentDefinition, type DefineAgentOptions, type MountedAgent } from "./define-agent.js";
|
|
51
|
+
/**
|
|
52
|
+
* Default path serving the card-signing public JWKS (the card's `jku`).
|
|
53
|
+
*
|
|
54
|
+
* From `@dynamicagents/g2a-protocol`, because the gatekeeper serves its own JWKS at
|
|
55
|
+
* the same path and fetches ours from whatever `jku` says — see
|
|
56
|
+
* {@link file://../a2a/verify.ts} for why the two sides share a package rather
|
|
57
|
+
* than a comment. Re-exported so this stays importable from
|
|
58
|
+
* `@dynamicagents/core/worker`, where it has always lived.
|
|
59
|
+
*/
|
|
60
|
+
export { JWKS_PATH } from "@dynamicagents/g2a-protocol";
|
|
61
|
+
/** The two secrets a mount signs and verifies with, already read off `env`. */
|
|
62
|
+
export interface A2ASecrets {
|
|
63
|
+
/** Ed25519 private JWK, as JSON. See {@link A2ASecretsEnv.A2A_SIGNING_KEY}. */
|
|
64
|
+
signingKey: string;
|
|
65
|
+
/** Origin allowlist. See {@link A2ASecretsEnv.GATEKEEPER_ORIGINS}. */
|
|
66
|
+
gatekeeperOrigins: string;
|
|
67
|
+
}
|
|
68
|
+
/** One agent served on this origin — everything that differs between tenants. */
|
|
69
|
+
export interface TenantAgent {
|
|
70
|
+
/** The transport-independent half of this agent's card. */
|
|
71
|
+
manifest: AgentManifest;
|
|
72
|
+
/** Resolve the agent DO stub for a verified caller. */
|
|
73
|
+
resolveAgent: AgentResolver;
|
|
74
|
+
/** Start the durable turn. Must be idempotent — see {@link TurnStarter}. */
|
|
75
|
+
startTurn: TurnStarter;
|
|
76
|
+
}
|
|
77
|
+
export interface A2AWorkerOptions<TEnv = A2ASecretsEnv> {
|
|
78
|
+
/**
|
|
79
|
+
* The **stub** card served at the well-known path.
|
|
80
|
+
*
|
|
81
|
+
* It describes the origin, not an agent. Every agent here is a tenant and its
|
|
82
|
+
* card is reached through `GetExtendedAgentCard`, so this card exists to be
|
|
83
|
+
* the one conformant, signed AgentCard at the URI RFC 8615 and the A2A IANA
|
|
84
|
+
* registration reserve — advertising the endpoint, the protocol binding and
|
|
85
|
+
* `extendedAgentCard`.
|
|
86
|
+
*
|
|
87
|
+
* It cannot enumerate the tenants: a card carries one interface entry and
|
|
88
|
+
* spec §8.3.2 has clients take the first, so listing siblings there would
|
|
89
|
+
* just make every client address the same one. Put their names in
|
|
90
|
+
* `description` for a human, and register them out of band.
|
|
91
|
+
*/
|
|
92
|
+
manifest: AgentManifest;
|
|
93
|
+
/**
|
|
94
|
+
* The agents on this origin, keyed by tenant id.
|
|
95
|
+
*
|
|
96
|
+
* The id is opaque to the protocol — "an opaque string used for routing
|
|
97
|
+
* requests to a specific agent or tenant when multiple agents are served
|
|
98
|
+
* behind a single A2A endpoint". A caller names one on every request and this
|
|
99
|
+
* Worker refuses a request that does not.
|
|
100
|
+
*
|
|
101
|
+
* The tenant chooses *which agent*; the verified `identity.key` still chooses
|
|
102
|
+
* *which instance of it*, so two callers of one tenant stay in separate
|
|
103
|
+
* Durable Objects exactly as before.
|
|
104
|
+
*
|
|
105
|
+
* The escape hatch, and still fully supported: use it for an agent whose
|
|
106
|
+
* routing or turn-start is genuinely its own. {@link A2AWorkerOptions.agents}
|
|
107
|
+
* is the shorter form for the ordinary case. Supply at least one of the two.
|
|
108
|
+
*/
|
|
109
|
+
tenants?: Record<string, TenantAgent>;
|
|
110
|
+
/**
|
|
111
|
+
* The agents on this origin, each declared once with
|
|
112
|
+
* {@link file://./define-agent.ts defineAgent}.
|
|
113
|
+
*
|
|
114
|
+
* Prefer this to {@link A2AWorkerOptions.tenants}: one declaration produces the
|
|
115
|
+
* tenant entry here *and* the resolver the agent's Workflow entrypoint uses, so
|
|
116
|
+
* the two cannot drift, and the binding names are type-constrained to bindings
|
|
117
|
+
* of the right kind. Merged over `tenants`, which is checked for duplicate
|
|
118
|
+
* tenant ids rather than silently overriding.
|
|
119
|
+
*/
|
|
120
|
+
agents?: readonly MountedAgent<TEnv>[];
|
|
121
|
+
/** Path serving the public JWKS. Defaults to {@link JWKS_PATH}. */
|
|
122
|
+
jwksPath?: string;
|
|
123
|
+
/** Path this agent answers JSON-RPC on. Defaults to `/a2a`. */
|
|
124
|
+
rpcPath?: string;
|
|
125
|
+
/**
|
|
126
|
+
* Where to read this deployment's two secrets. Defaults to the documented
|
|
127
|
+
* names, `env.A2A_SIGNING_KEY` and `env.GATEKEEPER_ORIGINS`.
|
|
128
|
+
*
|
|
129
|
+
* There is **one signing key per origin**, not one per tenant: the card is
|
|
130
|
+
* per-origin now, so the key the gatekeeper pins is too. Nothing was lost — the
|
|
131
|
+
* tenants share a Worker and an `env`, so they could always read each other's
|
|
132
|
+
* secrets, and separate keys never expressed a boundary that existed.
|
|
133
|
+
*
|
|
134
|
+
* ```ts
|
|
135
|
+
* secrets: (env) => ({
|
|
136
|
+
* signingKey: env.AGENT_KEY,
|
|
137
|
+
* gatekeeperOrigins: env.ALLOWED_GATEKEEPERS
|
|
138
|
+
* })
|
|
139
|
+
* ```
|
|
140
|
+
*
|
|
141
|
+
* Renaming does not weaken anything: the same key is still Ed25519, still
|
|
142
|
+
* signs the cards and every callback JWT, and its public half is still what
|
|
143
|
+
* the gatekeeper pins. Only where it is read from changes.
|
|
144
|
+
*/
|
|
145
|
+
secrets?: (env: TEnv) => A2ASecrets;
|
|
146
|
+
/**
|
|
147
|
+
* The audience a gatekeeper JWT must carry.
|
|
148
|
+
*
|
|
149
|
+
* Defaults to this deployment's **own endpoint** — `${origin}${rpcPath}`, the
|
|
150
|
+
* same URL its cards advertise as their interface — and that default is
|
|
151
|
+
* almost certainly what you want. Setting this is for a gatekeeper that mints
|
|
152
|
+
* something else.
|
|
153
|
+
*
|
|
154
|
+
* The audience is one half of a two-sided contract: whatever is required here
|
|
155
|
+
* has to be exactly what the calling gatekeeper *mints*, and a mismatch is a 401
|
|
156
|
+
* on every request. slack-gatekeeper mints
|
|
157
|
+
* `new URL(agent.a2aEndpoint).origin + pathname`, which is what this default
|
|
158
|
+
* matches.
|
|
159
|
+
*
|
|
160
|
+
* It proves the token was minted for **this deployment**, and deliberately
|
|
161
|
+
* says nothing about which agent on it — every tenant shares one endpoint and
|
|
162
|
+
* therefore one audience. {@link file://../a2a/verify.ts TENANT_CLAIM} is what
|
|
163
|
+
* separates them, and is the check to look at when reasoning about one agent
|
|
164
|
+
* spending another's token.
|
|
165
|
+
*/
|
|
166
|
+
audience?: string | ((url: URL) => string);
|
|
167
|
+
/**
|
|
168
|
+
* Advertise `securitySchemes` on the card. **Defaults to `false`** — see
|
|
169
|
+
* `BuildCardOptions.advertiseSecuritySchemes` for why, and read that note
|
|
170
|
+
* before turning it on.
|
|
171
|
+
*/
|
|
172
|
+
advertiseSecuritySchemes?: boolean;
|
|
173
|
+
/** Claim carrying the caller identity. Defaults to the Dynamic Agents namespace. */
|
|
174
|
+
identityClaim?: string;
|
|
175
|
+
/**
|
|
176
|
+
* Claim carrying the authorized tenant. Defaults to the Dynamic Agents namespace.
|
|
177
|
+
*
|
|
178
|
+
* Both claim names are one side of the same contract as `audience`: whatever
|
|
179
|
+
* is named here has to be exactly what the calling gatekeeper *mints*. Override
|
|
180
|
+
* them together, or not at all — a deployment fronted by something other than
|
|
181
|
+
* slack-gatekeeper that renames only the identity claim keeps reading the
|
|
182
|
+
* tenant from a key its gatekeeper never sets, and every request 401s on the
|
|
183
|
+
* empty-tenant comparison below.
|
|
184
|
+
*/
|
|
185
|
+
tenantClaim?: string;
|
|
186
|
+
/**
|
|
187
|
+
* Require a `taskPushNotificationConfig` on every `SendMessage`.
|
|
188
|
+
*
|
|
189
|
+
* Defaults to `true`, which is the accept-and-notify contract: the agent
|
|
190
|
+
* accepts synchronously and delivers out of band, so a send with nowhere to
|
|
191
|
+
* call back is a request that can never be answered. Set `false` only for an
|
|
192
|
+
* agent that replies inline.
|
|
193
|
+
*/
|
|
194
|
+
requirePushConfig?: boolean;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Build the Worker `fetch` handler.
|
|
198
|
+
*
|
|
199
|
+
* ```ts
|
|
200
|
+
* const handler = createA2AWorker({
|
|
201
|
+
* manifest: hostManifest,
|
|
202
|
+
* tenants: {
|
|
203
|
+
* reactive: { manifest, resolveAgent: getAgent, startTurn }
|
|
204
|
+
* }
|
|
205
|
+
* });
|
|
206
|
+
* export default { fetch: handler } satisfies ExportedHandler<Env>;
|
|
207
|
+
* ```
|
|
208
|
+
*
|
|
209
|
+
* Two overloads, because `secrets` is optional for exactly one shape of `env`.
|
|
210
|
+
* An `env` carrying the two documented names needs no reader; anything else has
|
|
211
|
+
* to say where its keys live, and the type system is where that gets enforced —
|
|
212
|
+
* the alternative is a `parsePrivateJwk` failure on the first request, which is
|
|
213
|
+
* both later and much harder to read.
|
|
214
|
+
*/
|
|
215
|
+
export declare function createA2AWorker<TEnv extends A2ASecretsEnv>(options: A2AWorkerOptions<TEnv>): (request: Request, env: TEnv) => Promise<Response>;
|
|
216
|
+
export declare function createA2AWorker<TEnv extends object>(options: A2AWorkerOptions<TEnv> & {
|
|
217
|
+
secrets: (env: TEnv) => A2ASecrets;
|
|
218
|
+
}): (request: Request, env: TEnv) => Promise<Response>;
|