@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,333 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/services/index.ts
21
+ var services_exports = {};
22
+ __export(services_exports, {
23
+ MockAgentService: () => MockAgentService,
24
+ NullAgentService: () => NullAgentService,
25
+ createAgentService: () => createAgentService,
26
+ decide: () => decide,
27
+ immediateScheduler: () => immediateScheduler,
28
+ mockScenarios: () => mockScenarios,
29
+ realScheduler: () => realScheduler
30
+ });
31
+ module.exports = __toCommonJS(services_exports);
32
+
33
+ // src/services/scenarios.ts
34
+ var SOURCES = [
35
+ { id: "src-acct", title: "Contoso Ltd", kind: "dataverse", entityLogicalName: "account", recordId: "11111111-1111-1111-1111-111111111111", snippet: "Annual revenue \xA34.2m, 38 employees." },
36
+ { id: "src-case", title: "Case 4417 \u2014 invoice mismatch", kind: "dataverse", entityLogicalName: "incident", recordId: "22222222-2222-2222-2222-222222222222", snippet: "Raised 12 Mar, priority High." },
37
+ { id: "src-kb", title: "KB-208: credit note policy", kind: "knowledge", url: "https://example.invalid/kb/208", snippet: "Credit notes above \xA35,000 require finance approval.", score: 0.82 },
38
+ { id: "src-doc", title: "Statement-Mar.pdf", kind: "document", snippet: "Page 3 lists the disputed line." },
39
+ { id: "src-web", title: "Companies House filing", kind: "web", url: "https://example.invalid/ch" }
40
+ ];
41
+ var mockScenarios = {
42
+ happy: {
43
+ text: "Contoso has three open cases. The oldest, case 4417, has been waiting eleven days for a credit note.",
44
+ status: "succeeded",
45
+ suggestions: ["Draft a credit note", "Show all three cases", "Who owns case 4417?"]
46
+ },
47
+ // ★ FIVE sources, but the text cites only [3] and [1] — and out of order. The panel must
48
+ // show TWO, renumbered 1 and 2 in first-appearance order. A fixture where every source
49
+ // is cited cannot tell a correct implementation from one that lists everything it was given.
50
+ grounded: {
51
+ text: "Credit notes over \xA35,000 need finance approval [3], and case 4417 is for \xA36,200 [1]. So this one needs sign-off.",
52
+ status: "succeeded",
53
+ sources: SOURCES
54
+ },
55
+ toolApproval: {
56
+ text: "I can raise the credit note for you \u2014 approve the details below and I will submit it.",
57
+ status: "succeeded",
58
+ toolCall: {
59
+ id: "call-1",
60
+ name: "create_credit_note",
61
+ argumentsJson: '{"accountId":"11111111-1111-1111-1111-111111111111","amount":6200,"currency":"GBP","reason":"Invoice mismatch, case 4417"}',
62
+ status: "proposed",
63
+ requiresApproval: true
64
+ }
65
+ },
66
+ toolAuto: {
67
+ text: "I checked the balance for you.",
68
+ status: "succeeded",
69
+ toolCall: { id: "call-2", name: "get_account_balance", argumentsJson: '{"accountId":"1111"}', status: "running" },
70
+ autoToolResult: '{"balance":6200,"currency":"GBP"}'
71
+ },
72
+ // ★ blocked and refused MUST look different on screen: a content filter fired, versus the
73
+ // model declining. They need different fixes, so they are different states.
74
+ blocked: {
75
+ text: "I can\u2019t help with that request.",
76
+ status: "blocked",
77
+ guarded: true,
78
+ failureMessage: "The response was withheld by the content filter.",
79
+ failureCode: "content_filter",
80
+ recoverable: false
81
+ },
82
+ refused: {
83
+ text: "I don\u2019t have enough information to answer that safely. Could you say which account you mean?",
84
+ status: "refused",
85
+ failureMessage: "The agent declined to answer.",
86
+ recoverable: true
87
+ },
88
+ error: {
89
+ text: "",
90
+ status: "error",
91
+ failureMessage: "The agent is rate limited. Try again shortly.",
92
+ failureCode: "rate_limited",
93
+ recoverable: true,
94
+ retryAfterMs: 4e3
95
+ },
96
+ notProvisioned: {
97
+ text: "",
98
+ status: "notProvisioned",
99
+ failureMessage: "No Foundry project is configured for this environment.",
100
+ failureCode: "not_provisioned",
101
+ recoverable: false,
102
+ unavailable: true
103
+ },
104
+ cancelled: {
105
+ text: "Let me pull the last six months of activity for Contoso and work through it case by case, starting with the oldest\u2026",
106
+ status: "cancelled",
107
+ failureMessage: "Stopped."
108
+ },
109
+ // The fixture the autoscroll and live-region gates run against: enough deltas that
110
+ // "once, not per token" is a meaningful distinction.
111
+ longAnswer: {
112
+ text: Array.from({ length: 200 }, (_, i) => `word${i + 1}`).join(" "),
113
+ status: "succeeded"
114
+ },
115
+ throws: {
116
+ text: "partial\u2026",
117
+ status: "error",
118
+ throwsMidStream: true,
119
+ failureMessage: "never reached \u2014 the iterator throws instead"
120
+ }
121
+ };
122
+
123
+ // src/services/MockAgentService.ts
124
+ var realScheduler = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
125
+ var immediateScheduler = () => Promise.resolve();
126
+ function mulberry32(seed) {
127
+ let a = seed >>> 0;
128
+ return () => {
129
+ a = a + 1831565813 >>> 0;
130
+ let t = Math.imul(a ^ a >>> 15, 1 | a);
131
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
132
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
133
+ };
134
+ }
135
+ var MockAgentService = class {
136
+ constructor(options = {}) {
137
+ this.name = "mock";
138
+ this.scenarioName = options.scenario ?? "happy";
139
+ this.scenario = mockScenarios[this.scenarioName];
140
+ this.scheduler = options.scheduler ?? realScheduler;
141
+ this.firstTokenMs = options.firstTokenMs ?? 400;
142
+ this.chunkMs = options.chunkMs ?? 28;
143
+ this.rand = mulberry32(options.seed ?? 1);
144
+ this.availabilityOverride = options.availability;
145
+ }
146
+ async isAvailable() {
147
+ if (this.availabilityOverride) return this.availabilityOverride;
148
+ if (this.scenario.unavailable) {
149
+ return {
150
+ available: false,
151
+ status: "notProvisioned",
152
+ reason: this.scenario.failureMessage ?? "Not provisioned."
153
+ };
154
+ }
155
+ return {
156
+ available: true,
157
+ capabilities: { streaming: true, tools: true, citations: true, attachments: true, cancel: true }
158
+ };
159
+ }
160
+ /**
161
+ * A prompt can select a scenario (`/blocked`, `/tool`, `/error`), so one story can walk
162
+ * every state without remounting.
163
+ */
164
+ resolveScenario(promptText) {
165
+ const slash = promptText.trim().match(/^\/(\w+)/)?.[1];
166
+ if (slash && slash in mockScenarios) {
167
+ return { name: slash, scenario: mockScenarios[slash] };
168
+ }
169
+ return { name: this.scenarioName, scenario: this.scenario };
170
+ }
171
+ async *send(turn) {
172
+ const { scenario } = this.resolveScenario(turn.text);
173
+ const messageId = `mock-${Math.floor(this.rand() * 1e9).toString(36)}`;
174
+ const turnId = `turn-${messageId}`;
175
+ const provider = this.name;
176
+ const finish = (result) => ({
177
+ type: "turn_end",
178
+ turnId,
179
+ messageId,
180
+ result: { ...result, provider }
181
+ });
182
+ yield { type: "turn_start", turnId, messageId, provider, model: "mock-1" };
183
+ if (turn.cancelSignal?.aborted) {
184
+ yield finish({ status: "cancelled", text: "", failure: { status: "cancelled", message: "Stopped." } });
185
+ return;
186
+ }
187
+ if (scenario.status === "notProvisioned" || scenario.status === "error" && !scenario.text) {
188
+ await this.scheduler(this.firstTokenMs);
189
+ yield finish({
190
+ status: scenario.status,
191
+ text: "",
192
+ failure: {
193
+ status: scenario.status,
194
+ message: scenario.failureMessage ?? "Failed.",
195
+ code: scenario.failureCode,
196
+ recoverable: scenario.recoverable,
197
+ retryAfterMs: scenario.retryAfterMs
198
+ }
199
+ });
200
+ return;
201
+ }
202
+ if (scenario.sources) {
203
+ yield { type: "status", messageId, label: "Searching Dataverse\u2026" };
204
+ await this.scheduler(this.firstTokenMs);
205
+ yield { type: "sources", messageId, sources: scenario.sources };
206
+ }
207
+ if (scenario.thinking) {
208
+ yield { type: "thinking_delta", messageId, text: scenario.thinking };
209
+ }
210
+ if (scenario.toolCall) {
211
+ yield { type: "tool_call", messageId, call: scenario.toolCall };
212
+ if (scenario.autoToolResult !== void 0) {
213
+ await this.scheduler(this.chunkMs);
214
+ yield {
215
+ type: "tool_result",
216
+ messageId,
217
+ callId: scenario.toolCall.id,
218
+ status: "succeeded",
219
+ resultText: scenario.autoToolResult
220
+ };
221
+ }
222
+ }
223
+ const chunks = scenario.chunks ?? scenario.text.split(/(?<=\s)/);
224
+ await this.scheduler(this.firstTokenMs);
225
+ let emitted = "";
226
+ for (const [i, chunk] of chunks.entries()) {
227
+ if (turn.cancelSignal?.aborted) {
228
+ yield finish({
229
+ status: "cancelled",
230
+ text: emitted,
231
+ failure: { status: "cancelled", message: "Stopped." }
232
+ });
233
+ return;
234
+ }
235
+ if (scenario.throwsMidStream && i === Math.floor(chunks.length / 2)) {
236
+ throw new Error("mock transport exploded mid-stream");
237
+ }
238
+ emitted += chunk;
239
+ yield { type: "text_delta", messageId, text: chunk };
240
+ await this.scheduler(this.chunkMs);
241
+ }
242
+ if (scenario.suggestions) {
243
+ yield { type: "suggestions", messageId, prompts: scenario.suggestions };
244
+ }
245
+ const failed = scenario.status !== "succeeded";
246
+ yield finish({
247
+ status: scenario.status,
248
+ text: emitted,
249
+ guarded: scenario.guarded,
250
+ usage: {
251
+ inputTokens: 128,
252
+ outputTokens: chunks.length,
253
+ costUsd: Number((chunks.length * 2e-6).toFixed(6)),
254
+ durationMs: this.firstTokenMs + chunks.length * this.chunkMs,
255
+ model: "mock-1"
256
+ },
257
+ failure: failed ? {
258
+ status: scenario.status,
259
+ message: scenario.failureMessage ?? "Failed.",
260
+ code: scenario.failureCode,
261
+ recoverable: scenario.recoverable,
262
+ retryAfterMs: scenario.retryAfterMs
263
+ } : void 0
264
+ });
265
+ }
266
+ };
267
+
268
+ // src/services/NullAgentService.ts
269
+ var NullAgentService = class {
270
+ constructor() {
271
+ this.name = "none";
272
+ }
273
+ async isAvailable() {
274
+ return {
275
+ available: false,
276
+ status: "notProvisioned",
277
+ reason: "No agent provider is configured for this host."
278
+ };
279
+ }
280
+ async *send(turn) {
281
+ const messageId = `null-${Date.now()}`;
282
+ const turnId = `turn-${messageId}`;
283
+ void turn;
284
+ yield { type: "turn_start", turnId, messageId, provider: this.name };
285
+ yield {
286
+ type: "turn_end",
287
+ turnId,
288
+ messageId,
289
+ result: {
290
+ status: "notProvisioned",
291
+ text: "",
292
+ provider: this.name,
293
+ failure: {
294
+ status: "notProvisioned",
295
+ message: "No agent provider is configured for this host.",
296
+ code: "not_provisioned",
297
+ recoverable: false
298
+ }
299
+ }
300
+ };
301
+ }
302
+ };
303
+
304
+ // src/services/createAgentService.ts
305
+ function decide(config) {
306
+ switch (config.provider) {
307
+ case "custom":
308
+ return config.factory ? { provider: config.factory(), reason: "explicit factory (provider: custom)" } : { provider: new NullAgentService(), reason: "provider: custom but no factory supplied" };
309
+ case "mock":
310
+ return { provider: new MockAgentService(), reason: "provider: mock" };
311
+ case "none":
312
+ return { provider: new NullAgentService(), reason: "provider: none (the default)" };
313
+ default:
314
+ return {
315
+ provider: new NullAgentService(),
316
+ reason: `unrecognised provider ${String(config.provider)} \u2014 falling back to none`
317
+ };
318
+ }
319
+ }
320
+ function createAgentService(config = { provider: "none" }) {
321
+ return decide(config).provider;
322
+ }
323
+ // Annotate the CommonJS export names for ESM import in node:
324
+ 0 && (module.exports = {
325
+ MockAgentService,
326
+ NullAgentService,
327
+ createAgentService,
328
+ decide,
329
+ immediateScheduler,
330
+ mockScenarios,
331
+ realScheduler
332
+ });
333
+ //# sourceMappingURL=services.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/services/index.ts","../src/services/scenarios.ts","../src/services/MockAgentService.ts","../src/services/NullAgentService.ts","../src/services/createAgentService.ts"],"sourcesContent":["// @dataverse-kit/agent-kit/services — the seam and the fake.\n//\n// Framework-agnostic (no react, no @fluentui), so a transport adapter can implement\n// IAgentService in a Node broker or an Azure Function and share these types with the browser.\n//\n// ★ The kit ships NO real transport. Foundry, Copilot Studio / Direct Line and broker\n// adapters belong in the consuming app, because they are where credentials live and the\n// kit must never hold one.\nexport type {\n AgentAttachment,\n AgentAvailability,\n AgentCapabilities,\n AgentCitation,\n AgentEvent,\n AgentFailure,\n AgentMessage,\n AgentParticipant,\n AgentProviderName,\n AgentRole,\n AgentRunStatus,\n AgentServiceConfig,\n AgentSource,\n AgentToolCall,\n AgentTurn,\n AgentTurnResult,\n AgentUsage,\n IAgentService,\n ToolCallStatus,\n} from '../core/protocol';\n\nexport { MockAgentService, realScheduler, immediateScheduler } from './MockAgentService';\nexport type { MockAgentServiceOptions, Scheduler } from './MockAgentService';\nexport { NullAgentService } from './NullAgentService';\nexport { createAgentService, decide } from './createAgentService';\nexport { mockScenarios } from './scenarios';\nexport type { MockScenario, MockScenarioName } from './scenarios';\n","import type { AgentRunStatus, AgentSource, AgentToolCall } from '../core/protocol';\n\n/**\n * What the mock can be driven to do. There is one scenario per terminal state plus the\n * interesting mid-stream shapes, because a Storybook that cannot SHOW a state is a Storybook\n * in which that state is never reviewed — and a test suite that cannot REACH one is a suite\n * in which it is never asserted.\n */\nexport type MockScenarioName =\n | 'happy'\n | 'grounded'\n | 'toolApproval'\n | 'toolAuto'\n | 'blocked'\n | 'refused'\n | 'error'\n | 'notProvisioned'\n | 'cancelled'\n | 'longAnswer'\n | 'throws';\n\nexport interface MockScenario {\n /** The answer, streamed word by word unless `chunks` overrides it. */\n text: string;\n chunks?: string[];\n status: AgentRunStatus;\n guarded?: boolean;\n failureMessage?: string;\n failureCode?: string;\n recoverable?: boolean;\n retryAfterMs?: number;\n sources?: AgentSource[];\n /** Emitted mid-stream, before the terminal event. */\n toolCall?: AgentToolCall;\n /** When set, the tool resolves itself rather than waiting for host approval. */\n autoToolResult?: string;\n suggestions?: string[];\n thinking?: string;\n /** Throw from inside the iterator — the failure channel a Promise-shaped seam does not have. */\n throwsMidStream?: boolean;\n /** Never becomes available; `send()` still yields a well-formed turn. */\n unavailable?: boolean;\n}\n\nconst SOURCES: AgentSource[] = [\n { id: 'src-acct', title: 'Contoso Ltd', kind: 'dataverse', entityLogicalName: 'account', recordId: '11111111-1111-1111-1111-111111111111', snippet: 'Annual revenue £4.2m, 38 employees.' },\n { id: 'src-case', title: 'Case 4417 — invoice mismatch', kind: 'dataverse', entityLogicalName: 'incident', recordId: '22222222-2222-2222-2222-222222222222', snippet: 'Raised 12 Mar, priority High.' },\n { id: 'src-kb', title: 'KB-208: credit note policy', kind: 'knowledge', url: 'https://example.invalid/kb/208', snippet: 'Credit notes above £5,000 require finance approval.', score: 0.82 },\n { id: 'src-doc', title: 'Statement-Mar.pdf', kind: 'document', snippet: 'Page 3 lists the disputed line.' },\n { id: 'src-web', title: 'Companies House filing', kind: 'web', url: 'https://example.invalid/ch' },\n];\n\nexport const mockScenarios: Record<MockScenarioName, MockScenario> = {\n happy: {\n text: 'Contoso has three open cases. The oldest, case 4417, has been waiting eleven days for a credit note.',\n status: 'succeeded',\n suggestions: ['Draft a credit note', 'Show all three cases', 'Who owns case 4417?'],\n },\n\n // ★ FIVE sources, but the text cites only [3] and [1] — and out of order. The panel must\n // show TWO, renumbered 1 and 2 in first-appearance order. A fixture where every source\n // is cited cannot tell a correct implementation from one that lists everything it was given.\n grounded: {\n text: 'Credit notes over £5,000 need finance approval [3], and case 4417 is for £6,200 [1]. So this one needs sign-off.',\n status: 'succeeded',\n sources: SOURCES,\n },\n\n toolApproval: {\n text: 'I can raise the credit note for you — approve the details below and I will submit it.',\n status: 'succeeded',\n toolCall: {\n id: 'call-1',\n name: 'create_credit_note',\n argumentsJson: '{\"accountId\":\"11111111-1111-1111-1111-111111111111\",\"amount\":6200,\"currency\":\"GBP\",\"reason\":\"Invoice mismatch, case 4417\"}',\n status: 'proposed',\n requiresApproval: true,\n },\n },\n\n toolAuto: {\n text: 'I checked the balance for you.',\n status: 'succeeded',\n toolCall: { id: 'call-2', name: 'get_account_balance', argumentsJson: '{\"accountId\":\"1111\"}', status: 'running' },\n autoToolResult: '{\"balance\":6200,\"currency\":\"GBP\"}',\n },\n\n // ★ blocked and refused MUST look different on screen: a content filter fired, versus the\n // model declining. They need different fixes, so they are different states.\n blocked: {\n text: 'I can’t help with that request.',\n status: 'blocked',\n guarded: true,\n failureMessage: 'The response was withheld by the content filter.',\n failureCode: 'content_filter',\n recoverable: false,\n },\n\n refused: {\n text: 'I don’t have enough information to answer that safely. Could you say which account you mean?',\n status: 'refused',\n failureMessage: 'The agent declined to answer.',\n recoverable: true,\n },\n\n error: {\n text: '',\n status: 'error',\n failureMessage: 'The agent is rate limited. Try again shortly.',\n failureCode: 'rate_limited',\n recoverable: true,\n retryAfterMs: 4000,\n },\n\n notProvisioned: {\n text: '',\n status: 'notProvisioned',\n failureMessage: 'No Foundry project is configured for this environment.',\n failureCode: 'not_provisioned',\n recoverable: false,\n unavailable: true,\n },\n\n cancelled: {\n text: 'Let me pull the last six months of activity for Contoso and work through it case by case, starting with the oldest…',\n status: 'cancelled',\n failureMessage: 'Stopped.',\n },\n\n // The fixture the autoscroll and live-region gates run against: enough deltas that\n // \"once, not per token\" is a meaningful distinction.\n longAnswer: {\n text: Array.from({ length: 200 }, (_, i) => `word${i + 1}`).join(' '),\n status: 'succeeded',\n },\n\n throws: {\n text: 'partial…',\n status: 'error',\n throwsMidStream: true,\n failureMessage: 'never reached — the iterator throws instead',\n },\n};\n","import type {\n AgentAvailability,\n AgentEvent,\n AgentTurn,\n AgentTurnResult,\n IAgentService,\n} from '../core/protocol';\nimport { mockScenarios, type MockScenario, type MockScenarioName } from './scenarios';\n\n/** Injectable so tests run at zero delay and Storybook runs at human speed. */\nexport type Scheduler = (ms: number) => Promise<void>;\n\nexport const realScheduler: Scheduler = (ms) =>\n new Promise((resolve) => setTimeout(resolve, ms));\nexport const immediateScheduler: Scheduler = () => Promise.resolve();\n\nexport interface MockAgentServiceOptions {\n scenario?: MockScenarioName;\n scheduler?: Scheduler;\n /** Delay before the first token, so the typing indicator is visible and screenshottable. */\n firstTokenMs?: number;\n chunkMs?: number;\n /**\n * ★ A CONSTRUCTOR ARGUMENT, never module state.\n *\n * tsup runs with `splitting: false`, so every entry gets a self-contained bundle and any\n * module-level `let` is physically duplicated per entry — a story importing from `.` and a\n * test importing from `./services` would get two independent counters. Measured on\n * surface-kit: three copies of the same function body across its entries.\n */\n seed?: number;\n /** Override availability; otherwise derived from the scenario. */\n availability?: AgentAvailability;\n}\n\n/** A tiny deterministic PRNG so Chromatic snapshots do not drift. */\nfunction mulberry32(seed: number): () => number {\n let a = seed >>> 0;\n return () => {\n a = (a + 0x6d2b79f5) >>> 0;\n let t = Math.imul(a ^ (a >>> 15), 1 | a);\n t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n}\n\n/**\n * The in-package fake. It is the only \"provider\" agent-kit ships: real transports (Foundry,\n * Copilot Studio, a broker) live in the consuming app, so the kit never holds a credential.\n *\n * ★ It can reach EVERY `AgentRunStatus`. A test asserts that, because a mock that cannot\n * produce a state is a state nothing downstream is ever tested against.\n */\nexport class MockAgentService implements IAgentService {\n readonly name = 'mock';\n\n private readonly scenario: MockScenario;\n private readonly scenarioName: MockScenarioName;\n private readonly scheduler: Scheduler;\n private readonly firstTokenMs: number;\n private readonly chunkMs: number;\n private readonly rand: () => number;\n private readonly availabilityOverride?: AgentAvailability;\n\n constructor(options: MockAgentServiceOptions = {}) {\n this.scenarioName = options.scenario ?? 'happy';\n this.scenario = mockScenarios[this.scenarioName];\n this.scheduler = options.scheduler ?? realScheduler;\n this.firstTokenMs = options.firstTokenMs ?? 400;\n this.chunkMs = options.chunkMs ?? 28;\n this.rand = mulberry32(options.seed ?? 1);\n this.availabilityOverride = options.availability;\n }\n\n async isAvailable(): Promise<AgentAvailability> {\n if (this.availabilityOverride) return this.availabilityOverride;\n if (this.scenario.unavailable) {\n return {\n available: false,\n status: 'notProvisioned',\n reason: this.scenario.failureMessage ?? 'Not provisioned.',\n };\n }\n return {\n available: true,\n capabilities: { streaming: true, tools: true, citations: true, attachments: true, cancel: true },\n };\n }\n\n /**\n * A prompt can select a scenario (`/blocked`, `/tool`, `/error`), so one story can walk\n * every state without remounting.\n */\n private resolveScenario(promptText: string): { name: MockScenarioName; scenario: MockScenario } {\n const slash = promptText.trim().match(/^\\/(\\w+)/)?.[1] as MockScenarioName | undefined;\n if (slash && slash in mockScenarios) {\n return { name: slash, scenario: mockScenarios[slash] };\n }\n return { name: this.scenarioName, scenario: this.scenario };\n }\n\n async *send(turn: AgentTurn): AsyncIterable<AgentEvent> {\n const { scenario } = this.resolveScenario(turn.text);\n const messageId = `mock-${Math.floor(this.rand() * 1e9).toString(36)}`;\n const turnId = `turn-${messageId}`;\n const provider = this.name;\n\n const finish = (result: Omit<AgentTurnResult, 'provider'>): AgentEvent => ({\n type: 'turn_end',\n turnId,\n messageId,\n result: { ...result, provider },\n });\n\n yield { type: 'turn_start', turnId, messageId, provider, model: 'mock-1' };\n\n if (turn.cancelSignal?.aborted) {\n yield finish({ status: 'cancelled', text: '', failure: { status: 'cancelled', message: 'Stopped.' } });\n return;\n }\n\n // Terminal-before-any-text scenarios still emit a well-formed turn.\n if (scenario.status === 'notProvisioned' || (scenario.status === 'error' && !scenario.text)) {\n await this.scheduler(this.firstTokenMs);\n yield finish({\n status: scenario.status,\n text: '',\n failure: {\n status: scenario.status,\n message: scenario.failureMessage ?? 'Failed.',\n code: scenario.failureCode,\n recoverable: scenario.recoverable,\n retryAfterMs: scenario.retryAfterMs,\n },\n });\n return;\n }\n\n if (scenario.sources) {\n yield { type: 'status', messageId, label: 'Searching Dataverse…' };\n await this.scheduler(this.firstTokenMs);\n yield { type: 'sources', messageId, sources: scenario.sources };\n }\n\n if (scenario.thinking) {\n yield { type: 'thinking_delta', messageId, text: scenario.thinking };\n }\n\n if (scenario.toolCall) {\n yield { type: 'tool_call', messageId, call: scenario.toolCall };\n if (scenario.autoToolResult !== undefined) {\n await this.scheduler(this.chunkMs);\n yield {\n type: 'tool_result',\n messageId,\n callId: scenario.toolCall.id,\n status: 'succeeded',\n resultText: scenario.autoToolResult,\n };\n }\n }\n\n const chunks = scenario.chunks ?? scenario.text.split(/(?<=\\s)/);\n await this.scheduler(this.firstTokenMs);\n\n let emitted = '';\n for (const [i, chunk] of chunks.entries()) {\n if (turn.cancelSignal?.aborted) {\n yield finish({\n status: 'cancelled',\n text: emitted,\n failure: { status: 'cancelled', message: 'Stopped.' },\n });\n return;\n }\n if (scenario.throwsMidStream && i === Math.floor(chunks.length / 2)) {\n // The failure channel an AsyncIterable has and a Promise does not. A consumer must\n // surface this as a notice, not by unmounting the tree.\n throw new Error('mock transport exploded mid-stream');\n }\n emitted += chunk;\n yield { type: 'text_delta', messageId, text: chunk };\n await this.scheduler(this.chunkMs);\n }\n\n if (scenario.suggestions) {\n yield { type: 'suggestions', messageId, prompts: scenario.suggestions };\n }\n\n const failed = scenario.status !== 'succeeded';\n yield finish({\n status: scenario.status,\n text: emitted,\n guarded: scenario.guarded,\n usage: {\n inputTokens: 128,\n outputTokens: chunks.length,\n costUsd: Number((chunks.length * 0.000002).toFixed(6)),\n durationMs: this.firstTokenMs + chunks.length * this.chunkMs,\n model: 'mock-1',\n },\n failure: failed\n ? {\n status: scenario.status as Exclude<typeof scenario.status, 'succeeded'>,\n message: scenario.failureMessage ?? 'Failed.',\n code: scenario.failureCode,\n recoverable: scenario.recoverable,\n retryAfterMs: scenario.retryAfterMs,\n }\n : undefined,\n });\n }\n}\n","import type { AgentAvailability, AgentEvent, AgentTurn, IAgentService } from '../core/protocol';\n\n/**\n * The default provider: answers nothing, politely.\n *\n * ★ `none` is the DEFAULT and must stay so. A kit dropped into a host nobody has wired must\n * not silently start talking to a service because someone shipped a new build. Carried\n * from `ILlmService`, where the same rule is load-bearing in a regulated process.\n *\n * ★ It still yields a well-formed turn — `turn_start` then one `turn_end` — rather than\n * throwing or yielding nothing. An unconfigured kit should render \"not configured\", not\n * a spinner that never resolves.\n */\nexport class NullAgentService implements IAgentService {\n readonly name = 'none';\n\n async isAvailable(): Promise<AgentAvailability> {\n return {\n available: false,\n status: 'notProvisioned',\n reason: 'No agent provider is configured for this host.',\n };\n }\n\n async *send(turn: AgentTurn): AsyncIterable<AgentEvent> {\n const messageId = `null-${Date.now()}`;\n const turnId = `turn-${messageId}`;\n void turn;\n yield { type: 'turn_start', turnId, messageId, provider: this.name };\n yield {\n type: 'turn_end',\n turnId,\n messageId,\n result: {\n status: 'notProvisioned',\n text: '',\n provider: this.name,\n failure: {\n status: 'notProvisioned',\n message: 'No agent provider is configured for this host.',\n code: 'not_provisioned',\n recoverable: false,\n },\n },\n };\n }\n}\n","import type { AgentServiceConfig, IAgentService } from '../core/protocol';\nimport { MockAgentService } from './MockAgentService';\nimport { NullAgentService } from './NullAgentService';\n\nexport interface ServiceDecision {\n provider: IAgentService;\n /** Why this one was chosen — surfaced in diagnostics so a misconfiguration is legible. */\n reason: string;\n}\n\n/**\n * Provider selection, as ONE ladder.\n *\n * ★ `decide()` is exported alongside `createAgentService()` and they share a return type, so\n * the factory and any diagnostics view cannot drift — the shape `ServiceFactory.decide()`\n * uses in `@dataverse-kit/api-service` for exactly the same reason.\n *\n * ★ An UNRECOGNISED provider name falls back to `none`, never to a working provider. Carried\n * from `createLlmService`: a typo must produce silence, not an unintended call to a model.\n */\nexport function decide(config: AgentServiceConfig): ServiceDecision {\n switch (config.provider) {\n case 'custom':\n return config.factory\n ? { provider: config.factory(), reason: 'explicit factory (provider: custom)' }\n : { provider: new NullAgentService(), reason: 'provider: custom but no factory supplied' };\n case 'mock':\n return { provider: new MockAgentService(), reason: 'provider: mock' };\n case 'none':\n return { provider: new NullAgentService(), reason: 'provider: none (the default)' };\n default:\n return {\n provider: new NullAgentService(),\n reason: `unrecognised provider ${String(config.provider)} — falling back to none`,\n };\n }\n}\n\nexport function createAgentService(\n config: AgentServiceConfig = { provider: 'none' },\n): IAgentService {\n return decide(config).provider;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,118 @@
1
+ import { c as AgentRunStatus, f as AgentSource, h as AgentToolCall, I as IAgentService, d as AgentAvailability, o as AgentTurn, k as AgentEvent, n as AgentServiceConfig } from './protocol-CZJ1v9Hq.cjs';
2
+ export { i as AgentAttachment, j as AgentCapabilities, g as AgentCitation, b as AgentFailure, A as AgentMessage, a as AgentParticipant, l as AgentProviderName, m as AgentRole, p as AgentTurnResult, e as AgentUsage, T as ToolCallStatus } from './protocol-CZJ1v9Hq.cjs';
3
+
4
+ /**
5
+ * What the mock can be driven to do. There is one scenario per terminal state plus the
6
+ * interesting mid-stream shapes, because a Storybook that cannot SHOW a state is a Storybook
7
+ * in which that state is never reviewed — and a test suite that cannot REACH one is a suite
8
+ * in which it is never asserted.
9
+ */
10
+ type MockScenarioName = 'happy' | 'grounded' | 'toolApproval' | 'toolAuto' | 'blocked' | 'refused' | 'error' | 'notProvisioned' | 'cancelled' | 'longAnswer' | 'throws';
11
+ interface MockScenario {
12
+ /** The answer, streamed word by word unless `chunks` overrides it. */
13
+ text: string;
14
+ chunks?: string[];
15
+ status: AgentRunStatus;
16
+ guarded?: boolean;
17
+ failureMessage?: string;
18
+ failureCode?: string;
19
+ recoverable?: boolean;
20
+ retryAfterMs?: number;
21
+ sources?: AgentSource[];
22
+ /** Emitted mid-stream, before the terminal event. */
23
+ toolCall?: AgentToolCall;
24
+ /** When set, the tool resolves itself rather than waiting for host approval. */
25
+ autoToolResult?: string;
26
+ suggestions?: string[];
27
+ thinking?: string;
28
+ /** Throw from inside the iterator — the failure channel a Promise-shaped seam does not have. */
29
+ throwsMidStream?: boolean;
30
+ /** Never becomes available; `send()` still yields a well-formed turn. */
31
+ unavailable?: boolean;
32
+ }
33
+ declare const mockScenarios: Record<MockScenarioName, MockScenario>;
34
+
35
+ /** Injectable so tests run at zero delay and Storybook runs at human speed. */
36
+ type Scheduler = (ms: number) => Promise<void>;
37
+ declare const realScheduler: Scheduler;
38
+ declare const immediateScheduler: Scheduler;
39
+ interface MockAgentServiceOptions {
40
+ scenario?: MockScenarioName;
41
+ scheduler?: Scheduler;
42
+ /** Delay before the first token, so the typing indicator is visible and screenshottable. */
43
+ firstTokenMs?: number;
44
+ chunkMs?: number;
45
+ /**
46
+ * ★ A CONSTRUCTOR ARGUMENT, never module state.
47
+ *
48
+ * tsup runs with `splitting: false`, so every entry gets a self-contained bundle and any
49
+ * module-level `let` is physically duplicated per entry — a story importing from `.` and a
50
+ * test importing from `./services` would get two independent counters. Measured on
51
+ * surface-kit: three copies of the same function body across its entries.
52
+ */
53
+ seed?: number;
54
+ /** Override availability; otherwise derived from the scenario. */
55
+ availability?: AgentAvailability;
56
+ }
57
+ /**
58
+ * The in-package fake. It is the only "provider" agent-kit ships: real transports (Foundry,
59
+ * Copilot Studio, a broker) live in the consuming app, so the kit never holds a credential.
60
+ *
61
+ * ★ It can reach EVERY `AgentRunStatus`. A test asserts that, because a mock that cannot
62
+ * produce a state is a state nothing downstream is ever tested against.
63
+ */
64
+ declare class MockAgentService implements IAgentService {
65
+ readonly name = "mock";
66
+ private readonly scenario;
67
+ private readonly scenarioName;
68
+ private readonly scheduler;
69
+ private readonly firstTokenMs;
70
+ private readonly chunkMs;
71
+ private readonly rand;
72
+ private readonly availabilityOverride?;
73
+ constructor(options?: MockAgentServiceOptions);
74
+ isAvailable(): Promise<AgentAvailability>;
75
+ /**
76
+ * A prompt can select a scenario (`/blocked`, `/tool`, `/error`), so one story can walk
77
+ * every state without remounting.
78
+ */
79
+ private resolveScenario;
80
+ send(turn: AgentTurn): AsyncIterable<AgentEvent>;
81
+ }
82
+
83
+ /**
84
+ * The default provider: answers nothing, politely.
85
+ *
86
+ * ★ `none` is the DEFAULT and must stay so. A kit dropped into a host nobody has wired must
87
+ * not silently start talking to a service because someone shipped a new build. Carried
88
+ * from `ILlmService`, where the same rule is load-bearing in a regulated process.
89
+ *
90
+ * ★ It still yields a well-formed turn — `turn_start` then one `turn_end` — rather than
91
+ * throwing or yielding nothing. An unconfigured kit should render "not configured", not
92
+ * a spinner that never resolves.
93
+ */
94
+ declare class NullAgentService implements IAgentService {
95
+ readonly name = "none";
96
+ isAvailable(): Promise<AgentAvailability>;
97
+ send(turn: AgentTurn): AsyncIterable<AgentEvent>;
98
+ }
99
+
100
+ interface ServiceDecision {
101
+ provider: IAgentService;
102
+ /** Why this one was chosen — surfaced in diagnostics so a misconfiguration is legible. */
103
+ reason: string;
104
+ }
105
+ /**
106
+ * Provider selection, as ONE ladder.
107
+ *
108
+ * ★ `decide()` is exported alongside `createAgentService()` and they share a return type, so
109
+ * the factory and any diagnostics view cannot drift — the shape `ServiceFactory.decide()`
110
+ * uses in `@dataverse-kit/api-service` for exactly the same reason.
111
+ *
112
+ * ★ An UNRECOGNISED provider name falls back to `none`, never to a working provider. Carried
113
+ * from `createLlmService`: a typo must produce silence, not an unintended call to a model.
114
+ */
115
+ declare function decide(config: AgentServiceConfig): ServiceDecision;
116
+ declare function createAgentService(config?: AgentServiceConfig): IAgentService;
117
+
118
+ export { AgentAvailability, AgentEvent, AgentRunStatus, AgentServiceConfig, AgentSource, AgentToolCall, AgentTurn, IAgentService, MockAgentService, type MockAgentServiceOptions, type MockScenario, type MockScenarioName, NullAgentService, type Scheduler, createAgentService, decide, immediateScheduler, mockScenarios, realScheduler };
@@ -0,0 +1,118 @@
1
+ import { c as AgentRunStatus, f as AgentSource, h as AgentToolCall, I as IAgentService, d as AgentAvailability, o as AgentTurn, k as AgentEvent, n as AgentServiceConfig } from './protocol-CZJ1v9Hq.js';
2
+ export { i as AgentAttachment, j as AgentCapabilities, g as AgentCitation, b as AgentFailure, A as AgentMessage, a as AgentParticipant, l as AgentProviderName, m as AgentRole, p as AgentTurnResult, e as AgentUsage, T as ToolCallStatus } from './protocol-CZJ1v9Hq.js';
3
+
4
+ /**
5
+ * What the mock can be driven to do. There is one scenario per terminal state plus the
6
+ * interesting mid-stream shapes, because a Storybook that cannot SHOW a state is a Storybook
7
+ * in which that state is never reviewed — and a test suite that cannot REACH one is a suite
8
+ * in which it is never asserted.
9
+ */
10
+ type MockScenarioName = 'happy' | 'grounded' | 'toolApproval' | 'toolAuto' | 'blocked' | 'refused' | 'error' | 'notProvisioned' | 'cancelled' | 'longAnswer' | 'throws';
11
+ interface MockScenario {
12
+ /** The answer, streamed word by word unless `chunks` overrides it. */
13
+ text: string;
14
+ chunks?: string[];
15
+ status: AgentRunStatus;
16
+ guarded?: boolean;
17
+ failureMessage?: string;
18
+ failureCode?: string;
19
+ recoverable?: boolean;
20
+ retryAfterMs?: number;
21
+ sources?: AgentSource[];
22
+ /** Emitted mid-stream, before the terminal event. */
23
+ toolCall?: AgentToolCall;
24
+ /** When set, the tool resolves itself rather than waiting for host approval. */
25
+ autoToolResult?: string;
26
+ suggestions?: string[];
27
+ thinking?: string;
28
+ /** Throw from inside the iterator — the failure channel a Promise-shaped seam does not have. */
29
+ throwsMidStream?: boolean;
30
+ /** Never becomes available; `send()` still yields a well-formed turn. */
31
+ unavailable?: boolean;
32
+ }
33
+ declare const mockScenarios: Record<MockScenarioName, MockScenario>;
34
+
35
+ /** Injectable so tests run at zero delay and Storybook runs at human speed. */
36
+ type Scheduler = (ms: number) => Promise<void>;
37
+ declare const realScheduler: Scheduler;
38
+ declare const immediateScheduler: Scheduler;
39
+ interface MockAgentServiceOptions {
40
+ scenario?: MockScenarioName;
41
+ scheduler?: Scheduler;
42
+ /** Delay before the first token, so the typing indicator is visible and screenshottable. */
43
+ firstTokenMs?: number;
44
+ chunkMs?: number;
45
+ /**
46
+ * ★ A CONSTRUCTOR ARGUMENT, never module state.
47
+ *
48
+ * tsup runs with `splitting: false`, so every entry gets a self-contained bundle and any
49
+ * module-level `let` is physically duplicated per entry — a story importing from `.` and a
50
+ * test importing from `./services` would get two independent counters. Measured on
51
+ * surface-kit: three copies of the same function body across its entries.
52
+ */
53
+ seed?: number;
54
+ /** Override availability; otherwise derived from the scenario. */
55
+ availability?: AgentAvailability;
56
+ }
57
+ /**
58
+ * The in-package fake. It is the only "provider" agent-kit ships: real transports (Foundry,
59
+ * Copilot Studio, a broker) live in the consuming app, so the kit never holds a credential.
60
+ *
61
+ * ★ It can reach EVERY `AgentRunStatus`. A test asserts that, because a mock that cannot
62
+ * produce a state is a state nothing downstream is ever tested against.
63
+ */
64
+ declare class MockAgentService implements IAgentService {
65
+ readonly name = "mock";
66
+ private readonly scenario;
67
+ private readonly scenarioName;
68
+ private readonly scheduler;
69
+ private readonly firstTokenMs;
70
+ private readonly chunkMs;
71
+ private readonly rand;
72
+ private readonly availabilityOverride?;
73
+ constructor(options?: MockAgentServiceOptions);
74
+ isAvailable(): Promise<AgentAvailability>;
75
+ /**
76
+ * A prompt can select a scenario (`/blocked`, `/tool`, `/error`), so one story can walk
77
+ * every state without remounting.
78
+ */
79
+ private resolveScenario;
80
+ send(turn: AgentTurn): AsyncIterable<AgentEvent>;
81
+ }
82
+
83
+ /**
84
+ * The default provider: answers nothing, politely.
85
+ *
86
+ * ★ `none` is the DEFAULT and must stay so. A kit dropped into a host nobody has wired must
87
+ * not silently start talking to a service because someone shipped a new build. Carried
88
+ * from `ILlmService`, where the same rule is load-bearing in a regulated process.
89
+ *
90
+ * ★ It still yields a well-formed turn — `turn_start` then one `turn_end` — rather than
91
+ * throwing or yielding nothing. An unconfigured kit should render "not configured", not
92
+ * a spinner that never resolves.
93
+ */
94
+ declare class NullAgentService implements IAgentService {
95
+ readonly name = "none";
96
+ isAvailable(): Promise<AgentAvailability>;
97
+ send(turn: AgentTurn): AsyncIterable<AgentEvent>;
98
+ }
99
+
100
+ interface ServiceDecision {
101
+ provider: IAgentService;
102
+ /** Why this one was chosen — surfaced in diagnostics so a misconfiguration is legible. */
103
+ reason: string;
104
+ }
105
+ /**
106
+ * Provider selection, as ONE ladder.
107
+ *
108
+ * ★ `decide()` is exported alongside `createAgentService()` and they share a return type, so
109
+ * the factory and any diagnostics view cannot drift — the shape `ServiceFactory.decide()`
110
+ * uses in `@dataverse-kit/api-service` for exactly the same reason.
111
+ *
112
+ * ★ An UNRECOGNISED provider name falls back to `none`, never to a working provider. Carried
113
+ * from `createLlmService`: a typo must produce silence, not an unintended call to a model.
114
+ */
115
+ declare function decide(config: AgentServiceConfig): ServiceDecision;
116
+ declare function createAgentService(config?: AgentServiceConfig): IAgentService;
117
+
118
+ export { AgentAvailability, AgentEvent, AgentRunStatus, AgentServiceConfig, AgentSource, AgentToolCall, AgentTurn, IAgentService, MockAgentService, type MockAgentServiceOptions, type MockScenario, type MockScenarioName, NullAgentService, type Scheduler, createAgentService, decide, immediateScheduler, mockScenarios, realScheduler };