@nylorun/runtime 0.43.0-beta → 0.44.0-beta
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/CHANGELOG.md +31 -0
- package/dist/api/a2a/endpoint.js +1 -1
- package/dist/api/a2a/push.js +14 -1
- package/dist/api/a2a/routes.js +40 -26
- package/dist/api/a2a/tasks.d.ts +14 -0
- package/dist/api/a2a/tasks.js +42 -0
- package/dist/api/components.d.ts +1158 -280
- package/dist/artifacts/disk.js +7 -0
- package/dist/harness/main.js +8 -4
- package/dist/management-openapi.json +14 -4
- package/dist/openapi.json +433 -3
- package/dist/sandbox/pods/lifecycle.d.ts +5 -1
- package/dist/sandbox/pods/lifecycle.js +8 -0
- package/dist/sandbox/pods/network-gate.d.ts +6 -0
- package/dist/sandbox/pods/network-gate.js +20 -4
- package/dist/store/postgres/store.js +11 -0
- package/dist/store/types.d.ts +11 -1
- package/dist/tenant/commands.d.ts +36 -7
- package/dist/tenant/commands.js +158 -40
- package/dist/tenant/context.d.ts +11 -0
- package/dist/tenant/sweep.js +44 -15
- package/dist/tuning.d.ts +3 -0
- package/dist/tuning.js +12 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.44.0-beta
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 1d3a5c5: Queued messages no longer wait for good (Host feature `queue-recovery`). When a turn ends and its
|
|
8
|
+
Agent refuses to start the next one for the session's queued messages (its disk being copied, its
|
|
9
|
+
pod lost, the Agent deleted), the messages wait with the session's `queueStalled`, and the Tenant
|
|
10
|
+
sweep tries the start again (Host tuning `sweep.queueRetryMs`, 30 seconds), as does the end of the
|
|
11
|
+
Agent's disk copy. A message that has waited longer than the new Runtime limit `queuedStartMs`
|
|
12
|
+
(15 minutes) is removed with the new event `queue.failed` (`queue.start_expired`, with the
|
|
13
|
+
refusal's code as `reason`); a refusal no retry changes (a turn request the version no longer
|
|
14
|
+
resolves, another harness) fails the messages at once (`queue.start_refused`), and the messages
|
|
15
|
+
behind them are tried next. Before, nothing started such a queue until another message arrived.
|
|
16
|
+
|
|
17
|
+
On the A2A endpoint (profile v2), a queued task that fails is `TASK_STATE_FAILED` with a status
|
|
18
|
+
message saying why, in `GetTask`, its pushes and a blocking `SendMessage`. `CancelTask` on a queued
|
|
19
|
+
task now takes it out of the queue, whether a task runs in its context or not, and answers
|
|
20
|
+
`TASK_STATE_CANCELED` (the new event `queue.cancelled`), pushed; before, it was
|
|
21
|
+
`TaskNotCancelableError`.
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- 63e0577: Every running pod Agent now has an idle timer, whatever woke it. A pod woken by a disk copy or by commands through the session's sandbox route, and one whose idle timer fired during a turn, no longer runs past its idle limit; the Tenant sweep arms the timer of a pod already running past it.
|
|
26
|
+
- b23a75c: A pod's engine checks its NetworkPolicy quickly on a wake. When the pod's disk already holds the engine's volume id, one 150 ms probe of the API server (`harness.wakeNetworkProbeMs`, `harness.wakeNetworkConfirmations`) replaces three of a second, so a woken computer joins about 2.9 s sooner. A first start keeps the full check, and a wake whose probe connects goes back to it.
|
|
27
|
+
- Pin core to the tested release.
|
|
28
|
+
- Pin harness to the tested release.
|
|
29
|
+
- Updated dependencies [1d3a5c5]
|
|
30
|
+
- Updated dependencies
|
|
31
|
+
- @nylorun/core@0.38.0-beta
|
|
32
|
+
- @nylorun/harness@0.34.1-beta
|
|
33
|
+
|
|
3
34
|
## 0.43.0-beta
|
|
4
35
|
|
|
5
36
|
### Minor Changes
|
package/dist/api/a2a/endpoint.js
CHANGED
|
@@ -72,7 +72,7 @@ export function a2aRoutes(api) {
|
|
|
72
72
|
path: "/v1/a2a/agents/{agentId}",
|
|
73
73
|
tags: TAGS,
|
|
74
74
|
summary: "Call an agent over A2A",
|
|
75
|
-
description: "A2A 1.0 JSON-RPC, for a subject, under the Nylorun profile v2 (Host feature `a2a-profile-v2`). `SendMessage` blocks up to the Runtime limit `a2aWaitMs` (5 minutes by default; then the task is answered still `WORKING`) unless `returnImmediately`; `GetTask` (with `historyLength`), `CancelTask` and `ListTasks`; and the four push notification config methods, plus `configuration.taskPushNotificationConfig` in `SendMessage`: each update is POSTed as one `StreamResponse`, signed with Standard Webhooks (`webhook-id`, `webhook-timestamp`, `webhook-signature` with the Runtime's `a2a.push.secret`) and `X-A2A-Notification-Token`, at least once, retried for up to 24 hours, to hosts the Runtime allows (`a2a.push.allowHosts` for private ones). A message to a busy context is queued and answered with the task that will take it (`TASK_STATE_SUBMITTED`); `message.metadata.delivery: \"reject\"` asks for `UnsupportedOperationError` (reason `CONTEXT_BUSY`) instead. `message.metadata.removeTools` (Host feature `remove-tools`) names the tools a new message's turn may not use, as a session message's `removeTools`. A message is text, one data part, or text with `raw` files (stored as artifacts of the context's session) and `url` files (fetched by the Runtime), each at most the Runtime limit `inlineFileBytes` (20 MiB by default). An approval, question or connection the task waits for (`AUTH_REQUIRED` or `INPUT_REQUIRED`) is shown under `interactions/v1` and answered on the same task with `{ interactionId, approved }` or `{ interactionId, value }` under that URI in the message's `metadata`; text never approves. Artifacts carry the turn's output and the files it wrote, as `url` parts at `/v1/a2a/files/…` with `files/v1`; the task's metadata carries `progress/v1`, `telemetry/v1` and `effects/v1`. Extension data is answered only for the extensions activated with `A2A-Extensions`. A context is one session per subject, agent and `contextId`; a task is one turn. JSON-RPC errors are answered with 200; limits, scopes and unavailable streams stay HTTP errors. Not served: streaming, `GetExtendedAgentCard`.",
|
|
75
|
+
description: "A2A 1.0 JSON-RPC, for a subject, under the Nylorun profile v2 (Host feature `a2a-profile-v2`). `SendMessage` blocks up to the Runtime limit `a2aWaitMs` (5 minutes by default; then the task is answered still `WORKING`) unless `returnImmediately`; `GetTask` (with `historyLength`), `CancelTask` and `ListTasks`; and the four push notification config methods, plus `configuration.taskPushNotificationConfig` in `SendMessage`: each update is POSTed as one `StreamResponse`, signed with Standard Webhooks (`webhook-id`, `webhook-timestamp`, `webhook-signature` with the Runtime's `a2a.push.secret`) and `X-A2A-Notification-Token`, at least once, retried for up to 24 hours, to hosts the Runtime allows (`a2a.push.allowHosts` for private ones). A message to a busy context is queued and answered with the task that will take it (`TASK_STATE_SUBMITTED`); `message.metadata.delivery: \"reject\"` asks for `UnsupportedOperationError` (reason `CONTEXT_BUSY`) instead. A queued task never waits for good (Host feature `queue-recovery`): when its Agent refuses its turn's start (a disk copy, a lost pod), the start is tried again until the Runtime limit `queuedStartMs` (15 minutes by default), and then, or at once for a refusal no retry changes, the task is `TASK_STATE_FAILED` with a status message saying why; `CancelTask` on a queued task takes it out of the queue, a task running in the context or not, and answers `TASK_STATE_CANCELED`. Both are pushed. `message.metadata.removeTools` (Host feature `remove-tools`) names the tools a new message's turn may not use, as a session message's `removeTools`. A message is text, one data part, or text with `raw` files (stored as artifacts of the context's session) and `url` files (fetched by the Runtime), each at most the Runtime limit `inlineFileBytes` (20 MiB by default). An approval, question or connection the task waits for (`AUTH_REQUIRED` or `INPUT_REQUIRED`) is shown under `interactions/v1` and answered on the same task with `{ interactionId, approved }` or `{ interactionId, value }` under that URI in the message's `metadata`; text never approves. Artifacts carry the turn's output and the files it wrote, as `url` parts at `/v1/a2a/files/…` with `files/v1`; the task's metadata carries `progress/v1`, `telemetry/v1` and `effects/v1`. Extension data is answered only for the extensions activated with `A2A-Extensions`. A context is one session per subject, agent and `contextId`; a task is one turn. JSON-RPC errors are answered with 200; limits, scopes and unavailable streams stay HTTP errors. Not served: streaming, `GetExtendedAgentCard`.",
|
|
76
76
|
externalDocs: A2A_SPECIFICATION,
|
|
77
77
|
request: {
|
|
78
78
|
params: agentId,
|
package/dist/api/a2a/push.js
CHANGED
|
@@ -5,7 +5,7 @@ import { traceIdOf } from "../../traces/ids.js";
|
|
|
5
5
|
import { hostTuning } from "../../tuning.js";
|
|
6
6
|
import { ALL_EXTENSIONS, keepExtensions } from "./extensions.js";
|
|
7
7
|
import { a2aFail, } from "./protocol.js";
|
|
8
|
-
import { ENDED, callerTraceId, updatesOf } from "./tasks.js";
|
|
8
|
+
import { ENDED, callerTraceId, endsQueued, updatesOf } from "./tasks.js";
|
|
9
9
|
/** The most push configs one task keeps. A constant of the profile, not a setting. */
|
|
10
10
|
export const MAX_PUSH_CONFIGS = 10;
|
|
11
11
|
/** Events one pump reads at a time. */
|
|
@@ -172,6 +172,19 @@ export async function pumpSession(ctx, sessionId) {
|
|
|
172
172
|
// A queued message's task follows the turn that takes it.
|
|
173
173
|
if (event.type === "command.message" && event.turnId && payload.queuedMessageId === turnId)
|
|
174
174
|
turnId = event.turnId;
|
|
175
|
+
// Or ends without one: it failed or was cancelled while queued (`queue-recovery`).
|
|
176
|
+
if (endsQueued(event, turnId)) {
|
|
177
|
+
const view = { contextId: config.contextId, taskId: config.taskId, baseUrl: config.baseUrl };
|
|
178
|
+
for (const update of updatesOf(event, view))
|
|
179
|
+
await t.enqueuePush({
|
|
180
|
+
sessionId,
|
|
181
|
+
taskId: config.taskId,
|
|
182
|
+
configId: config.configId,
|
|
183
|
+
body: JSON.stringify(keepExtensions(update, new Set(config.extensions))),
|
|
184
|
+
});
|
|
185
|
+
done = true;
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
175
188
|
if (event.turnId !== turnId)
|
|
176
189
|
continue;
|
|
177
190
|
if (event.type === "command.message" && traceId === null)
|
package/dist/api/a2a/routes.js
CHANGED
|
@@ -18,7 +18,10 @@
|
|
|
18
18
|
* - A message to a busy context is **queued** (`delivery: "queue"`, protocol 17) and answered
|
|
19
19
|
* with the task that will take it: `SUBMITTED`, its id the queued message's own until a turn
|
|
20
20
|
* takes it, then that turn's state. `metadata.delivery: "reject"` keeps v1's
|
|
21
|
-
* `UnsupportedOperationError` (reason `CONTEXT_BUSY`).
|
|
21
|
+
* `UnsupportedOperationError` (reason `CONTEXT_BUSY`). A queued task never waits for good (Host
|
|
22
|
+
* feature `queue-recovery`): one whose turn the Agent refused to start for longer than the
|
|
23
|
+
* Runtime limit `queuedStartMs`, or for good, is `FAILED`; `CancelTask` takes one out of the
|
|
24
|
+
* queue as `CANCELED`. Both are pushed.
|
|
22
25
|
* - An open interaction shows on the status message under `interactions/v1`; the caller answers
|
|
23
26
|
* on the same task with `{ interactionId, approved }` (the Runtime's `approve`, recorded with
|
|
24
27
|
* the caller as `approvedBy`) or `{ interactionId, value }` (`respond`). Text never approves.
|
|
@@ -39,11 +42,11 @@ import { keepExtensions } from "./extensions.js";
|
|
|
39
42
|
import { indexedTurnEvents, listTasks, parseListParams } from "./list.js";
|
|
40
43
|
import { A2aError, a2aFail, checkVersion, jsonRpcError, jsonRpcResult, parseCreatePushParams, parseEnvelope, parseListPushParams, parsePushConfigRef, parseSendParams, parseTaskParams, } from "./protocol.js";
|
|
41
44
|
import { configView, kickPushes, pushEnabled, pushPolicy, savePushConfig, } from "./push.js";
|
|
42
|
-
import { ENDED, SETTLED_EVENTS, buildTask, callerTraceId, contextSessionId, newContextId, parseTaskId, pendingInteraction, pendingInteractions, submittedTask, taskIdOf, } from "./tasks.js";
|
|
45
|
+
import { ENDED, SETTLED_EVENTS, buildTask, callerTraceId, contextSessionId, droppedTask, endsQueued, newContextId, parseTaskId, pendingInteraction, pendingInteractions, submittedTask, taskIdOf, } from "./tasks.js";
|
|
43
46
|
import { currentModelDescriptor } from "../../model/descriptor.js";
|
|
44
47
|
import { decodeCursor } from "../../record/cursor.js";
|
|
45
48
|
import { accessOf } from "../../tenant/auth.js";
|
|
46
|
-
import { command } from "../../tenant/commands.js";
|
|
49
|
+
import { cancelQueuedMessage, command } from "../../tenant/commands.js";
|
|
47
50
|
import { loadSession, reachesAgent, } from "../../tenant/context.js";
|
|
48
51
|
import { agentGrantsOf, liveEnvironment } from "../../tenant/agents.js";
|
|
49
52
|
import { HttpError, fail } from "../../tenant/http.js";
|
|
@@ -213,7 +216,9 @@ async function indexedTask(caller, row, session, options = {}) {
|
|
|
213
216
|
* The caller's task `taskId`, found through the turn index. Its last part is a turn, or a queued
|
|
214
217
|
* message's id: while the message waits the task is `SUBMITTED`, and once a turn took it (the
|
|
215
218
|
* index keeps the queued messages each turn took) the task is that turn's, under the id the
|
|
216
|
-
* caller knows.
|
|
219
|
+
* caller knows. A queued message that failed or was cancelled before a turn took it (Host feature
|
|
220
|
+
* `queue-recovery`) is `FAILED` or `CANCELED`, from the event that ended it. Only a message still
|
|
221
|
+
* in the queue, or no longer in it and taken by no turn, is looked for in the session's history.
|
|
217
222
|
*/
|
|
218
223
|
async function ownedTask(caller, taskId, historyLength) {
|
|
219
224
|
const ids = parseTaskId(taskId) ?? taskNotFound(taskId);
|
|
@@ -229,18 +234,16 @@ async function ownedTask(caller, taskId, historyLength) {
|
|
|
229
234
|
const taken = await t.turnOfQueuedMessage(sessionId, ids.turnId);
|
|
230
235
|
return taken ? { turn: taken } : {};
|
|
231
236
|
});
|
|
232
|
-
if (found.
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
queued,
|
|
243
|
-
};
|
|
237
|
+
if (found.turn === undefined) {
|
|
238
|
+
// Queued, or a queued message that failed or was cancelled: its history says which.
|
|
239
|
+
const events = await sessionEvents(caller.ctx, sessionId);
|
|
240
|
+
const queued = events.find((event) => event.type === "message.queued" && payloadOf(event).queuedMessageId === ids.turnId);
|
|
241
|
+
const dropped = queued ? events.find((event) => endsQueued(event, ids.turnId)) : undefined;
|
|
242
|
+
const owned = { session, sessionId, contextId: ids.contextId, turnId: ids.turnId, events: [] };
|
|
243
|
+
if (dropped)
|
|
244
|
+
return { ...owned, task: droppedTask(ids.contextId, taskId, dropped), dropped };
|
|
245
|
+
if (queued && found.queued)
|
|
246
|
+
return { ...owned, task: submittedTask(ids.contextId, taskId, queued), queued };
|
|
244
247
|
}
|
|
245
248
|
const row = found.turn ?? taskNotFound(taskId);
|
|
246
249
|
const built = (await indexedTask(caller, { ...row, a2aContextId: ids.contextId }, session, {
|
|
@@ -503,13 +506,21 @@ function replyTarget(owned) {
|
|
|
503
506
|
return a2aFail("unsupportedOperation", "The task waits for an approval, which text never gives: answer it under the interactions/v1 extension ({ interactionId, approved }), or cancel the task", { taskId, interactionId: pending.id });
|
|
504
507
|
return pending.id;
|
|
505
508
|
}
|
|
506
|
-
|
|
509
|
+
/**
|
|
510
|
+
* `CancelTask`. A queued task (`SUBMITTED`) leaves its context's queue and is `CANCELED`
|
|
511
|
+
* (`queue.cancelled`, pushed), whether a task runs in the context or not (Host feature
|
|
512
|
+
* `queue-recovery`); one a turn took meanwhile is cancelled as that turn. A running or waiting
|
|
513
|
+
* task's turn is cancelled. An ended task is not cancelable, unless it is `CANCELED` already.
|
|
514
|
+
*/
|
|
515
|
+
async function cancelTask(caller, taskId, again = true) {
|
|
507
516
|
const owned = await ownedTask(caller, taskId);
|
|
508
|
-
if (owned.queued)
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
517
|
+
if (owned.queued) {
|
|
518
|
+
const cancelled = await cancelQueuedMessage(caller.ctx, owned.sessionId, owned.turnId, caller.scope, "Canceled by the A2A client");
|
|
519
|
+
// Gone from the queue meanwhile: a turn took it, or it failed. Answer as the task is now.
|
|
520
|
+
if (!cancelled && again)
|
|
521
|
+
return cancelTask(caller, taskId, false);
|
|
522
|
+
return (await ownedTask(caller, taskId)).task;
|
|
523
|
+
}
|
|
513
524
|
if (owned.session.activeTurnId !== owned.turnId) {
|
|
514
525
|
if (owned.task.status.state === "TASK_STATE_CANCELED")
|
|
515
526
|
return owned.task;
|
|
@@ -566,8 +577,8 @@ async function createPushConfig(caller, params) {
|
|
|
566
577
|
requirePush(caller);
|
|
567
578
|
const { taskId, config } = parseCreatePushParams(params);
|
|
568
579
|
const owned = await ownedTask(caller, taskId);
|
|
569
|
-
const last = owned.queued ?? owned.events.at(-1);
|
|
570
|
-
const ended = owned.events.some((event) => ENDED.has(event.type));
|
|
580
|
+
const last = owned.dropped ?? owned.queued ?? owned.events.at(-1);
|
|
581
|
+
const ended = owned.dropped !== undefined || owned.events.some((event) => ENDED.has(event.type));
|
|
571
582
|
const configId = config.id ?? createHash("sha256").update(`${config.url}\u0000${config.token ?? ""}`).digest("hex").slice(0, 16);
|
|
572
583
|
return registerPush(caller, { ...config, id: configId }, {
|
|
573
584
|
sessionId: owned.sessionId,
|
|
@@ -575,7 +586,7 @@ async function createPushConfig(caller, params) {
|
|
|
575
586
|
taskId: owned.task.id,
|
|
576
587
|
turnId: owned.turnId,
|
|
577
588
|
nextSeq: last ? last.seq + 1 : 0,
|
|
578
|
-
...(owned.queued ? {} : { traceId: await turnTraceId(caller.ctx, owned.sessionId, owned.turnId, owned.events) }),
|
|
589
|
+
...(owned.queued || owned.dropped ? {} : { traceId: await turnTraceId(caller.ctx, owned.sessionId, owned.turnId, owned.events) }),
|
|
579
590
|
done: ended,
|
|
580
591
|
});
|
|
581
592
|
}
|
|
@@ -615,7 +626,8 @@ async function deletePushConfig(caller, params) {
|
|
|
615
626
|
* Waits until the task's turn ends, pauses or has an uncertain effect, the caller hangs up, or
|
|
616
627
|
* the Tenant's Runtime limit `a2aWaitMs` passes: after it, the task is returned as it is
|
|
617
628
|
* (`WORKING`, or `SUBMITTED` while queued) and keeps running, and the caller polls `GetTask` or
|
|
618
|
-
* waits for pushes. A queued message's task is followed to the turn that takes it
|
|
629
|
+
* waits for pushes. A queued message's task is followed to the turn that takes it, or to its
|
|
630
|
+
* `queue.failed` or `queue.cancelled`. Follows the
|
|
619
631
|
* session's shared stream from the command's cursor, so the events the task is built from are
|
|
620
632
|
* there when it returns. A feed that ends (reset, deleted, closing) ends the wait: the task says
|
|
621
633
|
* what the session says then.
|
|
@@ -639,6 +651,8 @@ async function settled(ctx, sessionId, accepted, signal) {
|
|
|
639
651
|
event.turnId &&
|
|
640
652
|
payloadOf(event).queuedMessageId === accepted.queuedMessageId)
|
|
641
653
|
turnId = event.turnId;
|
|
654
|
+
if (turnId === undefined && endsQueued(event, accepted.queuedMessageId))
|
|
655
|
+
return;
|
|
642
656
|
if (turnId !== undefined && event.turnId === turnId && SETTLED_EVENTS.has(event.type))
|
|
643
657
|
return;
|
|
644
658
|
}
|
package/dist/api/a2a/tasks.d.ts
CHANGED
|
@@ -48,6 +48,14 @@ export declare function interruptedState(interaction: PendingInteraction): TaskS
|
|
|
48
48
|
/** The status message that shows an open interaction, with its `interactions/v1` data. */
|
|
49
49
|
export declare function interactionMessage(interaction: PendingInteraction, contextId: string, taskId: string): Message;
|
|
50
50
|
export declare const ENDED: ReadonlySet<string>;
|
|
51
|
+
/**
|
|
52
|
+
* Events that end a queued message's task without a turn (Host feature `queue-recovery`): its
|
|
53
|
+
* start was refused past the Runtime limit `queuedStartMs`, or for good (`queue.failed`), or a
|
|
54
|
+
* client cancelled it (`queue.cancelled`). Each names the queued messages it ends.
|
|
55
|
+
*/
|
|
56
|
+
export declare const QUEUE_ENDED: ReadonlySet<string>;
|
|
57
|
+
/** Whether `event` ends queued message `queuedMessageId`'s task (`QUEUE_ENDED`). */
|
|
58
|
+
export declare function endsQueued(event: LiveEvent, queuedMessageId: string): boolean;
|
|
51
59
|
/** Events after which a blocking `SendMessage` returns: the turn ended or is interrupted. */
|
|
52
60
|
export declare const SETTLED_EVENTS: ReadonlySet<string>;
|
|
53
61
|
/** The turn's conversation as A2A messages: what the caller sent and what the agent said. */
|
|
@@ -116,3 +124,9 @@ export declare function buildTask(input: BuildTaskInput): Task | undefined;
|
|
|
116
124
|
* event, until a turn takes it.
|
|
117
125
|
*/
|
|
118
126
|
export declare function submittedTask(contextId: string, taskId: string, queued: LiveEvent): Task;
|
|
127
|
+
/**
|
|
128
|
+
* A queued message's task that ended without a turn (Host feature `queue-recovery`), from the
|
|
129
|
+
* `queue.failed` or `queue.cancelled` event that names it: `FAILED`, saying why it never started,
|
|
130
|
+
* or `CANCELED`.
|
|
131
|
+
*/
|
|
132
|
+
export declare function droppedTask(contextId: string, taskId: string, ended: LiveEvent): Task;
|
package/dist/api/a2a/tasks.js
CHANGED
|
@@ -112,6 +112,19 @@ export function interactionMessage(interaction, contextId, taskId) {
|
|
|
112
112
|
};
|
|
113
113
|
}
|
|
114
114
|
export const ENDED = new Set(["turn.completed", "turn.failed", "turn.cancelled"]);
|
|
115
|
+
/**
|
|
116
|
+
* Events that end a queued message's task without a turn (Host feature `queue-recovery`): its
|
|
117
|
+
* start was refused past the Runtime limit `queuedStartMs`, or for good (`queue.failed`), or a
|
|
118
|
+
* client cancelled it (`queue.cancelled`). Each names the queued messages it ends.
|
|
119
|
+
*/
|
|
120
|
+
export const QUEUE_ENDED = new Set(["queue.failed", "queue.cancelled"]);
|
|
121
|
+
/** Whether `event` ends queued message `queuedMessageId`'s task (`QUEUE_ENDED`). */
|
|
122
|
+
export function endsQueued(event, queuedMessageId) {
|
|
123
|
+
if (!QUEUE_ENDED.has(event.type))
|
|
124
|
+
return false;
|
|
125
|
+
const ids = event.payload?.queuedMessageIds;
|
|
126
|
+
return Array.isArray(ids) && ids.includes(queuedMessageId);
|
|
127
|
+
}
|
|
115
128
|
/** Events after which a blocking `SendMessage` returns: the turn ended or is interrupted. */
|
|
116
129
|
export const SETTLED_EVENTS = new Set([
|
|
117
130
|
...ENDED,
|
|
@@ -170,6 +183,11 @@ export function historyOf(events, contextId, taskId) {
|
|
|
170
183
|
function agentMessage(messageId, text, contextId, taskId) {
|
|
171
184
|
return { messageId, role: "ROLE_AGENT", parts: [{ text }], contextId, taskId };
|
|
172
185
|
}
|
|
186
|
+
/** What a failed queued message's task says: why its turn never started. */
|
|
187
|
+
function queueFailureText(payload) {
|
|
188
|
+
const reason = typeof payload.error?.message === "string" ? ` ${payload.error.message}` : "";
|
|
189
|
+
return `The message never started a turn.${reason}`;
|
|
190
|
+
}
|
|
173
191
|
function failureText(code) {
|
|
174
192
|
return typeof code === "string" && code !== ""
|
|
175
193
|
? `The agent failed (${code}).`
|
|
@@ -319,6 +337,15 @@ export function updatesOf(event, view) {
|
|
|
319
337
|
const file = fileArtifact(payload, view.baseUrl);
|
|
320
338
|
return file ? [artifact(file)] : [];
|
|
321
339
|
}
|
|
340
|
+
case "queue.failed":
|
|
341
|
+
// A queued message's task that no turn will take (Host feature `queue-recovery`).
|
|
342
|
+
return [
|
|
343
|
+
status("TASK_STATE_FAILED", {
|
|
344
|
+
message: agentMessage(`${taskId}:failed`, queueFailureText(payload), contextId, taskId),
|
|
345
|
+
}),
|
|
346
|
+
];
|
|
347
|
+
case "queue.cancelled":
|
|
348
|
+
return [status("TASK_STATE_CANCELED")];
|
|
322
349
|
case "turn.completed":
|
|
323
350
|
case "turn.failed":
|
|
324
351
|
case "turn.cancelled": {
|
|
@@ -468,3 +495,18 @@ export function submittedTask(contextId, taskId, queued) {
|
|
|
468
495
|
history: [],
|
|
469
496
|
};
|
|
470
497
|
}
|
|
498
|
+
/**
|
|
499
|
+
* A queued message's task that ended without a turn (Host feature `queue-recovery`), from the
|
|
500
|
+
* `queue.failed` or `queue.cancelled` event that names it: `FAILED`, saying why it never started,
|
|
501
|
+
* or `CANCELED`.
|
|
502
|
+
*/
|
|
503
|
+
export function droppedTask(contextId, taskId, ended) {
|
|
504
|
+
const [update] = updatesOf(ended, { contextId, taskId });
|
|
505
|
+
const status = update && "statusUpdate" in update ? update.statusUpdate.status : undefined;
|
|
506
|
+
return {
|
|
507
|
+
id: taskId,
|
|
508
|
+
contextId,
|
|
509
|
+
status: status ?? { state: "TASK_STATE_FAILED", timestamp: ended.time },
|
|
510
|
+
history: [],
|
|
511
|
+
};
|
|
512
|
+
}
|