@hanphone/dsh-a2a 0.1.0 → 0.3.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/README.md +133 -87
- package/README.zh.md +64 -87
- package/cordis.patch.yml +4 -7
- package/lib/client.js +228 -36
- package/lib/index.js +1271 -625
- package/lib/types/api.d.ts +43 -13
- package/lib/types/api.d.ts.map +1 -1
- package/lib/types/api.js +60 -20
- package/lib/types/api.js.map +1 -1
- package/lib/types/client/index.d.ts +50 -14
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/commands.d.ts +4 -2
- package/lib/types/commands.d.ts.map +1 -1
- package/lib/types/commands.js +59 -46
- package/lib/types/commands.js.map +1 -1
- package/lib/types/events.d.ts +32 -9
- package/lib/types/events.d.ts.map +1 -1
- package/lib/types/index.d.ts +21 -32
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +178 -213
- package/lib/types/index.js.map +1 -1
- package/lib/types/outbound/calls.d.ts.map +1 -1
- package/lib/types/outbound/calls.js +8 -6
- package/lib/types/outbound/calls.js.map +1 -1
- package/lib/types/protocol.d.ts +174 -109
- package/lib/types/protocol.d.ts.map +1 -1
- package/lib/types/protocol.js +58 -34
- package/lib/types/protocol.js.map +1 -1
- package/lib/types/server/a2a-server.d.ts +7 -1
- package/lib/types/server/a2a-server.d.ts.map +1 -1
- package/lib/types/server/a2a-server.js +11 -3
- package/lib/types/server/a2a-server.js.map +1 -1
- package/lib/types/server/card.d.ts +6 -30
- package/lib/types/server/card.d.ts.map +1 -1
- package/lib/types/server/card.js +13 -49
- package/lib/types/server/card.js.map +1 -1
- package/lib/types/server/exec/agent-runtime.d.ts +3 -0
- package/lib/types/server/exec/agent-runtime.d.ts.map +1 -1
- package/lib/types/server/exec/agent-runtime.js +2 -1
- package/lib/types/server/exec/agent-runtime.js.map +1 -1
- package/lib/types/server/identity.d.ts +38 -0
- package/lib/types/server/identity.d.ts.map +1 -0
- package/lib/types/server/identity.js +89 -0
- package/lib/types/server/identity.js.map +1 -0
- package/lib/types/server/inbound-registry.d.ts +60 -0
- package/lib/types/server/inbound-registry.d.ts.map +1 -0
- package/lib/types/server/inbound-registry.js +86 -0
- package/lib/types/server/inbound-registry.js.map +1 -0
- package/lib/types/server/routes.d.ts +5 -1
- package/lib/types/server/routes.d.ts.map +1 -1
- package/lib/types/server/routes.js +9 -5
- package/lib/types/server/routes.js.map +1 -1
- package/lib/types/server/store.d.ts +37 -1
- package/lib/types/server/store.d.ts.map +1 -1
- package/lib/types/server/store.js +14 -0
- package/lib/types/server/store.js.map +1 -1
- package/lib/types/servers/inbound-manager.d.ts +131 -0
- package/lib/types/servers/inbound-manager.d.ts.map +1 -0
- package/lib/types/servers/inbound-manager.js +312 -0
- package/lib/types/servers/inbound-manager.js.map +1 -0
- package/lib/types/servers/outbound-manager.d.ts +129 -0
- package/lib/types/servers/outbound-manager.d.ts.map +1 -0
- package/lib/types/servers/outbound-manager.js +238 -0
- package/lib/types/servers/outbound-manager.js.map +1 -0
- package/lib/types/service.d.ts +108 -23
- package/lib/types/service.d.ts.map +1 -1
- package/lib/types/service.js +39 -18
- package/lib/types/service.js.map +1 -1
- package/package.json +3 -3
- package/src/api.ts +70 -31
- package/src/client/index.ts +339 -81
- package/src/commands.ts +59 -43
- package/src/events.ts +14 -7
- package/src/index.ts +204 -249
- package/src/outbound/calls.ts +8 -6
- package/src/protocol.ts +204 -95
- package/src/server/a2a-server.ts +16 -4
- package/src/server/card.ts +13 -62
- package/src/server/exec/agent-runtime.ts +5 -2
- package/src/server/identity.ts +109 -0
- package/src/server/inbound-registry.ts +129 -0
- package/src/server/routes.ts +6 -4
- package/src/server/store.ts +50 -4
- package/src/servers/inbound-manager.ts +385 -0
- package/src/servers/outbound-manager.ts +289 -0
- package/src/service.ts +136 -28
- package/lib/tsconfig.client.tsbuildinfo +0 -1
- package/lib/tsconfig.tsbuildinfo +0 -1
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service identity: the inbound AgentCard's name/description/version,
|
|
3
|
+
* editable at runtime from the GUI dashboard and persisted in the `a2a`
|
|
4
|
+
* domain's `identity` table. Skills stay derived from the live tool registry
|
|
5
|
+
* (the "card derives from ctx.tools" design); identity editing builds a fresh
|
|
6
|
+
* card preserving the endpoint URL and swaps it onto the server, so routes
|
|
7
|
+
* and the facade see the new value immediately.
|
|
8
|
+
* @module dsh-a2a/server/identity
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { buildCard, type CardOptions } from './card.ts'
|
|
12
|
+
import type { AgentCard, AgentSkill } from '../protocol.ts'
|
|
13
|
+
import type { A2aDomain } from './store.ts'
|
|
14
|
+
|
|
15
|
+
/** Persisted service identity (only the card identity fields; skills derive). */
|
|
16
|
+
export interface A2aIdentity {
|
|
17
|
+
readonly name: string
|
|
18
|
+
readonly description: string
|
|
19
|
+
readonly version: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const IDENTITY_KEY = 'service'
|
|
23
|
+
|
|
24
|
+
function decode(raw: string | undefined): A2aIdentity | undefined {
|
|
25
|
+
if (raw === undefined) return undefined
|
|
26
|
+
try {
|
|
27
|
+
const parsed = JSON.parse(raw) as Partial<A2aIdentity>
|
|
28
|
+
if (typeof parsed.name !== 'string' || typeof parsed.description !== 'string' || typeof parsed.version !== 'string') {
|
|
29
|
+
return undefined
|
|
30
|
+
}
|
|
31
|
+
return { name: parsed.name, description: parsed.description, version: parsed.version }
|
|
32
|
+
} catch {
|
|
33
|
+
return undefined
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function encode(value: A2aIdentity): string {
|
|
38
|
+
return JSON.stringify(value)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Read the persisted identity (undefined when never configured). */
|
|
42
|
+
export function readIdentity(domain: A2aDomain): A2aIdentity | undefined {
|
|
43
|
+
return decode(domain.identity.get(IDENTITY_KEY))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Persist a new identity (fire-and-forget like the task store writes). */
|
|
47
|
+
export function writeIdentity(domain: A2aDomain, identity: A2aIdentity): void {
|
|
48
|
+
void domain.identity.put(IDENTITY_KEY, encode(identity)).catch((err: unknown) => {
|
|
49
|
+
throw new Error(`a2a: identity persistence failed: ${String(err)}`)
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Build the AgentCard options for a given base URL/path and the override
|
|
55
|
+
* identity. `identity` (persisted) wins over the composition `defaults`; when
|
|
56
|
+
* no identity is stored, the composition defaults stand.
|
|
57
|
+
*/
|
|
58
|
+
export function cardOptionsFor(
|
|
59
|
+
baseUrl: string,
|
|
60
|
+
endpointPath: string,
|
|
61
|
+
defaults: { readonly name: string; readonly description: string; readonly version: string },
|
|
62
|
+
identity: A2aIdentity | undefined,
|
|
63
|
+
skills: readonly AgentSkill[],
|
|
64
|
+
authToken?: string,
|
|
65
|
+
): CardOptions {
|
|
66
|
+
return {
|
|
67
|
+
baseUrl,
|
|
68
|
+
endpointPath,
|
|
69
|
+
name: identity?.name ?? defaults.name,
|
|
70
|
+
description: identity?.description ?? defaults.description,
|
|
71
|
+
version: identity?.version ?? defaults.version,
|
|
72
|
+
skills,
|
|
73
|
+
...(authToken !== undefined ? { authToken } : {}),
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Build a fresh AgentCard from an existing one plus a new identity and skill
|
|
79
|
+
* list, preserving the endpoint URL (baseUrl/path) and security scheme.
|
|
80
|
+
*/
|
|
81
|
+
export function rebuildCardWithIdentity(card: AgentCard, identity: A2aIdentity, skills: readonly AgentSkill[]): AgentCard {
|
|
82
|
+
return buildCard({
|
|
83
|
+
baseUrl: endpointBaseOf(card),
|
|
84
|
+
endpointPath: endpointPathOf(card),
|
|
85
|
+
name: identity.name,
|
|
86
|
+
description: identity.description,
|
|
87
|
+
version: identity.version,
|
|
88
|
+
skills,
|
|
89
|
+
// Preserve the advertised scheme; buildCard only uses the presence (never
|
|
90
|
+
// the value) to declare securitySchemes.
|
|
91
|
+
...(card.securitySchemes !== undefined ? { authToken: 'preserved-scheme' } : {}),
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function endpointBaseOf(card: AgentCard): string {
|
|
96
|
+
const url = card.supportedInterfaces?.[0]?.url
|
|
97
|
+
if (!url) return 'http://127.0.0.1'
|
|
98
|
+
return url.replace(/\/[^/]*$/, '')
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function endpointPathOf(card: AgentCard): string {
|
|
102
|
+
const url = card.supportedInterfaces?.[0]?.url
|
|
103
|
+
if (!url) return '/a2a'
|
|
104
|
+
try {
|
|
105
|
+
return new URL(url).pathname
|
|
106
|
+
} catch {
|
|
107
|
+
return '/a2a'
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inbound connection registry: which remote peers are talking to this DSH's
|
|
3
|
+
* A2A server. Fed by the A2A server's `onInbound` hook (every JSON-RPC
|
|
4
|
+
* request / SSE open), surfaced through the dashboard API, and disconnectable
|
|
5
|
+
* (`closePeer` cancels the peer's active tasks and drops the record).
|
|
6
|
+
* @module dsh-a2a/server/inbound-registry
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** One live inbound connection (observed peer). */
|
|
10
|
+
export interface InboundPeerRecord {
|
|
11
|
+
/** Stable per-process id (random). */
|
|
12
|
+
readonly id: string
|
|
13
|
+
/** Short display label derived from the request source. */
|
|
14
|
+
readonly label: string
|
|
15
|
+
/** Source socket address, when visible ("127.0.0.1:54321"). */
|
|
16
|
+
readonly source: string | null
|
|
17
|
+
/** First-seen ISO timestamp. */
|
|
18
|
+
readonly firstSeen: string
|
|
19
|
+
/** Last-activity ISO timestamp. */
|
|
20
|
+
readonly lastSeen: string
|
|
21
|
+
/** Number of tasks this peer created or continued. */
|
|
22
|
+
readonly taskCount: number
|
|
23
|
+
/** Ids of tasks still running for this peer. */
|
|
24
|
+
readonly activeTaskIds: readonly string[]
|
|
25
|
+
/** True while at least one streaming (SSE) connection is open. */
|
|
26
|
+
readonly streaming: boolean
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Control and observation contract the dashboard API and facade use. */
|
|
30
|
+
export interface InboundRegistry {
|
|
31
|
+
list(): readonly InboundPeerRecord[]
|
|
32
|
+
/** Cancel a peer's active tasks and remove its record. */
|
|
33
|
+
closePeer(peerId: string): { readonly ok: boolean; readonly message: string }
|
|
34
|
+
/** Active task ids of one peer (the facade cancels them on close). */
|
|
35
|
+
activeTasksOf(peerId: string): readonly string[]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Mutable tracking state behind one peer record. */
|
|
39
|
+
interface PeerState {
|
|
40
|
+
readonly id: string
|
|
41
|
+
readonly label: string
|
|
42
|
+
readonly source: string | null
|
|
43
|
+
readonly firstSeen: string
|
|
44
|
+
lastSeen: string
|
|
45
|
+
taskCount: number
|
|
46
|
+
readonly active: Set<string>
|
|
47
|
+
streamingCount: number
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function newPeerId(): string {
|
|
51
|
+
return `peer-${crypto.randomUUID()}`
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** In-memory inbound peer registry. */
|
|
55
|
+
export class LiveInboundRegistry implements InboundRegistry {
|
|
56
|
+
private readonly peers = new Map<string, PeerState>()
|
|
57
|
+
|
|
58
|
+
private findBySource(source: string | null): PeerState | undefined {
|
|
59
|
+
if (source === null) return undefined
|
|
60
|
+
for (const peer of this.peers.values()) {
|
|
61
|
+
if (peer.source === source) return peer
|
|
62
|
+
}
|
|
63
|
+
return undefined
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Observe one inbound event: method, source, task ids, streaming flag. */
|
|
67
|
+
note(input: { readonly method: string; readonly source?: string; readonly taskIds: readonly string[]; readonly streaming: boolean }): void {
|
|
68
|
+
const now = new Date().toISOString()
|
|
69
|
+
const source = input.source ?? null
|
|
70
|
+
let peer = this.findBySource(source)
|
|
71
|
+
if (peer === undefined) {
|
|
72
|
+
peer = {
|
|
73
|
+
id: newPeerId(),
|
|
74
|
+
label: source ?? 'unknown',
|
|
75
|
+
source,
|
|
76
|
+
firstSeen: now,
|
|
77
|
+
lastSeen: now,
|
|
78
|
+
taskCount: 0,
|
|
79
|
+
active: new Set<string>(),
|
|
80
|
+
streamingCount: 0,
|
|
81
|
+
}
|
|
82
|
+
this.peers.set(peer.id, peer)
|
|
83
|
+
}
|
|
84
|
+
peer.lastSeen = now
|
|
85
|
+
peer.taskCount += input.taskIds.length
|
|
86
|
+
for (const id of input.taskIds) peer.active.add(id)
|
|
87
|
+
if (input.streaming) peer.streamingCount += 1
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** A task settled: drop it from every peer's active set. */
|
|
91
|
+
settle(taskId: string): void {
|
|
92
|
+
for (const peer of this.peers.values()) {
|
|
93
|
+
if (peer.active.delete(taskId)) peer.lastSeen = new Date().toISOString()
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Decrement streaming count when an SSE connection closes. */
|
|
98
|
+
endStream(source?: string): void {
|
|
99
|
+
const peer = this.findBySource(source ?? null)
|
|
100
|
+
if (peer !== undefined && peer.streamingCount > 0) {
|
|
101
|
+
peer.streamingCount -= 1
|
|
102
|
+
peer.lastSeen = new Date().toISOString()
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
list(): readonly InboundPeerRecord[] {
|
|
107
|
+
return [...this.peers.values()].map((p) => ({
|
|
108
|
+
id: p.id,
|
|
109
|
+
label: p.label,
|
|
110
|
+
source: p.source,
|
|
111
|
+
firstSeen: p.firstSeen,
|
|
112
|
+
lastSeen: p.lastSeen,
|
|
113
|
+
taskCount: p.taskCount,
|
|
114
|
+
activeTaskIds: [...p.active],
|
|
115
|
+
streaming: p.streamingCount > 0,
|
|
116
|
+
})).sort((a, b) => (a.lastSeen < b.lastSeen ? 1 : -1))
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
closePeer(peerId: string): { readonly ok: boolean; readonly message: string } {
|
|
120
|
+
const peer = this.peers.get(peerId)
|
|
121
|
+
if (peer === undefined) return { ok: false, message: `inbound peer ${peerId} not found` }
|
|
122
|
+
this.peers.delete(peerId)
|
|
123
|
+
return { ok: true, message: `inbound peer ${peer.label} closed` }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
activeTasksOf(peerId: string): readonly string[] {
|
|
127
|
+
return [...(this.peers.get(peerId)?.active ?? [])]
|
|
128
|
+
}
|
|
129
|
+
}
|
package/src/server/routes.ts
CHANGED
|
@@ -17,7 +17,7 @@ export interface WebServerLike {
|
|
|
17
17
|
}): () => void
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
const
|
|
20
|
+
const DEFAULT_CARD_PATH = '/.well-known/agent-card.json'
|
|
21
21
|
|
|
22
22
|
/** Runtime-registered HTTP routes for one A2A server. */
|
|
23
23
|
export class A2aRoutes {
|
|
@@ -27,6 +27,8 @@ export class A2aRoutes {
|
|
|
27
27
|
constructor(
|
|
28
28
|
private readonly webServer: WebServerLike,
|
|
29
29
|
private readonly server: A2AServer,
|
|
30
|
+
/** AgentCard route path; default = the well-known A2A path. */
|
|
31
|
+
private readonly cardPath: string = DEFAULT_CARD_PATH,
|
|
30
32
|
) {}
|
|
31
33
|
|
|
32
34
|
get active(): boolean {
|
|
@@ -39,7 +41,7 @@ export class A2aRoutes {
|
|
|
39
41
|
this.disposers.push(
|
|
40
42
|
this.webServer.register({
|
|
41
43
|
kind: 'exact',
|
|
42
|
-
path:
|
|
44
|
+
path: this.cardPath,
|
|
43
45
|
handler: (_req, res) => {
|
|
44
46
|
res.writeHead(200, { 'content-type': 'application/json' })
|
|
45
47
|
res.end(JSON.stringify(this.server.card))
|
|
@@ -100,8 +102,8 @@ export class A2aRoutes {
|
|
|
100
102
|
return
|
|
101
103
|
}
|
|
102
104
|
|
|
103
|
-
// GET AgentCard at
|
|
104
|
-
const out = await this.server.handle(toServerReq(req), path ===
|
|
105
|
+
// GET AgentCard at this instance's card path; anything else is the server's call.
|
|
106
|
+
const out = await this.server.handle(toServerReq(req), path === this.cardPath ? '' : '')
|
|
105
107
|
res.writeHead(out.status, { 'content-type': out.contentType, ...(out.headers ?? {}) })
|
|
106
108
|
res.end(out.body)
|
|
107
109
|
}
|
package/src/server/store.ts
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
type DomainFacility,
|
|
16
16
|
type KvTable,
|
|
17
17
|
} from '@deepseek-ai/dsh-storage-domain'
|
|
18
|
-
import { TaskState, type Part } from '../protocol.ts'
|
|
18
|
+
import { TaskState, type AgentSkill, type Part } from '../protocol.ts'
|
|
19
19
|
|
|
20
20
|
/** One durable inbound/outbound task record. */
|
|
21
21
|
export interface TaskRecord {
|
|
@@ -32,6 +32,8 @@ export interface TaskRecord {
|
|
|
32
32
|
readonly executor: 'session' | 'subagent'
|
|
33
33
|
readonly summary: string | null
|
|
34
34
|
readonly error?: { readonly code: string; readonly message: string }
|
|
35
|
+
/** The inbound server instance this task arrived through. */
|
|
36
|
+
readonly serverId?: string
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
/** One durable context→session binding (inbound conversation ↔ DSH session). */
|
|
@@ -50,6 +52,33 @@ export interface OutboundAgentRecord {
|
|
|
50
52
|
readonly lastCardAt: string | null
|
|
51
53
|
}
|
|
52
54
|
|
|
55
|
+
/** One durable inbound server instance. */
|
|
56
|
+
export interface InboundServerRecord {
|
|
57
|
+
readonly id: string
|
|
58
|
+
readonly name: string
|
|
59
|
+
readonly description: string
|
|
60
|
+
readonly version: string
|
|
61
|
+
readonly endpointPath: string
|
|
62
|
+
/** Agent preset id composing inbound sessions; absent = deployment default. */
|
|
63
|
+
readonly preset?: string
|
|
64
|
+
readonly authTokenEnv?: string
|
|
65
|
+
/** Advertised skills (creator-entered declarations, default = preset name). */
|
|
66
|
+
readonly skills: readonly AgentSkill[]
|
|
67
|
+
readonly enabled: boolean
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** One durable outbound server connection instance. */
|
|
71
|
+
export interface OutboundServerRecord {
|
|
72
|
+
readonly id: string
|
|
73
|
+
readonly name: string
|
|
74
|
+
readonly agentCardUrl: string
|
|
75
|
+
readonly bearerTokenEnv?: string
|
|
76
|
+
/** Preset composing this DSH's local session when talking to the remote. */
|
|
77
|
+
readonly preset?: string
|
|
78
|
+
readonly enabled: boolean
|
|
79
|
+
readonly timeoutMs: number
|
|
80
|
+
}
|
|
81
|
+
|
|
53
82
|
/** JSON-encoded record schema shared by every `a2a` table. */
|
|
54
83
|
const json = z.string()
|
|
55
84
|
|
|
@@ -61,6 +90,9 @@ export const a2aDomainSpec = defineDomain({
|
|
|
61
90
|
tasks: domainTable<string, string>(json),
|
|
62
91
|
contexts: domainTable<string, string>(json),
|
|
63
92
|
agents: domainTable<string, string>(json),
|
|
93
|
+
identity: domainTable<string, string>(json),
|
|
94
|
+
inbound_servers: domainTable<string, string>(json),
|
|
95
|
+
outbound_servers: domainTable<string, string>(json),
|
|
64
96
|
},
|
|
65
97
|
})
|
|
66
98
|
|
|
@@ -107,6 +139,18 @@ export class A2aDomain {
|
|
|
107
139
|
return this.handle.table('agents')
|
|
108
140
|
}
|
|
109
141
|
|
|
142
|
+
get identity(): KvTable<string, string> {
|
|
143
|
+
return this.handle.table('identity')
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
get inbound_servers(): KvTable<string, string> {
|
|
147
|
+
return this.handle.table('inbound_servers')
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
get outbound_servers(): KvTable<string, string> {
|
|
151
|
+
return this.handle.table('outbound_servers')
|
|
152
|
+
}
|
|
153
|
+
|
|
110
154
|
async close(): Promise<void> {
|
|
111
155
|
await this.handle.close()
|
|
112
156
|
}
|
|
@@ -114,7 +158,7 @@ export class A2aDomain {
|
|
|
114
158
|
|
|
115
159
|
/** Task-store contract shared by the domain-backed and memory stores. */
|
|
116
160
|
export interface TaskStore {
|
|
117
|
-
create(input: { contextId: string; skill: string; parts: readonly Part[]; remotePeerId: string | null }): TaskRecord
|
|
161
|
+
create(input: { contextId: string; skill: string; parts: readonly Part[]; remotePeerId: string | null; serverId?: string }): TaskRecord
|
|
118
162
|
get(taskId: string): TaskRecord | undefined
|
|
119
163
|
list(): TaskRecord[]
|
|
120
164
|
setState(taskId: string, state: TaskState, message?: { code: string; text: string }): TaskRecord
|
|
@@ -141,7 +185,7 @@ export class MemoryTaskStore implements TaskStore {
|
|
|
141
185
|
private tasks = new Map<string, TaskRecord>()
|
|
142
186
|
private contexts = new Map<string, string>()
|
|
143
187
|
|
|
144
|
-
create(input: { contextId: string; skill: string; parts: readonly Part[]; remotePeerId: string | null }): TaskRecord {
|
|
188
|
+
create(input: { contextId: string; skill: string; parts: readonly Part[]; remotePeerId: string | null; serverId?: string }): TaskRecord {
|
|
145
189
|
const record: TaskRecord = {
|
|
146
190
|
taskId: `a2a-${crypto.randomUUID()}`,
|
|
147
191
|
contextId: input.contextId,
|
|
@@ -155,6 +199,7 @@ export class MemoryTaskStore implements TaskStore {
|
|
|
155
199
|
artifacts: [],
|
|
156
200
|
executor: 'session',
|
|
157
201
|
summary: null,
|
|
202
|
+
...(input.serverId !== undefined ? { serverId: input.serverId } : {}),
|
|
158
203
|
}
|
|
159
204
|
this.tasks.set(record.taskId, record)
|
|
160
205
|
return record
|
|
@@ -239,7 +284,7 @@ export class DomainTaskStore implements TaskStore {
|
|
|
239
284
|
}
|
|
240
285
|
}
|
|
241
286
|
|
|
242
|
-
create(input: { contextId: string; skill: string; parts: readonly Part[]; remotePeerId: string | null }): TaskRecord {
|
|
287
|
+
create(input: { contextId: string; skill: string; parts: readonly Part[]; remotePeerId: string | null; serverId?: string }): TaskRecord {
|
|
243
288
|
const record: TaskRecord = {
|
|
244
289
|
taskId: `a2a-${crypto.randomUUID()}`,
|
|
245
290
|
contextId: input.contextId,
|
|
@@ -253,6 +298,7 @@ export class DomainTaskStore implements TaskStore {
|
|
|
253
298
|
artifacts: [],
|
|
254
299
|
executor: 'session',
|
|
255
300
|
summary: null,
|
|
301
|
+
...(input.serverId !== undefined ? { serverId: input.serverId } : {}),
|
|
256
302
|
}
|
|
257
303
|
this.records.set(record.taskId, record)
|
|
258
304
|
// Persist eagerly; a failed write must fail the task, not silently lose it.
|