@amaster.ai/dsh-a2a 0.1.24 → 0.1.26
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/README.md +39 -0
- package/lib/bridge.js +48 -3
- package/lib/executor.js +5 -1
- package/lib/inbox.js +944 -0
- package/lib/index.js +38 -3
- package/lib/legacy-stream.js +1 -1
- package/lib/server.js +43 -2
- package/lib/task-store.js +46 -12
- package/lib/types/bridge.d.ts +4 -0
- package/lib/types/inbox.d.ts +149 -0
- package/lib/types/index.d.ts +15 -0
- package/lib/types/server.d.ts +7 -2
- package/lib/types/task-store.d.ts +5 -1
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -16,17 +16,20 @@ import { ServerCallContext } from '@a2a-js/sdk/server';
|
|
|
16
16
|
import Schema from '@deepseek-ai/schemastery';
|
|
17
17
|
import { A2aBridge } from './bridge.js';
|
|
18
18
|
import { DshAgentExecutor } from './executor.js';
|
|
19
|
+
import { INBOX_EXTENSION, NativeInbox } from './inbox.js';
|
|
19
20
|
import { startA2aServer } from './server.js';
|
|
20
21
|
import { GcsTaskStore } from './stores/gcs.js';
|
|
21
22
|
import { RedisTaskStore } from './stores/redis.js';
|
|
22
23
|
import { MemoryTaskStore, SanitizedTaskStore } from './task-store.js';
|
|
23
24
|
export { defaultApprovalCodec } from './approval.js';
|
|
25
|
+
export { INBOX_EXTENSION } from './inbox.js';
|
|
24
26
|
/** Event emitted when one A2A user message enters the dsh inbox. */
|
|
25
27
|
export const A2A_MESSAGE_ADMITTED_EVENT = 'a2a/message-admitted';
|
|
26
28
|
export const name = 'dsh-a2a';
|
|
27
29
|
/** The bridge creates and owns agents through the registry service. */
|
|
28
30
|
export const inject = ['agents'];
|
|
29
31
|
export const Config = Schema.object({
|
|
32
|
+
inbox: Schema.object({ enabled: Schema.boolean().default(false) }).description('Use durable native inbox execution (requires the upstream native inbox capability).'),
|
|
30
33
|
enabled: Schema.boolean().default(false),
|
|
31
34
|
host: Schema.string().default('127.0.0.1'),
|
|
32
35
|
port: Schema.natural().default(41241),
|
|
@@ -108,7 +111,10 @@ export function apply(ctx, config) {
|
|
|
108
111
|
subagentTools: config.subagentTools,
|
|
109
112
|
});
|
|
110
113
|
const store = createTaskStore(config);
|
|
114
|
+
const inbox = config.inbox?.enabled ? new NativeInbox(ctx, bridge, store) : undefined;
|
|
111
115
|
await store.init?.();
|
|
116
|
+
if (inbox)
|
|
117
|
+
ctx.provide('a2aInbox', inbox);
|
|
112
118
|
ctx.provide('a2aTasks', {
|
|
113
119
|
async clearContext(contextId, tenant = '') {
|
|
114
120
|
const call = new ServerCallContext({ tenant });
|
|
@@ -136,6 +142,16 @@ export function apply(ctx, config) {
|
|
|
136
142
|
});
|
|
137
143
|
const executor = new DshAgentExecutor(bridge, (taskId) => store.suppress(taskId));
|
|
138
144
|
const server = await startA2aServer({
|
|
145
|
+
extensions: inbox
|
|
146
|
+
? [
|
|
147
|
+
{
|
|
148
|
+
uri: INBOX_EXTENSION,
|
|
149
|
+
description: 'Native inbox steering and parked admission',
|
|
150
|
+
required: false,
|
|
151
|
+
params: undefined,
|
|
152
|
+
},
|
|
153
|
+
]
|
|
154
|
+
: [],
|
|
139
155
|
host: config.host,
|
|
140
156
|
port: config.port,
|
|
141
157
|
basePath: config.basePath,
|
|
@@ -148,16 +164,35 @@ export function apply(ctx, config) {
|
|
|
148
164
|
},
|
|
149
165
|
executor,
|
|
150
166
|
taskStore: store,
|
|
167
|
+
beforeMessage: inbox
|
|
168
|
+
? (message, context) => inbox.beforeMessage(message, context)
|
|
169
|
+
: undefined,
|
|
170
|
+
beforeTask: inbox ? (taskId, context) => inbox.beforeTask(taskId, context) : undefined,
|
|
151
171
|
approvalGuard: (message) => bridge.reserveApprovalMessage(message),
|
|
152
172
|
beforeWorkingTaskMessage: (message, headers) => bridge.beforeWorkingTaskMessage(message, headers),
|
|
153
173
|
});
|
|
154
174
|
serverPort = server.port;
|
|
175
|
+
if (inbox)
|
|
176
|
+
inbox.server = server;
|
|
155
177
|
console.log(`[dsh-a2a] A2A endpoint: http://${config.host}:${server.port}${config.basePath}/ ` +
|
|
156
178
|
`(agent card: /.well-known/agent-card.json)`);
|
|
157
179
|
return async () => {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
180
|
+
const failures = [];
|
|
181
|
+
for (const cleanup of [
|
|
182
|
+
() => inbox?.shutdown(),
|
|
183
|
+
() => server.close(),
|
|
184
|
+
() => bridge.dispose(),
|
|
185
|
+
() => store.close?.(),
|
|
186
|
+
]) {
|
|
187
|
+
try {
|
|
188
|
+
await cleanup();
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
failures.push(error);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (failures.length)
|
|
195
|
+
throw new AggregateError(failures, 'A2A shutdown failed.');
|
|
161
196
|
};
|
|
162
197
|
}
|
|
163
198
|
}
|
package/lib/legacy-stream.js
CHANGED
package/lib/server.js
CHANGED
|
@@ -16,7 +16,7 @@ import { createServer } from 'node:http';
|
|
|
16
16
|
import { A2A_PROTOCOL_VERSION, AGENT_CARD_PATH, TaskState, } from '@a2a-js/sdk';
|
|
17
17
|
import { duplicateInterfacesForLegacy } from '@a2a-js/sdk/compat/v0_3';
|
|
18
18
|
import { RequestMalformedError } from '@a2a-js/sdk/errors';
|
|
19
|
-
import { DefaultExecutionEventBus, DefaultRequestHandler, ServerCallContext, STATE_HEADERS_KEY, } from '@a2a-js/sdk/server';
|
|
19
|
+
import { DefaultExecutionEventBus, DefaultRequestHandler, ExecutionEventQueue, ResultManager, ServerCallContext, STATE_HEADERS_KEY, } from '@a2a-js/sdk/server';
|
|
20
20
|
import { agentCardHandler, jsonRpcHandler, UserBuilder } from '@a2a-js/sdk/server/express';
|
|
21
21
|
import express from 'express';
|
|
22
22
|
import { legacyStreamHandler } from './legacy-stream.js';
|
|
@@ -59,7 +59,7 @@ export async function startA2aServer(options) {
|
|
|
59
59
|
capabilities: {
|
|
60
60
|
streaming: true,
|
|
61
61
|
pushNotifications: false,
|
|
62
|
-
extensions: [],
|
|
62
|
+
extensions: options.extensions ?? [],
|
|
63
63
|
extendedAgentCard: false,
|
|
64
64
|
},
|
|
65
65
|
securitySchemes: {},
|
|
@@ -189,22 +189,39 @@ export async function startA2aServer(options) {
|
|
|
189
189
|
}
|
|
190
190
|
};
|
|
191
191
|
const requestHandler = new (class extends DefaultRequestHandler {
|
|
192
|
+
async beforeMessage(message, context) {
|
|
193
|
+
if (!message)
|
|
194
|
+
return;
|
|
195
|
+
try {
|
|
196
|
+
return await options.beforeMessage?.(message, context);
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
throw new RequestMalformedError(error instanceof Error ? error.message : String(error));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
192
202
|
async sendMessage(...args) {
|
|
193
203
|
const release = guardApproval(args[0].message);
|
|
204
|
+
let releaseAdmission;
|
|
194
205
|
try {
|
|
195
206
|
if (!release)
|
|
196
207
|
await beforeWorkingTaskMessage(args[0].message, args[1]);
|
|
208
|
+
if (!release)
|
|
209
|
+
releaseAdmission = await this.beforeMessage(args[0].message, args[1]);
|
|
197
210
|
return await approvalReply.run(Boolean(release), () => super.sendMessage(...args));
|
|
198
211
|
}
|
|
199
212
|
finally {
|
|
213
|
+
releaseAdmission?.();
|
|
200
214
|
release?.();
|
|
201
215
|
}
|
|
202
216
|
}
|
|
203
217
|
async *sendMessageStream(...args) {
|
|
204
218
|
const release = guardApproval(args[0].message);
|
|
219
|
+
let releaseAdmission;
|
|
205
220
|
try {
|
|
206
221
|
if (!release)
|
|
207
222
|
await beforeWorkingTaskMessage(args[0].message, args[1]);
|
|
223
|
+
if (!release)
|
|
224
|
+
releaseAdmission = await this.beforeMessage(args[0].message, args[1]);
|
|
208
225
|
const stream = observeTask(super.sendMessageStream(...args));
|
|
209
226
|
try {
|
|
210
227
|
if (release) {
|
|
@@ -220,10 +237,16 @@ export async function startA2aServer(options) {
|
|
|
220
237
|
}
|
|
221
238
|
}
|
|
222
239
|
finally {
|
|
240
|
+
releaseAdmission?.();
|
|
223
241
|
release?.();
|
|
224
242
|
}
|
|
225
243
|
}
|
|
244
|
+
async cancelTask(...args) {
|
|
245
|
+
await options.beforeTask?.(args[0].id, args[1]);
|
|
246
|
+
return super.cancelTask(...args);
|
|
247
|
+
}
|
|
226
248
|
async *resubscribe(...args) {
|
|
249
|
+
await options.beforeTask?.(args[0].id, args[1]);
|
|
227
250
|
const task = taskBuses.get(args[0].id);
|
|
228
251
|
if (!task) {
|
|
229
252
|
yield* observeTask(super.resubscribe(...args));
|
|
@@ -445,6 +468,24 @@ export async function startA2aServer(options) {
|
|
|
445
468
|
server.listen(options.port, options.host, () => resolve());
|
|
446
469
|
});
|
|
447
470
|
return {
|
|
471
|
+
restoreTask(task, context) {
|
|
472
|
+
const bus = busManager.createOrGetByTaskId(task.id);
|
|
473
|
+
const queue = new ExecutionEventQueue(bus);
|
|
474
|
+
const result = new ResultManager(options.taskStore, context);
|
|
475
|
+
void (async () => {
|
|
476
|
+
try {
|
|
477
|
+
for await (const event of queue.events())
|
|
478
|
+
await result.processEvent(event);
|
|
479
|
+
}
|
|
480
|
+
catch (error) {
|
|
481
|
+
console.error('[dsh-a2a] restored task persistence failed:', error);
|
|
482
|
+
}
|
|
483
|
+
finally {
|
|
484
|
+
queue.stop();
|
|
485
|
+
}
|
|
486
|
+
})();
|
|
487
|
+
return bus;
|
|
488
|
+
},
|
|
448
489
|
port: server.address().port,
|
|
449
490
|
close: () => new Promise((resolve) => {
|
|
450
491
|
for (const stop of stopHeartbeats)
|
package/lib/task-store.js
CHANGED
|
@@ -63,6 +63,8 @@ export class SanitizedTaskStore {
|
|
|
63
63
|
inner;
|
|
64
64
|
lastState = new Map();
|
|
65
65
|
writes = new Map();
|
|
66
|
+
// Terminal task ids stay guarded until unload, like deleted-id tombstones.
|
|
67
|
+
terminalStates = new Map();
|
|
66
68
|
liveHistory = new Map();
|
|
67
69
|
// ponytail: deleted ids stay tombstoned until plugin unload; use expiring ids if clear volume grows.
|
|
68
70
|
deleted = new Set();
|
|
@@ -73,6 +75,11 @@ export class SanitizedTaskStore {
|
|
|
73
75
|
await this.inner.init?.();
|
|
74
76
|
}
|
|
75
77
|
async close() {
|
|
78
|
+
let pending;
|
|
79
|
+
do {
|
|
80
|
+
pending = [...this.writes.values()];
|
|
81
|
+
await Promise.all(pending);
|
|
82
|
+
} while ([...this.writes.values()].some((write) => !pending.includes(write)));
|
|
76
83
|
this.liveHistory.clear();
|
|
77
84
|
await this.inner.close?.();
|
|
78
85
|
}
|
|
@@ -88,9 +95,24 @@ export class SanitizedTaskStore {
|
|
|
88
95
|
this.lastState.delete(taskId);
|
|
89
96
|
this.writes.delete(taskId);
|
|
90
97
|
}
|
|
91
|
-
|
|
98
|
+
checkpoint(task, context) {
|
|
99
|
+
return this.save(task, context, true);
|
|
100
|
+
}
|
|
101
|
+
async save(task, context, checkpoint = false) {
|
|
92
102
|
if (this.deleted.has(task.id))
|
|
93
103
|
return;
|
|
104
|
+
const state = task.status?.state;
|
|
105
|
+
const terminal = this.terminalStates.get(task.id);
|
|
106
|
+
if (terminal !== undefined && state !== terminal)
|
|
107
|
+
return;
|
|
108
|
+
if (state !== undefined &&
|
|
109
|
+
[
|
|
110
|
+
TaskState.TASK_STATE_COMPLETED,
|
|
111
|
+
TaskState.TASK_STATE_CANCELED,
|
|
112
|
+
TaskState.TASK_STATE_FAILED,
|
|
113
|
+
TaskState.TASK_STATE_REJECTED,
|
|
114
|
+
].includes(state))
|
|
115
|
+
this.terminalStates.set(task.id, state);
|
|
94
116
|
const history = structuredClone(task.history ?? []);
|
|
95
117
|
const current = task.status?.message;
|
|
96
118
|
if (task.status?.state === TaskState.TASK_STATE_WORKING && current) {
|
|
@@ -120,23 +142,35 @@ export class SanitizedTaskStore {
|
|
|
120
142
|
else
|
|
121
143
|
history.push(final);
|
|
122
144
|
}
|
|
123
|
-
this.liveHistory.
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const state = task.status?.state;
|
|
128
|
-
if (state !== undefined && this.lastState.get(task.id) === state)
|
|
145
|
+
const previousHistory = this.liveHistory.get(task.id);
|
|
146
|
+
const live = { scope: scopeOf(context), history };
|
|
147
|
+
this.liveHistory.set(task.id, live);
|
|
148
|
+
if (!checkpoint && state !== undefined && this.lastState.get(task.id) === state)
|
|
129
149
|
return;
|
|
130
150
|
const write = (this.writes.get(task.id) ?? Promise.resolve()).then(() => this.inner.save(sanitizeTask({ ...task, history }), context));
|
|
131
|
-
|
|
132
|
-
|
|
151
|
+
const tail = write.catch(() => { });
|
|
152
|
+
this.writes.set(task.id, tail);
|
|
153
|
+
try {
|
|
154
|
+
await write;
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
if (this.liveHistory.get(task.id) === live) {
|
|
158
|
+
if (previousHistory)
|
|
159
|
+
this.liveHistory.set(task.id, previousHistory);
|
|
160
|
+
else
|
|
161
|
+
this.liveHistory.delete(task.id);
|
|
162
|
+
}
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
133
165
|
if (state === TaskState.TASK_STATE_WORKING || state === TaskState.TASK_STATE_SUBMITTED) {
|
|
134
166
|
this.lastState.set(task.id, state);
|
|
135
167
|
}
|
|
136
168
|
else {
|
|
137
|
-
this.
|
|
138
|
-
|
|
139
|
-
|
|
169
|
+
if (this.writes.get(task.id) === tail) {
|
|
170
|
+
this.lastState.delete(task.id);
|
|
171
|
+
this.writes.delete(task.id);
|
|
172
|
+
this.liveHistory.delete(task.id);
|
|
173
|
+
}
|
|
140
174
|
}
|
|
141
175
|
}
|
|
142
176
|
async load(taskId, context) {
|
package/lib/types/bridge.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ import type { Context } from '@deepseek-ai/cordis';
|
|
|
19
19
|
import type { AgentHandle } from '@deepseek-ai/dsh-agent';
|
|
20
20
|
import type { ContentBlock } from '@deepseek-ai/dsh-llm/types';
|
|
21
21
|
import { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
22
|
+
import type { NativeInbox } from './inbox.js';
|
|
22
23
|
import { SessionTranslator } from './translator.js';
|
|
23
24
|
export interface BridgeOptions {
|
|
24
25
|
/** Absolute working directory for spawned agents. */
|
|
@@ -63,6 +64,7 @@ export interface TaskEntry {
|
|
|
63
64
|
export declare class A2aBridge {
|
|
64
65
|
private readonly ctx;
|
|
65
66
|
private readonly options;
|
|
67
|
+
inbox?: NativeInbox;
|
|
66
68
|
private readonly tasks;
|
|
67
69
|
private readonly bySession;
|
|
68
70
|
private readonly clearing;
|
|
@@ -100,6 +102,8 @@ export declare class A2aBridge {
|
|
|
100
102
|
};
|
|
101
103
|
private findApproval;
|
|
102
104
|
waitForTurn(entry: TaskEntry): Promise<void>;
|
|
105
|
+
/** The native inbox selects task ownership before assembling its first prompt. */
|
|
106
|
+
bindInboxTask(entry: TaskEntry, taskId: string, translator: SessionTranslator, bus: ExecutionEventBus, completion: Promise<void>, controller: AbortController): void;
|
|
103
107
|
/** Inspect a message for a live task before the SDK starts its next request. */
|
|
104
108
|
beforeWorkingTaskMessage(a2aMessage: Message, requestHeaders: unknown): Promise<void>;
|
|
105
109
|
/** Include content preparation in the work a context clear must drain. */
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { Message } from '@a2a-js/sdk';
|
|
2
|
+
import { type ExecutionEventBus, type RequestContext, ServerCallContext } from '@a2a-js/sdk/server';
|
|
3
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
4
|
+
import type { Agent, AssistantStreamFrame, InboxTarget } from '@deepseek-ai/dsh-agent';
|
|
5
|
+
import type { ContentBlock, UserMessage } from '@deepseek-ai/dsh-llm/types';
|
|
6
|
+
import type { Session, SessionEvent } from '@deepseek-ai/dsh-session';
|
|
7
|
+
import type { A2aBridge } from './bridge.js';
|
|
8
|
+
import type { A2aTurnContext } from './index.js';
|
|
9
|
+
import type { A2aServer } from './server.js';
|
|
10
|
+
import type { ManagedTaskStore } from './task-store.js';
|
|
11
|
+
export declare const INBOX_EXTENSION = "https://github.com/TGYD-helige/dsh-plugins/blob/main/docs/protocol/inbox-v1.md";
|
|
12
|
+
export interface A2aInboxEvent {
|
|
13
|
+
phase: string;
|
|
14
|
+
contextId: string;
|
|
15
|
+
at: number;
|
|
16
|
+
pending: number;
|
|
17
|
+
target?: 'next-turn' | 'next-step';
|
|
18
|
+
input?: string;
|
|
19
|
+
position?: number;
|
|
20
|
+
paused?: boolean;
|
|
21
|
+
agentStatus?: string;
|
|
22
|
+
taskId?: string;
|
|
23
|
+
taskIds?: string[];
|
|
24
|
+
messageId?: string;
|
|
25
|
+
referenceTaskIds?: string[];
|
|
26
|
+
nativeMessageId?: string;
|
|
27
|
+
queuedAt?: number;
|
|
28
|
+
selectedAt?: number;
|
|
29
|
+
turn?: number;
|
|
30
|
+
step?: number;
|
|
31
|
+
}
|
|
32
|
+
interface InboxSource {
|
|
33
|
+
kind: 'a2a';
|
|
34
|
+
taskId: string;
|
|
35
|
+
/** JSON encoding removes protobuf optional undefined fields before durable admission. */
|
|
36
|
+
message: string;
|
|
37
|
+
target: InboxTarget;
|
|
38
|
+
queuedAt?: number;
|
|
39
|
+
}
|
|
40
|
+
declare module '@deepseek-ai/dsh-llm/types' {
|
|
41
|
+
interface MessageSourceMap {
|
|
42
|
+
a2a: InboxSource;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
declare module '@deepseek-ai/dsh-session/types' {
|
|
46
|
+
interface SessionEventMap {
|
|
47
|
+
'agent/inbox/paused': {
|
|
48
|
+
paused: boolean;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
declare module '@deepseek-ai/cordis' {
|
|
53
|
+
interface Events {
|
|
54
|
+
/** Native inbox lifecycle; content consumers must honor their capture settings. */
|
|
55
|
+
'a2a/inbox'(detail: A2aInboxEvent): void;
|
|
56
|
+
'agent/before-assembly'(detail: {
|
|
57
|
+
agent: Agent;
|
|
58
|
+
messages: UserMessage[];
|
|
59
|
+
turn: number;
|
|
60
|
+
step: number;
|
|
61
|
+
signal: AbortSignal;
|
|
62
|
+
}): void | Promise<void>;
|
|
63
|
+
/** Selected input, before system-prompt assembly; restored input has no original headers. */
|
|
64
|
+
'a2a/before-assembly'(detail: A2aTurnContext): void | Promise<void>;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/** Same-process queue control; wire messages and task subscriptions remain standard A2A. */
|
|
68
|
+
export interface A2aInbox {
|
|
69
|
+
resolve(contextId: string): Promise<Agent>;
|
|
70
|
+
content(message: Message, contextId: string): Promise<ContentBlock[]>;
|
|
71
|
+
/** Admit a same-process host follow-up under an existing task's durable identity. */
|
|
72
|
+
enqueue(taskId: string, message: Message, options?: {
|
|
73
|
+
target?: InboxTarget;
|
|
74
|
+
wakeup?: boolean;
|
|
75
|
+
}): Promise<void>;
|
|
76
|
+
pause(contextId: string): Promise<void>;
|
|
77
|
+
/** Cancellation is terminal; retained pending input stays paused until explicit resume. */
|
|
78
|
+
cancel(taskId: string, options?: {
|
|
79
|
+
retainInbox?: boolean;
|
|
80
|
+
}): Promise<string | undefined>;
|
|
81
|
+
/** Recovery does not override a persisted pause unless resume is explicitly true. */
|
|
82
|
+
wake(contextId: string, options?: {
|
|
83
|
+
resume?: boolean;
|
|
84
|
+
context?: ServerCallContext;
|
|
85
|
+
}): Promise<string[]>;
|
|
86
|
+
}
|
|
87
|
+
/** Fold native input mutations; a claimed input is recoverable only before user/message. */
|
|
88
|
+
export declare function recoverInbox(events: readonly SessionEvent[]): {
|
|
89
|
+
paused: boolean;
|
|
90
|
+
pending: UserMessage[];
|
|
91
|
+
committed: Set<string>;
|
|
92
|
+
};
|
|
93
|
+
export declare class NativeInbox implements A2aInbox {
|
|
94
|
+
private readonly ctx;
|
|
95
|
+
private readonly bridge;
|
|
96
|
+
private readonly store;
|
|
97
|
+
private readonly contexts;
|
|
98
|
+
private readonly resolving;
|
|
99
|
+
private stopping;
|
|
100
|
+
private shutdownPromise?;
|
|
101
|
+
private readonly waking;
|
|
102
|
+
private readonly preadmitted;
|
|
103
|
+
server?: A2aServer;
|
|
104
|
+
constructor(ctx: Context, bridge: A2aBridge, store: ManagedTaskStore);
|
|
105
|
+
content(message: Message, contextId: string): Promise<ContentBlock[]>;
|
|
106
|
+
resolve(contextId: string): Promise<Agent>;
|
|
107
|
+
enqueue(taskId: string, message: Message, options?: {
|
|
108
|
+
target?: InboxTarget;
|
|
109
|
+
wakeup?: boolean;
|
|
110
|
+
}): Promise<void>;
|
|
111
|
+
private context;
|
|
112
|
+
private observe;
|
|
113
|
+
private observeInput;
|
|
114
|
+
private flush;
|
|
115
|
+
private checkpoint;
|
|
116
|
+
private pending;
|
|
117
|
+
private task;
|
|
118
|
+
private controls;
|
|
119
|
+
/** Reject follow-up admission at the request boundary, without terminating accepted work. */
|
|
120
|
+
beforeMessage(message: Message, context: ServerCallContext): Promise<undefined | (() => void)>;
|
|
121
|
+
/** Reattach a cold task before the SDK decides whether cancellation has a live executor. */
|
|
122
|
+
beforeTask(taskId: string, context: ServerCallContext): Promise<void>;
|
|
123
|
+
private outcome;
|
|
124
|
+
private terminal;
|
|
125
|
+
private admit;
|
|
126
|
+
execute(request: RequestContext, bus: ExecutionEventBus): Promise<void>;
|
|
127
|
+
private restoreTask;
|
|
128
|
+
private drive;
|
|
129
|
+
private prepare;
|
|
130
|
+
private publish;
|
|
131
|
+
onSessionEvent(session: Session, event: SessionEvent): boolean;
|
|
132
|
+
private settle;
|
|
133
|
+
onAssistantStream(agent: Agent, frame: AssistantStreamFrame): boolean;
|
|
134
|
+
private fail;
|
|
135
|
+
pause(contextId: string): Promise<void>;
|
|
136
|
+
wake(contextId: string, options?: {
|
|
137
|
+
resume?: boolean;
|
|
138
|
+
context?: ServerCallContext;
|
|
139
|
+
}): Promise<string[]>;
|
|
140
|
+
private wakeInner;
|
|
141
|
+
cancel(taskId: string, options?: {
|
|
142
|
+
retainInbox?: boolean;
|
|
143
|
+
}): Promise<string | undefined>;
|
|
144
|
+
private cancelTask;
|
|
145
|
+
clear(contextId: string): Promise<void>;
|
|
146
|
+
onDisposed(session: Session): void;
|
|
147
|
+
shutdown(): Promise<void>;
|
|
148
|
+
}
|
|
149
|
+
export {};
|
package/lib/types/index.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ import Schema from '@deepseek-ai/schemastery';
|
|
|
19
19
|
export type { A2aApproval, A2aApprovalCodec, A2aApprovalReply } from './approval.js';
|
|
20
20
|
export { defaultApprovalCodec } from './approval.js';
|
|
21
21
|
export type { A2aFileMaterializer } from './content.js';
|
|
22
|
+
export type { A2aInbox, A2aInboxEvent } from './inbox.js';
|
|
23
|
+
export { INBOX_EXTENSION } from './inbox.js';
|
|
22
24
|
export type { SubagentToolSource } from './translator.js';
|
|
23
25
|
/** Event emitted when one A2A user message enters the dsh inbox. */
|
|
24
26
|
export declare const A2A_MESSAGE_ADMITTED_EVENT: "a2a/message-admitted";
|
|
@@ -67,6 +69,11 @@ export declare const name = "dsh-a2a";
|
|
|
67
69
|
/** The bridge creates and owns agents through the registry service. */
|
|
68
70
|
export declare const inject: string[];
|
|
69
71
|
export declare const Config: Schema<Schemastery.ObjectS<{
|
|
72
|
+
inbox: Schema<Schemastery.ObjectS<{
|
|
73
|
+
enabled: Schema<boolean, boolean>;
|
|
74
|
+
}>, Schemastery.ObjectT<{
|
|
75
|
+
enabled: Schema<boolean, boolean>;
|
|
76
|
+
}>>;
|
|
70
77
|
enabled: Schema<boolean, boolean>;
|
|
71
78
|
host: Schema<string, string>;
|
|
72
79
|
port: Schema<number, number>;
|
|
@@ -144,6 +151,11 @@ export declare const Config: Schema<Schemastery.ObjectS<{
|
|
|
144
151
|
keyFilename: Schema<string, string>;
|
|
145
152
|
}>>;
|
|
146
153
|
}>, Schemastery.ObjectT<{
|
|
154
|
+
inbox: Schema<Schemastery.ObjectS<{
|
|
155
|
+
enabled: Schema<boolean, boolean>;
|
|
156
|
+
}>, Schemastery.ObjectT<{
|
|
157
|
+
enabled: Schema<boolean, boolean>;
|
|
158
|
+
}>>;
|
|
147
159
|
enabled: Schema<boolean, boolean>;
|
|
148
160
|
host: Schema<string, string>;
|
|
149
161
|
port: Schema<number, number>;
|
|
@@ -222,6 +234,9 @@ export declare const Config: Schema<Schemastery.ObjectS<{
|
|
|
222
234
|
}>>;
|
|
223
235
|
}>>;
|
|
224
236
|
export interface A2aPluginConfig {
|
|
237
|
+
inbox?: {
|
|
238
|
+
enabled: boolean;
|
|
239
|
+
};
|
|
225
240
|
enabled: boolean;
|
|
226
241
|
host: string;
|
|
227
242
|
port: number;
|
package/lib/types/server.d.ts
CHANGED
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
* The SSE framing and error envelopes are the SDK's; this layer only binds
|
|
12
12
|
* the server, builds the card, and optionally adds transport heartbeats.
|
|
13
13
|
*/
|
|
14
|
-
import { type Message } from '@a2a-js/sdk';
|
|
15
|
-
import { type AgentExecutor, type TaskStore } from '@a2a-js/sdk/server';
|
|
14
|
+
import { type AgentExtension, type Message, type Task } from '@a2a-js/sdk';
|
|
15
|
+
import { type AgentExecutor, DefaultExecutionEventBus, ServerCallContext, type TaskStore } from '@a2a-js/sdk/server';
|
|
16
16
|
export interface A2aServerOptions {
|
|
17
|
+
extensions?: AgentExtension[];
|
|
17
18
|
host: string;
|
|
18
19
|
port: number;
|
|
19
20
|
basePath: string;
|
|
@@ -32,8 +33,12 @@ export interface A2aServerOptions {
|
|
|
32
33
|
};
|
|
33
34
|
approvalGuard?: (message: Message) => undefined | (() => void);
|
|
34
35
|
beforeWorkingTaskMessage?: (message: Message, requestHeaders: unknown) => Promise<void>;
|
|
36
|
+
beforeMessage?: (message: Message, context: ServerCallContext) => Promise<undefined | (() => void)>;
|
|
37
|
+
beforeTask?: (taskId: string, context: ServerCallContext) => Promise<void>;
|
|
35
38
|
}
|
|
36
39
|
export interface A2aServer {
|
|
40
|
+
/** Reattach persistence and task subscriptions without admitting another message. */
|
|
41
|
+
restoreTask(task: Task, context: ServerCallContext): DefaultExecutionEventBus;
|
|
37
42
|
/** The bound port (differs from options.port when 0). */
|
|
38
43
|
port: number;
|
|
39
44
|
close(): Promise<void>;
|
|
@@ -21,6 +21,8 @@ import { type ServerCallContext, type TaskStore } from '@a2a-js/sdk/server';
|
|
|
21
21
|
export declare function sanitizeTask(task: Task): Task;
|
|
22
22
|
/** TaskStore with the optional lifecycle our backends add. */
|
|
23
23
|
export interface ManagedTaskStore extends TaskStore {
|
|
24
|
+
/** Durable admission checkpoint even when task state has not changed. */
|
|
25
|
+
checkpoint?(task: Task, context: ServerCallContext): Promise<void>;
|
|
24
26
|
init?(): Promise<void>;
|
|
25
27
|
delete(taskId: string, context: ServerCallContext): Promise<void>;
|
|
26
28
|
close?(): Promise<void>;
|
|
@@ -41,6 +43,7 @@ export declare class SanitizedTaskStore implements ManagedTaskStore {
|
|
|
41
43
|
private readonly inner;
|
|
42
44
|
private readonly lastState;
|
|
43
45
|
private readonly writes;
|
|
46
|
+
private readonly terminalStates;
|
|
44
47
|
private readonly liveHistory;
|
|
45
48
|
private readonly deleted;
|
|
46
49
|
constructor(inner: ManagedTaskStore);
|
|
@@ -49,7 +52,8 @@ export declare class SanitizedTaskStore implements ManagedTaskStore {
|
|
|
49
52
|
/** Drop SDK saves that may arrive after a clear rejects an in-flight request. */
|
|
50
53
|
suppress(taskId: string): void;
|
|
51
54
|
delete(taskId: string, context: ServerCallContext): Promise<void>;
|
|
52
|
-
|
|
55
|
+
checkpoint(task: Task, context: ServerCallContext): Promise<void>;
|
|
56
|
+
save(task: Task, context: ServerCallContext, checkpoint?: boolean): Promise<void>;
|
|
53
57
|
load(taskId: string, context: ServerCallContext): Promise<Task | undefined>;
|
|
54
58
|
list(params: ListTasksRequest, context: ServerCallContext): Promise<ListTasksResponse>;
|
|
55
59
|
}
|
package/package.json
CHANGED