@dataverse-kit/agent-kit 0.2.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.
@@ -0,0 +1,270 @@
1
+ /**
2
+ * The conversational seam.
3
+ *
4
+ * One interface, several providers, chosen by configuration — so WHICH model answers is a
5
+ * config decision, not a rebuild. Generalises the cleanest streaming contract in the estate
6
+ * (`@dataverse-kit/claude-bridge`'s `ClaudeTransport`) while keeping the four constraints
7
+ * that `ILlmService` (LabelToBottleScanningAge's scan-verification console) established by
8
+ * shipping into a regulated process:
9
+ *
10
+ * ★★ 1. THERE IS NO WRITE METHOD, AND THAT IS THE DESIGN.
11
+ * `send()` yields events and nothing else. There is no create, no update, no action,
12
+ * no tool invocation. A provider that wanted to write would have to change THIS file,
13
+ * which makes the guarantee reviewable in one place instead of trusted across a prompt.
14
+ * ★ Honest caveat: modelling `tool_call` events means the kit now renders agents that
15
+ * HAVE tools. It still cannot invoke one — the host runs the tool and feeds the
16
+ * outcome back as a `tool_result` — but the guarantee is now about the kit, not
17
+ * about the agent.
18
+ *
19
+ * ★★ 2. NO PROVIDER MAY TAKE A SECRET.
20
+ * A custom page, a static web app and a PCF control all run in the operator's browser,
21
+ * so anything handed to one is readable by the operator — an API key, a Direct Line
22
+ * secret, a client secret. Providers therefore take an ENDPOINT and let a server hold
23
+ * the credential. `AgentServiceConfig` has no field for a key, deliberately, and
24
+ * adding one would be the defect. A test asserts it.
25
+ *
26
+ * ★★ 3. NEITHER METHOD THROWS. Failures are DATA.
27
+ * A transport failure is a `turn_end` carrying a failure, so the UI shows a message
28
+ * instead of an unmounted tree.
29
+ *
30
+ * ★★ 4. THE HOST SUPPLIES THE GROUNDING; THE AGENT EXPLAINS IT.
31
+ * `AgentTurn.grounding` carries facts the host computed deterministically. The model
32
+ * must never be the thing that DERIVES them — a hallucinated identifier is worse than
33
+ * no answer. The same object feeds the grounding UI, so "what the model was told" and
34
+ * "what the screen showed" cannot drift.
35
+ */
36
+ /** Who produced a message. `tool` is a tool RESULT, rendered distinctly from an assistant turn. */
37
+ type AgentRole = 'user' | 'assistant' | 'system' | 'tool';
38
+ interface AgentParticipant {
39
+ id: string;
40
+ displayName: string;
41
+ kind: 'user' | 'agent' | 'system';
42
+ initials?: string;
43
+ imageUrl?: string;
44
+ }
45
+ interface AgentAttachment {
46
+ id: string;
47
+ name: string;
48
+ mediaType: string;
49
+ sizeBytes?: number;
50
+ url?: string;
51
+ /** Inline content, mirroring ClaudeTransport's image shape. */
52
+ base64?: string;
53
+ }
54
+ interface AgentSource {
55
+ id: string;
56
+ title: string;
57
+ kind: 'dataverse' | 'document' | 'web' | 'knowledge' | 'other';
58
+ url?: string;
59
+ snippet?: string;
60
+ /** So a host can open the record. The kit never calls Xrm itself — it raises a callback. */
61
+ entityLogicalName?: string;
62
+ recordId?: string;
63
+ /** 0..1, and ONLY when the retriever actually reported one. Absent means "not reported",
64
+ * which the UI must show as such rather than as zero confidence. */
65
+ score?: number;
66
+ }
67
+ interface AgentCitation {
68
+ /** The marker as it appears in the text, e.g. `1` for `[1]`. */
69
+ marker: string;
70
+ sourceId: string;
71
+ start?: number;
72
+ end?: number;
73
+ }
74
+ type ToolCallStatus = 'proposed' | 'running' | 'succeeded' | 'failed' | 'denied';
75
+ interface AgentToolCall {
76
+ id: string;
77
+ name: string;
78
+ /** RAW, exactly as the model emitted it. Never lossily pre-parsed: a half-streamed
79
+ * arguments blob is not valid JSON, and the approval UI must show what was actually
80
+ * proposed, not a reconstruction of it. */
81
+ argumentsJson: string;
82
+ status: ToolCallStatus;
83
+ startedAt?: number;
84
+ endedAt?: number;
85
+ resultText?: string;
86
+ failure?: AgentFailure;
87
+ /** The kit RENDERS the request and raises onApprove/onDeny. The HOST runs the tool. */
88
+ requiresApproval?: boolean;
89
+ }
90
+ /** Terminal outcomes. Six, not `{ok|error}`.
91
+ *
92
+ * ★ `notProvisioned` is separated from `error` because with on-demand Azure an absent
93
+ * service is a NORMAL state; reporting it as a failure is what makes an app look broken.
94
+ * (`503 -> notProvisioned` — see runStatus.ts.)
95
+ * ★ `cancelled` is separated because the kit ships a stop button, and a user-initiated
96
+ * stop rendered as an error is a bug.
97
+ * ★ `blocked` (a content filter fired) and `refused` (the model declined) must look
98
+ * different on screen: they need different fixes. */
99
+ type AgentRunStatus = 'succeeded' | 'blocked' | 'refused' | 'error' | 'notProvisioned' | 'cancelled';
100
+ /** The single failure shape. There is deliberately no separate `AgentError`: `recoverable`
101
+ * (which drives retry affordances, carried from ClaudeTransport) lives here, and two
102
+ * overlapping error types would be two places to keep a status ladder honest. */
103
+ interface AgentFailure {
104
+ status: Exclude<AgentRunStatus, 'succeeded'>;
105
+ /** Safe to display; providers sanitise. */
106
+ message: string;
107
+ /** e.g. 'content_filter' | 'not_provisioned' | 'rate_limited'. */
108
+ code?: string;
109
+ /** Drives retry affordances. Carried from ClaudeTransport's `recoverable`. */
110
+ recoverable?: boolean;
111
+ retryAfterMs?: number;
112
+ correlationId?: string;
113
+ }
114
+ interface AgentUsage {
115
+ inputTokens?: number;
116
+ outputTokens?: number;
117
+ cachedTokens?: number;
118
+ costUsd?: number;
119
+ durationMs?: number;
120
+ model?: string;
121
+ }
122
+ interface AgentMessage {
123
+ id: string;
124
+ role: AgentRole;
125
+ authorId: string;
126
+ text: string;
127
+ createdAt: number;
128
+ streaming?: boolean;
129
+ /** Kept SEPARATE from `text` so it can be collapsed, withheld, or never announced. */
130
+ thinking?: string;
131
+ attachments?: AgentAttachment[];
132
+ citations?: AgentCitation[];
133
+ sources?: AgentSource[];
134
+ toolCalls?: AgentToolCall[];
135
+ failure?: AgentFailure;
136
+ /** The provider WITHHELD a generated answer and substituted safe text. Reported so the UI
137
+ * can say so rather than passing the substitute off as the model's words. */
138
+ guarded?: boolean;
139
+ /** Attribute the answer; never imply authority. */
140
+ provider?: string;
141
+ usage?: AgentUsage;
142
+ /** Host-defined. The kit never reads it. */
143
+ meta?: Record<string, unknown>;
144
+ }
145
+ interface AgentTurnResult {
146
+ status: AgentRunStatus;
147
+ /** The FULL assembled answer. This single property is what lets a non-streaming provider
148
+ * implement the seam with exactly two events (`turn_start`, `turn_end`) and still produce
149
+ * a complete message through the same reducer — no faked deltas. */
150
+ text: string;
151
+ guarded?: boolean;
152
+ provider: string;
153
+ model?: string;
154
+ usage?: AgentUsage;
155
+ failure?: AgentFailure;
156
+ correlationId?: string;
157
+ }
158
+ /**
159
+ * The stream protocol. Ordered, and terminated by EXACTLY ONE `turn_end`.
160
+ *
161
+ * ★ One terminal event, not Claude's `done | error` pair. Folding them into `turn_end` +
162
+ * `status` is what makes the reducer's switch total (the repo sets
163
+ * `noFallthroughCasesInSwitch`, so a new kind without a case is a free compile error) and
164
+ * what makes blocked / refused / notProvisioned / cancelled first-class outcomes rather
165
+ * than error-shaped ones.
166
+ * ★ Every event carries `messageId`: two agents can share one stream (a chat turn and a
167
+ * background ops run), and the ops surfaces need to tell them apart.
168
+ */
169
+ type AgentEvent = {
170
+ type: 'turn_start';
171
+ turnId: string;
172
+ messageId: string;
173
+ model?: string;
174
+ provider?: string;
175
+ sessionId?: string;
176
+ correlationId?: string;
177
+ } | {
178
+ type: 'text_delta';
179
+ messageId: string;
180
+ text: string;
181
+ } | {
182
+ type: 'thinking_delta';
183
+ messageId: string;
184
+ text: string;
185
+ } | {
186
+ type: 'tool_call';
187
+ messageId: string;
188
+ call: AgentToolCall;
189
+ } | {
190
+ type: 'tool_result';
191
+ messageId: string;
192
+ callId: string;
193
+ status: ToolCallStatus;
194
+ resultText?: string;
195
+ failure?: AgentFailure;
196
+ } | {
197
+ type: 'sources';
198
+ messageId: string;
199
+ sources: AgentSource[];
200
+ } | {
201
+ type: 'citation';
202
+ messageId: string;
203
+ citation: AgentCitation;
204
+ } | {
205
+ type: 'suggestions';
206
+ messageId: string;
207
+ prompts: string[];
208
+ }
209
+ /** Ephemeral chrome ("Searching Dataverse…"), never message content. */
210
+ | {
211
+ type: 'status';
212
+ messageId: string;
213
+ label: string;
214
+ } | {
215
+ type: 'turn_end';
216
+ turnId: string;
217
+ messageId: string;
218
+ result: AgentTurnResult;
219
+ };
220
+ interface AgentTurn {
221
+ text: string;
222
+ history: readonly AgentMessage[];
223
+ /** See constraint 4 at the top of this file. */
224
+ grounding?: Record<string, unknown>;
225
+ attachments?: AgentAttachment[];
226
+ conversationId?: string;
227
+ /** Implementations MUST honour this and terminate with `status: 'cancelled'`. */
228
+ cancelSignal?: AbortSignal;
229
+ }
230
+ /** What a provider can do. Drives which affordances render AT ALL — a stop button over a
231
+ * provider that cannot cancel is a lie. */
232
+ interface AgentCapabilities {
233
+ streaming: boolean;
234
+ tools: boolean;
235
+ citations: boolean;
236
+ attachments: boolean;
237
+ cancel: boolean;
238
+ }
239
+ interface AgentAvailability {
240
+ available: boolean;
241
+ /** Human-readable and shown in diagnostics. "Not licensed" and "not on this host" need
242
+ * different fixes, so they must not collapse to one boolean. */
243
+ reason?: string;
244
+ status?: AgentRunStatus;
245
+ capabilities?: AgentCapabilities;
246
+ }
247
+ interface IAgentService {
248
+ /** Stable id, matching the configured provider name. */
249
+ readonly name: string;
250
+ /** Can this provider actually answer here? PROBED, never assumed. Never throws. */
251
+ isAvailable(): Promise<AgentAvailability>;
252
+ /** Ask. Yields in order, emits exactly one `turn_end`. Never throws. */
253
+ send(turn: AgentTurn): AsyncIterable<AgentEvent>;
254
+ }
255
+ /** ★ `none` is the DEFAULT and must stay so: a kit dropped into an unwired host must not
256
+ * silently start talking to something. */
257
+ type AgentProviderName = 'none' | 'mock' | 'custom';
258
+ interface AgentServiceConfig {
259
+ provider: AgentProviderName;
260
+ /** Absolute https URL of a server that holds the credential and speaks to the real model.
261
+ * ★ There is no `apiKey` here on purpose — see constraint 2 at the top of this file. */
262
+ endpoint?: string;
263
+ /** Names WHICH agent answers. Not a credential, and safe in a control property. */
264
+ agentId?: string;
265
+ conversationId?: string;
266
+ /** Escape hatch for a host-supplied implementation (`provider: 'custom'`). */
267
+ factory?: () => IAgentService;
268
+ }
269
+
270
+ export type { AgentMessage as A, IAgentService as I, ToolCallStatus as T, AgentParticipant as a, AgentFailure as b, AgentRunStatus as c, AgentAvailability as d, AgentUsage as e, AgentSource as f, AgentCitation as g, AgentToolCall as h, AgentAttachment as i, AgentCapabilities as j, AgentEvent as k, AgentProviderName as l, AgentRole as m, AgentServiceConfig as n, AgentTurn as o, AgentTurnResult as p };
@@ -0,0 +1,270 @@
1
+ /**
2
+ * The conversational seam.
3
+ *
4
+ * One interface, several providers, chosen by configuration — so WHICH model answers is a
5
+ * config decision, not a rebuild. Generalises the cleanest streaming contract in the estate
6
+ * (`@dataverse-kit/claude-bridge`'s `ClaudeTransport`) while keeping the four constraints
7
+ * that `ILlmService` (LabelToBottleScanningAge's scan-verification console) established by
8
+ * shipping into a regulated process:
9
+ *
10
+ * ★★ 1. THERE IS NO WRITE METHOD, AND THAT IS THE DESIGN.
11
+ * `send()` yields events and nothing else. There is no create, no update, no action,
12
+ * no tool invocation. A provider that wanted to write would have to change THIS file,
13
+ * which makes the guarantee reviewable in one place instead of trusted across a prompt.
14
+ * ★ Honest caveat: modelling `tool_call` events means the kit now renders agents that
15
+ * HAVE tools. It still cannot invoke one — the host runs the tool and feeds the
16
+ * outcome back as a `tool_result` — but the guarantee is now about the kit, not
17
+ * about the agent.
18
+ *
19
+ * ★★ 2. NO PROVIDER MAY TAKE A SECRET.
20
+ * A custom page, a static web app and a PCF control all run in the operator's browser,
21
+ * so anything handed to one is readable by the operator — an API key, a Direct Line
22
+ * secret, a client secret. Providers therefore take an ENDPOINT and let a server hold
23
+ * the credential. `AgentServiceConfig` has no field for a key, deliberately, and
24
+ * adding one would be the defect. A test asserts it.
25
+ *
26
+ * ★★ 3. NEITHER METHOD THROWS. Failures are DATA.
27
+ * A transport failure is a `turn_end` carrying a failure, so the UI shows a message
28
+ * instead of an unmounted tree.
29
+ *
30
+ * ★★ 4. THE HOST SUPPLIES THE GROUNDING; THE AGENT EXPLAINS IT.
31
+ * `AgentTurn.grounding` carries facts the host computed deterministically. The model
32
+ * must never be the thing that DERIVES them — a hallucinated identifier is worse than
33
+ * no answer. The same object feeds the grounding UI, so "what the model was told" and
34
+ * "what the screen showed" cannot drift.
35
+ */
36
+ /** Who produced a message. `tool` is a tool RESULT, rendered distinctly from an assistant turn. */
37
+ type AgentRole = 'user' | 'assistant' | 'system' | 'tool';
38
+ interface AgentParticipant {
39
+ id: string;
40
+ displayName: string;
41
+ kind: 'user' | 'agent' | 'system';
42
+ initials?: string;
43
+ imageUrl?: string;
44
+ }
45
+ interface AgentAttachment {
46
+ id: string;
47
+ name: string;
48
+ mediaType: string;
49
+ sizeBytes?: number;
50
+ url?: string;
51
+ /** Inline content, mirroring ClaudeTransport's image shape. */
52
+ base64?: string;
53
+ }
54
+ interface AgentSource {
55
+ id: string;
56
+ title: string;
57
+ kind: 'dataverse' | 'document' | 'web' | 'knowledge' | 'other';
58
+ url?: string;
59
+ snippet?: string;
60
+ /** So a host can open the record. The kit never calls Xrm itself — it raises a callback. */
61
+ entityLogicalName?: string;
62
+ recordId?: string;
63
+ /** 0..1, and ONLY when the retriever actually reported one. Absent means "not reported",
64
+ * which the UI must show as such rather than as zero confidence. */
65
+ score?: number;
66
+ }
67
+ interface AgentCitation {
68
+ /** The marker as it appears in the text, e.g. `1` for `[1]`. */
69
+ marker: string;
70
+ sourceId: string;
71
+ start?: number;
72
+ end?: number;
73
+ }
74
+ type ToolCallStatus = 'proposed' | 'running' | 'succeeded' | 'failed' | 'denied';
75
+ interface AgentToolCall {
76
+ id: string;
77
+ name: string;
78
+ /** RAW, exactly as the model emitted it. Never lossily pre-parsed: a half-streamed
79
+ * arguments blob is not valid JSON, and the approval UI must show what was actually
80
+ * proposed, not a reconstruction of it. */
81
+ argumentsJson: string;
82
+ status: ToolCallStatus;
83
+ startedAt?: number;
84
+ endedAt?: number;
85
+ resultText?: string;
86
+ failure?: AgentFailure;
87
+ /** The kit RENDERS the request and raises onApprove/onDeny. The HOST runs the tool. */
88
+ requiresApproval?: boolean;
89
+ }
90
+ /** Terminal outcomes. Six, not `{ok|error}`.
91
+ *
92
+ * ★ `notProvisioned` is separated from `error` because with on-demand Azure an absent
93
+ * service is a NORMAL state; reporting it as a failure is what makes an app look broken.
94
+ * (`503 -> notProvisioned` — see runStatus.ts.)
95
+ * ★ `cancelled` is separated because the kit ships a stop button, and a user-initiated
96
+ * stop rendered as an error is a bug.
97
+ * ★ `blocked` (a content filter fired) and `refused` (the model declined) must look
98
+ * different on screen: they need different fixes. */
99
+ type AgentRunStatus = 'succeeded' | 'blocked' | 'refused' | 'error' | 'notProvisioned' | 'cancelled';
100
+ /** The single failure shape. There is deliberately no separate `AgentError`: `recoverable`
101
+ * (which drives retry affordances, carried from ClaudeTransport) lives here, and two
102
+ * overlapping error types would be two places to keep a status ladder honest. */
103
+ interface AgentFailure {
104
+ status: Exclude<AgentRunStatus, 'succeeded'>;
105
+ /** Safe to display; providers sanitise. */
106
+ message: string;
107
+ /** e.g. 'content_filter' | 'not_provisioned' | 'rate_limited'. */
108
+ code?: string;
109
+ /** Drives retry affordances. Carried from ClaudeTransport's `recoverable`. */
110
+ recoverable?: boolean;
111
+ retryAfterMs?: number;
112
+ correlationId?: string;
113
+ }
114
+ interface AgentUsage {
115
+ inputTokens?: number;
116
+ outputTokens?: number;
117
+ cachedTokens?: number;
118
+ costUsd?: number;
119
+ durationMs?: number;
120
+ model?: string;
121
+ }
122
+ interface AgentMessage {
123
+ id: string;
124
+ role: AgentRole;
125
+ authorId: string;
126
+ text: string;
127
+ createdAt: number;
128
+ streaming?: boolean;
129
+ /** Kept SEPARATE from `text` so it can be collapsed, withheld, or never announced. */
130
+ thinking?: string;
131
+ attachments?: AgentAttachment[];
132
+ citations?: AgentCitation[];
133
+ sources?: AgentSource[];
134
+ toolCalls?: AgentToolCall[];
135
+ failure?: AgentFailure;
136
+ /** The provider WITHHELD a generated answer and substituted safe text. Reported so the UI
137
+ * can say so rather than passing the substitute off as the model's words. */
138
+ guarded?: boolean;
139
+ /** Attribute the answer; never imply authority. */
140
+ provider?: string;
141
+ usage?: AgentUsage;
142
+ /** Host-defined. The kit never reads it. */
143
+ meta?: Record<string, unknown>;
144
+ }
145
+ interface AgentTurnResult {
146
+ status: AgentRunStatus;
147
+ /** The FULL assembled answer. This single property is what lets a non-streaming provider
148
+ * implement the seam with exactly two events (`turn_start`, `turn_end`) and still produce
149
+ * a complete message through the same reducer — no faked deltas. */
150
+ text: string;
151
+ guarded?: boolean;
152
+ provider: string;
153
+ model?: string;
154
+ usage?: AgentUsage;
155
+ failure?: AgentFailure;
156
+ correlationId?: string;
157
+ }
158
+ /**
159
+ * The stream protocol. Ordered, and terminated by EXACTLY ONE `turn_end`.
160
+ *
161
+ * ★ One terminal event, not Claude's `done | error` pair. Folding them into `turn_end` +
162
+ * `status` is what makes the reducer's switch total (the repo sets
163
+ * `noFallthroughCasesInSwitch`, so a new kind without a case is a free compile error) and
164
+ * what makes blocked / refused / notProvisioned / cancelled first-class outcomes rather
165
+ * than error-shaped ones.
166
+ * ★ Every event carries `messageId`: two agents can share one stream (a chat turn and a
167
+ * background ops run), and the ops surfaces need to tell them apart.
168
+ */
169
+ type AgentEvent = {
170
+ type: 'turn_start';
171
+ turnId: string;
172
+ messageId: string;
173
+ model?: string;
174
+ provider?: string;
175
+ sessionId?: string;
176
+ correlationId?: string;
177
+ } | {
178
+ type: 'text_delta';
179
+ messageId: string;
180
+ text: string;
181
+ } | {
182
+ type: 'thinking_delta';
183
+ messageId: string;
184
+ text: string;
185
+ } | {
186
+ type: 'tool_call';
187
+ messageId: string;
188
+ call: AgentToolCall;
189
+ } | {
190
+ type: 'tool_result';
191
+ messageId: string;
192
+ callId: string;
193
+ status: ToolCallStatus;
194
+ resultText?: string;
195
+ failure?: AgentFailure;
196
+ } | {
197
+ type: 'sources';
198
+ messageId: string;
199
+ sources: AgentSource[];
200
+ } | {
201
+ type: 'citation';
202
+ messageId: string;
203
+ citation: AgentCitation;
204
+ } | {
205
+ type: 'suggestions';
206
+ messageId: string;
207
+ prompts: string[];
208
+ }
209
+ /** Ephemeral chrome ("Searching Dataverse…"), never message content. */
210
+ | {
211
+ type: 'status';
212
+ messageId: string;
213
+ label: string;
214
+ } | {
215
+ type: 'turn_end';
216
+ turnId: string;
217
+ messageId: string;
218
+ result: AgentTurnResult;
219
+ };
220
+ interface AgentTurn {
221
+ text: string;
222
+ history: readonly AgentMessage[];
223
+ /** See constraint 4 at the top of this file. */
224
+ grounding?: Record<string, unknown>;
225
+ attachments?: AgentAttachment[];
226
+ conversationId?: string;
227
+ /** Implementations MUST honour this and terminate with `status: 'cancelled'`. */
228
+ cancelSignal?: AbortSignal;
229
+ }
230
+ /** What a provider can do. Drives which affordances render AT ALL — a stop button over a
231
+ * provider that cannot cancel is a lie. */
232
+ interface AgentCapabilities {
233
+ streaming: boolean;
234
+ tools: boolean;
235
+ citations: boolean;
236
+ attachments: boolean;
237
+ cancel: boolean;
238
+ }
239
+ interface AgentAvailability {
240
+ available: boolean;
241
+ /** Human-readable and shown in diagnostics. "Not licensed" and "not on this host" need
242
+ * different fixes, so they must not collapse to one boolean. */
243
+ reason?: string;
244
+ status?: AgentRunStatus;
245
+ capabilities?: AgentCapabilities;
246
+ }
247
+ interface IAgentService {
248
+ /** Stable id, matching the configured provider name. */
249
+ readonly name: string;
250
+ /** Can this provider actually answer here? PROBED, never assumed. Never throws. */
251
+ isAvailable(): Promise<AgentAvailability>;
252
+ /** Ask. Yields in order, emits exactly one `turn_end`. Never throws. */
253
+ send(turn: AgentTurn): AsyncIterable<AgentEvent>;
254
+ }
255
+ /** ★ `none` is the DEFAULT and must stay so: a kit dropped into an unwired host must not
256
+ * silently start talking to something. */
257
+ type AgentProviderName = 'none' | 'mock' | 'custom';
258
+ interface AgentServiceConfig {
259
+ provider: AgentProviderName;
260
+ /** Absolute https URL of a server that holds the credential and speaks to the real model.
261
+ * ★ There is no `apiKey` here on purpose — see constraint 2 at the top of this file. */
262
+ endpoint?: string;
263
+ /** Names WHICH agent answers. Not a credential, and safe in a control property. */
264
+ agentId?: string;
265
+ conversationId?: string;
266
+ /** Escape hatch for a host-supplied implementation (`provider: 'custom'`). */
267
+ factory?: () => IAgentService;
268
+ }
269
+
270
+ export type { AgentMessage as A, IAgentService as I, ToolCallStatus as T, AgentParticipant as a, AgentFailure as b, AgentRunStatus as c, AgentAvailability as d, AgentUsage as e, AgentSource as f, AgentCitation as g, AgentToolCall as h, AgentAttachment as i, AgentCapabilities as j, AgentEvent as k, AgentProviderName as l, AgentRole as m, AgentServiceConfig as n, AgentTurn as o, AgentTurnResult as p };