@nxisai/agent-harness 0.4.1 → 0.6.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 (137) hide show
  1. package/README.md +25 -2
  2. package/dist/handoff/a2a.d.ts +152 -0
  3. package/dist/handoff/a2a.d.ts.map +1 -0
  4. package/dist/handoff/a2a.js +227 -0
  5. package/dist/handoff/a2a.js.map +1 -0
  6. package/dist/handoff/controller.d.ts +181 -0
  7. package/dist/handoff/controller.d.ts.map +1 -0
  8. package/dist/handoff/controller.js +543 -0
  9. package/dist/handoff/controller.js.map +1 -0
  10. package/dist/handoff/envelope.d.ts +130 -0
  11. package/dist/handoff/envelope.d.ts.map +1 -0
  12. package/dist/handoff/envelope.js +178 -0
  13. package/dist/handoff/envelope.js.map +1 -0
  14. package/dist/handoff/index.d.ts +16 -0
  15. package/dist/handoff/index.d.ts.map +1 -0
  16. package/dist/handoff/index.js +12 -0
  17. package/dist/handoff/index.js.map +1 -0
  18. package/dist/handoff/tools.d.ts +36 -0
  19. package/dist/handoff/tools.d.ts.map +1 -0
  20. package/dist/handoff/tools.js +148 -0
  21. package/dist/handoff/tools.js.map +1 -0
  22. package/dist/harness/harness.d.ts +4 -2
  23. package/dist/harness/harness.d.ts.map +1 -1
  24. package/dist/harness/harness.js +61 -16
  25. package/dist/harness/harness.js.map +1 -1
  26. package/dist/harness/router/coordinator.d.ts +13 -0
  27. package/dist/harness/router/coordinator.d.ts.map +1 -1
  28. package/dist/harness/router/coordinator.js +7 -0
  29. package/dist/harness/router/coordinator.js.map +1 -1
  30. package/dist/harness/tools/action-tools.d.ts.map +1 -1
  31. package/dist/harness/tools/action-tools.js +7 -5
  32. package/dist/harness/tools/action-tools.js.map +1 -1
  33. package/dist/harness/tools/bridge.d.ts.map +1 -1
  34. package/dist/harness/tools/bridge.js +33 -12
  35. package/dist/harness/tools/bridge.js.map +1 -1
  36. package/dist/harness/tools/discovery.d.ts +12 -1
  37. package/dist/harness/tools/discovery.d.ts.map +1 -1
  38. package/dist/harness/tools/discovery.js +19 -1
  39. package/dist/harness/tools/discovery.js.map +1 -1
  40. package/dist/harness/tools/local-context.d.ts +49 -9
  41. package/dist/harness/tools/local-context.d.ts.map +1 -1
  42. package/dist/harness/tools/local-context.js +377 -22
  43. package/dist/harness/tools/local-context.js.map +1 -1
  44. package/dist/harness/tools/open-page.d.ts +16 -0
  45. package/dist/harness/tools/open-page.d.ts.map +1 -1
  46. package/dist/harness/tools/open-page.js +45 -9
  47. package/dist/harness/tools/open-page.js.map +1 -1
  48. package/dist/harness/tools/retry.d.ts +53 -0
  49. package/dist/harness/tools/retry.d.ts.map +1 -0
  50. package/dist/harness/tools/retry.js +96 -0
  51. package/dist/harness/tools/retry.js.map +1 -0
  52. package/dist/harness/tools/schema.d.ts +18 -0
  53. package/dist/harness/tools/schema.d.ts.map +1 -1
  54. package/dist/harness/tools/schema.js +38 -0
  55. package/dist/harness/tools/schema.js.map +1 -1
  56. package/dist/harness/tools/site-actions.d.ts.map +1 -1
  57. package/dist/harness/tools/site-actions.js +3 -2
  58. package/dist/harness/tools/site-actions.js.map +1 -1
  59. package/dist/harness/tools/site-nav.d.ts +168 -0
  60. package/dist/harness/tools/site-nav.d.ts.map +1 -0
  61. package/dist/harness/tools/site-nav.js +316 -0
  62. package/dist/harness/tools/site-nav.js.map +1 -0
  63. package/dist/harness/tools/site-pages.d.ts +4 -0
  64. package/dist/harness/tools/site-pages.d.ts.map +1 -1
  65. package/dist/harness/tools/site-pages.js +19 -3
  66. package/dist/harness/tools/site-pages.js.map +1 -1
  67. package/dist/harness/tools/source.d.ts +9 -0
  68. package/dist/harness/tools/source.d.ts.map +1 -1
  69. package/dist/harness/tools/source.js +54 -14
  70. package/dist/harness/tools/source.js.map +1 -1
  71. package/dist/harness/tools/webmcp.d.ts +39 -3
  72. package/dist/harness/tools/webmcp.d.ts.map +1 -1
  73. package/dist/harness/tools/webmcp.js +93 -14
  74. package/dist/harness/tools/webmcp.js.map +1 -1
  75. package/dist/index.d.ts +6 -1
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +5 -1
  78. package/dist/index.js.map +1 -1
  79. package/dist/types.d.ts +2 -0
  80. package/dist/types.d.ts.map +1 -1
  81. package/dist/ui/agent-fullscreen.d.ts.map +1 -1
  82. package/dist/ui/agent-fullscreen.js +276 -2
  83. package/dist/ui/agent-fullscreen.js.map +1 -1
  84. package/dist/ui/tailwind.generated.d.ts +1 -1
  85. package/dist/ui/tailwind.generated.d.ts.map +1 -1
  86. package/dist/ui/tailwind.generated.js +1 -1
  87. package/dist/ui/tailwind.generated.js.map +1 -1
  88. package/package.json +2 -1
  89. package/src/a2ui/cards.ts +177 -0
  90. package/src/a2ui/emit.ts +147 -0
  91. package/src/a2ui/form.ts +117 -0
  92. package/src/a2ui/host.ts +73 -0
  93. package/src/a2ui/index.ts +22 -0
  94. package/src/a2ui/stepper.ts +211 -0
  95. package/src/a2ui/theme.ts +134 -0
  96. package/src/a2ui/to-v09.ts +118 -0
  97. package/src/engine.ts +701 -0
  98. package/src/handoff/a2a.ts +326 -0
  99. package/src/handoff/controller.ts +661 -0
  100. package/src/handoff/envelope.ts +216 -0
  101. package/src/handoff/index.ts +53 -0
  102. package/src/handoff/tools.ts +178 -0
  103. package/src/harness/harness.ts +981 -0
  104. package/src/harness/plan.ts +91 -0
  105. package/src/harness/policy.ts +59 -0
  106. package/src/harness/renderer.ts +208 -0
  107. package/src/harness/router/coordinator.ts +241 -0
  108. package/src/harness/router/rules.ts +65 -0
  109. package/src/harness/slots.ts +117 -0
  110. package/src/harness/steps.ts +156 -0
  111. package/src/harness/tools/action-tools.ts +870 -0
  112. package/src/harness/tools/bridge.ts +393 -0
  113. package/src/harness/tools/client.ts +26 -0
  114. package/src/harness/tools/discovery.ts +73 -0
  115. package/src/harness/tools/local-context.ts +480 -0
  116. package/src/harness/tools/open-page.ts +273 -0
  117. package/src/harness/tools/pending-action.ts +232 -0
  118. package/src/harness/tools/resume.ts +134 -0
  119. package/src/harness/tools/retry.ts +117 -0
  120. package/src/harness/tools/schema.ts +245 -0
  121. package/src/harness/tools/site-actions.ts +356 -0
  122. package/src/harness/tools/site-nav.ts +432 -0
  123. package/src/harness/tools/site-pages.ts +288 -0
  124. package/src/harness/tools/source.ts +353 -0
  125. package/src/harness/tools/webmcp.ts +200 -0
  126. package/src/index.ts +196 -0
  127. package/src/providers/capability.ts +167 -0
  128. package/src/providers/proxy.ts +191 -0
  129. package/src/providers/types.ts +150 -0
  130. package/src/types.ts +258 -0
  131. package/src/ui/agent-fullscreen.ts +4262 -0
  132. package/src/ui/agent.tw.css +49 -0
  133. package/src/ui/config.ts +138 -0
  134. package/src/ui/index.ts +32 -0
  135. package/src/ui/journey.ts +57 -0
  136. package/src/ui/tailwind.generated.ts +2 -0
  137. package/src/webmcp-contract.ts +65 -0
