@amaster.ai/dsh-a2a 0.1.34 → 0.1.35
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 +278 -7
- package/lib/admission.js +25 -0
- package/lib/bridge.js +45 -23
- package/lib/content.js +163 -30
- package/lib/executor.js +38 -3
- package/lib/inbox-api.js +117 -0
- package/lib/inbox-changes.js +116 -0
- package/lib/inbox.js +557 -43
- package/lib/index.js +33 -4
- package/lib/legacy-stream.js +20 -5
- package/lib/server.js +159 -26
- package/lib/stores/gcs.js +6 -5
- package/lib/stores/redis.js +5 -4
- package/lib/stream-connection.js +75 -0
- package/lib/task-store.js +22 -4
- package/lib/types/admission.d.ts +10 -0
- package/lib/types/bridge.d.ts +8 -0
- package/lib/types/content.d.ts +7 -1
- package/lib/types/executor.d.ts +2 -0
- package/lib/types/inbox-api.d.ts +5 -0
- package/lib/types/inbox-changes.d.ts +25 -0
- package/lib/types/inbox.d.ts +57 -1
- package/lib/types/index.d.ts +197 -142
- package/lib/types/server.d.ts +13 -1
- package/lib/types/stream-connection.d.ts +8 -0
- package/package.json +25 -15
package/lib/task-store.js
CHANGED
|
@@ -85,12 +85,12 @@ export class MemoryTaskStore {
|
|
|
85
85
|
this.bucket(context).delete(taskId);
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
|
+
const TERMINAL_REOPEN_GUARD_MS = 24 * 60 * 60 * 1000;
|
|
88
89
|
/** Saves task snapshots on state transitions while keeping active deltas in process. */
|
|
89
90
|
export class SanitizedTaskStore {
|
|
90
91
|
inner;
|
|
91
92
|
lastState = new Map();
|
|
92
93
|
writes = new Map();
|
|
93
|
-
// Terminal task ids stay guarded until unload, like deleted-id tombstones.
|
|
94
94
|
terminalStates = new Map();
|
|
95
95
|
liveHistory = new Map();
|
|
96
96
|
// ponytail: deleted ids stay tombstoned until plugin unload; use expiring ids if clear volume grows.
|
|
@@ -108,6 +108,7 @@ export class SanitizedTaskStore {
|
|
|
108
108
|
await Promise.all(pending);
|
|
109
109
|
} while ([...this.writes.values()].some((write) => !pending.includes(write)));
|
|
110
110
|
this.liveHistory.clear();
|
|
111
|
+
this.terminalStates.clear();
|
|
111
112
|
await this.inner.close?.();
|
|
112
113
|
}
|
|
113
114
|
/** Drop SDK saves that may arrive after a clear rejects an in-flight request. */
|
|
@@ -121,6 +122,7 @@ export class SanitizedTaskStore {
|
|
|
121
122
|
await this.inner.delete(taskId, context);
|
|
122
123
|
this.lastState.delete(taskId);
|
|
123
124
|
this.writes.delete(taskId);
|
|
125
|
+
this.terminalStates.delete(taskId);
|
|
124
126
|
}
|
|
125
127
|
checkpoint(task, context) {
|
|
126
128
|
return this.save(task, context, true);
|
|
@@ -129,8 +131,11 @@ export class SanitizedTaskStore {
|
|
|
129
131
|
if (this.deleted.has(task.id))
|
|
130
132
|
return;
|
|
131
133
|
const state = task.status?.state;
|
|
134
|
+
const now = Date.now();
|
|
132
135
|
const terminal = this.terminalStates.get(task.id);
|
|
133
|
-
if (terminal
|
|
136
|
+
if (terminal && terminal.expiresAt <= now)
|
|
137
|
+
this.terminalStates.delete(task.id);
|
|
138
|
+
else if (terminal && state !== terminal.state)
|
|
134
139
|
return;
|
|
135
140
|
if (state !== undefined &&
|
|
136
141
|
[
|
|
@@ -138,8 +143,21 @@ export class SanitizedTaskStore {
|
|
|
138
143
|
TaskState.TASK_STATE_CANCELED,
|
|
139
144
|
TaskState.TASK_STATE_FAILED,
|
|
140
145
|
TaskState.TASK_STATE_REJECTED,
|
|
141
|
-
].includes(state))
|
|
142
|
-
|
|
146
|
+
].includes(state)) {
|
|
147
|
+
// Map order matches expiry order: only the expired prefix needs scanning.
|
|
148
|
+
for (const [id, guard] of this.terminalStates) {
|
|
149
|
+
if (guard.expiresAt > now)
|
|
150
|
+
break;
|
|
151
|
+
this.terminalStates.delete(id);
|
|
152
|
+
}
|
|
153
|
+
// Re-registering an id refreshes both its deadline and insertion order.
|
|
154
|
+
this.terminalStates.delete(task.id);
|
|
155
|
+
this.terminalStates.set(task.id, {
|
|
156
|
+
state,
|
|
157
|
+
expiresAt: now + TERMINAL_REOPEN_GUARD_MS,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
// shortcut: WORKING deltas clone and concatenate per event; use chunk accumulators if long streams make this hot path measurable.
|
|
143
161
|
const history = structuredClone(task.history ?? []);
|
|
144
162
|
const current = task.status?.message;
|
|
145
163
|
if (current?.role === Role.ROLE_USER)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type AdmissionRelease = (onCurrent?: () => void) => void;
|
|
2
|
+
export interface Admission {
|
|
3
|
+
ready: Promise<void>;
|
|
4
|
+
release: AdmissionRelease;
|
|
5
|
+
}
|
|
6
|
+
/** Small keyed FIFO gate. Cleanup runs only when the releasing tail is still current. */
|
|
7
|
+
export declare class AdmissionGate {
|
|
8
|
+
private readonly tails;
|
|
9
|
+
acquire(key: string): Admission;
|
|
10
|
+
}
|
package/lib/types/bridge.d.ts
CHANGED
|
@@ -26,6 +26,10 @@ export interface BridgeOptions {
|
|
|
26
26
|
cwd: string;
|
|
27
27
|
/** Upload root for persisted file parts; absent = the OS temp dir. */
|
|
28
28
|
uploadsDir?: string;
|
|
29
|
+
/** Allow every private URL host in a trusted intranet. */
|
|
30
|
+
downloadAllowPrivate?: boolean;
|
|
31
|
+
/** Private URL file hosts explicitly allowed by the deployment. */
|
|
32
|
+
downloadAllowedHosts?: readonly string[];
|
|
29
33
|
/** Optional provider/model overrides for created agents. */
|
|
30
34
|
agentOptions?: {
|
|
31
35
|
provider?: string;
|
|
@@ -42,6 +46,8 @@ export interface TaskEntry {
|
|
|
42
46
|
taskId: string;
|
|
43
47
|
sessionId: SessionId;
|
|
44
48
|
handle: AgentHandle;
|
|
49
|
+
/** An existing native producer retains agent lifecycle ownership. */
|
|
50
|
+
borrowed?: boolean;
|
|
45
51
|
translator: SessionTranslator;
|
|
46
52
|
/** The in-flight execute()'s bus; null between turns. */
|
|
47
53
|
bus: ExecutionEventBus | null;
|
|
@@ -69,6 +75,7 @@ export declare class A2aBridge {
|
|
|
69
75
|
private readonly bySession;
|
|
70
76
|
private readonly clearing;
|
|
71
77
|
private readonly creating;
|
|
78
|
+
private readonly admissions;
|
|
72
79
|
private readonly executing;
|
|
73
80
|
private readonly asked;
|
|
74
81
|
private readonly approvals;
|
|
@@ -88,6 +95,7 @@ export declare class A2aBridge {
|
|
|
88
95
|
freshTask: boolean;
|
|
89
96
|
}>;
|
|
90
97
|
private ensureTaskInner;
|
|
98
|
+
private createAgent;
|
|
91
99
|
/** A2A parts → dsh content blocks; throws only when no part is usable. */
|
|
92
100
|
buildContent(message: Message, contextId: string): Promise<ContentBlock[]>;
|
|
93
101
|
isClearing(contextId: string): boolean;
|
package/lib/types/content.d.ts
CHANGED
|
@@ -34,7 +34,13 @@ interface Materialization {
|
|
|
34
34
|
contextId: string;
|
|
35
35
|
materializer: A2aFileMaterializer;
|
|
36
36
|
}
|
|
37
|
-
export
|
|
37
|
+
export interface UrlDownloadOptions {
|
|
38
|
+
/** Allow every private/loopback host in a trusted intranet; HTTP(S), size and time limits still apply. */
|
|
39
|
+
allowPrivate?: boolean;
|
|
40
|
+
/** Private/loopback hosts are refused unless explicitly named here. */
|
|
41
|
+
allowedHosts?: readonly string[];
|
|
42
|
+
}
|
|
43
|
+
export declare function buildMessageContent(parts: readonly Part[], attachments: AttachmentStore | undefined, dir: string, materialization?: Materialization, downloads?: UrlDownloadOptions): Promise<ContentBlock[]>;
|
|
38
44
|
/** The directory non-image file parts persist into without a materializer. */
|
|
39
45
|
export declare function uploadsDir(root?: string): string;
|
|
40
46
|
export {};
|
package/lib/types/executor.d.ts
CHANGED
|
@@ -22,6 +22,8 @@ import type { A2aBridge } from './bridge.js';
|
|
|
22
22
|
export declare class DshAgentExecutor implements AgentExecutor {
|
|
23
23
|
private readonly bridge;
|
|
24
24
|
private readonly suppressTask?;
|
|
25
|
+
private readonly admissions;
|
|
26
|
+
private readonly admissionFailures;
|
|
25
27
|
constructor(bridge: A2aBridge, suppressTask?: ((taskId: string) => void) | undefined);
|
|
26
28
|
execute(requestContext: RequestContext, eventBus: ExecutionEventBus): Promise<void>;
|
|
27
29
|
private executeInner;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** JSON-RPC method extension; standard task methods stay with the official SDK. */
|
|
2
|
+
import { type AgentCard } from '@a2a-js/sdk';
|
|
3
|
+
import type { RequestHandler } from 'express';
|
|
4
|
+
import { type NativeInbox } from './inbox.js';
|
|
5
|
+
export declare function inboxRpcHandler(inbox: NativeInbox, card: AgentCard): RequestHandler;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import type { Session } from '@deepseek-ai/dsh-session';
|
|
3
|
+
export interface InboxChange {
|
|
4
|
+
contextId: string;
|
|
5
|
+
version: number;
|
|
6
|
+
}
|
|
7
|
+
declare module '@deepseek-ai/cordis' {
|
|
8
|
+
interface Events {
|
|
9
|
+
'a2a/inbox-checkpointed'(change: InboxChange): void;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/** Invalidation only: DSH owns the queue, sequence and durability barrier. */
|
|
13
|
+
export declare class InboxChanges {
|
|
14
|
+
private readonly ctx;
|
|
15
|
+
private readonly states;
|
|
16
|
+
private readonly listeners;
|
|
17
|
+
private readonly disposed;
|
|
18
|
+
private readonly detach;
|
|
19
|
+
private stopped;
|
|
20
|
+
constructor(ctx: Context, managed: (contextId: string) => boolean, enabled?: boolean);
|
|
21
|
+
watch(session: Session, listener: (change: InboxChange) => void, closed: () => void): () => void;
|
|
22
|
+
checkpoint(session: Session): Promise<InboxChange>;
|
|
23
|
+
close(): void;
|
|
24
|
+
private closeSession;
|
|
25
|
+
}
|
package/lib/types/inbox.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { GoalMessageSource } from '@deepseek-ai/dsh-goal';
|
|
|
6
6
|
import type { ContentBlock } from '@deepseek-ai/dsh-llm/types';
|
|
7
7
|
import type { Session, SessionEvent } from '@deepseek-ai/dsh-session';
|
|
8
8
|
import type { A2aBridge } from './bridge.js';
|
|
9
|
+
import { InboxChanges } from './inbox-changes.js';
|
|
9
10
|
import type { A2aTurnContext } from './index.js';
|
|
10
11
|
import type { A2aServer } from './server.js';
|
|
11
12
|
import { type ManagedTaskStore } from './task-store.js';
|
|
@@ -45,6 +46,13 @@ declare module '@deepseek-ai/dsh-llm/types' {
|
|
|
45
46
|
goal: GoalMessageSource;
|
|
46
47
|
}
|
|
47
48
|
}
|
|
49
|
+
declare module '@deepseek-ai/dsh-session/types' {
|
|
50
|
+
interface SessionEventMap {
|
|
51
|
+
'agent/inbox/paused': {
|
|
52
|
+
paused: boolean;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
48
56
|
declare module '@deepseek-ai/cordis' {
|
|
49
57
|
interface Events {
|
|
50
58
|
/** Native inbox lifecycle; content consumers must honor their capture settings. */
|
|
@@ -55,6 +63,12 @@ declare module '@deepseek-ai/cordis' {
|
|
|
55
63
|
}
|
|
56
64
|
/** Same-process queue control; wire messages and task subscriptions remain standard A2A. */
|
|
57
65
|
export interface A2aInbox {
|
|
66
|
+
get(contextId: string, page?: InboxPageOptions): Promise<InboxSnapshot>;
|
|
67
|
+
update(contextId: string, id: string, message: Message, page?: InboxPageOptions): Promise<InboxSnapshot>;
|
|
68
|
+
move(contextId: string, id: string, index: number, page?: InboxPageOptions): Promise<InboxSnapshot>;
|
|
69
|
+
remove(contextId: string, id: string, page?: InboxPageOptions): Promise<InboxSnapshot>;
|
|
70
|
+
clearPending(contextId: string, page?: InboxPageOptions): Promise<InboxSnapshot>;
|
|
71
|
+
executePending(contextId: string, id?: string, page?: InboxPageOptions): Promise<InboxSnapshot>;
|
|
58
72
|
resolve(contextId: string): Promise<Agent>;
|
|
59
73
|
content(message: Message, contextId: string): Promise<ContentBlock[]>;
|
|
60
74
|
/** Admit a same-process host follow-up under an existing task's durable identity. */
|
|
@@ -64,14 +78,42 @@ export interface A2aInbox {
|
|
|
64
78
|
}): Promise<void>;
|
|
65
79
|
/** Transfer one unclaimed independent input into the active task. */
|
|
66
80
|
promote(contextId: string, messageId: string, taskId: string): Promise<void>;
|
|
81
|
+
/** Explicitly resume terminal retained input under a fresh task identity. */
|
|
82
|
+
resume(contextId: string, taskId: string): Promise<string>;
|
|
83
|
+
/** Pause admission and the current driver while retaining pending input. */
|
|
84
|
+
pause(contextId: string): Promise<void>;
|
|
85
|
+
/** Wake retained work in queue order without creating a replacement task. */
|
|
86
|
+
wake(contextId: string): Promise<string[]>;
|
|
67
87
|
/** Standard cancellation is terminal and removes only this task's pending input. */
|
|
68
88
|
cancel(taskId: string): Promise<string | undefined>;
|
|
69
89
|
}
|
|
90
|
+
export interface InboxSnapshot {
|
|
91
|
+
contextId: string;
|
|
92
|
+
version: number;
|
|
93
|
+
nextTurn: Array<{
|
|
94
|
+
id: string;
|
|
95
|
+
message: unknown;
|
|
96
|
+
editable: boolean;
|
|
97
|
+
}>;
|
|
98
|
+
nextStep: Array<{
|
|
99
|
+
id: string;
|
|
100
|
+
message: unknown;
|
|
101
|
+
editable: boolean;
|
|
102
|
+
}>;
|
|
103
|
+
totalSize: number;
|
|
104
|
+
nextPageToken: string;
|
|
105
|
+
}
|
|
106
|
+
export interface InboxPageOptions {
|
|
107
|
+
pageSize?: number;
|
|
108
|
+
pageToken?: string;
|
|
109
|
+
}
|
|
110
|
+
export declare function validateInboxPage(page?: InboxPageOptions): void;
|
|
70
111
|
export declare class NativeInbox implements A2aInbox {
|
|
71
112
|
private readonly ctx;
|
|
72
113
|
private readonly bridge;
|
|
73
114
|
private readonly store;
|
|
74
115
|
private readonly retainPending;
|
|
116
|
+
readonly changes: InboxChanges;
|
|
75
117
|
private readonly contexts;
|
|
76
118
|
private readonly resolving;
|
|
77
119
|
private stopping;
|
|
@@ -79,10 +121,20 @@ export declare class NativeInbox implements A2aInbox {
|
|
|
79
121
|
private readonly preadmitted;
|
|
80
122
|
server?: A2aServer;
|
|
81
123
|
private readonly persistence?;
|
|
82
|
-
constructor(ctx: Context, bridge: A2aBridge, store: ManagedTaskStore, retainPending?: boolean);
|
|
124
|
+
constructor(ctx: Context, bridge: A2aBridge, store: ManagedTaskStore, retainPending?: boolean, notifications?: boolean);
|
|
83
125
|
private unloading;
|
|
84
126
|
content(message: Message, contextId: string): Promise<ContentBlock[]>;
|
|
85
127
|
resolve(contextId: string): Promise<Agent>;
|
|
128
|
+
get(contextId: string, page?: InboxPageOptions): Promise<InboxSnapshot>;
|
|
129
|
+
private editQueue;
|
|
130
|
+
private queued;
|
|
131
|
+
update(contextId: string, id: string, message: Message, page?: InboxPageOptions): Promise<InboxSnapshot>;
|
|
132
|
+
move(contextId: string, id: string, index: number, page?: InboxPageOptions): Promise<InboxSnapshot>;
|
|
133
|
+
private reorder;
|
|
134
|
+
private removed;
|
|
135
|
+
remove(contextId: string, id: string, page?: InboxPageOptions): Promise<InboxSnapshot>;
|
|
136
|
+
clearPending(contextId: string, page?: InboxPageOptions): Promise<InboxSnapshot>;
|
|
137
|
+
executePending(contextId: string, id?: string, page?: InboxPageOptions): Promise<InboxSnapshot>;
|
|
86
138
|
enqueue(taskId: string, message: Message, options?: {
|
|
87
139
|
target?: InboxTarget;
|
|
88
140
|
wakeup?: boolean;
|
|
@@ -90,6 +142,7 @@ export declare class NativeInbox implements A2aInbox {
|
|
|
90
142
|
private moveInput;
|
|
91
143
|
promote(contextId: string, messageId: string, taskId: string): Promise<void>;
|
|
92
144
|
private promoteInput;
|
|
145
|
+
resume(contextId: string, taskId: string): Promise<string>;
|
|
93
146
|
/** Repair only an incomplete ownership move, never an ordinary DSH claim. */
|
|
94
147
|
private recoverPromotions;
|
|
95
148
|
private recoverRetained;
|
|
@@ -122,6 +175,9 @@ export declare class NativeInbox implements A2aInbox {
|
|
|
122
175
|
private discard;
|
|
123
176
|
cancel(taskId: string): Promise<string | undefined>;
|
|
124
177
|
private cancelTask;
|
|
178
|
+
private requeueClaimed;
|
|
179
|
+
pause(contextId: string): Promise<void>;
|
|
180
|
+
wake(contextId: string): Promise<string[]>;
|
|
125
181
|
clear(contextId: string): Promise<void>;
|
|
126
182
|
onDisposed(session: Session): void;
|
|
127
183
|
shutdown(): Promise<void>;
|