@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
package/README.md
CHANGED
|
@@ -147,39 +147,71 @@ wildcard.
|
|
|
147
147
|
> interoperate across this change in either direction, so they deploy together and
|
|
148
148
|
> registered agents are re-registered.
|
|
149
149
|
|
|
150
|
-
### 3.
|
|
150
|
+
### 3. Write your Durable Object
|
|
151
151
|
|
|
152
|
-
|
|
153
|
-
instance,
|
|
152
|
+
`LoopingAgent` is the DO body every agent has: the runtime and database built once
|
|
153
|
+
per instance, one continuous Session per verified caller, the gateway callback
|
|
154
|
+
channel, and the task lifecycle a Workflow drives. Three seams are yours.
|
|
154
155
|
|
|
155
156
|
```ts
|
|
156
|
-
import {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
157
|
+
import { LoopingAgent, type PluginHost } from "@loopingai/core/host";
|
|
158
|
+
|
|
159
|
+
export class MyAgent extends LoopingAgent<Env> {
|
|
160
|
+
protected agentConfig() {
|
|
161
|
+
return {
|
|
162
|
+
model: {
|
|
163
|
+
chatModelId: "@cf/zai-org/glm-5.2",
|
|
164
|
+
fallbackChatModelId: "@cf/meta/llama-4-scout-17b-16e-instruct"
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
protected agentPlugins(host: PluginHost<Env>) {
|
|
169
|
+
return [scraper({ apiKey: host.env.SCRAPER_API_KEY })];
|
|
170
|
+
}
|
|
171
|
+
protected agentSoul(capabilities: string) {
|
|
172
|
+
return soulPrompt(capabilities);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Everything that would otherwise be a module-level constant is resolved from those,
|
|
178
|
+
once per instance. Resolving a registry at _import_ time is the one thing this
|
|
179
|
+
package exists to prevent: it freezes the registry before `env` exists (which on
|
|
180
|
+
Workers is always), defeats tree-shaking, and makes runtime plugin selection
|
|
181
|
+
impossible.
|
|
182
|
+
|
|
183
|
+
`createAgentRuntime` and `AgentDB` are still exported and still work on a bare
|
|
184
|
+
`Agent<Env>` — but everything the base class does is lifecycle with an ordering
|
|
185
|
+
that is load-bearing and invisible (migrations awaited before the first RPC, the
|
|
186
|
+
guarded terminal write, the cancellation verdict that must be read and not
|
|
187
|
+
probed for), and hand-rolling it is how two agents in one repo drift apart.
|
|
188
|
+
|
|
189
|
+
### 4. Delegate, if your agent delegates
|
|
190
|
+
|
|
191
|
+
`@loopingai/core/round` adds the other half: a durable Subtask DAG, wave
|
|
192
|
+
scheduling, isolated subagent execution, and the round loop over them.
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
import { RoundAgentBase, type RoundPolicy } from "@loopingai/core/round";
|
|
196
|
+
|
|
197
|
+
export class MyAgent extends RoundAgentBase<Env> {
|
|
198
|
+
// …the three seams above, plus:
|
|
199
|
+
protected roundPolicy(): RoundPolicy {
|
|
200
|
+
return policy;
|
|
201
|
+
}
|
|
202
|
+
protected subagentClass() {
|
|
203
|
+
return MySubagent;
|
|
176
204
|
}
|
|
177
205
|
}
|
|
178
206
|
```
|
|
179
207
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
208
|
+
#### The round policy
|
|
209
|
+
|
|
210
|
+
Core ships the machine and none of the words. `RoundPolicy` is every string the
|
|
211
|
+
loop emits — the round contract the model is held to, the note appended when the
|
|
212
|
+
budget is spent, and the three user-facing messages. Nothing has a default: a
|
|
213
|
+
lent-out round contract is exactly the house prompt copy this package refuses to
|
|
214
|
+
have.
|
|
183
215
|
|
|
184
216
|
---
|
|
185
217
|
|
|
@@ -194,6 +226,8 @@ not drag in the A2A adapter, and the test harness cannot reach a production bund
|
|
|
194
226
|
| `@loopingai/core/a2a` | card signing, JWKS, gateway-JWT verify, push notify, task store, executor |
|
|
195
227
|
| `@loopingai/core/worker` | `createA2AWorker()` — the whole zero-trust edge |
|
|
196
228
|
| `@loopingai/core/agent` | session, history, model runtime, inference, budget, control tools |
|
|
229
|
+
| `@loopingai/core/host` | `LoopingAgent` — the Durable Object body — and `PluginHost` |
|
|
230
|
+
| `@loopingai/core/round` | the delegating round loop: `RoundAgentBase`, `runHandleTask`, `runTurn` |
|
|
197
231
|
| `@loopingai/core/subtasks` | delegation types, decomposition, the `delegate` tool, wave scheduler |
|
|
198
232
|
| `@loopingai/core/subagent` | `RecipeSubagentBase`, resumable runs, fingerprinting, workspace |
|
|
199
233
|
| `@loopingai/core/db` | `AgentDB`, `notify_tasks` + `subtasks` schema, migrations, `PluginStore` |
|
|
@@ -388,13 +422,34 @@ await withDb("accepts a turn once", async (db) => {
|
|
|
388
422
|
(a scripted `LanguageModel`), so a loop can be driven with no model call at all.
|
|
389
423
|
- **Fixtures** — Ed25519 keypairs and a gateway-JWT signer, so the zero-trust path is
|
|
390
424
|
exercisable end to end without a real gateway.
|
|
425
|
+
- **`createAgentHarness`** — the assembly of all of the above: send one A2A turn the
|
|
426
|
+
way a gateway does, and capture what comes back.
|
|
427
|
+
|
|
428
|
+
```ts
|
|
429
|
+
const harness = createAgentHarness({ worker, env, tenant: "reactive" });
|
|
430
|
+
using _ = harness.interceptGateway();
|
|
431
|
+
|
|
432
|
+
const accepted = await harness.send("what's the weather?");
|
|
433
|
+
expect(accepted.status.state).toBe(TaskState.TASK_STATE_SUBMITTED);
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
It exists because the pieces above were never the hard part. The audience is the
|
|
437
|
+
**endpoint**, not the origin; the tenant claim has to match the tenant in the
|
|
438
|
+
body; `SendMessage` is refused without a push config; and the gateway's JWKS has
|
|
439
|
+
to be reachable or every spec below it reports a 401 about something else. Four
|
|
440
|
+
facts, wrong the first time in every consumer that wrote this by hand.
|
|
391
441
|
|
|
392
442
|
---
|
|
393
443
|
|
|
394
444
|
## What core deliberately does _not_ contain
|
|
395
445
|
|
|
396
|
-
-
|
|
397
|
-
|
|
446
|
+
- **Prompt copy of any kind.** Not a soul, not a round contract, not a user-facing
|
|
447
|
+
failure message. `@loopingai/core/round` ships the whole delegating loop but takes
|
|
448
|
+
every word it says from a [`RoundPolicy`](#the-round-policy) you write, because a
|
|
449
|
+
run must never execute under an identity nobody chose.
|
|
450
|
+
- **A loop you cannot replace.** `/round` is opt-in and its own subpath. An agent
|
|
451
|
+
whose turn is a single inference extends `LoopingAgent` directly, writes its own
|
|
452
|
+
loop, and carries none of the delegation machinery in its bundle.
|
|
398
453
|
- The main agent's soul. Core ships no prompt copy.
|
|
399
454
|
- Config _values_ — model ids, budgets, limits. Core ships the shapes and safe
|
|
400
455
|
defaults, and `resolveConfig` validates your overrides.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { GatewayIdentity } from "./verify.js";
|
|
2
|
+
/**
|
|
3
|
+
* Per-request system-prompt suffix describing the verified calling gateway-agent
|
|
4
|
+
* instance, from the gateway identity JWT.
|
|
5
|
+
*
|
|
6
|
+
* Core ships this and not a soul, and the line between them is the point: a soul
|
|
7
|
+
* is prompt copy expressing who an agent *is*, which nobody else can write for
|
|
8
|
+
* you. This is a **rendering of a protocol fact** — who the gateway proved it was
|
|
9
|
+
* — and that fact has one correct rendering. Two agents disagreeing about how to
|
|
10
|
+
* describe their caller would be a bug, not a personality. It is the same
|
|
11
|
+
* category as `renderSubagentPrompt`'s budget section, which core also owns.
|
|
12
|
+
*
|
|
13
|
+
* **Advisory context only.** This is the calling *agent instance*, not the human
|
|
14
|
+
* on the other end of it, so an agent's soul must never let the model read it as
|
|
15
|
+
* "who you're talking to" — point the model at whatever speaker wrapper the
|
|
16
|
+
* gateway applies instead.
|
|
17
|
+
*
|
|
18
|
+
* Deliberately neutral about what a workspace is: `GatewayIdentity.workspaceId`
|
|
19
|
+
* is whatever the calling gateway partitions by, and core does not know that it
|
|
20
|
+
* is a Slack team. Override `LoopingAgent.callerContext` for a deployment that
|
|
21
|
+
* wants to name it.
|
|
22
|
+
*/
|
|
23
|
+
export declare function callerContext(identity: GatewayIdentity): string;
|
|
24
|
+
//# sourceMappingURL=caller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"caller.d.ts","sourceRoot":"","sources":["../../src/a2a/caller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,CAW/D"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-request system-prompt suffix describing the verified calling gateway-agent
|
|
3
|
+
* instance, from the gateway identity JWT.
|
|
4
|
+
*
|
|
5
|
+
* Core ships this and not a soul, and the line between them is the point: a soul
|
|
6
|
+
* is prompt copy expressing who an agent *is*, which nobody else can write for
|
|
7
|
+
* you. This is a **rendering of a protocol fact** — who the gateway proved it was
|
|
8
|
+
* — and that fact has one correct rendering. Two agents disagreeing about how to
|
|
9
|
+
* describe their caller would be a bug, not a personality. It is the same
|
|
10
|
+
* category as `renderSubagentPrompt`'s budget section, which core also owns.
|
|
11
|
+
*
|
|
12
|
+
* **Advisory context only.** This is the calling *agent instance*, not the human
|
|
13
|
+
* on the other end of it, so an agent's soul must never let the model read it as
|
|
14
|
+
* "who you're talking to" — point the model at whatever speaker wrapper the
|
|
15
|
+
* gateway applies instead.
|
|
16
|
+
*
|
|
17
|
+
* Deliberately neutral about what a workspace is: `GatewayIdentity.workspaceId`
|
|
18
|
+
* is whatever the calling gateway partitions by, and core does not know that it
|
|
19
|
+
* is a Slack team. Override `LoopingAgent.callerContext` for a deployment that
|
|
20
|
+
* wants to name it.
|
|
21
|
+
*/
|
|
22
|
+
export function callerContext(identity) {
|
|
23
|
+
const label = identity.name ?? identity.key;
|
|
24
|
+
if (!label) {
|
|
25
|
+
return "\n\nCalling agent instance: unknown (the gateway did not include an agent identity).";
|
|
26
|
+
}
|
|
27
|
+
const withKind = identity.kind ? `${label} (${identity.kind})` : label;
|
|
28
|
+
const lines = ["", "", `Calling agent instance: ${withKind}.`];
|
|
29
|
+
if (identity.workspaceId != null) {
|
|
30
|
+
lines.push(`Calling workspace: ${identity.workspaceId}.`);
|
|
31
|
+
}
|
|
32
|
+
return lines.join("\n");
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=caller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"caller.js","sourceRoot":"","sources":["../../src/a2a/caller.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,aAAa,CAAC,QAAyB;IACrD,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,IAAI,QAAQ,CAAC,GAAG,CAAC;IAC5C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,sFAAsF,CAAC;IAChG,CAAC;IACD,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,KAAK,KAAK,QAAQ,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC;IACvE,MAAM,KAAK,GAAG,CAAC,EAAE,EAAE,EAAE,EAAE,2BAA2B,QAAQ,GAAG,CAAC,CAAC;IAC/D,IAAI,QAAQ,CAAC,WAAW,IAAI,IAAI,EAAE,CAAC;QACjC,KAAK,CAAC,IAAI,CAAC,sBAAsB,QAAQ,CAAC,WAAW,GAAG,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
|
package/dist/a2a/card.d.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { JWK } from "jose";
|
|
2
2
|
import { AgentCard } from "@a2a-js/sdk";
|
|
3
|
-
/**
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* The card signature uses the one algorithm every Looping A2A signature uses,
|
|
5
|
+
* and the default RPC path is the one the gateway also knows. Both come from
|
|
6
|
+
* `@loopingai/a2a-protocol` — see {@link file://./verify.ts} for why that is a
|
|
7
|
+
* separate package rather than a constant declared on each side.
|
|
8
|
+
*/
|
|
9
|
+
export { A2A_RPC_PATH } from "@loopingai/a2a-protocol";
|
|
5
10
|
/**
|
|
6
11
|
* The transport-independent half of an {@link AgentCard} — everything that does
|
|
7
12
|
* not depend on the request origin. {@link buildBaseCard} adds
|
package/dist/a2a/card.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"card.d.ts","sourceRoot":"","sources":["../../src/a2a/card.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC;AAChC,OAAO,EAEL,SAAS,EAEV,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"card.d.ts","sourceRoot":"","sources":["../../src/a2a/card.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC;AAChC,OAAO,EAEL,SAAS,EAEV,MAAM,aAAa,CAAC;AAOrB;;;;;GAKG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAEvD;;;;;;;;GAQG;AACH,MAAM,MAAM,aAAa,GAAG,IAAI,CAC9B,SAAS,EACP,MAAM,GACN,aAAa,GACb,SAAS,GACT,cAAc,GACd,mBAAmB,GACnB,oBAAoB,GACpB,QAAQ,CACX,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEpD,MAAM,WAAW,iBAAiB;IAChC,4DAA4D;IAC5D,UAAU,EAAE,GAAG,GAAG;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAClC,wEAAwE;IACxE,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,gBAAgB;IAC/B,6EAA6E;IAC7E,MAAM,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;;;;;OAUG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACpC;AAUD;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,aAAa,EACvB,IAAI,EAAE,gBAAgB,GACrB,SAAS,CAkDX;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,SAAS,EACf,GAAG,EAAE,iBAAiB,GACrB,OAAO,CAAC,aAAa,CAAC,CASxB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,SAAS,EACf,GAAG,EAAE,iBAAiB,GACrB,OAAO,CAAC,SAAS,CAAC,CAGpB;AAED;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,SAAS,CAEnD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,CAAC,YAAY,CAAC,CAI5E;AAED,iFAAiF;AACjF,wBAAgB,cAAc,CAAC,UAAU,EAAE,GAAG,GAAG;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG;IACjE,IAAI,EAAE,GAAG,EAAE,CAAC;CACb,CAIA"}
|
package/dist/a2a/card.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { A2A_PROTOCOL_VERSION, AgentCard, generateAgentCardSignature } from "@a2a-js/sdk";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import { A2A_JWS_ALG, A2A_RPC_PATH, endpointUrl } from "@loopingai/a2a-protocol";
|
|
3
|
+
/**
|
|
4
|
+
* The card signature uses the one algorithm every Looping A2A signature uses,
|
|
5
|
+
* and the default RPC path is the one the gateway also knows. Both come from
|
|
6
|
+
* `@loopingai/a2a-protocol` — see {@link file://./verify.ts} for why that is a
|
|
7
|
+
* separate package rather than a constant declared on each side.
|
|
8
|
+
*/
|
|
9
|
+
export { A2A_RPC_PATH } from "@loopingai/a2a-protocol";
|
|
6
10
|
/**
|
|
7
11
|
* The `httpAuthSecurityScheme` describing the gateway's Bearer JWT, and the
|
|
8
12
|
* requirement that references it. Split out so the requirement can be advertised
|
|
@@ -40,7 +44,12 @@ export function buildBaseCard(manifest, opts) {
|
|
|
40
44
|
},
|
|
41
45
|
supportedInterfaces: [
|
|
42
46
|
{
|
|
43
|
-
|
|
47
|
+
// The gateway reads this URL off the card and mints `aud` from it with
|
|
48
|
+
// `audienceFor`; this Worker derives its expected audience the same
|
|
49
|
+
// way. `endpointUrl` is what makes those provably equal — the protocol
|
|
50
|
+
// package pins `audienceFor(endpointUrl(o, p)) === endpointUrl(o, p)`
|
|
51
|
+
// for every path, rather than leaving both sides to concatenate.
|
|
52
|
+
url: endpointUrl(opts.origin, rpcPath),
|
|
44
53
|
protocolBinding: "JSONRPC",
|
|
45
54
|
protocolVersion: A2A_PROTOCOL_VERSION,
|
|
46
55
|
tenant: opts.tenant ?? ""
|
|
@@ -86,7 +95,7 @@ export function buildBaseCard(manifest, opts) {
|
|
|
86
95
|
*/
|
|
87
96
|
export async function signCard(card, cfg) {
|
|
88
97
|
const sign = generateAgentCardSignature(cfg.privateJwk, {
|
|
89
|
-
alg:
|
|
98
|
+
alg: A2A_JWS_ALG,
|
|
90
99
|
kid: cfg.privateJwk.kid,
|
|
91
100
|
typ: "JOSE",
|
|
92
101
|
jku: cfg.jku
|
|
@@ -148,6 +157,6 @@ export function parsePrivateJwk(raw) {
|
|
|
148
157
|
export function publicCardJwks(privateJwk) {
|
|
149
158
|
const { d: _d, ...pub } = privateJwk;
|
|
150
159
|
void _d;
|
|
151
|
-
return { keys: [{ ...pub, use: "sig", alg:
|
|
160
|
+
return { keys: [{ ...pub, use: "sig", alg: A2A_JWS_ALG }] };
|
|
152
161
|
}
|
|
153
162
|
//# sourceMappingURL=card.js.map
|
package/dist/a2a/card.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"card.js","sourceRoot":"","sources":["../../src/a2a/card.ts"],"names":[],"mappings":"AACA,OAAO,EACL,oBAAoB,EACpB,SAAS,EACT,0BAA0B,EAC3B,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"card.js","sourceRoot":"","sources":["../../src/a2a/card.ts"],"names":[],"mappings":"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"}
|
package/dist/a2a/index.d.ts
CHANGED
|
@@ -5,10 +5,25 @@
|
|
|
5
5
|
* JWKS, prove ourselves with ours), the accept-and-notify task lifecycle, and the
|
|
6
6
|
* narrowed task types that survive Durable Object RPC. Nothing here knows what an
|
|
7
7
|
* agent *does* — past {@link A2AExecutor} everything is plain strings.
|
|
8
|
+
*
|
|
9
|
+
* ## The wire values come from `@loopingai/a2a-protocol`
|
|
10
|
+
*
|
|
11
|
+
* Claim names, the algorithm, the well-known paths and the audience rule are a
|
|
12
|
+
* two-sided contract with a token issuer, and the issuer — looping-gateway — is
|
|
13
|
+
* not an agent and must not import this package. They live in a zero-dependency
|
|
14
|
+
* leaf both sides depend on, and are re-exported here so an agent's imports are
|
|
15
|
+
* unchanged: `IDENTITY_CLAIM`, `TENANT_CLAIM`, `A2A_RPC_PATH` and
|
|
16
|
+
* `GatewayIdentity` still come from `@loopingai/core/a2a`.
|
|
17
|
+
*
|
|
18
|
+
* The pure helpers are re-exported too, because an agent that builds a URL by
|
|
19
|
+
* hand is an agent that can disagree with the gateway about it.
|
|
8
20
|
*/
|
|
21
|
+
export { A2A_JWS_ALG, audienceFor, endpointUrl, jwksUrl } from "@loopingai/a2a-protocol";
|
|
9
22
|
export { IDENTITY_CLAIM, TENANT_CLAIM, GatewayAuthError, bearerToken, normalizeGatewayOrigins, verifyGatewayToken, type GatewayIdentity, type VerifyOptions } from "./verify.js";
|
|
10
23
|
export { A2A_RPC_PATH, buildBaseCard, signCard, wireCard, parsePrivateJwk, publicCardJwks, type AgentManifest, type BuildCardOptions, type CardSigningConfig, type WireAgentCard } from "./card.js";
|
|
11
24
|
export { NOTIFICATION_TOKEN_HEADER, buildSubmittedTask, buildWorkingTask, buildCompletedTask, buildFailedTask, buildNoReplyCompletedTask, signCallbackJwt, postNotification } from "./notify.js";
|
|
25
|
+
export { callerContext } from "./caller.js";
|
|
26
|
+
export { createPushChannel, type PushChannel, type TurnPushContext } from "./push.js";
|
|
12
27
|
export { taskStateLabel, type PlainArtifact, type PlainMessage, type PlainPart, type PlainStatus, type PlainTask } from "./task.js";
|
|
13
28
|
export { textPart, partsText, textOf, agentTextMessage, inboundText, InboundPartError, MAX_INBOUND_TEXT_BYTES } from "./parts.js";
|
|
14
29
|
export { buildCallContext, extensionHeaders } from "./context.js";
|
package/dist/a2a/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/a2a/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/a2a/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EACL,WAAW,EACX,WAAW,EACX,WAAW,EACX,OAAO,EACR,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACX,uBAAuB,EACvB,kBAAkB,EAClB,KAAK,eAAe,EACpB,KAAK,aAAa,EACnB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,YAAY,EACZ,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,eAAe,EACf,cAAc,EACd,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EACnB,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,yBAAyB,EACzB,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,yBAAyB,EACzB,eAAe,EACf,gBAAgB,EACjB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EACL,iBAAiB,EACjB,KAAK,WAAW,EAChB,KAAK,eAAe,EACrB,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,cAAc,EACd,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,SAAS,EACf,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,QAAQ,EACR,SAAS,EACT,MAAM,EACN,gBAAgB,EAChB,WAAW,EACX,gBAAgB,EAChB,sBAAsB,EACvB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAElE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEnD,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,mBAAmB,EACnB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,WAAW,EACjB,MAAM,eAAe,CAAC;AAEvB,YAAY,EACV,aAAa,EACb,SAAS,EACT,YAAY,EACZ,aAAa,EACd,MAAM,iBAAiB,CAAC"}
|
package/dist/a2a/index.js
CHANGED
|
@@ -5,10 +5,25 @@
|
|
|
5
5
|
* JWKS, prove ourselves with ours), the accept-and-notify task lifecycle, and the
|
|
6
6
|
* narrowed task types that survive Durable Object RPC. Nothing here knows what an
|
|
7
7
|
* agent *does* — past {@link A2AExecutor} everything is plain strings.
|
|
8
|
+
*
|
|
9
|
+
* ## The wire values come from `@loopingai/a2a-protocol`
|
|
10
|
+
*
|
|
11
|
+
* Claim names, the algorithm, the well-known paths and the audience rule are a
|
|
12
|
+
* two-sided contract with a token issuer, and the issuer — looping-gateway — is
|
|
13
|
+
* not an agent and must not import this package. They live in a zero-dependency
|
|
14
|
+
* leaf both sides depend on, and are re-exported here so an agent's imports are
|
|
15
|
+
* unchanged: `IDENTITY_CLAIM`, `TENANT_CLAIM`, `A2A_RPC_PATH` and
|
|
16
|
+
* `GatewayIdentity` still come from `@loopingai/core/a2a`.
|
|
17
|
+
*
|
|
18
|
+
* The pure helpers are re-exported too, because an agent that builds a URL by
|
|
19
|
+
* hand is an agent that can disagree with the gateway about it.
|
|
8
20
|
*/
|
|
21
|
+
export { A2A_JWS_ALG, audienceFor, endpointUrl, jwksUrl } from "@loopingai/a2a-protocol";
|
|
9
22
|
export { IDENTITY_CLAIM, TENANT_CLAIM, GatewayAuthError, bearerToken, normalizeGatewayOrigins, verifyGatewayToken } from "./verify.js";
|
|
10
23
|
export { A2A_RPC_PATH, buildBaseCard, signCard, wireCard, parsePrivateJwk, publicCardJwks } from "./card.js";
|
|
11
24
|
export { NOTIFICATION_TOKEN_HEADER, buildSubmittedTask, buildWorkingTask, buildCompletedTask, buildFailedTask, buildNoReplyCompletedTask, signCallbackJwt, postNotification } from "./notify.js";
|
|
25
|
+
export { callerContext } from "./caller.js";
|
|
26
|
+
export { createPushChannel } from "./push.js";
|
|
12
27
|
export { taskStateLabel } from "./task.js";
|
|
13
28
|
export { textPart, partsText, textOf, agentTextMessage, inboundText, InboundPartError, MAX_INBOUND_TEXT_BYTES } from "./parts.js";
|
|
14
29
|
export { buildCallContext, extensionHeaders } from "./context.js";
|
package/dist/a2a/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/a2a/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/a2a/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EACL,WAAW,EACX,WAAW,EACX,WAAW,EACX,OAAO,EACR,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACX,uBAAuB,EACvB,kBAAkB,EAGnB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,YAAY,EACZ,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,eAAe,EACf,cAAc,EAKf,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,yBAAyB,EACzB,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,yBAAyB,EACzB,eAAe,EACf,gBAAgB,EACjB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EACL,iBAAiB,EAGlB,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,cAAc,EAMf,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,QAAQ,EACR,SAAS,EACT,MAAM,EACN,gBAAgB,EAChB,WAAW,EACX,gBAAgB,EAChB,sBAAsB,EACvB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAElE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEnD,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,mBAAmB,EAIpB,MAAM,eAAe,CAAC"}
|
package/dist/a2a/notify.d.ts
CHANGED
|
@@ -2,12 +2,34 @@ import { type JWK } from "jose";
|
|
|
2
2
|
import { type Task } from "@a2a-js/sdk";
|
|
3
3
|
import type { PlainTask } from "./task.js";
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* `
|
|
5
|
+
* Outbound push-notification (accept + notify) helpers — the "notify" half of the
|
|
6
|
+
* async A2A contract. The gateway dispatches `SendMessage` with a
|
|
7
|
+
* `taskPushNotificationConfig` (webhook `url` + validation `token`), we accept
|
|
8
|
+
* immediately with a `submitted` Task, and later POST the terminal Task back to
|
|
9
|
+
* that webhook. This module builds the Task shapes and signs + sends that
|
|
10
|
+
* callback.
|
|
11
|
+
*
|
|
12
|
+
* The callback is authenticated exactly like the AgentCard: a short-lived EdDSA
|
|
13
|
+
* JWT signed by `A2A_SIGNING_KEY`, whose protected header `kid`+`jku` must equal
|
|
14
|
+
* the card's signing `kid`+`jku` (see {@link file://./card.ts} `signCard`) — the
|
|
15
|
+
* gateway pinned those at registration (Trust-On-First-Use) and verifies the
|
|
16
|
+
* callback token against that same public JWKS. No shared secret crosses the
|
|
17
|
+
* boundary; only our public key is ever used to verify.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* The two shared values on this hop — the signature algorithm and the header
|
|
21
|
+
* carrying the per-task validation `token` — come from
|
|
22
|
+
* `@loopingai/a2a-protocol`, the same package the gateway reads them from.
|
|
23
|
+
*
|
|
24
|
+
* The header used to be declared here and again in looping-gateway, each with a
|
|
25
|
+
* comment naming the other file. It is a slightly different case from the claim
|
|
26
|
+
* names: the value is `@a2a-js/sdk`'s own default for `tokenHeaderName`, not
|
|
27
|
+
* Looping's choice, but the SDK never *exports* it — it exists only as an
|
|
28
|
+
* inline fallback — so neither side could import it and both wrote it down.
|
|
29
|
+
*
|
|
30
|
+
* Re-exported so `@loopingai/core/a2a` keeps being where an agent finds it.
|
|
9
31
|
*/
|
|
10
|
-
export
|
|
32
|
+
export { NOTIFICATION_TOKEN_HEADER } from "@loopingai/a2a-protocol";
|
|
11
33
|
/**
|
|
12
34
|
* The `submitted` Task we return synchronously to accept a turn (A2A §7.2). The
|
|
13
35
|
* gateway only requires a non-empty `id`; the actual reply follows later via the
|
package/dist/a2a/notify.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notify.d.ts","sourceRoot":"","sources":["../../src/a2a/notify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,GAAG,EAAE,MAAM,MAAM,CAAC;AACzC,OAAO,EAAa,KAAK,IAAI,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"notify.d.ts","sourceRoot":"","sources":["../../src/a2a/notify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,GAAG,EAAE,MAAM,MAAM,CAAC;AACzC,OAAO,EAAa,KAAK,IAAI,EAAE,MAAM,aAAa,CAAC;AAOnD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAE3C;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AAqCpE;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,SAAS,CAEX;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,SAAS,CAEX;AAwBD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,GACV,SAAS,CAQX;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,GACZ,SAAS,CAQX;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,GACX,SAAS,CAQX;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,UAAU,EAAE,GAAG,GAAG;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,EACjC,IAAI,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACjC,OAAO,CAAC,MAAM,CAAC,CAWjB;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,IAAI,GACT,OAAO,CAAC,QAAQ,CAAC,CAanB"}
|
package/dist/a2a/notify.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SignJWT } from "jose";
|
|
2
2
|
import { TaskState } from "@a2a-js/sdk";
|
|
3
3
|
import { V1PushNotificationSerializer } from "@a2a-js/sdk/server";
|
|
4
|
+
import { A2A_JWS_ALG, NOTIFICATION_TOKEN_HEADER } from "@loopingai/a2a-protocol";
|
|
4
5
|
import { agentTextMessage } from "./parts.js";
|
|
5
6
|
/**
|
|
6
7
|
* Outbound push-notification (accept + notify) helpers — the "notify" half of the
|
|
@@ -17,15 +18,20 @@ import { agentTextMessage } from "./parts.js";
|
|
|
17
18
|
* callback token against that same public JWKS. No shared secret crosses the
|
|
18
19
|
* boundary; only our public key is ever used to verify.
|
|
19
20
|
*/
|
|
20
|
-
/** JWS algorithm — must match the card + gateway (`EdDSA`). */
|
|
21
|
-
const ALG = "EdDSA";
|
|
22
21
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
22
|
+
* The two shared values on this hop — the signature algorithm and the header
|
|
23
|
+
* carrying the per-task validation `token` — come from
|
|
24
|
+
* `@loopingai/a2a-protocol`, the same package the gateway reads them from.
|
|
25
|
+
*
|
|
26
|
+
* The header used to be declared here and again in looping-gateway, each with a
|
|
27
|
+
* comment naming the other file. It is a slightly different case from the claim
|
|
28
|
+
* names: the value is `@a2a-js/sdk`'s own default for `tokenHeaderName`, not
|
|
29
|
+
* Looping's choice, but the SDK never *exports* it — it exists only as an
|
|
30
|
+
* inline fallback — so neither side could import it and both wrote it down.
|
|
31
|
+
*
|
|
32
|
+
* Re-exported so `@loopingai/core/a2a` keeps being where an agent finds it.
|
|
27
33
|
*/
|
|
28
|
-
export
|
|
34
|
+
export { NOTIFICATION_TOKEN_HEADER } from "@loopingai/a2a-protocol";
|
|
29
35
|
/**
|
|
30
36
|
* Callback-JWT lifetime. The gateway enforces `maxTokenAge: 10m` with a 60s clock
|
|
31
37
|
* tolerance, so keep this comfortably under that.
|
|
@@ -142,7 +148,11 @@ export function buildFailedTask(taskId, contextId, text) {
|
|
|
142
148
|
*/
|
|
143
149
|
export async function signCallbackJwt(privateJwk, opts) {
|
|
144
150
|
return new SignJWT({})
|
|
145
|
-
.setProtectedHeader({
|
|
151
|
+
.setProtectedHeader({
|
|
152
|
+
alg: A2A_JWS_ALG,
|
|
153
|
+
kid: privateJwk.kid,
|
|
154
|
+
jku: opts.jku
|
|
155
|
+
})
|
|
146
156
|
.setAudience(opts.aud)
|
|
147
157
|
.setIssuedAt()
|
|
148
158
|
.setExpirationTime(CALLBACK_TOKEN_TTL)
|
package/dist/a2a/notify.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notify.js","sourceRoot":"","sources":["../../src/a2a/notify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAY,MAAM,MAAM,CAAC;AACzC,OAAO,EAAE,SAAS,EAAa,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,4BAA4B,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAG9C;;;;;;;;;;;;;;GAcG;AAEH
|
|
1
|
+
{"version":3,"file":"notify.js","sourceRoot":"","sources":["../../src/a2a/notify.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { Task } from "@a2a-js/sdk";
|
|
2
|
+
import type { OnContent } from "../agent/inference.js";
|
|
3
|
+
/**
|
|
4
|
+
* The gateway callback channel for one accepted turn.
|
|
5
|
+
*
|
|
6
|
+
* Every agent that accepts asynchronously has to do the same four things with
|
|
7
|
+
* the push config the gateway handed it: sign a callback JWT against the
|
|
8
|
+
* deployment's card key, POST `working` snapshots as the model reasons, POST the
|
|
9
|
+
* terminal Task, and never let a failed progress post take down a turn. That was
|
|
10
|
+
* written out longhand in four places across two agents in the starter — twice as
|
|
11
|
+
* a `streamWorking` closure inside a Durable Object, twice as a `notify` step
|
|
12
|
+
* inside a Workflow — with the signing, the header names and the swallow-and-log
|
|
13
|
+
* discipline duplicated each time.
|
|
14
|
+
*
|
|
15
|
+
* It is one object here because none of it is per-agent. What *is* per-agent is
|
|
16
|
+
* the notification **key**, and that is the one thing a caller supplies.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Everything needed to call a gateway back about one task.
|
|
20
|
+
*
|
|
21
|
+
* RPC-serializable by construction: it crosses the Workflow → Durable Object
|
|
22
|
+
* boundary on every turn, so it holds only strings.
|
|
23
|
+
*/
|
|
24
|
+
export interface TurnPushContext {
|
|
25
|
+
/** The accepted task id (echoed on every callback of this turn). */
|
|
26
|
+
taskId: string;
|
|
27
|
+
/** A2A context id, echoed on every callback. */
|
|
28
|
+
contextId: string;
|
|
29
|
+
/** Gateway push-notification webhook (also the callback JWT `aud`). */
|
|
30
|
+
pushUrl: string;
|
|
31
|
+
/** Per-task validation token the gateway set; echoed in the callback header. */
|
|
32
|
+
pushToken: string;
|
|
33
|
+
/** This agent's card-signing JWKS URL — the callback JWT `jku` (pinned key). */
|
|
34
|
+
jku: string;
|
|
35
|
+
}
|
|
36
|
+
export interface PushChannel {
|
|
37
|
+
/**
|
|
38
|
+
* POST one `working` Task snapshot, keyed by a stable semantic `key`.
|
|
39
|
+
*
|
|
40
|
+
* **Best-effort**: every failure is logged and swallowed, so a progress post
|
|
41
|
+
* can never abort generation or fail a durable step. The key is what lets the
|
|
42
|
+
* gateway dedupe a re-posted event on replay, so it must be derived from
|
|
43
|
+
* position (`r2:step:3`), never from content or a clock.
|
|
44
|
+
*/
|
|
45
|
+
working(text: string, key: string): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* An {@link OnContent} sink that posts each intermediate message as a
|
|
48
|
+
* `working` snapshot, signing the JWT once and reusing it across the turn.
|
|
49
|
+
*
|
|
50
|
+
* `key` maps a 0-based step ordinal to its notification key. An agent running
|
|
51
|
+
* one turn per task can use the bare index; an agent with rounds **must**
|
|
52
|
+
* include the round, or two rounds of one task collide on the gateway.
|
|
53
|
+
*/
|
|
54
|
+
stream(key: (stepIndex: number) => string): OnContent;
|
|
55
|
+
/**
|
|
56
|
+
* POST the terminal Task. **Throws on a non-2xx** so the calling Workflow step
|
|
57
|
+
* retries — the opposite of {@link working}, because this is the delivery the
|
|
58
|
+
* whole turn exists for.
|
|
59
|
+
*/
|
|
60
|
+
deliver(task: Task): Promise<void>;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Build the callback channel for one turn.
|
|
64
|
+
*
|
|
65
|
+
* `signingKey` is the deployment's Ed25519 private JWK as JSON — `A2A_SIGNING_KEY`
|
|
66
|
+
* by default. There is one per origin, not one per agent: the card sits at a
|
|
67
|
+
* well-known URI, which RFC 8615 defines per-authority, so this origin publishes
|
|
68
|
+
* one card and the gateway pins one key for every agent on it.
|
|
69
|
+
*/
|
|
70
|
+
export declare function createPushChannel(signingKey: string, push: TurnPushContext): PushChannel;
|
|
71
|
+
//# sourceMappingURL=push.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"push.d.ts","sourceRoot":"","sources":["../../src/a2a/push.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAQvD;;;;;;;;;;;;;;GAcG;AAEH;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC;IACf,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAC;IAChB,gFAAgF;IAChF,SAAS,EAAE,MAAM,CAAC;IAClB,gFAAgF;IAChF,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,WAAW;IAC1B;;;;;;;OAOG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD;;;;;;;OAOG;IACH,MAAM,CAAC,GAAG,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACtD;;;;OAIG;IACH,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACpC;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,eAAe,GACpB,WAAW,CAkDb"}
|
package/dist/a2a/push.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { parsePrivateJwk } from "./card.js";
|
|
2
|
+
import { buildWorkingTask, postNotification, signCallbackJwt } from "./notify.js";
|
|
3
|
+
/**
|
|
4
|
+
* Build the callback channel for one turn.
|
|
5
|
+
*
|
|
6
|
+
* `signingKey` is the deployment's Ed25519 private JWK as JSON — `A2A_SIGNING_KEY`
|
|
7
|
+
* by default. There is one per origin, not one per agent: the card sits at a
|
|
8
|
+
* well-known URI, which RFC 8615 defines per-authority, so this origin publishes
|
|
9
|
+
* one card and the gateway pins one key for every agent on it.
|
|
10
|
+
*/
|
|
11
|
+
export function createPushChannel(signingKey, push) {
|
|
12
|
+
// Signed lazily and reused: one turn can post many progress snapshots, and
|
|
13
|
+
// re-signing per message costs a key import each time for no benefit.
|
|
14
|
+
let jwt;
|
|
15
|
+
const sign = () => (jwt ??= signCallbackJwt(parsePrivateJwk(signingKey), {
|
|
16
|
+
jku: push.jku,
|
|
17
|
+
aud: push.pushUrl
|
|
18
|
+
}));
|
|
19
|
+
const post = async (task) => postNotification(push.pushUrl, push.pushToken, await sign(), task);
|
|
20
|
+
const working = async (text, key) => {
|
|
21
|
+
try {
|
|
22
|
+
const res = await post(buildWorkingTask(push.taskId, push.contextId, text, key));
|
|
23
|
+
if (!res.ok) {
|
|
24
|
+
console.warn("[push] working notification non-2xx", {
|
|
25
|
+
taskId: push.taskId,
|
|
26
|
+
key,
|
|
27
|
+
status: res.status
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
// Swallowed on purpose, including a signing failure: a turn that cannot
|
|
33
|
+
// report its progress is still a turn that should deliver its answer.
|
|
34
|
+
console.warn("[push] working notification failed", {
|
|
35
|
+
taskId: push.taskId,
|
|
36
|
+
key,
|
|
37
|
+
err: String(err)
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
return {
|
|
42
|
+
working,
|
|
43
|
+
stream(key) {
|
|
44
|
+
return (text, stepIndex) => working(text, key(stepIndex));
|
|
45
|
+
},
|
|
46
|
+
async deliver(task) {
|
|
47
|
+
const res = await post(task);
|
|
48
|
+
if (!res.ok) {
|
|
49
|
+
throw new Error(`gateway notification failed: HTTP ${res.status}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=push.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"push.js","sourceRoot":"","sources":["../../src/a2a/push.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EAChB,MAAM,aAAa,CAAC;AAgErB;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAC/B,UAAkB,EAClB,IAAqB;IAErB,2EAA2E;IAC3E,sEAAsE;IACtE,IAAI,GAAgC,CAAC;IACrC,MAAM,IAAI,GAAG,GAAoB,EAAE,CACjC,CAAC,GAAG,KAAK,eAAe,CAAC,eAAe,CAAC,UAAU,CAAC,EAAE;QACpD,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,GAAG,EAAE,IAAI,CAAC,OAAO;KAClB,CAAC,CAAC,CAAC;IAEN,MAAM,IAAI,GAAG,KAAK,EAAE,IAAU,EAAqB,EAAE,CACnD,gBAAgB,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,EAAE,MAAM,IAAI,EAAE,EAAE,IAAI,CAAC,CAAC;IAErE,MAAM,OAAO,GAAG,KAAK,EAAE,IAAY,EAAE,GAAW,EAAiB,EAAE;QACjE,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,IAAI,CACpB,gBAAgB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,GAAG,CAAC,CACzD,CAAC;YACF,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;gBACZ,OAAO,CAAC,IAAI,CAAC,qCAAqC,EAAE;oBAClD,MAAM,EAAE,IAAI,CAAC,MAAM;oBACnB,GAAG;oBACH,MAAM,EAAE,GAAG,CAAC,MAAM;iBACnB,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,wEAAwE;YACxE,sEAAsE;YACtE,OAAO,CAAC,IAAI,CAAC,oCAAoC,EAAE;gBACjD,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,GAAG;gBACH,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC;aACjB,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC;IAEF,OAAO;QACL,OAAO;QAEP,MAAM,CAAC,GAAG;YACR,OAAO,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC;QAC5D,CAAC;QAED,KAAK,CAAC,OAAO,CAAC,IAAI;YAChB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;YAC7B,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;gBACZ,MAAM,IAAI,KAAK,CAAC,qCAAqC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;YACrE,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|