@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.
Files changed (104) hide show
  1. package/README.md +83 -28
  2. package/dist/a2a/caller.d.ts +24 -0
  3. package/dist/a2a/caller.d.ts.map +1 -0
  4. package/dist/a2a/caller.js +34 -0
  5. package/dist/a2a/caller.js.map +1 -0
  6. package/dist/a2a/card.d.ts +7 -2
  7. package/dist/a2a/card.d.ts.map +1 -1
  8. package/dist/a2a/card.js +16 -7
  9. package/dist/a2a/card.js.map +1 -1
  10. package/dist/a2a/index.d.ts +15 -0
  11. package/dist/a2a/index.d.ts.map +1 -1
  12. package/dist/a2a/index.js +15 -0
  13. package/dist/a2a/index.js.map +1 -1
  14. package/dist/a2a/notify.d.ts +27 -5
  15. package/dist/a2a/notify.d.ts.map +1 -1
  16. package/dist/a2a/notify.js +18 -8
  17. package/dist/a2a/notify.js.map +1 -1
  18. package/dist/a2a/push.d.ts +71 -0
  19. package/dist/a2a/push.d.ts.map +1 -0
  20. package/dist/a2a/push.js +54 -0
  21. package/dist/a2a/push.js.map +1 -0
  22. package/dist/a2a/verify.d.ts +28 -36
  23. package/dist/a2a/verify.d.ts.map +1 -1
  24. package/dist/a2a/verify.js +20 -25
  25. package/dist/a2a/verify.js.map +1 -1
  26. package/dist/agent/index.d.ts +16 -5
  27. package/dist/agent/index.d.ts.map +1 -1
  28. package/dist/agent/index.js +16 -5
  29. package/dist/agent/index.js.map +1 -1
  30. package/dist/config.d.ts +49 -7
  31. package/dist/config.d.ts.map +1 -1
  32. package/dist/config.js +25 -3
  33. package/dist/config.js.map +1 -1
  34. package/dist/db/models/tasks.d.ts +19 -5
  35. package/dist/db/models/tasks.d.ts.map +1 -1
  36. package/dist/db/models/tasks.js +38 -8
  37. package/dist/db/models/tasks.js.map +1 -1
  38. package/dist/host/agent.d.ts +217 -0
  39. package/dist/host/agent.d.ts.map +1 -0
  40. package/dist/host/agent.js +306 -0
  41. package/dist/host/agent.js.map +1 -0
  42. package/dist/host/index.d.ts +21 -0
  43. package/dist/host/index.d.ts.map +1 -0
  44. package/dist/host/index.js +20 -0
  45. package/dist/host/index.js.map +1 -0
  46. package/dist/host/plugin-host.d.ts +47 -0
  47. package/dist/host/plugin-host.d.ts.map +1 -0
  48. package/dist/host/plugin-host.js +2 -0
  49. package/dist/host/plugin-host.js.map +1 -0
  50. package/dist/round/agent.d.ts +267 -0
  51. package/dist/round/agent.d.ts.map +1 -0
  52. package/dist/round/agent.js +691 -0
  53. package/dist/round/agent.js.map +1 -0
  54. package/dist/round/index.d.ts +25 -0
  55. package/dist/round/index.d.ts.map +1 -0
  56. package/dist/round/index.js +24 -0
  57. package/dist/round/index.js.map +1 -0
  58. package/dist/round/policy.d.ts +99 -0
  59. package/dist/round/policy.d.ts.map +1 -0
  60. package/dist/round/policy.js +2 -0
  61. package/dist/round/policy.js.map +1 -0
  62. package/dist/round/subagent.d.ts +67 -0
  63. package/dist/round/subagent.d.ts.map +1 -0
  64. package/dist/round/subagent.js +91 -0
  65. package/dist/round/subagent.js.map +1 -0
  66. package/dist/round/turn.d.ts +231 -0
  67. package/dist/round/turn.d.ts.map +1 -0
  68. package/dist/round/turn.js +497 -0
  69. package/dist/round/turn.js.map +1 -0
  70. package/dist/round/workflow.d.ts +109 -0
  71. package/dist/round/workflow.d.ts.map +1 -0
  72. package/dist/round/workflow.js +316 -0
  73. package/dist/round/workflow.js.map +1 -0
  74. package/dist/runtime/index.d.ts +5 -1
  75. package/dist/runtime/index.d.ts.map +1 -1
  76. package/dist/runtime/index.js.map +1 -1
  77. package/dist/testing/auth.d.ts.map +1 -1
  78. package/dist/testing/auth.js +10 -7
  79. package/dist/testing/auth.js.map +1 -1
  80. package/dist/testing/fixtures.d.ts +17 -0
  81. package/dist/testing/fixtures.d.ts.map +1 -1
  82. package/dist/testing/fixtures.js +17 -0
  83. package/dist/testing/fixtures.js.map +1 -1
  84. package/dist/testing/harness.d.ts +98 -0
  85. package/dist/testing/harness.d.ts.map +1 -0
  86. package/dist/testing/harness.js +139 -0
  87. package/dist/testing/harness.js.map +1 -0
  88. package/dist/testing/index.d.ts +2 -1
  89. package/dist/testing/index.d.ts.map +1 -1
  90. package/dist/testing/index.js +2 -1
  91. package/dist/testing/index.js.map +1 -1
  92. package/dist/testing/vcr-store.d.ts +14 -18
  93. package/dist/testing/vcr-store.d.ts.map +1 -1
  94. package/dist/testing/vcr-store.js +30 -30
  95. package/dist/testing/vcr-store.js.map +1 -1
  96. package/dist/worker/define-agent.d.ts +124 -0
  97. package/dist/worker/define-agent.d.ts.map +1 -0
  98. package/dist/worker/define-agent.js +21 -0
  99. package/dist/worker/define-agent.js.map +1 -0
  100. package/dist/worker/index.d.ts +29 -4
  101. package/dist/worker/index.d.ts.map +1 -1
  102. package/dist/worker/index.js +70 -12
  103. package/dist/worker/index.js.map +1 -1
  104. package/package.json +20 -6
