nexus-ai-pro 2.9.0 → 2.10.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/API_STABILITY.md +43 -0
- package/CHANGELOG.md +137 -1
- package/README.md +10 -5
- package/dist/core/lifecycle.d.ts +11 -1
- package/dist/core/lifecycle.js +9 -0
- package/dist/core/nexus.js +4 -0
- package/dist/deep-agent/helpers.d.ts +32 -0
- package/dist/deep-agent/helpers.js +62 -1
- package/dist/deep-agent/index.d.ts +10 -2
- package/dist/graph/graph.d.ts +5 -0
- package/dist/graph/graph.js +6 -0
- package/dist/lifecycle/index.d.ts +1 -1
- package/dist/protocols/a2a.d.ts +84 -1
- package/dist/protocols/a2a.js +178 -14
- package/dist/protocols/acp.d.ts +16 -1
- package/dist/protocols/acp.js +78 -17
- package/dist/protocols/shared.d.ts +15 -0
- package/dist/realtime/conversation.d.ts +28 -1
- package/dist/realtime/conversation.js +62 -1
- package/dist/realtime/gemini-live.transport.d.ts +134 -0
- package/dist/realtime/gemini-live.transport.js +769 -0
- package/dist/realtime/index.d.ts +8 -4
- package/dist/realtime/index.js +5 -3
- package/dist/realtime/openai-server.d.ts +49 -0
- package/dist/realtime/openai-server.js +103 -0
- package/dist/realtime/openai-sip.d.ts +114 -0
- package/dist/realtime/openai-sip.js +111 -0
- package/dist/realtime/openai-webrtc.transport.d.ts +7 -0
- package/dist/realtime/openai-webrtc.transport.js +4 -0
- package/dist/realtime/openai-websocket.transport.d.ts +5 -0
- package/dist/realtime/openai-websocket.transport.js +3 -0
- package/dist/realtime/provider.d.ts +20 -0
- package/dist/realtime/provider.js +17 -0
- package/dist/realtime/session.d.ts +51 -2
- package/dist/realtime/session.js +249 -20
- package/dist/realtime/types.d.ts +73 -2
- package/dist/types/lifecycle.d.ts +13 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist-cjs/core/lifecycle.d.ts +11 -1
- package/dist-cjs/core/lifecycle.js +9 -0
- package/dist-cjs/core/nexus.js +4 -0
- package/dist-cjs/deep-agent/helpers.d.ts +32 -0
- package/dist-cjs/deep-agent/helpers.js +62 -1
- package/dist-cjs/deep-agent/index.d.ts +10 -2
- package/dist-cjs/graph/graph.d.ts +5 -0
- package/dist-cjs/graph/graph.js +6 -0
- package/dist-cjs/lifecycle/index.d.ts +1 -1
- package/dist-cjs/protocols/a2a.d.ts +84 -1
- package/dist-cjs/protocols/a2a.js +178 -14
- package/dist-cjs/protocols/acp.d.ts +16 -1
- package/dist-cjs/protocols/acp.js +78 -17
- package/dist-cjs/protocols/shared.d.ts +15 -0
- package/dist-cjs/realtime/conversation.d.ts +28 -1
- package/dist-cjs/realtime/conversation.js +66 -1
- package/dist-cjs/realtime/gemini-live.transport.d.ts +134 -0
- package/dist-cjs/realtime/gemini-live.transport.js +773 -0
- package/dist-cjs/realtime/index.d.ts +8 -4
- package/dist-cjs/realtime/index.js +15 -1
- package/dist-cjs/realtime/openai-server.d.ts +49 -0
- package/dist-cjs/realtime/openai-server.js +108 -0
- package/dist-cjs/realtime/openai-sip.d.ts +114 -0
- package/dist-cjs/realtime/openai-sip.js +114 -0
- package/dist-cjs/realtime/openai-webrtc.transport.d.ts +7 -0
- package/dist-cjs/realtime/openai-webrtc.transport.js +4 -0
- package/dist-cjs/realtime/openai-websocket.transport.d.ts +5 -0
- package/dist-cjs/realtime/openai-websocket.transport.js +3 -0
- package/dist-cjs/realtime/provider.d.ts +20 -0
- package/dist-cjs/realtime/provider.js +19 -1
- package/dist-cjs/realtime/session.d.ts +51 -2
- package/dist-cjs/realtime/session.js +248 -19
- package/dist-cjs/realtime/types.d.ts +73 -2
- package/dist-cjs/types/lifecycle.d.ts +13 -0
- package/dist-cjs/version.d.ts +1 -1
- package/dist-cjs/version.js +1 -1
- package/llms.txt +1 -1
- package/package.json +33 -5
package/API_STABILITY.md
CHANGED
|
@@ -41,6 +41,49 @@ Both are covered by this policy:
|
|
|
41
41
|
Deep imports into `dist`, `dist-cjs`, or `src` are not supported. Import only from `nexus-ai-pro` or
|
|
42
42
|
one of its explicit subpaths.
|
|
43
43
|
|
|
44
|
+
## 2.10 stage (2.10.0)
|
|
45
|
+
|
|
46
|
+
Everything 2.10 adds is stable from the start and additive, unless this section says otherwise.
|
|
47
|
+
|
|
48
|
+
New and stable:
|
|
49
|
+
- on `nexus-ai-pro/protocols/a2a`: `A2aHandler`, which `a2aHandler()` now returns, with `owner`,
|
|
50
|
+
`recover()`, and `close()`; `A2aRecoveryReport`; `A2aTaskLease` and `A2aTaskRun`, and
|
|
51
|
+
`A2aTaskRecord.lease`, `run`, and `version`; `A2aTaskStore.claim()`, optional;
|
|
52
|
+
`A2aServerOptions.lease` and `recoverEveryMs`. The handler is still the request function it was;
|
|
53
|
+
- on `nexus-ai-pro/protocols/acp`: `AcpTurnRecord` and `AcpSessionRecord.turn`;
|
|
54
|
+
- on `nexus-ai-pro/deep-agent`: `DeepSubagent.remote`, `DeepRemoteHelper`, and `DeepRemoteRunResult`;
|
|
55
|
+
- `CompiledGraph.continueWith()`, and `ProtocolGraph.continueWith()` and `state()`, both optional;
|
|
56
|
+
- on `nexus-ai-pro/lifecycle`: `ProviderHealthOutcome`, `OperationLifecycle.reportHealth()`,
|
|
57
|
+
`OperationLifecycleLike.reportHealth()`, optional, and `LifecycleRuntime.providerHealth`;
|
|
58
|
+
- on `nexus-ai-pro/realtime`: `RealtimeSessionConfig.tenantId`, `userId`, `principal`,
|
|
59
|
+
`traceContext`, `conversationStore`, and `resume`; `RealtimeConversationOptions.replayItems`;
|
|
60
|
+
`RealtimeConversationStore`, `MemoryRealtimeConversationStore`, `storeRealtimeConversations()`,
|
|
61
|
+
`replayRealtimeConversation()`, and `REALTIME_REPLAY_PREFIX`; `GeminiLiveProvider` and
|
|
62
|
+
`GeminiLiveProviderOptions`; `RealtimeSession.sendImage()` and `sendVideoFrame()`,
|
|
63
|
+
`RealtimeSessionConfig.video`, `RealtimeImageInput`, `RealtimeImageOptions`, and
|
|
64
|
+
`RealtimeVideoOptions`; `RealtimeTransport.sendVideo()`, optional;
|
|
65
|
+
- `RealtimeConversationOptions.retainRawAudio`, `RealtimeConversationReducerOptions.inPlace`, and
|
|
66
|
+
`RealtimePeerConnectionLike.addTransceiver()`, optional;
|
|
67
|
+
- `nexus-ai-pro/realtime/gemini-live`, with `GeminiLiveTransport` and `GeminiLiveTransportOptions`;
|
|
68
|
+
- `nexus-ai-pro/realtime/openai-sip`, with `answerOpenAISipCalls()` and its types, also on
|
|
69
|
+
`nexus-ai-pro/realtime`;
|
|
70
|
+
- on `nexus-ai-pro/realtime/openai-server` and `nexus-ai-pro/realtime`: `acceptOpenAIRealtimeCall()`,
|
|
71
|
+
`rejectOpenAIRealtimeCall()`, `referOpenAIRealtimeCall()`, `hangupOpenAIRealtimeCall()`,
|
|
72
|
+
`verifyOpenAIWebhook()`, `OpenAIRealtimeCallOptions`, `OpenAIWebhookEvent`, `OpenAIWebhookHeaders`,
|
|
73
|
+
and `OpenAIWebhookVerifyOptions`; `OpenAIWebSocketTransportOptions.callId`.
|
|
74
|
+
|
|
75
|
+
The realtime family stays experimental, and all of these with it.
|
|
76
|
+
|
|
77
|
+
Changed, each to keep a promise the surface already made:
|
|
78
|
+
- A realtime session's raw events keep an audio event's size, `delta_bytes`, in place of its audio,
|
|
79
|
+
unless `retainRawAudio`: a session held every frame it played, about 13 MB a 3-minute call. The
|
|
80
|
+
`openai-events` export follows.
|
|
81
|
+
- A realtime session announces `conversation.updated` only to a listener, as before, but no longer
|
|
82
|
+
copies its record for each event when none is listening.
|
|
83
|
+
- A realtime session given a lifecycle runs each turn as a `realtime.turn` operation and charges it
|
|
84
|
+
as it finishes; its `realtime.session` operation reports the total and charges nothing more. Its
|
|
85
|
+
usage and cost are the sum of its turns', where each turn's replaced the last before.
|
|
86
|
+
|
|
44
87
|
## 2.9 stage (2.9.0)
|
|
45
88
|
|
|
46
89
|
Everything 2.9 adds is stable from the start and additive, unless this section says otherwise.
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,141 @@ Notable changes to this project are recorded here. The format follows [Keep a Ch
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [2.10.0] - 2026-10-16
|
|
8
|
+
|
|
9
|
+
Durable conversations, and a second realtime provider.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **A2A tasks that outlive their replica.** A working task's record now holds an `A2aTaskLease` and
|
|
14
|
+
an `A2aTaskRun`. The lease names the replica running the task, which renews it every third of
|
|
15
|
+
`lease.ms` (30 seconds); the run says what the task was asked to do. `a2aHandler()` returns an
|
|
16
|
+
`A2aHandler`: the request handler, plus `recover()` and `close()`. `recover()` takes over working
|
|
17
|
+
tasks whose lease lapsed, and carries each run on in one of three ways:
|
|
18
|
+
- from the graph's last checkpoint;
|
|
19
|
+
- by answering its questions again, when a resume never reached the graph;
|
|
20
|
+
- from the start, when the run left no checkpoint.
|
|
21
|
+
|
|
22
|
+
`recoverEveryMs` runs it on a timer. `A2aTaskStore.claim()` takes a lease in one step, so two
|
|
23
|
+
replicas recovering at once take a task once. `MemoryA2aTaskStore` has it, and `storeA2aTasks()`
|
|
24
|
+
builds it on `putIfVersion()`. A replica that finds its lease taken stops its run, and never
|
|
25
|
+
records its result over the other's.
|
|
26
|
+
- **ACP turns that outlive the agent.** While a prompt runs, its session record holds an
|
|
27
|
+
`AcpTurnRecord`. When the editor reopens the session with `session/load`, the agent carries the
|
|
28
|
+
turn on, streaming its updates: from the graph's last checkpoint, by answering again, or from the
|
|
29
|
+
start. A turn stopped while the editor was deciding an approval asks for it again.
|
|
30
|
+
- **Realtime on the client's lifecycle.** A session given `ai.lifecycle` runs as `realtime.session`,
|
|
31
|
+
and each model turn as `realtime.turn`, carrying the session's `tenantId`, `userId` (the
|
|
32
|
+
`principal`'s by default), and `traceContext`. A turn is admitted as it starts, rate-limited,
|
|
33
|
+
authorized, and holding the average turn cost against the budget, and charged its cost when it
|
|
34
|
+
finishes. A turn the lifecycle refuses is cancelled before the model runs it, and the session ends
|
|
35
|
+
with `lifecycle_refused`. Connections are reported to the client's provider health and circuit
|
|
36
|
+
breaker through the new `reportHealth()`, on `OperationLifecycleLike` and `OperationLifecycle`,
|
|
37
|
+
with `ProviderHealthOutcome` and `LifecycleRuntime.providerHealth`.
|
|
38
|
+
- **Realtime conversations that outlive their process.** `conversationStore`, a
|
|
39
|
+
`RealtimeConversationStore`, keeps the record after each committed turn:
|
|
40
|
+
`MemoryRealtimeConversationStore`, or `storeRealtimeConversations()` on any `Store`. `resume`
|
|
41
|
+
carries a conversation on, from its record or its id: same id, items, metrics, and metadata. On
|
|
42
|
+
connecting, the provider is given what was said and what the tools returned before anything new,
|
|
43
|
+
through `replayRealtimeConversation()`. Echoes carry `REALTIME_REPLAY_PREFIX` and are not recorded
|
|
44
|
+
twice. `conversation.replayItems` caps the replay.
|
|
45
|
+
- **Gemini Live, behind the same realtime session.** `GeminiLiveTransport`, on the new
|
|
46
|
+
`nexus-ai-pro/realtime/gemini-live`, speaks Gemini's Live API, and `GeminiLiveProvider`, on
|
|
47
|
+
`nexus-ai-pro/realtime`, creates sessions on it for `createRealtimeAgent()`. The session's
|
|
48
|
+
configuration becomes Gemini's setup; audio, messages, and tool results become its realtime input,
|
|
49
|
+
turns, and tool responses; and its audio, transcripts, tool calls, interruptions, and usage become
|
|
50
|
+
the events the session already reads. It takes an API key on a server or an ephemeral token
|
|
51
|
+
anywhere, and reconnects a dropped socket with Gemini's latest resumption handle.
|
|
52
|
+
- **Helpers on another server, whose runs outlive their worker.** A `DeepSubagent` with `remote`, a
|
|
53
|
+
`DeepRemoteHelper` such as `createRemoteGraph()` returns, runs on another agent server, as a run
|
|
54
|
+
leased to the worker that runs it: when that worker stops, another takes it over. The agent starts
|
|
55
|
+
the run under a key made from the call that started it, so delegating again or collecting it, in
|
|
56
|
+
this process or another, finds the same run; a background task is marked on its thread first, so a
|
|
57
|
+
run that never reached the server is started by the process that collects it. Its answer comes from
|
|
58
|
+
a `DeepRemoteRunResult`.
|
|
59
|
+
- **Phone calls answered by a realtime agent, over SIP.** `answerOpenAISipCalls()`, on the new
|
|
60
|
+
`nexus-ai-pro/realtime/openai-sip`, handles OpenAI's `realtime.call.incoming` webhook: it verifies
|
|
61
|
+
each delivery, claims it so a retry answers its call once, and answers the call with the session
|
|
62
|
+
`answer()` chooses from the caller, which attaches to the call and runs it with its tools,
|
|
63
|
+
record, and lifecycle; or declines it with a SIP status. `OpenAISipCall` transfers and hangs up.
|
|
64
|
+
The call control is on `nexus-ai-pro/realtime/openai-server` too: `acceptOpenAIRealtimeCall()`,
|
|
65
|
+
`rejectOpenAIRealtimeCall()`, `referOpenAIRealtimeCall()`, `hangupOpenAIRealtimeCall()`, and
|
|
66
|
+
`verifyOpenAIWebhook()`, on Web Crypto. `OpenAIWebSocketTransportOptions.callId` attaches a
|
|
67
|
+
transport to a call.
|
|
68
|
+
- **A published method and budget for realtime turn latency and session load.** `npm run
|
|
69
|
+
bench:realtime`, in the release gate, holds what a session adds to a message (under 5 ms at p99 for
|
|
70
|
+
one call, under 20 ms with 250 calls at real time), that a turn late in a long call costs what an
|
|
71
|
+
early one does, that 250 calls at real time keep up, the heap each session holds, and that ended
|
|
72
|
+
sessions leave nothing. Budgets are in `realtime-budget.json`.
|
|
73
|
+
- **Realtime over real WebRTC in a browser.** `npm run test:browser:realtime`, in `npm test`, drives a
|
|
74
|
+
session in headless Chromium against a peer connection in the page that plays the provider: real
|
|
75
|
+
SDP, ICE, DTLS, and data channel, the microphone up, the model's voice down, a tool call, and a
|
|
76
|
+
session with no microphone.
|
|
77
|
+
- `retainRawAudio` on a realtime conversation's options, `inPlace` on
|
|
78
|
+
`RealtimeConversationReducerOptions`, and `addTransceiver()`, optional, on
|
|
79
|
+
`RealtimePeerConnectionLike`.
|
|
80
|
+
- **Video and images in a realtime session.** `sendVideoFrame()` streams camera or screen frames:
|
|
81
|
+
as realtime video on Gemini Live, as images in the conversation elsewhere, spaced by
|
|
82
|
+
`RealtimeVideoOptions.minIntervalMs`. `sendImage()` adds an image, with text, to the conversation.
|
|
83
|
+
`RealtimeTransport.sendVideo()`, optional, is what a transport with video input implements.
|
|
84
|
+
- `continueWith()` on a compiled graph: `continue()`, returning the final result.
|
|
85
|
+
`ProtocolGraph.continueWith()` and `state()`, both optional, are what A2A and ACP carry a run on
|
|
86
|
+
with.
|
|
87
|
+
|
|
88
|
+
### Fixed
|
|
89
|
+
|
|
90
|
+
- The WebRTC transport trimmed the SDP answer's final line break, and Chromium refuses a description
|
|
91
|
+
whose last line has none, so a session in Chrome or Edge never connected.
|
|
92
|
+
- A WebRTC session that sent no microphone offered no audio to receive, so it never heard the model.
|
|
93
|
+
- A realtime session copied its whole conversation record twice for every event, so each event cost
|
|
94
|
+
more as the call went on: a turn took 39 ms by the 500th, and 250 calls took half a core. It now
|
|
95
|
+
applies events in place and copies the record only for a `conversation.updated` listener.
|
|
96
|
+
- An A2A `SendMessage` with `returnImmediately`, whose store failed while the task ran, raised an
|
|
97
|
+
unhandled rejection, which ends a Node process by default. A store that cannot record a task's
|
|
98
|
+
failure now leaves the task working, with its lease, for the replica that takes it over.
|
|
99
|
+
- A realtime session's usage and estimated cost are the sum of its responses'. Before, each
|
|
100
|
+
response's usage replaced the last, so a session recorded, and settled, only its last turn.
|
|
101
|
+
|
|
102
|
+
### Proof
|
|
103
|
+
|
|
104
|
+
- `tests/conversation-crash.test.ts`: an A2A task, an ACP turn, and a realtime conversation are each
|
|
105
|
+
killed at every write a clean run makes, and taken over elsewhere. Each ends as the clean run did,
|
|
106
|
+
or its caller was told it never started; nothing is left half done, and nothing is recorded twice.
|
|
107
|
+
The suite found three defects before they shipped, each fixed: an unhandled rejection from a
|
|
108
|
+
failing store; a task submitted to a replica that stopped before starting it, which no replica
|
|
109
|
+
would ever start; and a reopened session's save that dropped the turn it was about to carry on.
|
|
110
|
+
- `tests/deep-agent-remote-helpers.test.ts`: against two replicas of a real agent server, the agent's
|
|
111
|
+
process and the worker running its remote helper both die; the second replica takes the run over,
|
|
112
|
+
and the agent, resumed in a new process, collects that same run: one run, its answer, and the
|
|
113
|
+
agent's note not written again. Without the key, the resumed agent starts a second run, and the
|
|
114
|
+
proof fails.
|
|
115
|
+
- `tests/realtime-sip.test.ts`: a signed incoming-call delivery is accepted with the agent's session,
|
|
116
|
+
chosen from the caller's number; the session attaches by the call's id, books a table through its
|
|
117
|
+
tool when the caller asks, and hangs up. Forged, altered, and stale deliveries answer nothing; a
|
|
118
|
+
retry after a failed accept answers the call, and a duplicate, on one replica or two, answers it
|
|
119
|
+
once. Without the signature check, the claim, or the release, a test fails.
|
|
120
|
+
- `tests/realtime-gemini.test.ts`: one agent definition (instructions, a tool, conversation settings)
|
|
121
|
+
runs on OpenAI's realtime API and on Gemini Live, each over its own scripted wire protocol. The two
|
|
122
|
+
records match item for item: what the person asked, the tool call and its result, and what the model
|
|
123
|
+
answered, with the same usage, and the tool runs once on each. Without the tool response, or with
|
|
124
|
+
the request for a response not dropped after it, the proof fails.
|
|
125
|
+
- `tests/realtime-lifecycle.test.ts`: through a real client with a five-cent budget, two
|
|
126
|
+
two-cent turns run, and the third is cancelled as it starts and ends the session; the budget holds
|
|
127
|
+
four cents, and every operation names the tenant and user. Connection failures open the client's
|
|
128
|
+
circuit. Without the admission at the turn's start, the proof fails.
|
|
129
|
+
- `tests/realtime-resume.test.ts`: a session's process is killed mid-conversation on the memory and
|
|
130
|
+
SQLite stores. Another resumes it by id on a new transport, and gives the provider what was said
|
|
131
|
+
before anything new. The conversation goes on under its own id with nothing recorded twice.
|
|
132
|
+
Without the replay, or without skipping the echoes, the proof fails.
|
|
133
|
+
- `tests/a2a-takeover.test.ts`: a task whose replica dies halfway through a tool is taken over, on
|
|
134
|
+
the memory, SQLite, Postgres, and Redis stores. The model call the dead replica finished is not
|
|
135
|
+
made again, and the email it never sent is sent once. Two replicas recovering at the same moment
|
|
136
|
+
take the task over once, and a lease renewed while a slow task works is never taken. Without the
|
|
137
|
+
lease check, or carried on from the start instead of the checkpoint, the proof fails.
|
|
138
|
+
- `tests/acp-turns.test.ts`: an agent stopped halfway through a tool is started again and its
|
|
139
|
+
session reopened; the turn goes on without repeating the finished model call, and sends the email
|
|
140
|
+
once. A turn stopped during an approval asks again. Without the recovery on load, the proof fails.
|
|
141
|
+
|
|
7
142
|
## [2.9.0] - 2026-10-15
|
|
8
143
|
|
|
9
144
|
Video, and everything shipped, proven.
|
|
@@ -2559,7 +2694,8 @@ shared policy vocabulary is in place for a future release that revisits this.
|
|
|
2559
2694
|
- URL fetching rejects unsafe private-network targets and limits response reads.
|
|
2560
2695
|
- CLI and audit findings no longer disclose detected secret values.
|
|
2561
2696
|
|
|
2562
|
-
[Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.
|
|
2697
|
+
[Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.10.0...HEAD
|
|
2698
|
+
[2.10.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.9.0...v2.10.0
|
|
2563
2699
|
[2.9.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.8.0...v2.9.0
|
|
2564
2700
|
[2.8.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.7.0...v2.8.0
|
|
2565
2701
|
[2.7.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.6.0...v2.7.0
|
package/README.md
CHANGED
|
@@ -294,14 +294,19 @@ Shipped so far:
|
|
|
294
294
|
priced per second; online evaluation that reads every run however many share a millisecond, and
|
|
295
295
|
budget reservations a dead worker leaves settled by what its call did; a deep agent's helper limit
|
|
296
296
|
across processes; a media index that remembers what it holds; and the experimental families run
|
|
297
|
-
against their live services every week
|
|
297
|
+
against their live services every week;
|
|
298
|
+
- 2.10 durable conversations, and a second realtime provider: A2A tasks, ACP turns, and realtime
|
|
299
|
+
sessions taken over by another worker when theirs dies, each proven killed at every write; realtime
|
|
300
|
+
on the client's lifecycle; Gemini Live behind the same session, with video and images; phone calls
|
|
301
|
+
answered over SIP; helpers on another server whose runs outlive their worker; and realtime over
|
|
302
|
+
real WebRTC in a browser, with published budgets for turn latency and session load.
|
|
298
303
|
|
|
299
304
|
Upgrading from 1.x: `npx nexus migrate src --write` moves your imports, and
|
|
300
305
|
[MIGRATING.md](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/MIGRATING.md) covers the rest.
|
|
301
|
-
Next, 2.
|
|
302
|
-
|
|
303
|
-
embedding batch tier, media understanding, and video graduate once the live
|
|
304
|
-
each. The
|
|
306
|
+
Next, 2.11 separates where an agent's files live from where its code runs, with workspaces, and
|
|
307
|
+
completes telephony: a second provider, messaging, recording, conference, and transfer. Pinecone,
|
|
308
|
+
images, the Gemini embedding batch tier, media understanding, and video graduate once the live
|
|
309
|
+
workflow passes against each. The
|
|
305
310
|
[roadmap](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/ROADMAP.md) lists what comes after, in the order
|
|
306
311
|
applications ask for it.
|
|
307
312
|
|
package/dist/core/lifecycle.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RateLimitConfig } from '../types/config.js';
|
|
2
|
-
import type { LifecycleConfig, LifecycleStage, OperationDescriptor, OperationLifecycleLike, OperationResultInfo, OperationStartOptions, OperationTicket, ProviderCallContext } from '../types/lifecycle.js';
|
|
2
|
+
import type { ProviderHealthOutcome, LifecycleConfig, LifecycleStage, OperationDescriptor, OperationLifecycleLike, OperationResultInfo, OperationStartOptions, OperationTicket, ProviderCallContext } from '../types/lifecycle.js';
|
|
3
3
|
import type { AuditLogger } from '../ops/audit-logger.js';
|
|
4
4
|
import type { MetricsCollector } from '../ops/metrics.js';
|
|
5
5
|
import { type RateLimiter } from '../ops/rate-limiter.js';
|
|
@@ -45,6 +45,11 @@ export interface LifecycleRuntime {
|
|
|
45
45
|
config?: LifecycleConfig;
|
|
46
46
|
/** Default milliseconds an operation may run before its signal aborts. */
|
|
47
47
|
timeoutMs?: number;
|
|
48
|
+
/** Where `reportHealth()` goes: the client's provider health and circuit breaker. */
|
|
49
|
+
providerHealth?: {
|
|
50
|
+
recordSuccess(provider: string, latencyMs: number): void;
|
|
51
|
+
recordFailure(provider: string, error: unknown): void;
|
|
52
|
+
};
|
|
48
53
|
}
|
|
49
54
|
/** The family's part of one operation: what each stage does for it. Every stage but `execute` is optional. */
|
|
50
55
|
export interface OperationPlan<T> {
|
|
@@ -99,6 +104,11 @@ export declare class OperationLifecycle implements OperationLifecycleLike {
|
|
|
99
104
|
}, plan: OperationPlan<T>, options?: OperationStartOptions & {
|
|
100
105
|
audit?: Record<string, unknown>;
|
|
101
106
|
}): Promise<T>;
|
|
107
|
+
/**
|
|
108
|
+
* Reports how a provider answered a call a family made itself, such as a realtime connection, to the
|
|
109
|
+
* client's provider health and circuit breaker.
|
|
110
|
+
*/
|
|
111
|
+
reportHealth(provider: string, outcome: ProviderHealthOutcome): void;
|
|
102
112
|
/** True when nothing is configured, so a caller may skip work that only feeds the lifecycle. */
|
|
103
113
|
get inert(): boolean;
|
|
104
114
|
private checkRateLimit;
|
package/dist/core/lifecycle.js
CHANGED
|
@@ -91,6 +91,15 @@ export class OperationLifecycle {
|
|
|
91
91
|
throw error;
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
|
+
reportHealth(provider, outcome) {
|
|
95
|
+
const health = this.runtime.providerHealth;
|
|
96
|
+
if (!health || !provider)
|
|
97
|
+
return;
|
|
98
|
+
if (outcome.ok)
|
|
99
|
+
health.recordSuccess(provider, outcome.latencyMs);
|
|
100
|
+
else
|
|
101
|
+
health.recordFailure(provider, outcome.error);
|
|
102
|
+
}
|
|
94
103
|
get inert() {
|
|
95
104
|
const config = this.runtime.config;
|
|
96
105
|
return (!this.runtime.metrics?.enabled &&
|
package/dist/core/nexus.js
CHANGED
|
@@ -75,6 +75,10 @@ export class NexusAI {
|
|
|
75
75
|
rateLimiter: this.rateLimiter,
|
|
76
76
|
rateLimit: this.config.rateLimit,
|
|
77
77
|
config: this.config.lifecycle,
|
|
78
|
+
providerHealth: {
|
|
79
|
+
recordSuccess: (provider, latencyMs) => this.attemptHooks.onAttemptSuccess(provider, latencyMs),
|
|
80
|
+
recordFailure: (provider, error) => this.attemptHooks.onAttemptFailure(provider, error),
|
|
81
|
+
},
|
|
78
82
|
});
|
|
79
83
|
const familyRuntime = {
|
|
80
84
|
metrics: this.metrics,
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
* The helpers an agent creates are recorded on a thread of their own beside its own, before the call
|
|
11
11
|
* that creates one returns, so an agent resumed in another process counts them against its `max` and
|
|
12
12
|
* delegates to them again by name.
|
|
13
|
+
*
|
|
14
|
+
* A remote helper runs on another agent server, as a run that server's workers lease: when the worker
|
|
15
|
+
* running it stops, another takes it over. The agent starts it under a key made from the call that
|
|
16
|
+
* started it, so delegating again or collecting it, in this process or another, finds that same run.
|
|
13
17
|
*/
|
|
14
18
|
import { type CreateAgentOptions } from '../agent/create-agent.js';
|
|
15
19
|
import type { PermissionPolicyLike } from '../agent/permissions.js';
|
|
@@ -29,6 +33,33 @@ export interface DeepHelperLimits {
|
|
|
29
33
|
/** How many helpers the agent may create in one thread. Defaults to 5. */
|
|
30
34
|
max?: number;
|
|
31
35
|
}
|
|
36
|
+
/** How a run on another agent server ended, as `createRemoteGraph()` reports it. */
|
|
37
|
+
export interface DeepRemoteRunResult {
|
|
38
|
+
/** The run's id on that server. */
|
|
39
|
+
runId: string;
|
|
40
|
+
/** Where it ended: `succeeded`, `failed`, `cancelled`, `expired`, or `awaiting_input`. */
|
|
41
|
+
status: string;
|
|
42
|
+
/** What it produced. */
|
|
43
|
+
output?: unknown;
|
|
44
|
+
/** Why it failed. */
|
|
45
|
+
error?: {
|
|
46
|
+
message?: string;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* A helper on another agent server: what `createRemoteGraph()` returns, or anything that runs an input
|
|
51
|
+
* there and waits for it.
|
|
52
|
+
*/
|
|
53
|
+
export interface DeepRemoteHelper {
|
|
54
|
+
/**
|
|
55
|
+
* Runs an input there and waits for it to end. A second call with the same `idempotencyKey` waits
|
|
56
|
+
* for the run the first started, as the agent server does, rather than starting another.
|
|
57
|
+
*/
|
|
58
|
+
invoke(input: unknown, options: {
|
|
59
|
+
idempotencyKey: string;
|
|
60
|
+
signal?: AbortSignal;
|
|
61
|
+
}): Promise<DeepRemoteRunResult>;
|
|
62
|
+
}
|
|
32
63
|
/** What the deep agent hands the helper tools. */
|
|
33
64
|
export interface HelperSetup {
|
|
34
65
|
/** The helpers the application declared. */
|
|
@@ -39,6 +70,7 @@ export interface HelperSetup {
|
|
|
39
70
|
tools?: readonly string[];
|
|
40
71
|
model?: string;
|
|
41
72
|
maxIterations?: number;
|
|
73
|
+
remote?: DeepRemoteHelper;
|
|
42
74
|
}>;
|
|
43
75
|
/** Limits on created helpers, when the agent may create them. */
|
|
44
76
|
create?: DeepHelperLimits;
|
|
@@ -59,6 +59,7 @@ export function helperTools(setup) {
|
|
|
59
59
|
...(helper.model ? { model: helper.model } : {}),
|
|
60
60
|
maxIterations: helper.maxIterations ?? 20,
|
|
61
61
|
created: false,
|
|
62
|
+
...(helper.remote ? { remote: helper.remote } : {}),
|
|
62
63
|
};
|
|
63
64
|
}
|
|
64
65
|
await loadCreated(thread);
|
|
@@ -144,9 +145,50 @@ export function helperTools(setup) {
|
|
|
144
145
|
return { helper, ok: false, error: `The ${helper} helper stopped: ${why ?? 'it did not run'}` };
|
|
145
146
|
};
|
|
146
147
|
const thread = (parent, task) => `${parent ?? 'thread'}/helper/${task}`;
|
|
148
|
+
const remoteRun = async (helper, remote, task, key, signal) => {
|
|
149
|
+
const run = await remote.invoke(agentInput(`${helper.instructions}\n\n${task}`), {
|
|
150
|
+
idempotencyKey: key,
|
|
151
|
+
...(signal ? { signal } : {}),
|
|
152
|
+
});
|
|
153
|
+
if (run.status === 'succeeded')
|
|
154
|
+
return { helper: helper.name, ok: true, answer: remoteAnswer(run.output) };
|
|
155
|
+
const why = run.status === 'awaiting_input' ? 'it stopped to ask for input' : (run.error?.message ?? run.status);
|
|
156
|
+
return { helper: helper.name, ok: false, error: `The ${helper.name} helper stopped: ${why}` };
|
|
157
|
+
};
|
|
158
|
+
const markRemote = async (helper, task, threadId) => {
|
|
159
|
+
await setup.checkpointer?.put({
|
|
160
|
+
version: 2,
|
|
161
|
+
id: `${threadId}:remote`,
|
|
162
|
+
threadId,
|
|
163
|
+
step: 0,
|
|
164
|
+
state: {},
|
|
165
|
+
next: [],
|
|
166
|
+
tasks: [],
|
|
167
|
+
status: 'running',
|
|
168
|
+
interrupts: [],
|
|
169
|
+
createdAt: new Date().toISOString(),
|
|
170
|
+
metadata: { [MARK]: { name: helper.name, created: false, remote: true, task } },
|
|
171
|
+
});
|
|
172
|
+
};
|
|
147
173
|
const start = async (helper, task, threadId, context) => {
|
|
148
174
|
if (live.has(threadId))
|
|
149
175
|
return;
|
|
176
|
+
if (helper.remote) {
|
|
177
|
+
if (task !== undefined)
|
|
178
|
+
await markRemote(helper, task, threadId);
|
|
179
|
+
const entry = { helper: helper.name, settled: false, outcome: Promise.resolve(undefined) };
|
|
180
|
+
entry.outcome = remoteRun(helper, helper.remote, task ?? '', threadId)
|
|
181
|
+
.catch((error) => ({
|
|
182
|
+
helper: helper.name,
|
|
183
|
+
ok: false,
|
|
184
|
+
error: error instanceof Error ? error.message : String(error),
|
|
185
|
+
}))
|
|
186
|
+
.finally(() => {
|
|
187
|
+
entry.settled = true;
|
|
188
|
+
});
|
|
189
|
+
live.set(threadId, entry);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
150
192
|
const runner = graph(helper, true);
|
|
151
193
|
const stored = setup.checkpointer ? await runner.state(threadId) : undefined;
|
|
152
194
|
let ready = () => { };
|
|
@@ -220,6 +262,12 @@ export function helperTools(setup) {
|
|
|
220
262
|
const helper = await spec(String(args.agent ?? ''), typeof args.instructions === 'string' ? args.instructions : undefined, context.threadId ?? '');
|
|
221
263
|
const task = String(args.task ?? '');
|
|
222
264
|
const call = context.toolCallId ?? Math.random().toString(36).slice(2, 10);
|
|
265
|
+
if (args.background !== true && helper.remote) {
|
|
266
|
+
const ended = await remoteRun(helper, helper.remote, task, thread(context.threadId, `${helper.name}@${call}`), context.signal);
|
|
267
|
+
if (!ended.ok)
|
|
268
|
+
throw new Error(ended.error);
|
|
269
|
+
return ended.answer;
|
|
270
|
+
}
|
|
223
271
|
if (args.background !== true) {
|
|
224
272
|
const result = await graph(helper, false).invoke(agentInput(task), runOptions(context, `${helper.name}@${call}`, context.signal ? { signal: context.signal } : {}));
|
|
225
273
|
return (result.state.answer || `The ${helper.name} helper finished without an answer (${result.state.stopReason}).`);
|
|
@@ -265,7 +313,7 @@ export function helperTools(setup) {
|
|
|
265
313
|
const helper = mark?.created
|
|
266
314
|
? await spec(name, mark.instructions, context.threadId ?? '')
|
|
267
315
|
: await spec(name, undefined, context.threadId ?? '');
|
|
268
|
-
await start(helper, undefined, threadId, context);
|
|
316
|
+
await start(helper, mark?.remote ? (mark.task ?? '') : undefined, threadId, context);
|
|
269
317
|
}
|
|
270
318
|
const entry = live.get(threadId);
|
|
271
319
|
const waitMs = Math.min(Math.max(Number(args.wait_seconds) || 0, 0), 120) * 1_000;
|
|
@@ -285,3 +333,16 @@ export function helperTools(setup) {
|
|
|
285
333
|
};
|
|
286
334
|
return [delegate, collect];
|
|
287
335
|
}
|
|
336
|
+
function remoteAnswer(output) {
|
|
337
|
+
if (typeof output === 'string')
|
|
338
|
+
return output;
|
|
339
|
+
if (output && typeof output === 'object') {
|
|
340
|
+
const record = output;
|
|
341
|
+
if (typeof record.answer === 'string' && record.answer)
|
|
342
|
+
return record.answer;
|
|
343
|
+
const last = record.messages?.filter((message) => message.role === 'assistant').at(-1);
|
|
344
|
+
if (typeof last?.content === 'string' && last.content)
|
|
345
|
+
return last.content;
|
|
346
|
+
}
|
|
347
|
+
return JSON.stringify(output ?? null);
|
|
348
|
+
}
|
|
@@ -13,8 +13,8 @@ import { type ContextOffloadOptions } from '../agent/middleware/filesystem.js';
|
|
|
13
13
|
import { type ToolSelectorOptions } from '../agent/middleware/tool-selector.js';
|
|
14
14
|
import { type PermissionPolicyLike } from '../agent/permissions.js';
|
|
15
15
|
import { type Sandbox } from '../agent/sandbox.js';
|
|
16
|
-
import { type DeepHelperLimits } from './helpers.js';
|
|
17
|
-
export type { DeepHelperLimits } from './helpers.js';
|
|
16
|
+
import { type DeepHelperLimits, type DeepRemoteHelper } from './helpers.js';
|
|
17
|
+
export type { DeepHelperLimits, DeepRemoteHelper, DeepRemoteRunResult } from './helpers.js';
|
|
18
18
|
/** A helper the deep agent can hand a self-contained piece of work to, with a fresh context. */
|
|
19
19
|
export interface DeepSubagent {
|
|
20
20
|
/** Its name, as the agent delegates to it. */
|
|
@@ -33,6 +33,14 @@ export interface DeepSubagent {
|
|
|
33
33
|
model?: string;
|
|
34
34
|
/** Model calls it may make. Defaults to 20. */
|
|
35
35
|
maxIterations?: number;
|
|
36
|
+
/**
|
|
37
|
+
* Runs it on another agent server instead, such as `createRemoteGraph()` reaches: it is sent its
|
|
38
|
+
* `instructions` and the task, and its run's output is its answer. The run is leased to the
|
|
39
|
+
* server's worker that runs it, so another worker takes it over when that one stops, and the agent
|
|
40
|
+
* finds the same run again by the call that started it. `tools`, `model`, and `maxIterations` are
|
|
41
|
+
* the remote assistant's own.
|
|
42
|
+
*/
|
|
43
|
+
remote?: DeepRemoteHelper;
|
|
36
44
|
}
|
|
37
45
|
/** Instructions the agent loads only when a task needs them: a procedure, a checklist, a house style. */
|
|
38
46
|
export interface DeepSkill {
|
package/dist/graph/graph.d.ts
CHANGED
|
@@ -110,6 +110,11 @@ export declare class CompiledGraph<S extends ChannelSchema, I extends keyof S =
|
|
|
110
110
|
resumeInterruptsWith(threadId: string, answers: Record<string, unknown>, runOptions?: GraphRunOptions): Promise<GraphResult<S, O>>;
|
|
111
111
|
/** Continues a thread that stopped for any other reason, such as the step limit. */
|
|
112
112
|
continue(threadId: string, runOptions?: GraphRunOptions): AsyncIterable<GraphStepEvent<S>>;
|
|
113
|
+
/**
|
|
114
|
+
* Like `continue`, but returns the final result rather than the stream: what a replica calls to
|
|
115
|
+
* finish a run whose process stopped, from the last checkpoint it wrote.
|
|
116
|
+
*/
|
|
117
|
+
continueWith(threadId: string, runOptions?: GraphRunOptions): Promise<GraphResult<S, O>>;
|
|
113
118
|
/**
|
|
114
119
|
* Rewinds to an earlier superstep and runs forward from there.
|
|
115
120
|
*
|
package/dist/graph/graph.js
CHANGED
|
@@ -253,6 +253,12 @@ export class CompiledGraph {
|
|
|
253
253
|
return { ...checkpoint, status: 'running', error: undefined, drained: undefined };
|
|
254
254
|
});
|
|
255
255
|
}
|
|
256
|
+
async continueWith(threadId, runOptions = {}) {
|
|
257
|
+
let last;
|
|
258
|
+
for await (const event of this.continue(threadId, runOptions))
|
|
259
|
+
last = event;
|
|
260
|
+
return this.toResult(threadId, last);
|
|
261
|
+
}
|
|
256
262
|
resumeFrom(threadId, step, runOptions = {}) {
|
|
257
263
|
return this.run(threadId, runOptions, async () => {
|
|
258
264
|
const stored = await this.checkpointer()?.get(threadId, step);
|
|
@@ -5,4 +5,4 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export { BudgetExceededError, LIFECYCLE_STAGES, type LifecycleRuntime, OperationDeniedError, OperationLifecycle, type OperationPlan, } from '../core/lifecycle.js';
|
|
7
7
|
export { type BudgetLedgerOptions, type BudgetLedgerWithReport, type BudgetReservationChange, type BudgetUsageStore, budgetLedger, MemoryBudgetUsage, } from './budget.js';
|
|
8
|
-
export type { BudgetLedger, BudgetPeriod, BudgetRecoverOptions, BudgetRecoveryReport, BudgetReservation, BudgetReservationOutcome, BudgetReservationRecord, BudgetReservationStatus, LifecycleConfig, LifecycleHooks, LifecycleStage, OperationDescriptor, OperationFamily, OperationLifecycleLike, OperationOutcome, OperationResultInfo, OperationStartOptions, OperationTicket, ProviderCallContext, } from '../types/lifecycle.js';
|
|
8
|
+
export type { BudgetLedger, BudgetPeriod, BudgetRecoverOptions, BudgetRecoveryReport, BudgetReservation, BudgetReservationOutcome, BudgetReservationRecord, BudgetReservationStatus, LifecycleConfig, LifecycleHooks, LifecycleStage, OperationDescriptor, OperationFamily, OperationLifecycleLike, OperationOutcome, OperationResultInfo, OperationStartOptions, OperationTicket, ProviderCallContext, ProviderHealthOutcome, } from '../types/lifecycle.js';
|
package/dist/protocols/a2a.d.ts
CHANGED
|
@@ -176,6 +176,33 @@ export interface A2aTaskRecord {
|
|
|
176
176
|
push?: A2aPushNotificationConfig[];
|
|
177
177
|
/** ISO-8601 time it was last written. */
|
|
178
178
|
updatedAt: string;
|
|
179
|
+
/**
|
|
180
|
+
* Who is running the task, and until when, while it works. The replica running it renews the lease;
|
|
181
|
+
* once it lapses, another replica's `recover()` may take the task over.
|
|
182
|
+
*/
|
|
183
|
+
lease?: A2aTaskLease;
|
|
184
|
+
/** What the working run was asked to do, so a replica that takes it over can carry it on. */
|
|
185
|
+
run?: A2aTaskRun;
|
|
186
|
+
/** Bumped by every claim of the lease, so two replicas claiming one task cannot both win. */
|
|
187
|
+
version?: number;
|
|
188
|
+
}
|
|
189
|
+
/** A replica's hold on a working task. */
|
|
190
|
+
export interface A2aTaskLease {
|
|
191
|
+
/** The replica holding it. */
|
|
192
|
+
owner: string;
|
|
193
|
+
/** ISO-8601 time it lapses unless renewed. */
|
|
194
|
+
expiresAt: string;
|
|
195
|
+
}
|
|
196
|
+
/** A working run, as kept with its task: started by a message, or answering the task's questions. */
|
|
197
|
+
export interface A2aTaskRun {
|
|
198
|
+
/** `invoke` for a run a message started, `resume` for one answering a paused task's questions. */
|
|
199
|
+
kind: 'invoke' | 'resume';
|
|
200
|
+
/** The message that started it. */
|
|
201
|
+
message: A2aMessage;
|
|
202
|
+
/** The answers it resumed with, by interrupt id. */
|
|
203
|
+
answers?: Record<string, unknown>;
|
|
204
|
+
/** Who it runs for. */
|
|
205
|
+
principal?: Principal;
|
|
179
206
|
}
|
|
180
207
|
/** Which tasks `A2aTaskStore.list()` returns. */
|
|
181
208
|
export interface A2aTaskQuery {
|
|
@@ -209,6 +236,16 @@ export interface A2aTaskStore {
|
|
|
209
236
|
conversation(contextId: string): Promise<Message[] | undefined> | Message[] | undefined;
|
|
210
237
|
/** Writes a conversation's transcript. */
|
|
211
238
|
saveConversation(contextId: string, messages: Message[]): Promise<void> | void;
|
|
239
|
+
/**
|
|
240
|
+
* Takes, or renews, the lease of a working task, or of one submitted and not yet started, in one
|
|
241
|
+
* step: when the task has no lease, its lease has lapsed by `lease.now`, or `lease.owner` already
|
|
242
|
+
* holds it. Resolves to the record as claimed,
|
|
243
|
+
* or `undefined` when another replica holds it. Optional; without it, a claim is a read and a write,
|
|
244
|
+
* and two replicas recovering at once may both take one task.
|
|
245
|
+
*/
|
|
246
|
+
claim?(taskId: string, lease: A2aTaskLease & {
|
|
247
|
+
now: string;
|
|
248
|
+
}): Promise<A2aTaskRecord | undefined> | A2aTaskRecord | undefined;
|
|
212
249
|
}
|
|
213
250
|
/** Tasks and conversations in this process: the default, for one replica. */
|
|
214
251
|
export declare class MemoryA2aTaskStore implements A2aTaskStore {
|
|
@@ -231,6 +268,10 @@ export declare class MemoryA2aTaskStore implements A2aTaskStore {
|
|
|
231
268
|
conversation(contextId: string): Message[] | undefined;
|
|
232
269
|
/** Writes a conversation's transcript. */
|
|
233
270
|
saveConversation(contextId: string, messages: Message[]): void;
|
|
271
|
+
/** Takes or renews a working task's lease, in one step. */
|
|
272
|
+
claim(taskId: string, lease: A2aTaskLease & {
|
|
273
|
+
now: string;
|
|
274
|
+
}): A2aTaskRecord | undefined;
|
|
234
275
|
}
|
|
235
276
|
/**
|
|
236
277
|
* Tasks and conversations in any long-term `Store`: the memory, Redis, Postgres, or SQLite store, so
|
|
@@ -293,9 +334,51 @@ export interface A2aServerOptions {
|
|
|
293
334
|
extendedCard?: (principal: Principal) => Partial<A2aAgentCard> | Promise<Partial<A2aAgentCard>>;
|
|
294
335
|
/** How often `SubscribeToTask` reads a task another replica runs, in milliseconds. Defaults to 1 second. */
|
|
295
336
|
pollMs?: number;
|
|
337
|
+
/**
|
|
338
|
+
* The lease a replica holds on each task it runs: renewed every third of `ms` (30 seconds by
|
|
339
|
+
* default), and taken over by another replica's `recover()` once it lapses. `owner` names this
|
|
340
|
+
* replica; it defaults to a random id.
|
|
341
|
+
*/
|
|
342
|
+
lease?: {
|
|
343
|
+
ms?: number;
|
|
344
|
+
owner?: string;
|
|
345
|
+
};
|
|
346
|
+
/**
|
|
347
|
+
* Runs `recover()` every this many milliseconds, so a task a stopped replica was working on is taken
|
|
348
|
+
* over without a caller asking. Off by default; `close()` stops it.
|
|
349
|
+
*/
|
|
350
|
+
recoverEveryMs?: number;
|
|
296
351
|
/** Receives push delivery failures, which never fail a task. */
|
|
297
352
|
onError?: (error: unknown) => void;
|
|
298
353
|
}
|
|
354
|
+
/** What one `recover()` did. */
|
|
355
|
+
export interface A2aRecoveryReport {
|
|
356
|
+
/** Tasks whose lease had lapsed, taken over and carried on here. */
|
|
357
|
+
tookOver: string[];
|
|
358
|
+
/** Tasks another replica claimed first, or that finished meanwhile. */
|
|
359
|
+
skipped: number;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* An A2A agent's HTTP handler, with what keeps its tasks alive across replicas: `recover()` takes over
|
|
363
|
+
* the working tasks whose replica stopped renewing their lease, and `close()` stops the recovery timer.
|
|
364
|
+
*/
|
|
365
|
+
export interface A2aHandler {
|
|
366
|
+
/** Answers one HTTP request: `GET` the card, `POST` JSON-RPC. */
|
|
367
|
+
(request: Request): Promise<Response>;
|
|
368
|
+
/** This replica's name in the leases it holds. */
|
|
369
|
+
readonly owner: string;
|
|
370
|
+
/**
|
|
371
|
+
* Takes over working tasks whose lease lapsed, up to `limit` (10), and carries each on: from the
|
|
372
|
+
* graph's last checkpoint, by answering its questions again when the run was a resume that never
|
|
373
|
+
* reached the graph, or from the start when the run left no checkpoint. Resolves once they are
|
|
374
|
+
* claimed, not finished.
|
|
375
|
+
*/
|
|
376
|
+
recover(options?: {
|
|
377
|
+
limit?: number;
|
|
378
|
+
}): Promise<A2aRecoveryReport>;
|
|
379
|
+
/** Stops the recovery timer `recoverEveryMs` started. Tasks running here carry on. */
|
|
380
|
+
close(): void;
|
|
381
|
+
}
|
|
299
382
|
/**
|
|
300
383
|
* An HTTP handler that serves a graph as an A2A agent: `GET` the card, `POST` JSON-RPC. Mount it
|
|
301
384
|
* at the card's `url`; it answers the well-known card path under the same origin too.
|
|
@@ -306,7 +389,7 @@ export interface A2aServerOptions {
|
|
|
306
389
|
* });
|
|
307
390
|
* ```
|
|
308
391
|
*/
|
|
309
|
-
export declare function a2aHandler(graph: ProtocolGraph, options: A2aServerOptions):
|
|
392
|
+
export declare function a2aHandler(graph: ProtocolGraph, options: A2aServerOptions): A2aHandler;
|
|
310
393
|
/** Options for `a2aClient()`. */
|
|
311
394
|
export interface A2aClientOptions {
|
|
312
395
|
/** The agent: its card's URL, or its JSON-RPC endpoint, whose origin serves the card. */
|