@loopingai/core 0.1.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 +674 -0
- package/README.md +279 -0
- package/dist/a2a/agent-stub.d.ts +61 -0
- package/dist/a2a/agent-stub.d.ts.map +1 -0
- package/dist/a2a/agent-stub.js +2 -0
- package/dist/a2a/agent-stub.js.map +1 -0
- package/dist/a2a/card.d.ts +119 -0
- package/dist/a2a/card.d.ts.map +1 -0
- package/dist/a2a/card.js +115 -0
- package/dist/a2a/card.js.map +1 -0
- package/dist/a2a/context.d.ts +14 -0
- package/dist/a2a/context.d.ts.map +1 -0
- package/dist/a2a/context.js +55 -0
- package/dist/a2a/context.js.map +1 -0
- package/dist/a2a/executor.d.ts +85 -0
- package/dist/a2a/executor.d.ts.map +1 -0
- package/dist/a2a/executor.js +107 -0
- package/dist/a2a/executor.js.map +1 -0
- package/dist/a2a/index.d.ts +18 -0
- package/dist/a2a/index.d.ts.map +1 -0
- package/dist/a2a/index.js +17 -0
- package/dist/a2a/index.js.map +1 -0
- package/dist/a2a/notify.d.ts +75 -0
- package/dist/a2a/notify.d.ts.map +1 -0
- package/dist/a2a/notify.js +161 -0
- package/dist/a2a/notify.js.map +1 -0
- package/dist/a2a/parts.d.ts +46 -0
- package/dist/a2a/parts.d.ts.map +1 -0
- package/dist/a2a/parts.js +95 -0
- package/dist/a2a/parts.js.map +1 -0
- package/dist/a2a/task-store.d.ts +45 -0
- package/dist/a2a/task-store.d.ts.map +1 -0
- package/dist/a2a/task-store.js +100 -0
- package/dist/a2a/task-store.js.map +1 -0
- package/dist/a2a/task.d.ts +86 -0
- package/dist/a2a/task.d.ts.map +1 -0
- package/dist/a2a/task.js +16 -0
- package/dist/a2a/task.js.map +1 -0
- package/dist/a2a/verify.d.ts +64 -0
- package/dist/a2a/verify.d.ts.map +1 -0
- package/dist/a2a/verify.js +122 -0
- package/dist/a2a/verify.js.map +1 -0
- package/dist/agent/budget.d.ts +47 -0
- package/dist/agent/budget.d.ts.map +1 -0
- package/dist/agent/budget.js +43 -0
- package/dist/agent/budget.js.map +1 -0
- package/dist/agent/control.d.ts +110 -0
- package/dist/agent/control.d.ts.map +1 -0
- package/dist/agent/control.js +115 -0
- package/dist/agent/control.js.map +1 -0
- package/dist/agent/final-reply.d.ts +49 -0
- package/dist/agent/final-reply.d.ts.map +1 -0
- package/dist/agent/final-reply.js +52 -0
- package/dist/agent/final-reply.js.map +1 -0
- package/dist/agent/history.d.ts +83 -0
- package/dist/agent/history.d.ts.map +1 -0
- package/dist/agent/history.js +117 -0
- package/dist/agent/history.js.map +1 -0
- package/dist/agent/index.d.ts +17 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +17 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/inference.d.ts +52 -0
- package/dist/agent/inference.d.ts.map +1 -0
- package/dist/agent/inference.js +79 -0
- package/dist/agent/inference.js.map +1 -0
- package/dist/agent/model.d.ts +75 -0
- package/dist/agent/model.d.ts.map +1 -0
- package/dist/agent/model.js +62 -0
- package/dist/agent/model.js.map +1 -0
- package/dist/agent/session.d.ts +92 -0
- package/dist/agent/session.d.ts.map +1 -0
- package/dist/agent/session.js +80 -0
- package/dist/agent/session.js.map +1 -0
- package/dist/config.d.ts +159 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +92 -0
- package/dist/config.js.map +1 -0
- package/dist/contract/index.d.ts +10 -0
- package/dist/contract/index.d.ts.map +1 -0
- package/dist/contract/index.js +9 -0
- package/dist/contract/index.js.map +1 -0
- package/dist/contract/plugin.d.ts +163 -0
- package/dist/contract/plugin.d.ts.map +1 -0
- package/dist/contract/plugin.js +39 -0
- package/dist/contract/plugin.js.map +1 -0
- package/dist/contract/recipe.d.ts +170 -0
- package/dist/contract/recipe.d.ts.map +1 -0
- package/dist/contract/recipe.js +2 -0
- package/dist/contract/recipe.js.map +1 -0
- package/dist/contract/validation.d.ts +86 -0
- package/dist/contract/validation.d.ts.map +1 -0
- package/dist/contract/validation.js +76 -0
- package/dist/contract/validation.js.map +1 -0
- package/dist/db/db.d.ts +131 -0
- package/dist/db/db.d.ts.map +1 -0
- package/dist/db/db.js +91 -0
- package/dist/db/db.js.map +1 -0
- package/dist/db/index.d.ts +9 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +9 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/migrations/index.d.ts +21 -0
- package/dist/db/migrations/index.d.ts.map +1 -0
- package/dist/db/migrations/index.js +54 -0
- package/dist/db/migrations/index.js.map +1 -0
- package/dist/db/models/subtasks.d.ts +102 -0
- package/dist/db/models/subtasks.d.ts.map +1 -0
- package/dist/db/models/subtasks.js +285 -0
- package/dist/db/models/subtasks.js.map +1 -0
- package/dist/db/models/tasks.d.ts +92 -0
- package/dist/db/models/tasks.d.ts.map +1 -0
- package/dist/db/models/tasks.js +212 -0
- package/dist/db/models/tasks.js.map +1 -0
- package/dist/db/schema.d.ts +488 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +91 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/env.d.ts +46 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +40 -0
- package/dist/env.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/platform.d.ts +47 -0
- package/dist/platform.d.ts.map +1 -0
- package/dist/platform.js +47 -0
- package/dist/platform.js.map +1 -0
- package/dist/runtime/index.d.ts +86 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +123 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/tool-families.d.ts +33 -0
- package/dist/runtime/tool-families.d.ts.map +1 -0
- package/dist/runtime/tool-families.js +62 -0
- package/dist/runtime/tool-families.js.map +1 -0
- package/dist/subagent/fingerprint.d.ts +38 -0
- package/dist/subagent/fingerprint.d.ts.map +1 -0
- package/dist/subagent/fingerprint.js +97 -0
- package/dist/subagent/fingerprint.js.map +1 -0
- package/dist/subagent/index.d.ts +147 -0
- package/dist/subagent/index.d.ts.map +1 -0
- package/dist/subagent/index.js +299 -0
- package/dist/subagent/index.js.map +1 -0
- package/dist/subagent/prompt.d.ts +34 -0
- package/dist/subagent/prompt.d.ts.map +1 -0
- package/dist/subagent/prompt.js +46 -0
- package/dist/subagent/prompt.js.map +1 -0
- package/dist/subagent/run.d.ts +151 -0
- package/dist/subagent/run.d.ts.map +1 -0
- package/dist/subagent/run.js +491 -0
- package/dist/subagent/run.js.map +1 -0
- package/dist/subagent/workspace.d.ts +65 -0
- package/dist/subagent/workspace.d.ts.map +1 -0
- package/dist/subagent/workspace.js +53 -0
- package/dist/subagent/workspace.js.map +1 -0
- package/dist/subtasks/catalog.d.ts +40 -0
- package/dist/subtasks/catalog.d.ts.map +1 -0
- package/dist/subtasks/catalog.js +36 -0
- package/dist/subtasks/catalog.js.map +1 -0
- package/dist/subtasks/decomposition.d.ts +90 -0
- package/dist/subtasks/decomposition.d.ts.map +1 -0
- package/dist/subtasks/decomposition.js +205 -0
- package/dist/subtasks/decomposition.js.map +1 -0
- package/dist/subtasks/delegate.d.ts +77 -0
- package/dist/subtasks/delegate.d.ts.map +1 -0
- package/dist/subtasks/delegate.js +102 -0
- package/dist/subtasks/delegate.js.map +1 -0
- package/dist/subtasks/index.d.ts +11 -0
- package/dist/subtasks/index.d.ts.map +1 -0
- package/dist/subtasks/index.js +11 -0
- package/dist/subtasks/index.js.map +1 -0
- package/dist/subtasks/scheduler.d.ts +49 -0
- package/dist/subtasks/scheduler.d.ts.map +1 -0
- package/dist/subtasks/scheduler.js +48 -0
- package/dist/subtasks/scheduler.js.map +1 -0
- package/dist/subtasks/subtask-types.d.ts +100 -0
- package/dist/subtasks/subtask-types.d.ts.map +1 -0
- package/dist/subtasks/subtask-types.js +111 -0
- package/dist/subtasks/subtask-types.js.map +1 -0
- package/dist/subtasks/types.d.ts +321 -0
- package/dist/subtasks/types.d.ts.map +1 -0
- package/dist/subtasks/types.js +16 -0
- package/dist/subtasks/types.js.map +1 -0
- package/dist/testing/auth.d.ts +12 -0
- package/dist/testing/auth.d.ts.map +1 -0
- package/dist/testing/auth.js +25 -0
- package/dist/testing/auth.js.map +1 -0
- package/dist/testing/do.d.ts +30 -0
- package/dist/testing/do.d.ts.map +1 -0
- package/dist/testing/do.js +26 -0
- package/dist/testing/do.js.map +1 -0
- package/dist/testing/fake-session.d.ts +27 -0
- package/dist/testing/fake-session.d.ts.map +1 -0
- package/dist/testing/fake-session.js +38 -0
- package/dist/testing/fake-session.js.map +1 -0
- package/dist/testing/fixtures.d.ts +27 -0
- package/dist/testing/fixtures.d.ts.map +1 -0
- package/dist/testing/fixtures.js +67 -0
- package/dist/testing/fixtures.js.map +1 -0
- package/dist/testing/index.d.ts +31 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +31 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/mock-model.d.ts +43 -0
- package/dist/testing/mock-model.d.ts.map +1 -0
- package/dist/testing/mock-model.js +62 -0
- package/dist/testing/mock-model.js.map +1 -0
- package/dist/testing/node.d.ts +24 -0
- package/dist/testing/node.d.ts.map +1 -0
- package/dist/testing/node.js +24 -0
- package/dist/testing/node.js.map +1 -0
- package/dist/testing/vcr-global-setup.d.ts +3 -0
- package/dist/testing/vcr-global-setup.d.ts.map +1 -0
- package/dist/testing/vcr-global-setup.js +9 -0
- package/dist/testing/vcr-global-setup.js.map +1 -0
- package/dist/testing/vcr-shared.d.ts +21 -0
- package/dist/testing/vcr-shared.d.ts.map +1 -0
- package/dist/testing/vcr-shared.js +21 -0
- package/dist/testing/vcr-shared.js.map +1 -0
- package/dist/testing/vcr-spec.d.ts +17 -0
- package/dist/testing/vcr-spec.d.ts.map +1 -0
- package/dist/testing/vcr-spec.js +72 -0
- package/dist/testing/vcr-spec.js.map +1 -0
- package/dist/testing/vcr.d.ts +81 -0
- package/dist/testing/vcr.d.ts.map +1 -0
- package/dist/testing/vcr.js +211 -0
- package/dist/testing/vcr.js.map +1 -0
- package/dist/worker/index.d.ts +62 -0
- package/dist/worker/index.d.ts.map +1 -0
- package/dist/worker/index.js +194 -0
- package/dist/worker/index.js.map +1 -0
- package/eslint-rules/index.js +31 -0
- package/eslint-rules/no-deprecated-object-properties.js +81 -0
- package/package.json +147 -0
- package/scripts/generate-keys.mjs +53 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/a2a/context.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,UAAU,EACV,qBAAqB,EACtB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,+BAA+B,EAIhC,MAAM,oBAAoB,CAAC;AAG5B;;;;;;GAMG;AAEH;;;;;GAKG;AACH,MAAM,WAAW;IACc;IAA7B,YAA6B,QAAyB;QAAzB,aAAQ,GAAR,QAAQ,CAAiB;IAAG,CAAC;IAE1D,IAAI,eAAe;QACjB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,QAAQ;QACV,yEAAyE;QACzE,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,IAAI,EAAE,CAAC;IACjC,CAAC;CACF;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAC9B,OAAgB,EAChB,QAAyB;IAEzB,MAAM,OAAO,GAAmB,EAAE,CAAC;IACnC,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,OAAO,CAAC,OAAO;QAAE,OAAO,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;IAEnE,OAAO,+BAA+B,CAAC;QACrC,UAAU,EAAE,UAAU,CAAC,qBAAqB,CAC1C,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,IAAI,SAAS,CACxD;QACD,IAAI,EAAE,IAAI,WAAW,CAAC,QAAQ,CAAC;QAC/B,OAAO;QACP,gBAAgB,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,IAAI,SAAS;KACvE,CAAC,CAAC;AACL,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,gBAAgB,CAAC,OAA0B;IACzD,MAAM,SAAS,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAC9C,IAAI,CAAC,SAAS,EAAE,MAAM;QAAE,OAAO,EAAE,CAAC;IAClC,OAAO,EAAE,CAAC,qBAAqB,CAAC,EAAE,UAAU,CAAC,kBAAkB,CAAC,SAAS,CAAC,EAAE,CAAC;AAC/E,CAAC"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { type AgentExecutor, type ExecutionEventBus, type RequestContext } from "@a2a-js/sdk/server";
|
|
2
|
+
import type { GatewayIdentity } from "./verify.js";
|
|
3
|
+
import type { AgentResolver } from "./agent-stub.js";
|
|
4
|
+
/**
|
|
5
|
+
* Derive a deterministic workflow instance id for a turn. Keyed on the gateway's
|
|
6
|
+
* `messageId` (stable across dispatch retries), so re-creating it is a no-op —
|
|
7
|
+
* the turn runs exactly once. Sanitized to the id charset.
|
|
8
|
+
*/
|
|
9
|
+
export declare function workflowIdForMessage(messageId: string, prefix?: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Everything the executor knows about an accepted turn, handed to the
|
|
12
|
+
* consumer's {@link TurnStarter}.
|
|
13
|
+
*
|
|
14
|
+
* Core stops here deliberately: which workflow runs the turn, what else its
|
|
15
|
+
* params carry, and which binding it is created on are all the agent's, so core
|
|
16
|
+
* describes the turn and the agent starts it.
|
|
17
|
+
*/
|
|
18
|
+
export interface AcceptedTurn {
|
|
19
|
+
/** The gateway's message id — the idempotency key for the whole turn. */
|
|
20
|
+
messageId: string;
|
|
21
|
+
taskId: string;
|
|
22
|
+
contextId: string;
|
|
23
|
+
/** The caller's message, flattened to text. */
|
|
24
|
+
text: string;
|
|
25
|
+
identity: GatewayIdentity;
|
|
26
|
+
/** Webhook the terminal task is POSTed to. */
|
|
27
|
+
pushUrl: string;
|
|
28
|
+
/** Per-task validation token echoed on the callback. */
|
|
29
|
+
pushToken: string;
|
|
30
|
+
/** This agent's card-signing JWKS URL — the callback JWT `jku`. */
|
|
31
|
+
jku: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Start the durable turn. **Must be idempotent**: a dispatch retry calls it
|
|
35
|
+
* again with the same {@link AcceptedTurn.messageId}, and the conventional
|
|
36
|
+
* implementation swallows the workflow's "instance already exists" race — see
|
|
37
|
+
* {@link workflowIdForMessage} and {@link ignoreAlreadyExists}.
|
|
38
|
+
*/
|
|
39
|
+
export type TurnStarter = (turn: AcceptedTurn) => Promise<void>;
|
|
40
|
+
export interface ExecutorConfig {
|
|
41
|
+
identity: GatewayIdentity;
|
|
42
|
+
/** This agent's card-signing JWKS URL — the callback JWT `jku`. */
|
|
43
|
+
jku: string;
|
|
44
|
+
resolveAgent: AgentResolver;
|
|
45
|
+
startTurn: TurnStarter;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Run `create` and swallow the "instance already exists" retry race, so a
|
|
49
|
+
* {@link TurnStarter} is idempotent in the one way that actually happens.
|
|
50
|
+
*
|
|
51
|
+
* ```ts
|
|
52
|
+
* startTurn: (turn) =>
|
|
53
|
+
* ignoreAlreadyExists(() =>
|
|
54
|
+
* env.HANDLE_TASK_WORKFLOW.create({
|
|
55
|
+
* id: workflowIdForMessage(turn.messageId),
|
|
56
|
+
* params: { ...turn }
|
|
57
|
+
* })
|
|
58
|
+
* )
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export declare function ignoreAlreadyExists(create: () => Promise<unknown>): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* A2A executor for the **async accept + notify** contract. On `SendMessage` it
|
|
64
|
+
* does not block on generation: it records a `submitted` task in the caller's DO
|
|
65
|
+
* (idempotent on `messageId`), hands the turn to a durable workflow, and
|
|
66
|
+
* publishes the accepted task immediately as the response. The workflow
|
|
67
|
+
* generates the reply and POSTs it to the gateway's push-notification webhook
|
|
68
|
+
* out of band.
|
|
69
|
+
*
|
|
70
|
+
* The verified caller identity comes from the config — the outer Worker builds
|
|
71
|
+
* one executor per verified request. The push config comes from the request
|
|
72
|
+
* itself: v1.0 hands the executor the whole `SendMessageRequest` via
|
|
73
|
+
* {@link RequestContext.request}, so nothing has to be threaded around it.
|
|
74
|
+
*/
|
|
75
|
+
export declare class A2AExecutor implements AgentExecutor {
|
|
76
|
+
private readonly config;
|
|
77
|
+
constructor(config: ExecutorConfig);
|
|
78
|
+
execute: (requestContext: RequestContext, eventBus: ExecutionEventBus) => Promise<void>;
|
|
79
|
+
/**
|
|
80
|
+
* `CancelTask`: best-effort mark the task canceled in the DO and publish the
|
|
81
|
+
* canceled task. The in-flight workflow's `notify` step skips a canceled task.
|
|
82
|
+
*/
|
|
83
|
+
cancelTask: (taskId: string, eventBus: ExecutionEventBus) => Promise<void>;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=executor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../src/a2a/executor.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACpB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGrD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,MAAM,SAAS,GACd,MAAM,CAER;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,yEAAyE;IACzE,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,eAAe,CAAC;IAC1B,8CAA8C;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,wDAAwD;IACxD,SAAS,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEhE,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,eAAe,CAAC;IAC1B,mEAAmE;IACnE,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,aAAa,CAAC;IAC5B,SAAS,EAAE,WAAW,CAAC;CACxB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAC7B,OAAO,CAAC,IAAI,CAAC,CASf;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,WAAY,YAAW,aAAa;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,cAAc;IAEnD,OAAO,GACL,gBAAgB,cAAc,EAC9B,UAAU,iBAAiB,KAC1B,OAAO,CAAC,IAAI,CAAC,CA6Cd;IAEF;;;OAGG;IACH,UAAU,GACR,QAAQ,MAAM,EACd,UAAU,iBAAiB,KAC1B,OAAO,CAAC,IAAI,CAAC,CAMd;CACH"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { AgentEvent } from "@a2a-js/sdk/server";
|
|
2
|
+
import { textOf } from "./parts.js";
|
|
3
|
+
/**
|
|
4
|
+
* Derive a deterministic workflow instance id for a turn. Keyed on the gateway's
|
|
5
|
+
* `messageId` (stable across dispatch retries), so re-creating it is a no-op —
|
|
6
|
+
* the turn runs exactly once. Sanitized to the id charset.
|
|
7
|
+
*/
|
|
8
|
+
export function workflowIdForMessage(messageId, prefix = "turn") {
|
|
9
|
+
return `${prefix}-${messageId.replace(/[^A-Za-z0-9_-]/g, "-")}`;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Run `create` and swallow the "instance already exists" retry race, so a
|
|
13
|
+
* {@link TurnStarter} is idempotent in the one way that actually happens.
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* startTurn: (turn) =>
|
|
17
|
+
* ignoreAlreadyExists(() =>
|
|
18
|
+
* env.HANDLE_TASK_WORKFLOW.create({
|
|
19
|
+
* id: workflowIdForMessage(turn.messageId),
|
|
20
|
+
* params: { ...turn }
|
|
21
|
+
* })
|
|
22
|
+
* )
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export async function ignoreAlreadyExists(create) {
|
|
26
|
+
try {
|
|
27
|
+
await create();
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
if (err instanceof Error && /already exists|exist/i.test(err.message)) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
throw err;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A2A executor for the **async accept + notify** contract. On `SendMessage` it
|
|
38
|
+
* does not block on generation: it records a `submitted` task in the caller's DO
|
|
39
|
+
* (idempotent on `messageId`), hands the turn to a durable workflow, and
|
|
40
|
+
* publishes the accepted task immediately as the response. The workflow
|
|
41
|
+
* generates the reply and POSTs it to the gateway's push-notification webhook
|
|
42
|
+
* out of band.
|
|
43
|
+
*
|
|
44
|
+
* The verified caller identity comes from the config — the outer Worker builds
|
|
45
|
+
* one executor per verified request. The push config comes from the request
|
|
46
|
+
* itself: v1.0 hands the executor the whole `SendMessageRequest` via
|
|
47
|
+
* {@link RequestContext.request}, so nothing has to be threaded around it.
|
|
48
|
+
*/
|
|
49
|
+
export class A2AExecutor {
|
|
50
|
+
config;
|
|
51
|
+
constructor(config) {
|
|
52
|
+
this.config = config;
|
|
53
|
+
}
|
|
54
|
+
execute = async (requestContext, eventBus) => {
|
|
55
|
+
const pushConfig = requestContext.request.configuration?.taskPushNotificationConfig;
|
|
56
|
+
// Defensive: the Worker validates url + token before the executor runs, and
|
|
57
|
+
// must keep doing so — a throw here is turned into a `failed` task by the
|
|
58
|
+
// request handler, not into the JSON-RPC error the caller needs to see.
|
|
59
|
+
if (!pushConfig?.url || !pushConfig.token) {
|
|
60
|
+
throw new Error("taskPushNotificationConfig url and token are required");
|
|
61
|
+
}
|
|
62
|
+
const text = textOf(requestContext.userMessage);
|
|
63
|
+
const messageId = requestContext.userMessage.messageId;
|
|
64
|
+
const contextId = requestContext.contextId;
|
|
65
|
+
// `identity.key` is guaranteed non-null: the Worker rejects a keyless
|
|
66
|
+
// identity (400) before constructing this executor.
|
|
67
|
+
const stub = this.config.resolveAgent(this.config.identity);
|
|
68
|
+
// Record (or reuse) the submitted task, then start the durable turn. Both
|
|
69
|
+
// are idempotent, so a dispatch retry heals a crash between the two.
|
|
70
|
+
const accepted = await stub.beginTask({
|
|
71
|
+
messageId,
|
|
72
|
+
taskId: requestContext.taskId,
|
|
73
|
+
contextId
|
|
74
|
+
});
|
|
75
|
+
// Widened in one explicit step: DO-stub returns come back through
|
|
76
|
+
// Cloudflare's RPC type mapping, and letting that mapped type flow into a
|
|
77
|
+
// generic SDK call site instead exceeds TypeScript's instantiation depth on
|
|
78
|
+
// the v1.0 (proto-generated) model.
|
|
79
|
+
const task = accepted;
|
|
80
|
+
await this.config.startTurn({
|
|
81
|
+
messageId,
|
|
82
|
+
taskId: accepted.id,
|
|
83
|
+
contextId,
|
|
84
|
+
text,
|
|
85
|
+
identity: this.config.identity,
|
|
86
|
+
pushUrl: pushConfig.url,
|
|
87
|
+
pushToken: pushConfig.token,
|
|
88
|
+
jku: this.config.jku
|
|
89
|
+
});
|
|
90
|
+
// The accept ack: a `submitted` task, not a Message. Returned synchronously.
|
|
91
|
+
eventBus.publish(AgentEvent.task(task));
|
|
92
|
+
eventBus.finished();
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* `CancelTask`: best-effort mark the task canceled in the DO and publish the
|
|
96
|
+
* canceled task. The in-flight workflow's `notify` step skips a canceled task.
|
|
97
|
+
*/
|
|
98
|
+
cancelTask = async (taskId, eventBus) => {
|
|
99
|
+
const task = await this.config
|
|
100
|
+
.resolveAgent(this.config.identity)
|
|
101
|
+
.cancelTask(taskId);
|
|
102
|
+
if (task)
|
|
103
|
+
eventBus.publish(AgentEvent.task(task));
|
|
104
|
+
eventBus.finished();
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=executor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../src/a2a/executor.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EAIX,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEpC;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAClC,SAAiB,EACjB,MAAM,GAAG,MAAM;IAEf,OAAO,GAAG,MAAM,IAAI,SAAS,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,EAAE,CAAC;AAClE,CAAC;AA0CD;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,MAA8B;IAE9B,IAAI,CAAC;QACH,MAAM,MAAM,EAAE,CAAC;IACjB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,KAAK,IAAI,uBAAuB,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YACtE,OAAO;QACT,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,WAAW;IACO;IAA7B,YAA6B,MAAsB;QAAtB,WAAM,GAAN,MAAM,CAAgB;IAAG,CAAC;IAEvD,OAAO,GAAG,KAAK,EACb,cAA8B,EAC9B,QAA2B,EACZ,EAAE;QACjB,MAAM,UAAU,GACd,cAAc,CAAC,OAAO,CAAC,aAAa,EAAE,0BAA0B,CAAC;QACnE,4EAA4E;QAC5E,0EAA0E;QAC1E,wEAAwE;QACxE,IAAI,CAAC,UAAU,EAAE,GAAG,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;YAC1C,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAC;QAC3E,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;QAChD,MAAM,SAAS,GAAG,cAAc,CAAC,WAAW,CAAC,SAAS,CAAC;QACvD,MAAM,SAAS,GAAG,cAAc,CAAC,SAAS,CAAC;QAE3C,sEAAsE;QACtE,oDAAoD;QACpD,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAE5D,0EAA0E;QAC1E,qEAAqE;QACrE,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC;YACpC,SAAS;YACT,MAAM,EAAE,cAAc,CAAC,MAAM;YAC7B,SAAS;SACV,CAAC,CAAC;QACH,kEAAkE;QAClE,0EAA0E;QAC1E,4EAA4E;QAC5E,oCAAoC;QACpC,MAAM,IAAI,GAAS,QAAQ,CAAC;QAE5B,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC;YAC1B,SAAS;YACT,MAAM,EAAE,QAAQ,CAAC,EAAE;YACnB,SAAS;YACT,IAAI;YACJ,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ;YAC9B,OAAO,EAAE,UAAU,CAAC,GAAG;YACvB,SAAS,EAAE,UAAU,CAAC,KAAK;YAC3B,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG;SACrB,CAAC,CAAC;QAEH,6EAA6E;QAC7E,QAAQ,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QACxC,QAAQ,CAAC,QAAQ,EAAE,CAAC;IACtB,CAAC,CAAC;IAEF;;;OAGG;IACH,UAAU,GAAG,KAAK,EAChB,MAAc,EACd,QAA2B,EACZ,EAAE;QACjB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM;aAC3B,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC;aAClC,UAAU,CAAC,MAAM,CAAC,CAAC;QACtB,IAAI,IAAI;YAAE,QAAQ,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QAClD,QAAQ,CAAC,QAAQ,EAAE,CAAC;IACtB,CAAC,CAAC;CACH"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@loopingai/core/a2a` — the A2A protocol adapter.
|
|
3
|
+
*
|
|
4
|
+
* The zero-trust contract in both directions (verify the gateway with its public
|
|
5
|
+
* JWKS, prove ourselves with ours), the accept-and-notify task lifecycle, and the
|
|
6
|
+
* narrowed task types that survive Durable Object RPC. Nothing here knows what an
|
|
7
|
+
* agent *does* — past {@link A2AExecutor} everything is plain strings.
|
|
8
|
+
*/
|
|
9
|
+
export { IDENTITY_CLAIM, GatewayAuthError, bearerToken, normalizeGatewayOrigins, verifyGatewayToken, type GatewayIdentity, type VerifyOptions } from "./verify.js";
|
|
10
|
+
export { A2A_RPC_PATH, buildBaseCard, signCard, wireCard, parsePrivateJwk, publicCardJwks, type AgentManifest, type BuildCardOptions, type CardSigningConfig, type WireAgentCard } from "./card.js";
|
|
11
|
+
export { NOTIFICATION_TOKEN_HEADER, buildSubmittedTask, buildWorkingTask, buildCompletedTask, buildFailedTask, buildNoReplyCompletedTask, signCallbackJwt, postNotification } from "./notify.js";
|
|
12
|
+
export { taskStateLabel, type PlainArtifact, type PlainMessage, type PlainPart, type PlainStatus, type PlainTask } from "./task.js";
|
|
13
|
+
export { textPart, partsText, textOf, agentTextMessage, inboundText, InboundPartError, MAX_INBOUND_TEXT_BYTES } from "./parts.js";
|
|
14
|
+
export { buildCallContext, extensionHeaders } from "./context.js";
|
|
15
|
+
export { DurableTaskStore } from "./task-store.js";
|
|
16
|
+
export { A2AExecutor, workflowIdForMessage, ignoreAlreadyExists, type AcceptedTurn, type ExecutorConfig, type TurnStarter } from "./executor.js";
|
|
17
|
+
export type { AgentResolver, TaskAgent, TaskListPage, TaskListQuery } from "./agent-stub.js";
|
|
18
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/a2a/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,cAAc,EACd,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,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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@loopingai/core/a2a` — the A2A protocol adapter.
|
|
3
|
+
*
|
|
4
|
+
* The zero-trust contract in both directions (verify the gateway with its public
|
|
5
|
+
* JWKS, prove ourselves with ours), the accept-and-notify task lifecycle, and the
|
|
6
|
+
* narrowed task types that survive Durable Object RPC. Nothing here knows what an
|
|
7
|
+
* agent *does* — past {@link A2AExecutor} everything is plain strings.
|
|
8
|
+
*/
|
|
9
|
+
export { IDENTITY_CLAIM, GatewayAuthError, bearerToken, normalizeGatewayOrigins, verifyGatewayToken } from "./verify.js";
|
|
10
|
+
export { A2A_RPC_PATH, buildBaseCard, signCard, wireCard, parsePrivateJwk, publicCardJwks } from "./card.js";
|
|
11
|
+
export { NOTIFICATION_TOKEN_HEADER, buildSubmittedTask, buildWorkingTask, buildCompletedTask, buildFailedTask, buildNoReplyCompletedTask, signCallbackJwt, postNotification } from "./notify.js";
|
|
12
|
+
export { taskStateLabel } from "./task.js";
|
|
13
|
+
export { textPart, partsText, textOf, agentTextMessage, inboundText, InboundPartError, MAX_INBOUND_TEXT_BYTES } from "./parts.js";
|
|
14
|
+
export { buildCallContext, extensionHeaders } from "./context.js";
|
|
15
|
+
export { DurableTaskStore } from "./task-store.js";
|
|
16
|
+
export { A2AExecutor, workflowIdForMessage, ignoreAlreadyExists } from "./executor.js";
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/a2a/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,cAAc,EACd,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,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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { type JWK } from "jose";
|
|
2
|
+
import { type Task } from "@a2a-js/sdk";
|
|
3
|
+
import type { PlainTask } from "./task.js";
|
|
4
|
+
/**
|
|
5
|
+
* Header carrying the per-task validation `token` the gateway set in the
|
|
6
|
+
* `taskPushNotificationConfig`. Echoed verbatim so the gateway can correlate the
|
|
7
|
+
* callback to its pending task row. Must match looping-gateway's
|
|
8
|
+
* `NOTIFICATION_TOKEN_HEADER` (`src/a2a/notifications/remote.ts`).
|
|
9
|
+
*/
|
|
10
|
+
export declare const NOTIFICATION_TOKEN_HEADER = "x-a2a-notification-token";
|
|
11
|
+
/**
|
|
12
|
+
* The `submitted` Task we return synchronously to accept a turn (A2A §7.2). The
|
|
13
|
+
* gateway only requires a non-empty `id`; the actual reply follows later via the
|
|
14
|
+
* callback.
|
|
15
|
+
*/
|
|
16
|
+
export declare function buildSubmittedTask(taskId: string, contextId: string): PlainTask;
|
|
17
|
+
/**
|
|
18
|
+
* The terminal `completed` Task for a turn the agent deliberately did not answer
|
|
19
|
+
* (it called the `no_reply` tool — see the agent loop). Same
|
|
20
|
+
* shape as {@link buildSubmittedTask}: **no `status.message` at all**.
|
|
21
|
+
*
|
|
22
|
+
* The callback is still POSTed. The gateway's pending row has to resolve — we
|
|
23
|
+
* simply hand it nothing to post to Slack. There is no `messageId` because there
|
|
24
|
+
* is no message, so unlike {@link buildCompletedTask} nothing needs a stable id
|
|
25
|
+
* for the gateway to dedupe on: a `notify`-step retry re-delivers no content and
|
|
26
|
+
* is idempotent by construction.
|
|
27
|
+
*/
|
|
28
|
+
export declare function buildNoReplyCompletedTask(taskId: string, contextId: string): PlainTask;
|
|
29
|
+
/**
|
|
30
|
+
* A non-terminal `working` Task snapshot carrying an intermediate content message.
|
|
31
|
+
* Streamed live from the DO as the tool loop emits content before the final reply.
|
|
32
|
+
* `messageId` is derived from `${taskId}:${stepIndex}` — stable across re-runs (see
|
|
33
|
+
* {@link agentTextMessage}) so the gateway dedupes correctly on workflow replay.
|
|
34
|
+
*/
|
|
35
|
+
export declare function buildWorkingTask(taskId: string, contextId: string, text: string, stepIndex: number): PlainTask;
|
|
36
|
+
/**
|
|
37
|
+
* The terminal `completed` Task POSTed to the gateway callback. The `messageId` is
|
|
38
|
+
* deterministic (`${taskId}:final`, not a fresh UUID) because this is built in the
|
|
39
|
+
* workflow body, which re-runs on replay: a random id would change on a notify-step
|
|
40
|
+
* retry and the gateway would dedupe the final message as a new one and double-post.
|
|
41
|
+
*/
|
|
42
|
+
export declare function buildCompletedTask(taskId: string, contextId: string, reply: string): PlainTask;
|
|
43
|
+
/**
|
|
44
|
+
* The terminal `failed` Task POSTed to the gateway callback — an unexpected,
|
|
45
|
+
* non-transient failure aborted the turn.
|
|
46
|
+
*
|
|
47
|
+
* A2A v1.0 gives a task no structured error (`TaskStatus` is only
|
|
48
|
+
* `{state, message, timestamp}`), so the state *is* the failure signal and `text`
|
|
49
|
+
* is the only place to explain. Keep that text user-safe: the gateway renders it
|
|
50
|
+
* to a human, under its own "⚠️ *Agent …* (failed):" prefix.
|
|
51
|
+
*
|
|
52
|
+
* Shares the `${taskId}:final` messageId with {@link buildCompletedTask} by
|
|
53
|
+
* design: a Task terminates exactly once and the two states are mutually
|
|
54
|
+
* exclusive, so only one of them is ever built and posted — and a notify retry
|
|
55
|
+
* re-posts that same one under the same dedupe key.
|
|
56
|
+
*/
|
|
57
|
+
export declare function buildFailedTask(taskId: string, contextId: string, text: string): PlainTask;
|
|
58
|
+
/**
|
|
59
|
+
* Sign the callback JWT the gateway verifies against our pinned card key. The
|
|
60
|
+
* protected header mirrors the card signature (`kid`+`jku`); `aud` must equal the
|
|
61
|
+
* exact webhook URL the gateway handed us in the `taskPushNotificationConfig`.
|
|
62
|
+
*/
|
|
63
|
+
export declare function signCallbackJwt(privateJwk: JWK & {
|
|
64
|
+
kid: string;
|
|
65
|
+
}, opts: {
|
|
66
|
+
jku: string;
|
|
67
|
+
aud: string;
|
|
68
|
+
}): Promise<string>;
|
|
69
|
+
/**
|
|
70
|
+
* POST a Task snapshot to the gateway's push-notification webhook, wrapped in the
|
|
71
|
+
* v1.0 `StreamResponse` envelope. Returns the raw `Response` so the caller (the
|
|
72
|
+
* workflow's `notify` step) can decide whether a non-2xx warrants a retry.
|
|
73
|
+
*/
|
|
74
|
+
export declare function postNotification(url: string, token: string, jwt: string, task: Task): Promise<Response>;
|
|
75
|
+
//# sourceMappingURL=notify.d.ts.map
|
|
@@ -0,0 +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;AAGnD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAqB3C;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,6BAA6B,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;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GAChB,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,CAOjB;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"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { SignJWT } from "jose";
|
|
2
|
+
import { TaskState } from "@a2a-js/sdk";
|
|
3
|
+
import { V1PushNotificationSerializer } from "@a2a-js/sdk/server";
|
|
4
|
+
import { agentTextMessage } from "./parts.js";
|
|
5
|
+
/**
|
|
6
|
+
* Outbound push-notification (accept + notify) helpers — the "notify" half of the
|
|
7
|
+
* async A2A contract. The gateway dispatches `SendMessage` with a
|
|
8
|
+
* `taskPushNotificationConfig` (webhook `url` + validation `token`), we accept
|
|
9
|
+
* immediately with a `submitted` Task, and later POST the terminal Task back to
|
|
10
|
+
* that webhook. This module builds the Task shapes and signs + sends that
|
|
11
|
+
* callback.
|
|
12
|
+
*
|
|
13
|
+
* The callback is authenticated exactly like the AgentCard: a short-lived EdDSA
|
|
14
|
+
* JWT signed by `A2A_SIGNING_KEY`, whose protected header `kid`+`jku` must equal
|
|
15
|
+
* the card's signing `kid`+`jku` (see {@link file://./card.ts} `signCard`) — the
|
|
16
|
+
* gateway pinned those at registration (Trust-On-First-Use) and verifies the
|
|
17
|
+
* callback token against that same public JWKS. No shared secret crosses the
|
|
18
|
+
* boundary; only our public key is ever used to verify.
|
|
19
|
+
*/
|
|
20
|
+
/** JWS algorithm — must match the card + gateway (`EdDSA`). */
|
|
21
|
+
const ALG = "EdDSA";
|
|
22
|
+
/**
|
|
23
|
+
* Header carrying the per-task validation `token` the gateway set in the
|
|
24
|
+
* `taskPushNotificationConfig`. Echoed verbatim so the gateway can correlate the
|
|
25
|
+
* callback to its pending task row. Must match looping-gateway's
|
|
26
|
+
* `NOTIFICATION_TOKEN_HEADER` (`src/a2a/notifications/remote.ts`).
|
|
27
|
+
*/
|
|
28
|
+
export const NOTIFICATION_TOKEN_HEADER = "x-a2a-notification-token";
|
|
29
|
+
/**
|
|
30
|
+
* Callback-JWT lifetime. The gateway enforces `maxTokenAge: 10m` with a 60s clock
|
|
31
|
+
* tolerance, so keep this comfortably under that.
|
|
32
|
+
*/
|
|
33
|
+
const CALLBACK_TOKEN_TTL = "5m";
|
|
34
|
+
/**
|
|
35
|
+
* The SDK's canonical v1.0 push-notification body encoder: the `StreamResponse`
|
|
36
|
+
* envelope as protobuf-JSON, with content type `application/a2a+json`. v1.0
|
|
37
|
+
* moved push notifications onto the same envelope the streaming transports use
|
|
38
|
+
* (v0.3 POSTed a bare `Task`), so the encoding is the SDK's rather than ours —
|
|
39
|
+
* the gateway decodes it with `StreamResponse.fromJSON`.
|
|
40
|
+
*/
|
|
41
|
+
const PUSH_SERIALIZER = new V1PushNotificationSerializer();
|
|
42
|
+
/** A Task snapshot in `state` carrying no message — nothing to say, only a state change. */
|
|
43
|
+
function buildBareTask(taskId, contextId, state) {
|
|
44
|
+
return {
|
|
45
|
+
id: taskId,
|
|
46
|
+
contextId,
|
|
47
|
+
status: {
|
|
48
|
+
state,
|
|
49
|
+
message: undefined,
|
|
50
|
+
timestamp: new Date().toISOString()
|
|
51
|
+
},
|
|
52
|
+
artifacts: [],
|
|
53
|
+
history: [],
|
|
54
|
+
metadata: undefined
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The `submitted` Task we return synchronously to accept a turn (A2A §7.2). The
|
|
59
|
+
* gateway only requires a non-empty `id`; the actual reply follows later via the
|
|
60
|
+
* callback.
|
|
61
|
+
*/
|
|
62
|
+
export function buildSubmittedTask(taskId, contextId) {
|
|
63
|
+
return buildBareTask(taskId, contextId, TaskState.TASK_STATE_SUBMITTED);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The terminal `completed` Task for a turn the agent deliberately did not answer
|
|
67
|
+
* (it called the `no_reply` tool — see the agent loop). Same
|
|
68
|
+
* shape as {@link buildSubmittedTask}: **no `status.message` at all**.
|
|
69
|
+
*
|
|
70
|
+
* The callback is still POSTed. The gateway's pending row has to resolve — we
|
|
71
|
+
* simply hand it nothing to post to Slack. There is no `messageId` because there
|
|
72
|
+
* is no message, so unlike {@link buildCompletedTask} nothing needs a stable id
|
|
73
|
+
* for the gateway to dedupe on: a `notify`-step retry re-delivers no content and
|
|
74
|
+
* is idempotent by construction.
|
|
75
|
+
*/
|
|
76
|
+
export function buildNoReplyCompletedTask(taskId, contextId) {
|
|
77
|
+
return buildBareTask(taskId, contextId, TaskState.TASK_STATE_COMPLETED);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* A Task snapshot POSTed to the gateway callback in a given `state`, carrying one
|
|
81
|
+
* `agent` message. The gateway reads the reply from `status.message.parts`, so
|
|
82
|
+
* the text lives there.
|
|
83
|
+
*/
|
|
84
|
+
function buildTaskUpdate(taskId, contextId, state, text, messageId) {
|
|
85
|
+
const task = buildBareTask(taskId, contextId, state);
|
|
86
|
+
task.status.message = agentTextMessage({
|
|
87
|
+
messageId,
|
|
88
|
+
text,
|
|
89
|
+
contextId,
|
|
90
|
+
taskId
|
|
91
|
+
});
|
|
92
|
+
return task;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* A non-terminal `working` Task snapshot carrying an intermediate content message.
|
|
96
|
+
* Streamed live from the DO as the tool loop emits content before the final reply.
|
|
97
|
+
* `messageId` is derived from `${taskId}:${stepIndex}` — stable across re-runs (see
|
|
98
|
+
* {@link agentTextMessage}) so the gateway dedupes correctly on workflow replay.
|
|
99
|
+
*/
|
|
100
|
+
export function buildWorkingTask(taskId, contextId, text, stepIndex) {
|
|
101
|
+
return buildTaskUpdate(taskId, contextId, TaskState.TASK_STATE_WORKING, text, `${taskId}:${stepIndex}`);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* The terminal `completed` Task POSTed to the gateway callback. The `messageId` is
|
|
105
|
+
* deterministic (`${taskId}:final`, not a fresh UUID) because this is built in the
|
|
106
|
+
* workflow body, which re-runs on replay: a random id would change on a notify-step
|
|
107
|
+
* retry and the gateway would dedupe the final message as a new one and double-post.
|
|
108
|
+
*/
|
|
109
|
+
export function buildCompletedTask(taskId, contextId, reply) {
|
|
110
|
+
return buildTaskUpdate(taskId, contextId, TaskState.TASK_STATE_COMPLETED, reply, `${taskId}:final`);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* The terminal `failed` Task POSTed to the gateway callback — an unexpected,
|
|
114
|
+
* non-transient failure aborted the turn.
|
|
115
|
+
*
|
|
116
|
+
* A2A v1.0 gives a task no structured error (`TaskStatus` is only
|
|
117
|
+
* `{state, message, timestamp}`), so the state *is* the failure signal and `text`
|
|
118
|
+
* is the only place to explain. Keep that text user-safe: the gateway renders it
|
|
119
|
+
* to a human, under its own "⚠️ *Agent …* (failed):" prefix.
|
|
120
|
+
*
|
|
121
|
+
* Shares the `${taskId}:final` messageId with {@link buildCompletedTask} by
|
|
122
|
+
* design: a Task terminates exactly once and the two states are mutually
|
|
123
|
+
* exclusive, so only one of them is ever built and posted — and a notify retry
|
|
124
|
+
* re-posts that same one under the same dedupe key.
|
|
125
|
+
*/
|
|
126
|
+
export function buildFailedTask(taskId, contextId, text) {
|
|
127
|
+
return buildTaskUpdate(taskId, contextId, TaskState.TASK_STATE_FAILED, text, `${taskId}:final`);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Sign the callback JWT the gateway verifies against our pinned card key. The
|
|
131
|
+
* protected header mirrors the card signature (`kid`+`jku`); `aud` must equal the
|
|
132
|
+
* exact webhook URL the gateway handed us in the `taskPushNotificationConfig`.
|
|
133
|
+
*/
|
|
134
|
+
export async function signCallbackJwt(privateJwk, opts) {
|
|
135
|
+
return new SignJWT({})
|
|
136
|
+
.setProtectedHeader({ alg: ALG, kid: privateJwk.kid, jku: opts.jku })
|
|
137
|
+
.setAudience(opts.aud)
|
|
138
|
+
.setIssuedAt()
|
|
139
|
+
.setExpirationTime(CALLBACK_TOKEN_TTL)
|
|
140
|
+
.sign(privateJwk);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* POST a Task snapshot to the gateway's push-notification webhook, wrapped in the
|
|
144
|
+
* v1.0 `StreamResponse` envelope. Returns the raw `Response` so the caller (the
|
|
145
|
+
* workflow's `notify` step) can decide whether a non-2xx warrants a retry.
|
|
146
|
+
*/
|
|
147
|
+
export async function postNotification(url, token, jwt, task) {
|
|
148
|
+
const { body, contentType } = PUSH_SERIALIZER.serialize({
|
|
149
|
+
payload: { $case: "task", value: task }
|
|
150
|
+
});
|
|
151
|
+
return fetch(url, {
|
|
152
|
+
method: "POST",
|
|
153
|
+
headers: {
|
|
154
|
+
"content-type": contentType,
|
|
155
|
+
authorization: `Bearer ${jwt}`,
|
|
156
|
+
[NOTIFICATION_TOKEN_HEADER]: token
|
|
157
|
+
},
|
|
158
|
+
body
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=notify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notify.js","sourceRoot":"","sources":["../../src/a2a/notify.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type Message, type Part } from "@a2a-js/sdk";
|
|
2
|
+
import type { PlainMessage, PlainPart } from "./task.js";
|
|
3
|
+
/**
|
|
4
|
+
* A `text` part carrying `text`. Typed as the narrowed {@link PlainPart} (which
|
|
5
|
+
* widens to `Part` for free) so a message built here can cross the DO RPC
|
|
6
|
+
* boundary — see {@link file://./task.ts}.
|
|
7
|
+
*/
|
|
8
|
+
export declare function textPart(text: string): PlainPart;
|
|
9
|
+
/** Concatenate the text of every `text` part, trimming surrounding whitespace. */
|
|
10
|
+
export declare function partsText(parts: Part[] | undefined): string;
|
|
11
|
+
/** The plain-text content of an inbound A2A message (what the caller said). */
|
|
12
|
+
export declare function textOf(message: Message): string;
|
|
13
|
+
/**
|
|
14
|
+
* Bounds the inbound user text carried in a durable Workflow payload (UTF-8).
|
|
15
|
+
* Workflow params have a platform size limit, and a caller's message is the one
|
|
16
|
+
* unbounded thing that goes into them.
|
|
17
|
+
*/
|
|
18
|
+
export declare const MAX_INBOUND_TEXT_BYTES: number;
|
|
19
|
+
/** Invalid inbound content that must not cross the A2A-to-workflow boundary. */
|
|
20
|
+
export declare class InboundPartError extends Error {
|
|
21
|
+
constructor(message: string);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Extract and validate the user-turn text. Rejects a message with no usable
|
|
25
|
+
* text, and enforces a single UTF-8 size bound *before* the text enters a
|
|
26
|
+
* workflow payload — past that point the failure is a workflow that will not
|
|
27
|
+
* start, with no request left to answer on.
|
|
28
|
+
*
|
|
29
|
+
* File and data parts are deliberately out of scope: only text crosses into the
|
|
30
|
+
* agent runtime.
|
|
31
|
+
*/
|
|
32
|
+
export declare function inboundText(message: Message): string;
|
|
33
|
+
/**
|
|
34
|
+
* An `agent`-role message carrying one text part, with the proto-required
|
|
35
|
+
* fields this agent never sets filled in. `messageId` is the gateway's dedupe
|
|
36
|
+
* key, so callers pass a **stable** id (never a fresh random per attempt) — a
|
|
37
|
+
* callback the workflow/DO re-runs must reuse the same id or the gateway
|
|
38
|
+
* treats the replay as a new message and double-posts.
|
|
39
|
+
*/
|
|
40
|
+
export declare function agentTextMessage(input: {
|
|
41
|
+
messageId: string;
|
|
42
|
+
text: string;
|
|
43
|
+
contextId: string;
|
|
44
|
+
taskId: string;
|
|
45
|
+
}): PlainMessage;
|
|
46
|
+
//# sourceMappingURL=parts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parts.d.ts","sourceRoot":"","sources":["../../src/a2a/parts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAmBzD;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAOhD;AAED,kFAAkF;AAClF,wBAAgB,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,SAAS,GAAG,MAAM,CAM3D;AAED,+EAA+E;AAC/E,wBAAgB,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAE/C;AAED;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,QAAa,CAAC;AAIjD,gFAAgF;AAChF,qBAAa,gBAAiB,SAAQ,KAAK;gBAC7B,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CASpD;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,YAAY,CAWf"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Role } from "@a2a-js/sdk";
|
|
2
|
+
/**
|
|
3
|
+
* Constructors and readers for A2A v1.0 message content — the one place the
|
|
4
|
+
* adapter reaches into the `@a2a-js/sdk` message shape. Everything past
|
|
5
|
+
* {@link file://./executor.ts A2AExecutor} works in plain strings, so the agent
|
|
6
|
+
* runtime never sees an A2A type.
|
|
7
|
+
*
|
|
8
|
+
* The v1.0 data model is generated from the protobuf schema, so the wire types
|
|
9
|
+
* are "all fields present": a `Part` carries a `content` oneof discriminated by
|
|
10
|
+
* `$case` (plus `filename`/`mediaType`) instead of v0.3's `kind`, and a
|
|
11
|
+
* `Message` carries empty strings and empty arrays rather than omitted
|
|
12
|
+
* optionals. Hand-writing those literals at every call site is noisy and easy to
|
|
13
|
+
* get subtly wrong, so every part/message this agent emits is built here.
|
|
14
|
+
*/
|
|
15
|
+
/** Media type stamped on the text parts this agent emits. */
|
|
16
|
+
const TEXT_MEDIA_TYPE = "text/plain";
|
|
17
|
+
/**
|
|
18
|
+
* A `text` part carrying `text`. Typed as the narrowed {@link PlainPart} (which
|
|
19
|
+
* widens to `Part` for free) so a message built here can cross the DO RPC
|
|
20
|
+
* boundary — see {@link file://./task.ts}.
|
|
21
|
+
*/
|
|
22
|
+
export function textPart(text) {
|
|
23
|
+
return {
|
|
24
|
+
content: { $case: "text", value: text },
|
|
25
|
+
metadata: undefined,
|
|
26
|
+
filename: "",
|
|
27
|
+
mediaType: TEXT_MEDIA_TYPE
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** Concatenate the text of every `text` part, trimming surrounding whitespace. */
|
|
31
|
+
export function partsText(parts) {
|
|
32
|
+
let out = "";
|
|
33
|
+
for (const part of parts ?? []) {
|
|
34
|
+
if (part.content?.$case === "text")
|
|
35
|
+
out += part.content.value;
|
|
36
|
+
}
|
|
37
|
+
return out.trim();
|
|
38
|
+
}
|
|
39
|
+
/** The plain-text content of an inbound A2A message (what the caller said). */
|
|
40
|
+
export function textOf(message) {
|
|
41
|
+
return partsText(message.parts);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Bounds the inbound user text carried in a durable Workflow payload (UTF-8).
|
|
45
|
+
* Workflow params have a platform size limit, and a caller's message is the one
|
|
46
|
+
* unbounded thing that goes into them.
|
|
47
|
+
*/
|
|
48
|
+
export const MAX_INBOUND_TEXT_BYTES = 256 * 1024;
|
|
49
|
+
const encoder = new TextEncoder();
|
|
50
|
+
/** Invalid inbound content that must not cross the A2A-to-workflow boundary. */
|
|
51
|
+
export class InboundPartError extends Error {
|
|
52
|
+
constructor(message) {
|
|
53
|
+
super(message);
|
|
54
|
+
this.name = "InboundPartError";
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Extract and validate the user-turn text. Rejects a message with no usable
|
|
59
|
+
* text, and enforces a single UTF-8 size bound *before* the text enters a
|
|
60
|
+
* workflow payload — past that point the failure is a workflow that will not
|
|
61
|
+
* start, with no request left to answer on.
|
|
62
|
+
*
|
|
63
|
+
* File and data parts are deliberately out of scope: only text crosses into the
|
|
64
|
+
* agent runtime.
|
|
65
|
+
*/
|
|
66
|
+
export function inboundText(message) {
|
|
67
|
+
const text = textOf(message);
|
|
68
|
+
if (!text) {
|
|
69
|
+
throw new InboundPartError("message has no usable text");
|
|
70
|
+
}
|
|
71
|
+
if (encoder.encode(text).byteLength > MAX_INBOUND_TEXT_BYTES) {
|
|
72
|
+
throw new InboundPartError("message text exceeds the size limit");
|
|
73
|
+
}
|
|
74
|
+
return text;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* An `agent`-role message carrying one text part, with the proto-required
|
|
78
|
+
* fields this agent never sets filled in. `messageId` is the gateway's dedupe
|
|
79
|
+
* key, so callers pass a **stable** id (never a fresh random per attempt) — a
|
|
80
|
+
* callback the workflow/DO re-runs must reuse the same id or the gateway
|
|
81
|
+
* treats the replay as a new message and double-posts.
|
|
82
|
+
*/
|
|
83
|
+
export function agentTextMessage(input) {
|
|
84
|
+
return {
|
|
85
|
+
messageId: input.messageId,
|
|
86
|
+
role: Role.ROLE_AGENT,
|
|
87
|
+
parts: [textPart(input.text)],
|
|
88
|
+
contextId: input.contextId,
|
|
89
|
+
taskId: input.taskId,
|
|
90
|
+
metadata: undefined,
|
|
91
|
+
extensions: [],
|
|
92
|
+
referenceTaskIds: []
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=parts.js.map
|