@@ -0,0 +1,139 @@
1
+ import { A2A_PROTOCOL_VERSION } from "@a2a-js/sdk";
2
+ import { A2A_RPC_PATH, NOTIFICATION_TOKEN_HEADER, endpointUrl, jwksUrl } from "@loopingai/a2a-protocol";
3
+ import { makeGatewayToken, TEST_TENANT } from "./auth.js";
4
+ import { AGENT_ORIGIN, GATEWAY_ORIGIN, gatewayPublicJwks } from "./fixtures.js";
5
+ /**
6
+ * Project the push envelope onto the three fields a spec usually asserts.
7
+ *
8
+ * Reads `status.message.parts` — the key the wire form actually uses. It read
9
+ * `content` until `harness.spec.ts` drove a real callback through it, which
10
+ * made `text` the empty string on every capture: silently, since a harness
11
+ * reporting `""` for what an agent said looks exactly like an agent that said
12
+ * nothing, and the no-reply completion is a legitimate outcome.
13
+ */
14
+ function projectCallback(body, token) {
15
+ const task = body.task ?? body;
16
+ const t = task;
17
+ const parts = t.status?.message?.parts ?? [];
18
+ return {
19
+ taskId: t.id ?? "",
20
+ state: t.status?.state ?? "",
21
+ text: parts
22
+ .map((p) => p.text ?? "")
23
+ .join("")
24
+ .trim(),
25
+ token,
26
+ body
27
+ };
28
+ }
29
+ export function createAgentHarness(options) {
30
+ const origin = options.origin ?? AGENT_ORIGIN;
31
+ const rpcPath = options.rpcPath ?? A2A_RPC_PATH;
32
+ // The audience a gateway actually mints: the endpoint this deployment serves,
33
+ // which is also exactly what its card advertises as its interface. Composed
34
+ // with the protocol package's own helper, so a harness cannot agree with the
35
+ // verifier while disagreeing with what a real gateway would send.
36
+ const endpoint = endpointUrl(origin, rpcPath);
37
+ const tenant = options.tenant ?? TEST_TENANT;
38
+ const pushUrl = `${GATEWAY_ORIGIN}/a2a/push`;
39
+ const pushToken = "harness-push-token";
40
+ const callbacks = [];
41
+ const token = (overrides = {}) => makeGatewayToken({
42
+ audience: endpoint,
43
+ tenant: overrides.tenant ?? tenant
44
+ });
45
+ const rpc = async (body, init = {}) => options.worker.fetch(new Request(endpoint, {
46
+ method: "POST",
47
+ headers: {
48
+ "content-type": "application/json",
49
+ "A2A-Version": A2A_PROTOCOL_VERSION,
50
+ authorization: `Bearer ${init.token ?? (await token())}`
51
+ },
52
+ body: JSON.stringify(body)
53
+ }), options.env);
54
+ return {
55
+ endpoint,
56
+ pushUrl,
57
+ callbacks,
58
+ token,
59
+ rpc,
60
+ async send(text, sendOptions = {}) {
61
+ const messageId = crypto.randomUUID();
62
+ const res = await rpc({
63
+ jsonrpc: "2.0",
64
+ id: 1,
65
+ method: "SendMessage",
66
+ // `SendMessageRequest` is **flat**: `tenant`, `message`, `configuration`,
67
+ // `metadata`. There is no `request` wrapper, and wrapping is silent when
68
+ // you do it — `fromJSON` drops unknown keys rather than rejecting them,
69
+ // so the whole turn decodes to an empty message with no push config and
70
+ // the agent refuses it for "missing" fields the caller did send.
71
+ //
72
+ // This harness shipped with exactly that envelope. Nothing caught it,
73
+ // because nothing in this package drove the harness against a real
74
+ // Worker until `harness.spec.ts` — which is the argument for that spec
75
+ // existing, made by the code it tests.
76
+ params: {
77
+ tenant,
78
+ message: {
79
+ messageId,
80
+ role: "ROLE_USER",
81
+ // `parts`, not `content`. The decoded `Message` exposes `parts`, and
82
+ // `fromJSON` silently yields an empty list for anything else — so a
83
+ // turn sent under the wrong key arrives as a message with no text
84
+ // and the agent answers a blank prompt.
85
+ parts: [{ text }],
86
+ ...(sendOptions.taskId ? { taskId: sendOptions.taskId } : {})
87
+ },
88
+ configuration: {
89
+ // Required by the accept-and-notify contract: an agent that replies
90
+ // out of band and is given nowhere to call back has accepted a turn
91
+ // it can never answer.
92
+ taskPushNotificationConfig: { url: pushUrl, token: pushToken }
93
+ }
94
+ }
95
+ });
96
+ const envelope = await res.json();
97
+ if (envelope.error) {
98
+ throw new Error(`SendMessage was refused (${envelope.error.code}): ${envelope.error.message}`);
99
+ }
100
+ const result = envelope.result;
101
+ const task = (result?.task ?? result);
102
+ if (!task) {
103
+ throw new Error(`SendMessage returned no task: ${JSON.stringify(envelope)}`);
104
+ }
105
+ return task;
106
+ },
107
+ interceptGateway() {
108
+ const original = globalThis.fetch;
109
+ globalThis.fetch = (async (input, init) => {
110
+ const url = typeof input === "string" ? input : String(input);
111
+ // The gateway's public JWKS — without it every token fails to verify and
112
+ // every spec below it reports a 401 that has nothing to do with its subject.
113
+ if (url === jwksUrl(GATEWAY_ORIGIN)) {
114
+ return new Response(gatewayPublicJwks(), {
115
+ headers: { "content-type": "application/json" }
116
+ });
117
+ }
118
+ if (url === pushUrl) {
119
+ const request = new Request(input, init);
120
+ const body = await request.json().catch(() => null);
121
+ callbacks.push(projectCallback(body, request.headers.get(NOTIFICATION_TOKEN_HEADER)));
122
+ return new Response(null, { status: 202 });
123
+ }
124
+ // Anything else is a call this harness did not expect. 404 rather than
125
+ // pass through: a spec that silently reaches the real network is a spec
126
+ // that passes for the wrong reason.
127
+ return new Response(`unexpected outbound fetch: ${url}`, {
128
+ status: 404
129
+ });
130
+ });
131
+ return {
132
+ [Symbol.dispose]() {
133
+ globalThis.fetch = original;
134
+ }
135
+ };
136
+ }
137
+ };
138
+ }
139
+ //# sourceMappingURL=harness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harness.js","sourceRoot":"","sources":["../../src/testing/harness.ts"],"names":[],"mappings":"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"}
@@ -26,6 +26,7 @@ export { VCR_CONTROL_ORIGIN, VCR_MARKER_HEADER, CASSETTE_NAME_RE, type VcrReleas
26
26
  export { FakeSession } from "./fake-session.js";
27
27
  export { mockModel, finalReply, type MockStep } from "./mock-model.js";
28
28
  export { makeGatewayToken, type GatewayTokenOptions } from "./auth.js";
29
- export { AGENT_ORIGIN, GATEWAY_ORIGIN, TEST_AGENT_PRIVATE_JWK, TEST_GATEWAY_PRIVATE_JWK, gatewayPublicJwks, testAgentMessage, testStatus, testTask } from "./fixtures.js";
29
+ export { AGENT_ORIGIN, GATEWAY_ORIGIN, TEST_AGENT_PRIVATE_JWK, TEST_GATEWAY_PRIVATE_JWK, TEST_MODELS, gatewayPublicJwks, testAgentMessage, testStatus, testTask } from "./fixtures.js";
30
30
  export { doStorage, makeDoHelpers, type DoTestHelpers } from "./do.js";
31
+ export { createAgentHarness, type AgentHarness, type AgentHarnessOptions, type CapturedCallback, type HarnessWorker } from "./harness.js";
31
32
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EACL,cAAc,EACd,eAAe,EACf,KAAK,qBAAqB,EAC3B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,KAAK,gBAAgB,EACtB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAEvE,OAAO,EAAE,gBAAgB,EAAE,KAAK,mBAAmB,EAAE,MAAM,WAAW,CAAC;AACvE,OAAO,EACL,YAAY,EACZ,cAAc,EACd,sBAAsB,EACtB,wBAAwB,EACxB,iBAAiB,EACjB,gBAAgB,EAChB,UAAU,EACV,QAAQ,EACT,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EACL,cAAc,EACd,eAAe,EACf,KAAK,qBAAqB,EAC3B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,KAAK,gBAAgB,EACtB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAEvE,OAAO,EAAE,gBAAgB,EAAE,KAAK,mBAAmB,EAAE,MAAM,WAAW,CAAC;AACvE,OAAO,EACL,YAAY,EACZ,cAAc,EACd,sBAAsB,EACtB,wBAAwB,EACxB,WAAW,EACX,iBAAiB,EACjB,gBAAgB,EAChB,UAAU,EACV,QAAQ,EACT,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,SAAS,CAAC;AAEvE,OAAO,EACL,kBAAkB,EAClB,KAAK,YAAY,EACjB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EACnB,MAAM,cAAc,CAAC"}
@@ -26,6 +26,7 @@ export { VCR_CONTROL_ORIGIN, VCR_MARKER_HEADER, CASSETTE_NAME_RE } from "./vcr-s
26
26
  export { FakeSession } from "./fake-session.js";
27
27
  export { mockModel, finalReply } from "./mock-model.js";
28
28
  export { makeGatewayToken } from "./auth.js";
29
- export { AGENT_ORIGIN, GATEWAY_ORIGIN, TEST_AGENT_PRIVATE_JWK, TEST_GATEWAY_PRIVATE_JWK, gatewayPublicJwks, testAgentMessage, testStatus, testTask } from "./fixtures.js";
29
+ export { AGENT_ORIGIN, GATEWAY_ORIGIN, TEST_AGENT_PRIVATE_JWK, TEST_GATEWAY_PRIVATE_JWK, TEST_MODELS, gatewayPublicJwks, testAgentMessage, testStatus, testTask } from "./fixtures.js";
30
30
  export { doStorage, makeDoHelpers } from "./do.js";
31
+ export { createAgentHarness } from "./harness.js";
31
32
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EACL,cAAc,EACd,eAAe,EAEhB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAEjB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAiB,MAAM,iBAAiB,CAAC;AAEvE,OAAO,EAAE,gBAAgB,EAA4B,MAAM,WAAW,CAAC;AACvE,OAAO,EACL,YAAY,EACZ,cAAc,EACd,sBAAsB,EACtB,wBAAwB,EACxB,iBAAiB,EACjB,gBAAgB,EAChB,UAAU,EACV,QAAQ,EACT,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,SAAS,EAAE,aAAa,EAAsB,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EACL,cAAc,EACd,eAAe,EAEhB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAEjB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAiB,MAAM,iBAAiB,CAAC;AAEvE,OAAO,EAAE,gBAAgB,EAA4B,MAAM,WAAW,CAAC;AACvE,OAAO,EACL,YAAY,EACZ,cAAc,EACd,sBAAsB,EACtB,wBAAwB,EACxB,WAAW,EACX,iBAAiB,EACjB,gBAAgB,EAChB,UAAU,EACV,QAAQ,EACT,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,SAAS,EAAE,aAAa,EAAsB,MAAM,SAAS,CAAC;AAEvE,OAAO,EACL,kBAAkB,EAKnB,MAAM,cAAc,CAAC"}
@@ -5,7 +5,7 @@ export interface RecordedRequest {
5
5
  headers: Record<string, string | string[]>;
6
6
  body: string;
7
7
  }
8
- /** A stored response. `body` is base64, as `SnapshotAgent` wrote it. */
8
+ /** A stored response. `body` is base64, so a binary payload survives the file. */
9
9
  export interface RecordedResponse {
10
10
  statusCode: number;
11
11
  headers: Record<string, string | string[]>;
@@ -48,10 +48,10 @@ export declare function replayable(response: RecordedResponse): ReplayableRespon
48
48
  * One cassette file, loaded lazily and written only when it has changed.
49
49
  *
50
50
  * Playback is **read-only**: nothing here mutates on a replay, which is what
51
- * stops a plain `npm test` from dirtying committed cassettes. `SnapshotAgent`
52
- * persisted its `callCount` and re-saved on close, so every run left the files
53
- * modified; the equivalent counter here ({@link #calls}) is in memory and dies
54
- * with the process.
51
+ * stops a plain `npm test` from dirtying committed cassettes. The sequence
52
+ * counter ({@link #calls}) is in memory and dies with the process, deliberately
53
+ * `SnapshotAgent` persisted its equivalent and re-saved on close, so every
54
+ * run left every cassette modified in git.
55
55
  */
56
56
  export declare class Cassette {
57
57
  #private;
@@ -60,16 +60,13 @@ export declare class Cassette {
60
60
  /** Every request in the file, as `METHOD url`, for error messages. */
61
61
  get recorded(): string[];
62
62
  /**
63
- * Read the file into memory. Tolerates both shapes: `SnapshotAgent` wrapped
64
- * each entry in `{ hash, snapshot: { request, responses } }`, this writes the
65
- * same wrapper (so a re-record diffs cleanly against an old cassette) but
66
- * ignores `hash`, `callCount` and `timestamp` — all three were volatile
67
- * bookkeeping and none is needed to replay.
63
+ * Read the file into memory, keying each entry by its own request rather than
64
+ * trusting anything stored alongside it.
68
65
  *
69
- * Entries are re-keyed on load rather than trusted, which is the point of the
70
- * rewrite. Two legacy entries that differed *only* by a request header now
71
- * collapse onto one key; their responses are concatenated in file order,
72
- * which is exactly the sequential-response semantics {@link match} walks.
66
+ * Two entries that key the same are a mistake rather than a merge: a request
67
+ * that was issued twice belongs in one entry with two `responses`, which is
68
+ * what {@link match} walks. Saying so here turns a hand-edit slip into a named
69
+ * error instead of a replay that silently serves the wrong response second.
73
70
  */
74
71
  load(): void;
75
72
  /**
@@ -77,10 +74,9 @@ export declare class Cassette {
77
74
  * recorded.
78
75
  *
79
76
  * Repeated identical requests walk `responses` in order and then **hold on
80
- * the last one**, matching what `SnapshotAgent` did
81
- * (`responses[min(callCount, length - 1)]`). A recording that captured one
82
- * response therefore replays it for every call, which is what a poll loop
83
- * needs.
77
+ * the last one**, so a recording that captured a single response replays it
78
+ * for every call which is what a poll loop needs when it runs a different
79
+ * number of times on replay than it did while recording.
84
80
  */
85
81
  match(method: string, url: string, body: string): ReplayableResponse | null;
86
82
  /** Append a live response. Repeats of a request extend its `responses`. */
@@ -1 +1 @@
1
- {"version":3,"file":"vcr-store.d.ts","sourceRoot":"","sources":["../../src/testing/vcr-store.ts"],"names":[],"mappings":"AAyBA,uFAAuF;AACvF,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC3C,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wEAAwE;AACxE,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,eAAe,CAAC;IACzB,SAAS,EAAE,gBAAgB,EAAE,CAAC;CAC/B;AAsBD,qFAAqF;AACrF,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;IAC5B,IAAI,EAAE,UAAU,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,iFAAiF;IACjF,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,sEAAsE;AACtE,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAG5E;AAaD;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,gBAAgB,GAAG,kBAAkB,CAazE;AAED;;;;;;;;GAQG;AACH,qBAAa,QAAQ;;gBASP,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB;IAOvD,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,sEAAsE;IACtE,IAAI,QAAQ,IAAI,MAAM,EAAE,CAIvB;IAED;;;;;;;;;;;OAWG;IACH,IAAI,IAAI,IAAI;IAiBZ;;;;;;;;;OASG;IACH,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI;IAW3E,2EAA2E;IAC3E,MAAM,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,gBAAgB,GAAG,IAAI;IAqBlE,gFAAgF;IAChF,KAAK,IAAI,IAAI;CASd"}
1
+ {"version":3,"file":"vcr-store.d.ts","sourceRoot":"","sources":["../../src/testing/vcr-store.ts"],"names":[],"mappings":"AAgCA,uFAAuF;AACvF,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC3C,IAAI,EAAE,MAAM,CAAC;CACd;AAED,kFAAkF;AAClF,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,eAAe,CAAC;IACzB,SAAS,EAAE,gBAAgB,EAAE,CAAC;CAC/B;AAsBD,qFAAqF;AACrF,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;IAC5B,IAAI,EAAE,UAAU,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,iFAAiF;IACjF,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,sEAAsE;AACtE,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAG5E;AAaD;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,gBAAgB,GAAG,kBAAkB,CAazE;AAED;;;;;;;;GAQG;AACH,qBAAa,QAAQ;;gBASP,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB;IAOvD,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,sEAAsE;IACtE,IAAI,QAAQ,IAAI,MAAM,EAAE,CAIvB;IAED;;;;;;;;OAQG;IACH,IAAI,IAAI,IAAI;IAoBZ;;;;;;;;OAQG;IACH,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI;IAW3E,2EAA2E;IAC3E,MAAM,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,gBAAgB,GAAG,IAAI;IAqBlE,gFAAgF;IAChF,KAAK,IAAI,IAAI;CAOd"}
@@ -16,8 +16,15 @@
16
16
  * are still stored (minus secrets, see {@link CassetteOptions.excludeHeaders})
17
17
  * because they are useful to read in a diff, but they cannot break a replay.
18
18
  *
19
- * The on-disk format is deliberately unchanged from `SnapshotAgent`'s, so
20
- * cassettes recorded before this rewrite replay untouched see {@link load}.
19
+ * The on-disk format owes `SnapshotAgent` nothing: a flat array of
20
+ * {@link CassetteEntry}. Cassettes it wrote are not readable, and deliberately
21
+ * so — its recorder keyed on `String(opts.body)`, and a Worker's POST body
22
+ * reaches a dispatcher as a `ReadableStream`, so every streamed request it ever
23
+ * captured stored the literal text `[object ReadableStream]` where the payload
24
+ * belonged. Those bodies are not recoverable from the file, and a reader that
25
+ * papered over it would have to match such entries on method + URL alone —
26
+ * quietly reintroducing the ambiguity this store exists to remove. Re-record
27
+ * instead.
21
28
  */
22
29
  import { createHash } from "node:crypto";
23
30
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
@@ -81,10 +88,10 @@ export function replayable(response) {
81
88
  * One cassette file, loaded lazily and written only when it has changed.
82
89
  *
83
90
  * Playback is **read-only**: nothing here mutates on a replay, which is what
84
- * stops a plain `npm test` from dirtying committed cassettes. `SnapshotAgent`
85
- * persisted its `callCount` and re-saved on close, so every run left the files
86
- * modified; the equivalent counter here ({@link #calls}) is in memory and dies
87
- * with the process.
91
+ * stops a plain `npm test` from dirtying committed cassettes. The sequence
92
+ * counter ({@link #calls}) is in memory and dies with the process, deliberately
93
+ * `SnapshotAgent` persisted its equivalent and re-saved on close, so every
94
+ * run left every cassette modified in git.
88
95
  */
89
96
  export class Cassette {
90
97
  #file;
@@ -106,16 +113,13 @@ export class Cassette {
106
113
  return [...this.#entries.values()].map((e) => `${e.request.method} ${e.request.url}`);
107
114
  }
108
115
  /**
109
- * Read the file into memory. Tolerates both shapes: `SnapshotAgent` wrapped
110
- * each entry in `{ hash, snapshot: { request, responses } }`, this writes the
111
- * same wrapper (so a re-record diffs cleanly against an old cassette) but
112
- * ignores `hash`, `callCount` and `timestamp` — all three were volatile
113
- * bookkeeping and none is needed to replay.
116
+ * Read the file into memory, keying each entry by its own request rather than
117
+ * trusting anything stored alongside it.
114
118
  *
115
- * Entries are re-keyed on load rather than trusted, which is the point of the
116
- * rewrite. Two legacy entries that differed *only* by a request header now
117
- * collapse onto one key; their responses are concatenated in file order,
118
- * which is exactly the sequential-response semantics {@link match} walks.
119
+ * Two entries that key the same are a mistake rather than a merge: a request
120
+ * that was issued twice belongs in one entry with two `responses`, which is
121
+ * what {@link match} walks. Saying so here turns a hand-edit slip into a named
122
+ * error instead of a replay that silently serves the wrong response second.
119
123
  */
120
124
  load() {
121
125
  if (!existsSync(this.#file))
@@ -124,16 +128,15 @@ export class Cassette {
124
128
  if (!Array.isArray(raw)) {
125
129
  throw new Error(`VCR cassette ${this.#file} is not a JSON array`);
126
130
  }
127
- for (const item of raw) {
128
- const entry = (item.snapshot ??
129
- item);
131
+ for (const entry of raw) {
130
132
  const { request, responses } = entry;
131
133
  const key = requestKey(request.method, request.url, request.body ?? "");
132
- const existing = this.#entries.get(key);
133
- if (existing)
134
- existing.responses.push(...responses);
135
- else
136
- this.#entries.set(key, { request, responses: [...responses] });
134
+ if (this.#entries.has(key)) {
135
+ throw new Error(`VCR cassette ${path.basename(this.#file)} has two entries for ` +
136
+ `${request.method} ${request.url} with the same body. Put the ` +
137
+ `responses in one entry's \`responses\` array instead.`);
138
+ }
139
+ this.#entries.set(key, { request, responses: [...responses] });
137
140
  }
138
141
  }
139
142
  /**
@@ -141,10 +144,9 @@ export class Cassette {
141
144
  * recorded.
142
145
  *
143
146
  * Repeated identical requests walk `responses` in order and then **hold on
144
- * the last one**, matching what `SnapshotAgent` did
145
- * (`responses[min(callCount, length - 1)]`). A recording that captured one
146
- * response therefore replays it for every call, which is what a poll loop
147
- * needs.
147
+ * the last one**, so a recording that captured a single response replays it
148
+ * for every call which is what a poll loop needs when it runs a different
149
+ * number of times on replay than it did while recording.
148
150
  */
149
151
  match(method, url, body) {
150
152
  const key = requestKey(method, url, body);
@@ -182,9 +184,7 @@ export class Cassette {
182
184
  if (!this.#dirty)
183
185
  return;
184
186
  mkdirSync(path.dirname(this.#file), { recursive: true });
185
- const entries = [...this.#entries.values()].map((snapshot) => ({
186
- snapshot
187
- }));
187
+ const entries = [...this.#entries.values()];
188
188
  writeFileSync(this.#file, `${JSON.stringify(entries, null, 2)}\n`);
189
189
  this.#dirty = false;
190
190
  }
@@ -1 +1 @@
1
- {"version":3,"file":"vcr-store.js","sourceRoot":"","sources":["../../src/testing/vcr-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"vcr-store.js","sourceRoot":"","sources":["../../src/testing/vcr-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,124 @@
1
+ import type { AgentManifest } from "../a2a/card.js";
2
+ import type { GatewayIdentity } 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: GatewayIdentity): 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 gateway
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: GatewayIdentity): DurableObjectStub<TAgent>;
113
+ /**
114
+ * Start this agent's durable turn, idempotently.
115
+ *
116
+ * The instance id is derived from the gateway'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>;
124
+ //# sourceMappingURL=define-agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-agent.d.ts","sourceRoot":"","sources":["../../src/worker/define-agent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,oBAAoB,CAAC;AAE5B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAEH;;;;GAIG;AACH,MAAM,WAAW,YAAY,CAAC,IAAI;IAChC,wDAAwD;IACxD,MAAM,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,QAAQ,EAAE,aAAa,CAAC;IACxB,YAAY,CAAC,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,eAAe,GAAG,SAAS,CAAC;IAC9D,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzD;AAED;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,kBAAkB,CACjC,IAAI,EACJ,MAAM,SAAS,GAAG,CAAC,oBAAoB;IAEvC;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,QAAQ,EAAE,aAAa,CAAC;IACxB;;;OAGG;IACH,KAAK,EAAE,CAAC,GAAG,EAAE,IAAI,KAAK,sBAAsB,CAAC,MAAM,CAAC,CAAC;IACrD,8CAA8C;IAC9C,QAAQ,EAAE,CAAC,GAAG,EAAE,IAAI,KAAK,QAAQ,CAAC,YAAY,CAAC,CAAC;CACjD;AAED,MAAM,WAAW,eAAe,CAC9B,IAAI,EACJ,MAAM,SAAS,GAAG,CAAC,oBAAoB;IAEvC,wDAAwD;IACxD,MAAM,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,QAAQ,EAAE,aAAa,CAAC;IACxB;;;;;;;;;;OAUG;IACH,YAAY,CAAC,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,eAAe,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC9E;;;;;;;OAOG;IACH,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzD;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,SAAS,GAAG,CAAC,oBAAoB,EACvE,OAAO,EAAE,kBAAkB,CAAC,IAAI,EAAE,MAAM,CAAC,GACxC,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,CAsB/B"}
@@ -0,0 +1,21 @@
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
+ }
21
+ //# sourceMappingURL=define-agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-agent.js","sourceRoot":"","sources":["../../src/worker/define-agent.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EAErB,MAAM,oBAAoB,CAAC;AAmI5B,MAAM,UAAU,WAAW,CACzB,OAAyC;IAEzC,OAAO;QACL,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAE1B,YAAY,CAAC,GAAG,EAAE,QAAQ;YACxB,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC;gBAClB,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;YACvE,CAAC;YACD,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAC9B,OAAO,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;QAC7C,CAAC;QAED,SAAS,CAAC,GAAG,EAAE,IAAI;YACjB,OAAO,mBAAmB,CAAC,GAAG,EAAE,CAC9B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;gBAC3B,EAAE,EAAE,oBAAoB,CAAC,IAAI,CAAC,SAAS,CAAC;gBACxC,MAAM,EAAE,EAAE,GAAG,IAAI,EAAE;aACpB,CAAC,CACH,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -2,6 +2,7 @@ import { type AgentManifest } from "../a2a/card.js";
2
2
  import { type TurnStarter } from "../a2a/executor.js";
3
3
  import type { AgentResolver } from "../a2a/agent-stub.js";
4
4
  import { type A2ASecretsEnv } from "../env.js";
5
+ import type { MountedAgent } from "./define-agent.js";
5
6
  /**
6
7
  * The A2A Worker: the zero-trust, no-shared-secrets edge every Looping agent
7
8
  * puts in front of its Durable Object.
@@ -53,8 +54,17 @@ import { type A2ASecretsEnv } from "../env.js";
53
54
  * agent on it*. The second is what makes `tenant` more than an unauthenticated
54
55
  * routing hint in the request body.
55
56
  */
56
- /** Default path serving the card-signing public JWKS (the card's `jku`). */
57
- export declare const JWKS_PATH = "/.well-known/jwks.json";
57
+ export { defineAgent, type AgentDefinition, type DefineAgentOptions, type MountedAgent } from "./define-agent.js";
58
+ /**
59
+ * Default path serving the card-signing public JWKS (the card's `jku`).
60
+ *
61
+ * From `@loopingai/a2a-protocol`, because the gateway serves its own JWKS at
62
+ * the same path and fetches ours from whatever `jku` says — see
63
+ * {@link file://../a2a/verify.ts} for why the two sides share a package rather
64
+ * than a comment. Re-exported so this stays importable from
65
+ * `@loopingai/core/worker`, where it has always lived.
66
+ */
67
+ export { JWKS_PATH } from "@loopingai/a2a-protocol";
58
68
  /** The two secrets a mount signs and verifies with, already read off `env`. */
59
69
  export interface A2ASecrets {
60
70
  /** Ed25519 private JWK, as JSON. See {@link A2ASecretsEnv.A2A_SIGNING_KEY}. */
@@ -88,7 +98,7 @@ export interface A2AWorkerOptions<TEnv = A2ASecretsEnv> {
88
98
  */
89
99
  manifest: AgentManifest;
90
100
  /**
91
- * The agents on this origin, keyed by tenant id. At least one.
101
+ * The agents on this origin, keyed by tenant id.
92
102
  *
93
103
  * The id is opaque to the protocol — "an opaque string used for routing
94
104
  * requests to a specific agent or tenant when multiple agents are served
@@ -98,8 +108,23 @@ export interface A2AWorkerOptions<TEnv = A2ASecretsEnv> {
98
108
  * The tenant chooses *which agent*; the verified `identity.key` still chooses
99
109
  * *which instance of it*, so two callers of one tenant stay in separate
100
110
  * Durable Objects exactly as before.
111
+ *
112
+ * The escape hatch, and still fully supported: use it for an agent whose
113
+ * routing or turn-start is genuinely its own. {@link A2AWorkerOptions.agents}
114
+ * is the shorter form for the ordinary case. Supply at least one of the two.
115
+ */
116
+ tenants?: Record<string, TenantAgent>;
117
+ /**
118
+ * The agents on this origin, each declared once with
119
+ * {@link file://./define-agent.ts defineAgent}.
120
+ *
121
+ * Prefer this to {@link A2AWorkerOptions.tenants}: one declaration produces the
122
+ * tenant entry here *and* the resolver the agent's Workflow entrypoint uses, so
123
+ * the two cannot drift, and the binding names are type-constrained to bindings
124
+ * of the right kind. Merged over `tenants`, which is checked for duplicate
125
+ * tenant ids rather than silently overriding.
101
126
  */
102
- tenants: Record<string, TenantAgent>;
127
+ agents?: readonly MountedAgent<TEnv>[];
103
128
  /** Path serving the public JWKS. Defaults to {@link JWKS_PATH}. */
104
129
  jwksPath?: string;
105
130
  /** Path this agent answers JSON-RPC on. Defaults to `/a2a`. */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/worker/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAOL,KAAK,aAAa,EACnB,MAAM,gBAAgB,CAAC;AAQxB,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAEpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAEH,4EAA4E;AAC5E,eAAO,MAAM,SAAS,2BAA2B,CAAC;AAKlD,+EAA+E;AAC/E,MAAM,WAAW,UAAU;IACzB,+EAA+E;IAC/E,UAAU,EAAE,MAAM,CAAC;IACnB,mEAAmE;IACnE,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,iFAAiF;AACjF,MAAM,WAAW,WAAW;IAC1B,2DAA2D;IAC3D,QAAQ,EAAE,aAAa,CAAC;IACxB,uDAAuD;IACvD,YAAY,EAAE,aAAa,CAAC;IAC5B,4EAA4E;IAC5E,SAAS,EAAE,WAAW,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB,CAAC,IAAI,GAAG,aAAa;IACpD;;;;;;;;;;;;;OAaG;IACH,QAAQ,EAAE,aAAa,CAAC;IACxB;;;;;;;;;;;OAWG;IACH,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACrC,mEAAmE;IACnE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,IAAI,KAAK,UAAU,CAAC;IACpC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,KAAK,MAAM,CAAC,CAAC;IAC3C;;;;OAIG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,6EAA6E;IAC7E,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;OAOG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AA2ED;;;;;;;;;;;;;;;;;;GAkBG;AAEH,wBAAgB,eAAe,CAAC,IAAI,SAAS,aAAa,EACxD,OAAO,EAAE,gBAAgB,CAAC,IAAI,CAAC,GAC9B,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEtD,wBAAgB,eAAe,CAAC,IAAI,SAAS,MAAM,EACjD,OAAO,EAAE,gBAAgB,CAAC,IAAI,CAAC,GAAG;IAChC,OAAO,EAAE,CAAC,GAAG,EAAE,IAAI,KAAK,UAAU,CAAC;CACpC,GACA,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/worker/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAOL,KAAK,aAAa,EACnB,MAAM,gBAAgB,CAAC;AAQxB,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AACpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAEH,OAAO,EACL,WAAW,EACX,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,YAAY,EAClB,MAAM,mBAAmB,CAAC;AAE3B;;;;;;;;GAQG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAKpD,+EAA+E;AAC/E,MAAM,WAAW,UAAU;IACzB,+EAA+E;IAC/E,UAAU,EAAE,MAAM,CAAC;IACnB,mEAAmE;IACnE,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,iFAAiF;AACjF,MAAM,WAAW,WAAW;IAC1B,2DAA2D;IAC3D,QAAQ,EAAE,aAAa,CAAC;IACxB,uDAAuD;IACvD,YAAY,EAAE,aAAa,CAAC;IAC5B,4EAA4E;IAC5E,SAAS,EAAE,WAAW,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB,CAAC,IAAI,GAAG,aAAa;IACpD;;;;;;;;;;;;;OAaG;IACH,QAAQ,EAAE,aAAa,CAAC;IACxB;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACtC;;;;;;;;;OASG;IACH,MAAM,CAAC,EAAE,SAAS,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;IACvC,mEAAmE;IACnE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,IAAI,KAAK,UAAU,CAAC;IACpC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,KAAK,MAAM,CAAC,CAAC;IAC3C;;;;OAIG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,6EAA6E;IAC7E,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;OAOG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AA2ED;;;;;;;;;;;;;;;;;;GAkBG;AAEH,wBAAgB,eAAe,CAAC,IAAI,SAAS,aAAa,EACxD,OAAO,EAAE,gBAAgB,CAAC,IAAI,CAAC,GAC9B,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEtD,wBAAgB,eAAe,CAAC,IAAI,SAAS,MAAM,EACjD,OAAO,EAAE,gBAAgB,CAAC,IAAI,CAAC,GAAG;IAChC,OAAO,EAAE,CAAC,GAAG,EAAE,IAAI,KAAK,UAAU,CAAC;CACpC,GACA,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC"}