package/README.md CHANGED
@@ -211,6 +211,7 @@ Both flags go false at a specialist handoff, which swaps the system prompt and t
211
211
  | `preload` | `'eager' \| 'idle' \| 'none'` | `'idle'` | When weights are fetched. Local provider only. Settable mid-session. |
212
212
  | `debug` | `boolean` | `false` | Shows tool receipts and route labels. |
213
213
  | `preferences` | `AgentPreferences` | session-scoped | See [Preferences](#preferences). |
214
+ | `handoff` | `HandoffConfig \| null` | `null` | Hand conversations to other agents and people. See [Handoff](#handoff-to-an-agent-or-a-person). |
214
215
 
215
216
  ### Attributes
216
217
 
@@ -435,7 +436,7 @@ const card = viewJourney(steps); // { title, steps, outcome, active, summary }
435
436
  ### `AgentCoordinator`
436
437
 
437
438
  ```ts
438
- new AgentCoordinator({ agents, rules, fallback, provider?, descriptions?, onRoute? })
439
+ new AgentCoordinator({ agents, rules, fallback, provider?, descriptions?, onRoute?, handoff? })
439
440
  coordinator.turn(goal, messages): Promise<TurnResult>
440
441
  coordinator.stop(): void // the classifier and the agent that took the turn
441
442
  coordinator.turn(goal, messages): Promise<TurnResult>
@@ -558,10 +559,12 @@ Serving requirements:
558
559
  - Discovery failure is never fatal. The agent keeps running with whatever the page registered itself, and `connectTools` returns the reason in `warning`.
559
560
  - Once connected, the harness also asks the server for every Schema.org Action on the site (one public `nxis_query`, cached in the tab for ten minutes), and registers them as tools, so the agent can reach the whole site's forms from whichever page it was opened on. Actions whose page is on another origin are ignored. A failed query leaves everything else working, and `connectTools` reports it in `warning`. `site-actions="false"` skips the query.
560
561
  - **One tool per form, not per page.** Actions sharing a name and a field list become a single tool: the listings demo's 38 per-business quote forms register as one `quote_form_anywhere`. The field whose default value differs between them — the site's own name for the target, such as `business` — becomes a required choice, and defaults to this page's action when the agent is on one. Where no field tells them apart, a `target` field named after each page is added instead.
561
- - **Named for the reach they add** — `quote_form_anywhere` beside the page's own `quote_form`. These are couriers rather than second copies of a form: one captures an action's inputs wherever the agent is, carries them to the page that can submit them, and calls the tool there, never filling a field itself. The distinct name is also mechanical: a repeated name is a duplicate registration, so the polyfill would replace the very tool the courier delegates to, and a native registry rejects it. On the page an action belongs to, the courier calls the page's tool directly and that page tool is hidden from the model, so there is one route per action rather than two.
562
+ - **Named for the reach they add** — `quote_form_anywhere` beside the page's own `quote_form`. These are couriers rather than second copies of a form: one captures an action's inputs wherever the agent is, carries them to the page that can submit them, and calls the tool there, never filling a field itself. The distinct name is also mechanical: a repeated name is a duplicate registration, which a WebMCP registry rejects — and the courier needs the page's tool to stay registered. On the page an action belongs to, the courier calls the page's tool directly and that page tool is hidden from the model, so there is one route per action rather than two.
562
563
  - **Only what a turn could need reaches the prompt.** Every registered tool's schema goes into every prompt, so a site with many distinct forms would fill the context with forms nobody asked about. Past `maxSiteTools` (default 8), the tools covering the current page are kept and the rest compete on how well they answer what was asked — a business named in the request matches the choice that names it. Only site-wide tools are ever dropped; the page's own tools and the retrieval tools stay.
563
564
  - **A form can only be submitted on its own page**, since running an action means filling that form. Calling one from elsewhere collects and confirms the values where the visitor is — the button reads "Open & submit" rather than "Submit", and the card says which page it will open — then stores the confirmed call, navigates, and submits once the page has loaded. The stored call holds what someone typed, so it lives in the tab's `sessionStorage`, expires after two minutes, and is read exactly once, which is what stops a reload from submitting twice.
564
565
  - **Results link to their pages, and the agent can open them.** A record's `url` is often the site root rather than its page, so the harness also fetches the site's page records (a second `nxis_query`, on `@type` containing `Page`, cached the same way) and adds the pages its actions live on. A result card gets an "Open page" link when its page can be told — from `mainEntityOfPage`, an `@id` formed from its page (`page.html#organization`), a `url` deeper than the root, a page whose `mainEntity` or `about` is the record, or a single page with the record's name — and an `open_page` tool lets "take me to that page" work in conversation — along with "take me home" (the page record at `/` or `/index.html`, else the site root) and "go back" (`history.back()`, only when the previous page was on this site). It needs no confirmation, only opens pages on this origin, and carries the conversation across, so the thread continues on the page it opened. `connectTools({ openPage: false })` leaves the tool out.
566
+ - **The agent can walk the site, not only open a page by name.** Every page's `BreadcrumbList` is fetched in a third cached `nxis_query` and the trails are inverted into a graph: each page's sections above it, the pages beside and under it, and the pages it links to (`significantLink`, `relatedLink`). `open_page` then understands `up` — "up a level", "the section above" — and the name of anything near the page, and its list of destinations is the pages around this one rather than the whole site. A `page_links` tool reports where the visitor is and what leads where, so "what else is in this section" is answered without navigating. A site that publishes no trails behaves exactly as before.
567
+ - **A rate-limited read is waited out, not reported as an empty site.** The MCP server answers a read over the site's limit with how long to wait ("Retry after 52s"), and the harness waits exactly that, twice at most, and never longer than a minute — then says so in words the agent can pass on. Connecting spends two reads (actions, pages); the breadcrumb trails are fetched the first time navigation needs them, so a small plan's budget is not gone before the visitor has asked anything.
565
568
  - `discover="false"` skips all of this.
566
569
 
567
570
  ### A2UI
@@ -572,6 +575,26 @@ import { emitAsk, emitStep, emitConfirm, toV09 } from '@nxisai/agent-harness/a2u
572
575
 
573
576
  Messages are authored against A2UI v1.0 and downgraded with `toV09` for the published `@a2ui/lit` renderer.
574
577
 
578
+ ## Handoff to an agent or a person
579
+
580
+ The assistant can hand a conversation to another agent that can finish what it can't, or to a person on the site's team. Both are reached over [A2A 1.0](https://a2a-protocol.org/latest/specification/), through a relay the site hosts on its own origin, so the page holds no credential and never talks to a target directly.
581
+
582
+ ```ts
583
+ agent.handoff = {
584
+ relay: '/api/handoff',
585
+ targets: {
586
+ booking: { kind: 'agent', label: 'Booking', description: 'books a service visit' },
587
+ team: { kind: 'person', label: 'Our team' },
588
+ },
589
+ };
590
+ ```
591
+
592
+ That offers two tools, `transfer_to_agent` and `talk_to_a_person`, listing only the targets both this config and the relay name. They are consequential: the visitor sees who the conversation goes to and the summary that goes with it, and presses **Send**. From then on what the visitor types goes to the target — its replies appear under its name, a bar above the input says who is listening, and **Back to the assistant** ends it. When the target finishes, the assistant carries on from its hand-back note ("Booked for Thursday, reference CL-4KDQ-7M2X"). A handoff survives page changes and reloads.
593
+
594
+ Headless, `HandoffController` runs the same handoffs and reports them as `handoff` events; `registerHandoffTools` offers the tools; `AgentCoordinator`'s `handoff` option routes a handed-off conversation's turns to its target.
595
+
596
+ [docs/handoff.md](docs/handoff.md) is the contract: the relay's endpoints, the handoff extension (`https://nxis.ai/a2a/handoff/v1`: the envelope, queue, operator and hand-back parts), the lifecycle, what makes a good target, and the adapter contract for putting a help desk behind A2A. [Copperline](https://github.com/nxisai/copperline-home-services) has a reference of each part on Netlify: the relay, an A2A booking agent built on `@a2a-js/sdk`, and a human desk with an operator console.
597
+
575
598
  ## Requirements
576
599
 
577
600
  - WebGPU: Chrome or Edge 113+, or Safari 18+.
@@ -0,0 +1,152 @@
1
+ /**
2
+ * A small A2A 1.0 client, over JSON-RPC, for talking to a site's handoff relay.
3
+ *
4
+ * Only what a handoff needs: send a message (streamed or not), read a task,
5
+ * subscribe to one, cancel one. The official `@a2a-js/sdk` covers the whole
6
+ * protocol, but brings a JWT library and protobuf and gRPC peers with it, and
7
+ * this harness ships nothing it doesn't use.
8
+ *
9
+ * The wire format is A2A 1.0's JSON: parts and stream events are told apart by
10
+ * which member they carry (`text`, `data`, `statusUpdate`, …), and enums are
11
+ * their names (`TASK_STATE_WORKING`, `ROLE_USER`). Servers still on 0.3 send
12
+ * `kind` discriminators and lower-case states; those are read too.
13
+ *
14
+ * @see https://a2a-protocol.org/latest/specification/
15
+ * @module
16
+ */
17
+ /** The A2A protocol version this client speaks, sent as `A2A-Version`. */
18
+ export declare const A2A_VERSION = "1.0";
19
+ /** A task's state, by its A2A 1.0 name. */
20
+ export type A2ATaskState = 'TASK_STATE_UNSPECIFIED' | 'TASK_STATE_SUBMITTED' | 'TASK_STATE_WORKING' | 'TASK_STATE_COMPLETED' | 'TASK_STATE_FAILED' | 'TASK_STATE_CANCELED' | 'TASK_STATE_INPUT_REQUIRED' | 'TASK_STATE_REJECTED' | 'TASK_STATE_AUTH_REQUIRED';
21
+ /** One piece of a message or artifact. Which member is present says what it is. */
22
+ export interface A2APart {
23
+ text?: string;
24
+ data?: unknown;
25
+ url?: string;
26
+ raw?: string;
27
+ filename?: string;
28
+ mediaType?: string;
29
+ metadata?: Record<string, unknown>;
30
+ }
31
+ /** A message, from the visitor (`ROLE_USER`) or the target (`ROLE_AGENT`). */
32
+ export interface A2AMessage {
33
+ messageId: string;
34
+ role: 'ROLE_USER' | 'ROLE_AGENT';
35
+ parts: A2APart[];
36
+ contextId?: string;
37
+ taskId?: string;
38
+ metadata?: Record<string, unknown>;
39
+ extensions?: string[];
40
+ }
41
+ /** Where a task stands, and the message that goes with it, if any. */
42
+ export interface A2ATaskStatus {
43
+ state: A2ATaskState;
44
+ message?: A2AMessage;
45
+ timestamp?: string;
46
+ }
47
+ /** Output a task produced. */
48
+ export interface A2AArtifact {
49
+ artifactId?: string;
50
+ name?: string;
51
+ parts: A2APart[];
52
+ metadata?: Record<string, unknown>;
53
+ }
54
+ /** A unit of work at the target. */
55
+ export interface A2ATask {
56
+ id: string;
57
+ contextId?: string;
58
+ status: A2ATaskStatus;
59
+ artifacts?: A2AArtifact[];
60
+ history?: A2AMessage[];
61
+ metadata?: Record<string, unknown>;
62
+ }
63
+ /** One event on a stream, or the result of a send: exactly one member is set. */
64
+ export interface A2AStreamResponse {
65
+ task?: A2ATask;
66
+ message?: A2AMessage;
67
+ statusUpdate?: {
68
+ taskId: string;
69
+ contextId?: string;
70
+ status: A2ATaskStatus;
71
+ metadata?: Record<string, unknown>;
72
+ };
73
+ artifactUpdate?: {
74
+ taskId: string;
75
+ contextId?: string;
76
+ artifact: A2AArtifact;
77
+ append?: boolean;
78
+ lastChunk?: boolean;
79
+ };
80
+ }
81
+ /** An error the relay or the target answered with. */
82
+ export declare class A2AError extends Error {
83
+ /** The JSON-RPC error code, or the HTTP status when the relay refused before A2A. */
84
+ readonly code: number;
85
+ /** The HTTP status of the response. */
86
+ readonly status?: number | undefined;
87
+ /** Seconds to wait, from `Retry-After`, when the relay is rate limiting. */
88
+ readonly retryAfter?: number | undefined;
89
+ constructor(message: string,
90
+ /** The JSON-RPC error code, or the HTTP status when the relay refused before A2A. */
91
+ code: number,
92
+ /** The HTTP status of the response. */
93
+ status?: number | undefined,
94
+ /** Seconds to wait, from `Retry-After`, when the relay is rate limiting. */
95
+ retryAfter?: number | undefined);
96
+ }
97
+ /** Options for {@link A2AClient}. */
98
+ export interface A2AClientOptions {
99
+ /** Where requests go: the relay's URL for one target, `/api/handoff/booking`. */
100
+ endpoint: string;
101
+ /** Extensions to announce in `A2A-Extensions`. */
102
+ extensions?: string[];
103
+ /** `fetch` to use. Defaults to the global. */
104
+ fetchImpl?: typeof fetch;
105
+ }
106
+ /** What a call returned, with the response headers the relay may have set. */
107
+ export interface A2AReply<T> {
108
+ result: T;
109
+ headers: Headers;
110
+ }
111
+ /** A stream of events, and the response headers it opened with. */
112
+ export interface A2AStream {
113
+ events: AsyncGenerator<A2AStreamResponse, void, undefined>;
114
+ headers: Headers;
115
+ }
116
+ /** A2A over JSON-RPC to one endpoint. */
117
+ export declare class A2AClient {
118
+ private readonly opts;
119
+ constructor(opts: A2AClientOptions);
120
+ /**
121
+ * Call a method that answers once: `SendMessage`, `GetTask`, `CancelTask`.
122
+ *
123
+ * @throws {@link A2AError} when the relay refuses or the target answers with an error.
124
+ */
125
+ call<T>(method: string, params: unknown, init?: {
126
+ signal?: AbortSignal;
127
+ headers?: Record<string, string>;
128
+ }): Promise<A2AReply<T>>;
129
+ /**
130
+ * Call a method that answers with a stream: `SendStreamingMessage`, `SubscribeToTask`.
131
+ *
132
+ * The generator ends when the server closes the stream, which it does at a
133
+ * terminal state, or earlier if the host cuts the response off.
134
+ *
135
+ * @throws {@link A2AError} when the relay refuses; an error event on the stream
136
+ * is thrown from the generator.
137
+ */
138
+ stream(method: string, params: unknown, init?: {
139
+ signal?: AbortSignal;
140
+ headers?: Record<string, string>;
141
+ }): Promise<A2AStream>;
142
+ private post;
143
+ }
144
+ /** A task state by its 1.0 name, whichever version sent it. */
145
+ export declare function taskState(raw: unknown): A2ATaskState;
146
+ /** A result in 1.0's shape, whether it came as 1.0 or 0.3 (`kind` discriminators). */
147
+ export declare function normaliseResponse(raw: A2AStreamResponse | Record<string, unknown>): A2AStreamResponse;
148
+ /** Whether a state ends the task. */
149
+ export declare function isTerminal(state: A2ATaskState): boolean;
150
+ /** A message id, unique enough for one conversation. */
151
+ export declare function newMessageId(): string;
152
+ //# sourceMappingURL=a2a.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"a2a.d.ts","sourceRoot":"","sources":["../../src/handoff/a2a.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,227 @@
1
+ /**
2
+ * A small A2A 1.0 client, over JSON-RPC, for talking to a site's handoff relay.
3
+ *
4
+ * Only what a handoff needs: send a message (streamed or not), read a task,
5
+ * subscribe to one, cancel one. The official `@a2a-js/sdk` covers the whole
6
+ * protocol, but brings a JWT library and protobuf and gRPC peers with it, and
7
+ * this harness ships nothing it doesn't use.
8
+ *
9
+ * The wire format is A2A 1.0's JSON: parts and stream events are told apart by
10
+ * which member they carry (`text`, `data`, `statusUpdate`, …), and enums are
11
+ * their names (`TASK_STATE_WORKING`, `ROLE_USER`). Servers still on 0.3 send
12
+ * `kind` discriminators and lower-case states; those are read too.
13
+ *
14
+ * @see https://a2a-protocol.org/latest/specification/
15
+ * @module
16
+ */
17
+ /** The A2A protocol version this client speaks, sent as `A2A-Version`. */
18
+ export const A2A_VERSION = '1.0';
19
+ /** An error the relay or the target answered with. */
20
+ export class A2AError extends Error {
21
+ constructor(message,
22
+ /** The JSON-RPC error code, or the HTTP status when the relay refused before A2A. */
23
+ code,
24
+ /** The HTTP status of the response. */
25
+ status,
26
+ /** Seconds to wait, from `Retry-After`, when the relay is rate limiting. */
27
+ retryAfter) {
28
+ super(message);
29
+ this.code = code;
30
+ this.status = status;
31
+ this.retryAfter = retryAfter;
32
+ this.name = 'A2AError';
33
+ }
34
+ }
35
+ let nextId = 1;
36
+ /** A2A over JSON-RPC to one endpoint. */
37
+ export class A2AClient {
38
+ constructor(opts) {
39
+ this.opts = opts;
40
+ }
41
+ /**
42
+ * Call a method that answers once: `SendMessage`, `GetTask`, `CancelTask`.
43
+ *
44
+ * @throws {@link A2AError} when the relay refuses or the target answers with an error.
45
+ */
46
+ async call(method, params, init = {}) {
47
+ const res = await this.post(method, params, 'application/json', init);
48
+ const body = (await res.json().catch(() => null));
49
+ if (!body)
50
+ throw new A2AError(`Empty answer to ${method}`, -32603, res.status);
51
+ if (body.error)
52
+ throw new A2AError(body.error.message, body.error.code, res.status);
53
+ return { result: body.result, headers: res.headers };
54
+ }
55
+ /**
56
+ * Call a method that answers with a stream: `SendStreamingMessage`, `SubscribeToTask`.
57
+ *
58
+ * The generator ends when the server closes the stream, which it does at a
59
+ * terminal state, or earlier if the host cuts the response off.
60
+ *
61
+ * @throws {@link A2AError} when the relay refuses; an error event on the stream
62
+ * is thrown from the generator.
63
+ */
64
+ async stream(method, params, init = {}) {
65
+ const res = await this.post(method, params, 'text/event-stream', init);
66
+ const type = res.headers.get('content-type') ?? '';
67
+ // A server may answer a streaming request with a single JSON response: an
68
+ // error, or a target without streaming that replied at once.
69
+ if (!type.includes('text/event-stream')) {
70
+ const body = (await res.json().catch(() => null));
71
+ if (body?.error)
72
+ throw new A2AError(body.error.message, body.error.code, res.status);
73
+ const once = body?.result;
74
+ return {
75
+ headers: res.headers,
76
+ events: (async function* () {
77
+ if (once)
78
+ yield normaliseResponse(once);
79
+ })(),
80
+ };
81
+ }
82
+ return { headers: res.headers, events: readEvents(res, init.signal) };
83
+ }
84
+ async post(method, params, accept, init) {
85
+ const doFetch = this.opts.fetchImpl ?? fetch;
86
+ const res = await doFetch(this.opts.endpoint, {
87
+ method: 'POST',
88
+ credentials: 'same-origin',
89
+ signal: init.signal,
90
+ headers: {
91
+ 'Content-Type': 'application/json',
92
+ Accept: accept,
93
+ 'A2A-Version': A2A_VERSION,
94
+ ...(this.opts.extensions?.length ? { 'A2A-Extensions': this.opts.extensions.join(',') } : {}),
95
+ ...init.headers,
96
+ },
97
+ body: JSON.stringify({ jsonrpc: '2.0', id: nextId++, method, params }),
98
+ });
99
+ if (!res.ok) {
100
+ const retry = Number(res.headers.get('retry-after'));
101
+ let message = `${method} refused (${res.status})`;
102
+ try {
103
+ const body = (await res.json());
104
+ const text = typeof body.error === 'string' ? body.error : (body.error?.message ?? body.message);
105
+ if (text)
106
+ message = text;
107
+ }
108
+ catch {
109
+ // Not JSON: keep the status line.
110
+ }
111
+ throw new A2AError(message, res.status, res.status, Number.isFinite(retry) && retry > 0 ? retry : undefined);
112
+ }
113
+ return res;
114
+ }
115
+ }
116
+ /** Server-sent events off a response body, as A2A stream responses. */
117
+ async function* readEvents(res, signal) {
118
+ if (!res.body)
119
+ return;
120
+ const reader = res.body.getReader();
121
+ const decoder = new TextDecoder();
122
+ let buffer = '';
123
+ try {
124
+ while (true) {
125
+ if (signal?.aborted)
126
+ return;
127
+ const { value, done } = await reader.read();
128
+ if (done)
129
+ break;
130
+ buffer += decoder.decode(value, { stream: true });
131
+ let cut;
132
+ while ((cut = boundary(buffer)) >= 0) {
133
+ const block = buffer.slice(0, cut);
134
+ buffer = buffer.slice(cut).replace(/^(\r?\n){2}/, '');
135
+ const event = parseBlock(block);
136
+ if (event)
137
+ yield event;
138
+ }
139
+ }
140
+ const tail = parseBlock(buffer);
141
+ if (tail)
142
+ yield tail;
143
+ }
144
+ finally {
145
+ reader.releaseLock?.();
146
+ }
147
+ }
148
+ /** Where the first complete event block ends, or -1. */
149
+ function boundary(buffer) {
150
+ const m = /\r?\n\r?\n/.exec(buffer);
151
+ return m ? m.index : -1;
152
+ }
153
+ /** One SSE block's `data:` lines, as a stream response; null for comments and keep-alives. */
154
+ function parseBlock(block) {
155
+ const data = block
156
+ .split(/\r?\n/)
157
+ .filter((line) => line.startsWith('data:'))
158
+ .map((line) => line.slice(5).replace(/^ /, ''))
159
+ .join('\n');
160
+ if (!data.trim())
161
+ return null;
162
+ const body = JSON.parse(data);
163
+ if (body.error)
164
+ throw new A2AError(body.error.message, body.error.code);
165
+ return body.result ? normaliseResponse(body.result) : null;
166
+ }
167
+ /** 0.3 event and state names, read as 1.0's. */
168
+ const LEGACY_STATES = {
169
+ submitted: 'TASK_STATE_SUBMITTED',
170
+ working: 'TASK_STATE_WORKING',
171
+ completed: 'TASK_STATE_COMPLETED',
172
+ failed: 'TASK_STATE_FAILED',
173
+ canceled: 'TASK_STATE_CANCELED',
174
+ 'input-required': 'TASK_STATE_INPUT_REQUIRED',
175
+ rejected: 'TASK_STATE_REJECTED',
176
+ 'auth-required': 'TASK_STATE_AUTH_REQUIRED',
177
+ };
178
+ /** A task state by its 1.0 name, whichever version sent it. */
179
+ export function taskState(raw) {
180
+ const s = String(raw ?? '');
181
+ if (s.startsWith('TASK_STATE_'))
182
+ return s;
183
+ return LEGACY_STATES[s] ?? 'TASK_STATE_UNSPECIFIED';
184
+ }
185
+ /** A result in 1.0's shape, whether it came as 1.0 or 0.3 (`kind` discriminators). */
186
+ export function normaliseResponse(raw) {
187
+ const r = raw;
188
+ if (r.task || r.message || r.statusUpdate || r.artifactUpdate) {
189
+ const out = { ...r };
190
+ if (out.task)
191
+ out.task = { ...out.task, status: { ...out.task.status, state: taskState(out.task.status?.state) } };
192
+ if (out.statusUpdate) {
193
+ out.statusUpdate = { ...out.statusUpdate, status: { ...out.statusUpdate.status, state: taskState(out.statusUpdate.status?.state) } };
194
+ }
195
+ return out;
196
+ }
197
+ switch (r.kind) {
198
+ case 'task':
199
+ return normaliseResponse({ task: r });
200
+ case 'message':
201
+ return { message: r };
202
+ case 'status-update':
203
+ return normaliseResponse({ statusUpdate: r });
204
+ case 'artifact-update':
205
+ return { artifactUpdate: r };
206
+ default:
207
+ // A bare task, as `GetTask` returns one.
208
+ if (typeof r.id === 'string' && r.status)
209
+ return normaliseResponse({ task: r });
210
+ return {};
211
+ }
212
+ }
213
+ /** Whether a state ends the task. */
214
+ export function isTerminal(state) {
215
+ return (state === 'TASK_STATE_COMPLETED' ||
216
+ state === 'TASK_STATE_FAILED' ||
217
+ state === 'TASK_STATE_CANCELED' ||
218
+ state === 'TASK_STATE_REJECTED');
219
+ }
220
+ /** A message id, unique enough for one conversation. */
221
+ export function newMessageId() {
222
+ const c = globalThis.crypto;
223
+ if (c?.randomUUID)
224
+ return c.randomUUID();
225
+ return `m-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
226
+ }
227
+ //# sourceMappingURL=a2a.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"a2a.js","sourceRoot":"","sources":["../../src/handoff/a2a.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,181 @@
1
+ import { type HandBack, type HandoffEnvelope } from './envelope.js';
2
+ /** Who a target is: software, or a person behind a desk. */
3
+ export type HandoffKind = 'agent' | 'person';
4
+ /** A target as the page knows it: a name, and how to present it. */
5
+ export interface HandoffTargetConfig {
6
+ kind: HandoffKind;
7
+ /** What the visitor reads: "You're talking with *Booking*". */
8
+ label: string;
9
+ /** What it handles, for the model choosing between targets. */
10
+ description?: string;
11
+ }
12
+ /** Where handoffs go. */
13
+ export interface HandoffConfig {
14
+ /** The relay on the site's own origin, `/api/handoff`. */
15
+ relay: string;
16
+ /** The targets, by the name the relay knows them by. */
17
+ targets: Record<string, HandoffTargetConfig>;
18
+ }
19
+ /** A target as the relay reports it, merged with the page's config. */
20
+ export interface HandoffTarget extends HandoffTargetConfig {
21
+ name: string;
22
+ /** Whether it can take a handoff now. A desk outside its hours cannot. */
23
+ available: boolean;
24
+ /** When a desk is staffed, as a person should read it. */
25
+ hours?: string;
26
+ /** How many are waiting at a desk. */
27
+ queueLength?: number;
28
+ /** Expected wait at a desk, in minutes. */
29
+ waitMinutes?: number | null;
30
+ /** Whether the relay allows the recent transcript to be shared with it. */
31
+ transcript: boolean;
32
+ }
33
+ /** Where a handoff stands. See the lifecycle table in `docs/handoff.md`. */
34
+ export type HandoffState = 'connecting' | 'queued' | 'active' | 'awaiting-visitor' | 'handed-back' | 'ended' | 'failed';
35
+ /** Whether a handoff in this state is over. */
36
+ export declare const isHandoffOver: (state: HandoffState) => boolean;
37
+ /** One conversation's handoff, as kept between pages. */
38
+ export interface HandoffSession {
39
+ conversationId: string;
40
+ target: string;
41
+ label: string;
42
+ kind: HandoffKind;
43
+ state: HandoffState;
44
+ taskId?: string;
45
+ contextId?: string;
46
+ /** The relay's `Nxis-Handoff-Key`, proving this browser started the task. */
47
+ key?: string;
48
+ /** Who is speaking at the target, once a person or agent has said. */
49
+ operator?: string;
50
+ /** Place in a desk's queue, while queued. */
51
+ queue?: {
52
+ position?: number;
53
+ waitMinutes?: number;
54
+ };
55
+ handBack?: HandBack;
56
+ /** Why it failed, in words for the visitor. */
57
+ error?: string;
58
+ startedAt: number;
59
+ updatedAt: number;
60
+ /** Message ids already shown, so a resumed task's history is not shown twice. */
61
+ seen: string[];
62
+ }
63
+ /** What the controller reports, as the `detail` of a `handoff` event. */
64
+ export type HandoffEvent = {
65
+ type: 'state';
66
+ conversationId: string;
67
+ session: HandoffSession;
68
+ state: HandoffState;
69
+ } | {
70
+ type: 'message';
71
+ conversationId: string;
72
+ session: HandoffSession;
73
+ /** Who said it: the operator's name, or the target's label. */
74
+ from: string;
75
+ fromKind: HandoffKind;
76
+ text: string;
77
+ } | {
78
+ type: 'handback';
79
+ conversationId: string;
80
+ session: HandoffSession;
81
+ handBack: HandBack;
82
+ };
83
+ /** Options for {@link HandoffController}. */
84
+ export interface HandoffControllerOptions extends HandoffConfig {
85
+ /** `fetch` to use. Defaults to the global. */
86
+ fetchImpl?: typeof fetch;
87
+ /** Where sessions are kept. Defaults to `localStorage`; null keeps them in memory only. */
88
+ storage?: Storage | null;
89
+ /** The clock. */
90
+ now?: () => number;
91
+ /** First wait before picking up a stream the host cut off. Doubles up to 15 s. */
92
+ resumeDelayMs?: number;
93
+ }
94
+ /** The response header a relay ties a task to this browser with. */
95
+ export declare const HANDOFF_KEY_HEADER = "Nxis-Handoff-Key";
96
+ /** Runs handoffs for every conversation on the page. Dispatches `handoff` events. */
97
+ export declare class HandoffController extends EventTarget {
98
+ private readonly opts;
99
+ private readonly sessions;
100
+ /** The stream open for each conversation, so a new one can close it first. */
101
+ private readonly live;
102
+ /** Consecutive resumes that brought nothing, per conversation, for backoff. */
103
+ private readonly idle;
104
+ private readonly timers;
105
+ private targetsCache;
106
+ /** Events reported so far, so a stream can tell whether it brought anything new. */
107
+ private reported;
108
+ private readonly storage;
109
+ private loaded;
110
+ constructor(opts: HandoffControllerOptions);
111
+ /** The page's target config. */
112
+ get config(): HandoffConfig;
113
+ /**
114
+ * The targets that can be offered: those both in the page's config and on
115
+ * the relay's list, with the relay's availability.
116
+ *
117
+ * Cached for thirty seconds. A relay that can't be reached offers none.
118
+ */
119
+ targets(): Promise<HandoffTarget[]>;
120
+ private fetchTargets;
121
+ /** A conversation's handoff, open or finished, if it has one. */
122
+ session(conversationId: string): HandoffSession | null;
123
+ /** A conversation's handoff while it is open, else null. */
124
+ active(conversationId: string | undefined): HandoffSession | null;
125
+ /**
126
+ * Hand a conversation to a target.
127
+ *
128
+ * Resolves once the relay has accepted it; what the target says arrives as
129
+ * events. Call only after the visitor has confirmed what is being sent.
130
+ *
131
+ * @throws When the target is not configured, the conversation is already
132
+ * handed off, or the relay refuses. The session is marked failed first.
133
+ */
134
+ start(conversationId: string, target: string, envelope: HandoffEnvelope): Promise<HandoffSession>;
135
+ /**
136
+ * Send what the visitor typed to the target handling this conversation.
137
+ *
138
+ * @throws When nothing is handed off, or the target hasn't answered yet.
139
+ */
140
+ send(conversationId: string, text: string): Promise<void>;
141
+ /**
142
+ * End a handoff at the visitor's request: "Back to the assistant".
143
+ *
144
+ * Cancels the task at the target. A target that won't cancel still loses the
145
+ * visitor: the handoff is ended here either way.
146
+ */
147
+ end(conversationId: string): Promise<void>;
148
+ /**
149
+ * Pick up an open handoff after a page change or reload.
150
+ *
151
+ * Subscribes to the task while it runs, or reads it once when it is waiting
152
+ * on the visitor. Safe to call for any conversation, at any time: it does
153
+ * nothing when there is no open handoff or a stream is already open.
154
+ */
155
+ resume(conversationId: string): Promise<void>;
156
+ /** Stop every stream and pending resume. The sessions stay saved for the next page. */
157
+ dispose(): void;
158
+ private client;
159
+ private keyHeader;
160
+ /** A fresh abort signal for a conversation's stream, closing the one before it. */
161
+ private openLive;
162
+ private closeLive;
163
+ /** Read a stream to its end, then resume it if the task is still running. */
164
+ private pump;
165
+ /** Read a task once, for a handoff that can't or needn't be streamed. */
166
+ private readOnce;
167
+ private apply;
168
+ private adopt;
169
+ private status;
170
+ private stateFor;
171
+ /** One message from the target: who is speaking, a hand-back, and the words. */
172
+ private message;
173
+ private parts;
174
+ private remember;
175
+ private setState;
176
+ private emit;
177
+ private now;
178
+ private load;
179
+ private save;
180
+ }
181
+ //# sourceMappingURL=controller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/handoff/controller.ts"],"names":[],"mappings":"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"}