@iloveagents/foundry-agent 0.3.1 → 0.4.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.
Files changed (51) hide show
  1. package/README.md +16 -0
  2. package/dist/client/agui-runner.d.ts +53 -0
  3. package/dist/client/agui-runner.js +320 -0
  4. package/dist/client/runner-events.d.ts +54 -0
  5. package/dist/client/runner-events.js +1 -0
  6. package/dist/client/service-fetch.d.ts +112 -0
  7. package/dist/client/service-fetch.js +244 -0
  8. package/dist/index.d.ts +7 -0
  9. package/dist/index.js +10 -0
  10. package/dist/msal/auth-config.d.ts +91 -0
  11. package/dist/msal/auth-config.js +70 -0
  12. package/dist/msal/auth-store.d.ts +95 -0
  13. package/dist/msal/auth-store.js +372 -0
  14. package/dist/msal/index.d.ts +3 -0
  15. package/dist/msal/index.js +3 -0
  16. package/dist/msal/token-fetch.d.ts +16 -0
  17. package/dist/msal/token-fetch.js +57 -0
  18. package/dist/store/citation-store.d.ts +42 -0
  19. package/dist/store/citation-store.js +14 -0
  20. package/dist/store/link-store.d.ts +29 -0
  21. package/dist/store/link-store.js +28 -0
  22. package/dist/store/streaming-status-store.d.ts +15 -0
  23. package/dist/store/streaming-status-store.js +9 -0
  24. package/dist/tools/registry.d.ts +48 -0
  25. package/dist/tools/registry.js +50 -0
  26. package/package.json +23 -9
  27. package/AGENTS.md +0 -91
  28. package/CHANGELOG.md +0 -182
  29. package/CLAUDE.md +0 -1
  30. package/src/__tests__/agui-runner.test.ts +0 -404
  31. package/src/__tests__/auth-store.test.ts +0 -596
  32. package/src/__tests__/citation-store.test.ts +0 -52
  33. package/src/__tests__/client-tool-registry.test.ts +0 -84
  34. package/src/__tests__/link-store.test.ts +0 -48
  35. package/src/__tests__/service-fetch.test.ts +0 -525
  36. package/src/__tests__/streaming-status-store.test.ts +0 -22
  37. package/src/__tests__/token-fetch.test.ts +0 -134
  38. package/src/client/agui-runner.ts +0 -382
  39. package/src/client/runner-events.ts +0 -27
  40. package/src/client/service-fetch.ts +0 -318
  41. package/src/index.ts +0 -27
  42. package/src/msal/auth-config.ts +0 -150
  43. package/src/msal/auth-store.ts +0 -517
  44. package/src/msal/index.ts +0 -14
  45. package/src/msal/token-fetch.ts +0 -68
  46. package/src/store/citation-store.ts +0 -52
  47. package/src/store/link-store.ts +0 -53
  48. package/src/store/streaming-status-store.ts +0 -21
  49. package/src/tools/registry.ts +0 -112
  50. package/tsconfig.json +0 -15
  51. package/vitest.config.ts +0 -8
package/README.md ADDED
@@ -0,0 +1,16 @@
1
+ # @iloveagents/foundry-agent
2
+
3
+ Cross-runtime AG-UI transport for Foundry UI.
4
+
5
+ This package contains the non-React pieces: `AGUIRunner`, service fetch helpers,
6
+ client-tool registry, streaming status store, citation/link stores, and the
7
+ optional MSAL subpath.
8
+
9
+ ## Imports
10
+
11
+ ```ts
12
+ import { AGUIRunner, createServiceFetch } from "@iloveagents/foundry-agent";
13
+ import { authStore, tokenFetch } from "@iloveagents/foundry-agent/msal";
14
+ ```
15
+
16
+ This package publishes built ESM JavaScript and `.d.ts` declarations.
@@ -0,0 +1,53 @@
1
+ /**
2
+ * AG-UI Runner — protocol engine for `@iloveagents/foundry-agent`.
3
+ *
4
+ * Drives the AG-UI SDK's `HttpAgent.runAgent()` with an `AgentSubscriber`,
5
+ * fans the subscriber callbacks out as a normalized `RunnerEvent` stream,
6
+ * and re-issues the run with appended assistant + tool messages whenever a
7
+ * client-side tool produces a result.
8
+ *
9
+ * Framework-agnostic — no React, no `@assistant-ui/*`. The assistant-ui shim
10
+ * in `@iloveagents/foundry-web-ui` (and future Outlook / Teams / native shells)
11
+ * translate `RunnerEvent`s into their own model.
12
+ *
13
+ * @see https://docs.ag-ui.com/sdk/js/client/http-agent
14
+ * @see https://docs.ag-ui.com/sdk/js/client/subscriber
15
+ */
16
+ import type { Context, Message } from "@ag-ui/core";
17
+ import type { ToolRegistry } from "../tools/registry.js";
18
+ import type { RunnerEvent } from "./runner-events.js";
19
+ export interface AGUIRunnerOptions {
20
+ url?: string;
21
+ threadId?: string;
22
+ /** Optional override for the underlying fetch — useful for auth-attached fetch. */
23
+ fetchFn?: typeof fetch;
24
+ }
25
+ export interface AGUIRunInput {
26
+ /** AG-UI messages — caller is responsible for runtime-specific message conversion. */
27
+ messages: Message[];
28
+ /** Optional state snapshot (e.g. context items) sent to the agent. */
29
+ state?: Record<string, unknown>;
30
+ /** Tool registry consulted for client-side tool dispatch + schemas. */
31
+ registry: ToolRegistry;
32
+ /** Aborts the run — generator returns cleanly when the signal fires. */
33
+ abortSignal?: AbortSignal;
34
+ /** Optional context payload forwarded with every turn (default: empty array). */
35
+ context?: Context[];
36
+ }
37
+ /**
38
+ * AG-UI Runner.
39
+ *
40
+ * Reusable across runs — instantiate once, call `run()` per turn batch.
41
+ * Maintains `threadId` and the `HttpAgent` state snapshot across calls.
42
+ */
43
+ export declare class AGUIRunner {
44
+ private readonly httpAgent;
45
+ constructor(options?: AGUIRunnerOptions);
46
+ get threadId(): string;
47
+ get state(): unknown;
48
+ /**
49
+ * Run a single AG-UI exchange (with multi-turn re-issue when client-side
50
+ * tool results are produced) and yield normalized events.
51
+ */
52
+ run(input: AGUIRunInput): AsyncGenerator<RunnerEvent>;
53
+ }
@@ -0,0 +1,320 @@
1
+ /**
2
+ * AG-UI Runner — protocol engine for `@iloveagents/foundry-agent`.
3
+ *
4
+ * Drives the AG-UI SDK's `HttpAgent.runAgent()` with an `AgentSubscriber`,
5
+ * fans the subscriber callbacks out as a normalized `RunnerEvent` stream,
6
+ * and re-issues the run with appended assistant + tool messages whenever a
7
+ * client-side tool produces a result.
8
+ *
9
+ * Framework-agnostic — no React, no `@assistant-ui/*`. The assistant-ui shim
10
+ * in `@iloveagents/foundry-web-ui` (and future Outlook / Teams / native shells)
11
+ * translate `RunnerEvent`s into their own model.
12
+ *
13
+ * @see https://docs.ag-ui.com/sdk/js/client/http-agent
14
+ * @see https://docs.ag-ui.com/sdk/js/client/subscriber
15
+ */
16
+ import { HttpAgent } from "@ag-ui/client";
17
+ function shouldPreserveAcrossVisibleHistory(message) {
18
+ return message.role === "system" || message.role === "developer" || message.role === "reasoning";
19
+ }
20
+ function mergeProtocolMessagesFromSnapshot(previousMessages, visibleMessages) {
21
+ if (previousMessages.length === 0)
22
+ return [...visibleMessages];
23
+ const visibleById = new Map(visibleMessages.map((message) => [message.id, message]));
24
+ const previousVisibleIds = new Set(previousMessages
25
+ .filter((message) => !shouldPreserveAcrossVisibleHistory(message))
26
+ .map((message) => message.id));
27
+ const isSameVisibleThread = visibleMessages.some((message) => previousVisibleIds.has(message.id));
28
+ if (!isSameVisibleThread)
29
+ return [...visibleMessages];
30
+ const merged = [];
31
+ const emitted = new Set();
32
+ for (const previous of previousMessages) {
33
+ const currentVisible = visibleById.get(previous.id);
34
+ if (currentVisible) {
35
+ merged.push(currentVisible);
36
+ emitted.add(currentVisible.id);
37
+ continue;
38
+ }
39
+ if (shouldPreserveAcrossVisibleHistory(previous)) {
40
+ merged.push(previous);
41
+ emitted.add(previous.id);
42
+ }
43
+ }
44
+ for (const message of visibleMessages) {
45
+ if (!emitted.has(message.id)) {
46
+ merged.push(message);
47
+ }
48
+ }
49
+ return merged;
50
+ }
51
+ /**
52
+ * Bridges callback-driven `AgentSubscriber` events into an async-iterable
53
+ * queue the runner's generator drains. Single-producer / single-consumer.
54
+ */
55
+ class EventQueue {
56
+ constructor() {
57
+ this.buffer = [];
58
+ this.resolve = null;
59
+ this.ended = false;
60
+ this.error = null;
61
+ }
62
+ push(value) {
63
+ this.buffer.push(value);
64
+ this.flush();
65
+ }
66
+ end(error) {
67
+ this.ended = true;
68
+ if (error !== undefined)
69
+ this.error = error;
70
+ this.flush();
71
+ }
72
+ flush() {
73
+ if (this.resolve) {
74
+ const r = this.resolve;
75
+ this.resolve = null;
76
+ r();
77
+ }
78
+ }
79
+ async *drain() {
80
+ for (;;) {
81
+ if (this.buffer.length > 0) {
82
+ yield this.buffer.shift();
83
+ continue;
84
+ }
85
+ if (this.ended) {
86
+ if (this.error)
87
+ throw this.error;
88
+ return;
89
+ }
90
+ await new Promise((r) => {
91
+ this.resolve = r;
92
+ });
93
+ }
94
+ }
95
+ }
96
+ /**
97
+ * AG-UI Runner.
98
+ *
99
+ * Reusable across runs — instantiate once, call `run()` per turn batch.
100
+ * Maintains `threadId` and the `HttpAgent` state snapshot across calls.
101
+ */
102
+ export class AGUIRunner {
103
+ constructor(options = {}) {
104
+ this.httpAgent = new HttpAgent({
105
+ url: options.url ?? "/api/agent",
106
+ threadId: options.threadId ?? crypto.randomUUID(),
107
+ ...(options.fetchFn ? { fetch: options.fetchFn } : {}),
108
+ });
109
+ }
110
+ get threadId() {
111
+ return this.httpAgent.threadId;
112
+ }
113
+ get state() {
114
+ return this.httpAgent.state;
115
+ }
116
+ /**
117
+ * Run a single AG-UI exchange (with multi-turn re-issue when client-side
118
+ * tool results are produced) and yield normalized events.
119
+ */
120
+ async *run(input) {
121
+ const { messages, state, registry, abortSignal, context } = input;
122
+ const toolCalls = new Map();
123
+ let currentMessages = mergeProtocolMessagesFromSnapshot(this.httpAgent.messages, messages);
124
+ // Replace once at the top with the reconciled AG-UI history. assistant-ui
125
+ // stores only visible chat turns, while AG-UI snapshots may contain
126
+ // model-visible but UI-hidden protocol messages such as system/developer
127
+ // guidance. Preserve those messages across turns when the visible history
128
+ // belongs to the same thread so the next request remains a complete AG-UI
129
+ // conversation without leaking system messages into the rendered chat.
130
+ this.httpAgent.setMessages(currentMessages);
131
+ if (state)
132
+ this.httpAgent.setState(state);
133
+ try {
134
+ for (;;) {
135
+ if (abortSignal?.aborted)
136
+ return;
137
+ const tools = registry.getActiveSchemas();
138
+ const queue = new EventQueue();
139
+ const runId = crypto.randomUUID();
140
+ // Per-turn text accumulator. The model can emit text BEFORE a
141
+ // tool call in the same turn ("Looking up X..." then ui_navigate);
142
+ // dropping it from the follow-up replay changes the next-turn
143
+ // context (no server-side thread state — full history rides each
144
+ // request) and causes inconsistent continuations.
145
+ let turnAssistantText = "";
146
+ // Build the request input now, before runAgent fires — dev tooling
147
+ // consumes this snapshot via the request-sent event. `runId` is
148
+ // pre-generated so the snapshot matches what runAgent emits.
149
+ const runInputSnapshot = {
150
+ threadId: this.httpAgent.threadId,
151
+ runId,
152
+ state: { ...(this.httpAgent.state || {}), ...(state ?? {}) },
153
+ messages: currentMessages,
154
+ tools,
155
+ context: context ?? [],
156
+ };
157
+ queue.push({ type: "turn-started" });
158
+ queue.push({ type: "streaming-status", status: { status: "thinking" } });
159
+ queue.push({ type: "request-sent", input: runInputSnapshot });
160
+ const subscriber = {
161
+ onRunStartedEvent: () => {
162
+ queue.push({ type: "run-started" });
163
+ queue.push({ type: "streaming-status", status: { status: "thinking" } });
164
+ },
165
+ onTextMessageStartEvent: () => {
166
+ queue.push({ type: "streaming-status", status: { status: "streaming" } });
167
+ },
168
+ onTextMessageContentEvent: ({ event }) => {
169
+ turnAssistantText += event.delta;
170
+ queue.push({ type: "text-delta", delta: event.delta });
171
+ },
172
+ onTextMessageEndEvent: () => {
173
+ queue.push({ type: "text-message-end" });
174
+ },
175
+ onToolCallStartEvent: ({ event }) => {
176
+ const id = event.toolCallId;
177
+ const name = event.toolCallName;
178
+ toolCalls.set(id, { id, name, args: "" });
179
+ queue.push({
180
+ type: "streaming-status",
181
+ status: { status: "calling", toolName: name },
182
+ });
183
+ queue.push({
184
+ type: "tool-call-start",
185
+ id,
186
+ name,
187
+ isClientSide: registry.isRegistered(name),
188
+ });
189
+ },
190
+ onToolCallArgsEvent: ({ event }) => {
191
+ const tc = toolCalls.get(event.toolCallId);
192
+ if (tc)
193
+ tc.args += event.delta;
194
+ queue.push({ type: "tool-call-args", id: event.toolCallId, delta: event.delta });
195
+ },
196
+ onToolCallEndEvent: async ({ event }) => {
197
+ const tc = toolCalls.get(event.toolCallId);
198
+ if (!tc)
199
+ return;
200
+ // Intercept client-side tools — execute locally and stash the
201
+ // result so the multi-turn loop can replay it on the next turn.
202
+ if (registry.isRegistered(tc.name)) {
203
+ try {
204
+ const resultJson = await registry.executeTool(tc.name, tc.args);
205
+ tc.result = JSON.parse(resultJson);
206
+ }
207
+ catch (err) {
208
+ tc.result = {
209
+ error: err instanceof Error ? err.message : "Client-side tool failed",
210
+ };
211
+ tc.isError = true;
212
+ }
213
+ }
214
+ queue.push({
215
+ type: "tool-call-end",
216
+ id: event.toolCallId,
217
+ args: tc.args,
218
+ result: tc.result,
219
+ isError: tc.isError,
220
+ });
221
+ },
222
+ onToolCallResultEvent: ({ event }) => {
223
+ const tc = toolCalls.get(event.toolCallId);
224
+ if (!tc)
225
+ return;
226
+ const content = event.content;
227
+ let parsedResult = content;
228
+ let isError = false;
229
+ try {
230
+ const parsed = JSON.parse(content);
231
+ parsedResult = parsed;
232
+ if (parsed && typeof parsed === "object" && "error" in parsed) {
233
+ isError = true;
234
+ }
235
+ }
236
+ catch {
237
+ parsedResult = content;
238
+ }
239
+ tc.result = parsedResult;
240
+ tc.isError = isError;
241
+ queue.push({
242
+ type: "tool-call-result",
243
+ id: event.toolCallId,
244
+ result: parsedResult,
245
+ isError,
246
+ });
247
+ },
248
+ onRunFinishedEvent: () => {
249
+ queue.push({ type: "streaming-status", status: { status: "idle" } });
250
+ queue.push({ type: "run-finished" });
251
+ },
252
+ onRunErrorEvent: ({ event }) => {
253
+ queue.push({ type: "streaming-status", status: { status: "idle" } });
254
+ queue.push({ type: "run-error", message: event.message ?? "Run error" });
255
+ },
256
+ };
257
+ // Wire abort: AG-UI exposes abortRun(); call it when the caller's signal fires.
258
+ const onAbort = () => this.httpAgent.abortRun();
259
+ abortSignal?.addEventListener("abort", onAbort, { once: true });
260
+ // Kick off the run — completion ends the queue. Errors during the
261
+ // run surface via onRunErrorEvent; transport-level rejections
262
+ // (e.g. fetch failure) are propagated through queue.end(err).
263
+ const runPromise = this.httpAgent
264
+ .runAgent({ runId, tools, context: context ?? [] }, subscriber)
265
+ .then(() => queue.end())
266
+ .catch((err) => queue.end(err));
267
+ try {
268
+ for await (const evt of queue.drain()) {
269
+ yield evt;
270
+ if (abortSignal?.aborted) {
271
+ this.httpAgent.abortRun();
272
+ return;
273
+ }
274
+ }
275
+ }
276
+ finally {
277
+ abortSignal?.removeEventListener("abort", onAbort);
278
+ await runPromise; // ensure the run task is settled
279
+ }
280
+ // Decide whether to re-issue: any client-side tool that resolved
281
+ // during this turn and hasn't been replayed yet.
282
+ const pendingClientTools = Array.from(toolCalls.values()).filter((tc) => registry.isRegistered(tc.name) && tc.result !== undefined && !tc.followedUp);
283
+ if (pendingClientTools.length === 0)
284
+ break;
285
+ for (const tc of pendingClientTools)
286
+ tc.followedUp = true;
287
+ // Build follow-up messages with the SAME toolCallId the agent emitted.
288
+ // Tool results are appended to the agent's message history (AG-UI
289
+ // convention — append, never replace). Preserve any pre-tool text
290
+ // the model emitted in this turn so the next-turn context matches
291
+ // what the user actually saw.
292
+ const assistantMsg = {
293
+ id: crypto.randomUUID(),
294
+ role: "assistant",
295
+ content: turnAssistantText,
296
+ toolCalls: pendingClientTools.map((tc) => ({
297
+ id: tc.id,
298
+ type: "function",
299
+ function: { name: tc.name, arguments: tc.args },
300
+ })),
301
+ };
302
+ const toolResultMsgs = pendingClientTools.map((tc) => ({
303
+ id: crypto.randomUUID(),
304
+ role: "tool",
305
+ toolCallId: tc.id,
306
+ content: typeof tc.result === "string" ? tc.result : JSON.stringify(tc.result),
307
+ }));
308
+ currentMessages = [...currentMessages, assistantMsg, ...toolResultMsgs];
309
+ for (const m of [assistantMsg, ...toolResultMsgs]) {
310
+ this.httpAgent.addMessage(m);
311
+ }
312
+ }
313
+ }
314
+ finally {
315
+ // Normalized terminal status — idempotent if onRunFinishedEvent already
316
+ // fired one. Consumers ignore duplicate idle transitions.
317
+ yield { type: "streaming-status", status: { status: "idle" } };
318
+ }
319
+ }
320
+ }
@@ -0,0 +1,54 @@
1
+ import type { StreamingStatus } from "../store/streaming-status-store.js";
2
+ /**
3
+ * Normalized event stream emitted by `AGUIRunner.run()`. The runner is
4
+ * framework-agnostic; consumers (e.g. the assistant-ui shim in
5
+ * `@iloveagents/foundry-web-ui`) translate these events into their own runtime model.
6
+ *
7
+ * `request-sent` carries the exact AG-UI request payload — used by host
8
+ * dev-tooling to capture each turn.
9
+ *
10
+ * `turn-started` fires at the top of every iteration of the multi-turn loop
11
+ * (initial request + each client-tool follow-up). Consumers reset their
12
+ * per-turn buffers (e.g. `currentText`) here.
13
+ */
14
+ export type RunnerEvent = {
15
+ type: "request-sent";
16
+ input: Record<string, unknown>;
17
+ } | {
18
+ type: "turn-started";
19
+ } | {
20
+ type: "run-started";
21
+ } | {
22
+ type: "text-delta";
23
+ delta: string;
24
+ } | {
25
+ type: "text-message-end";
26
+ } | {
27
+ type: "tool-call-start";
28
+ id: string;
29
+ name: string;
30
+ isClientSide: boolean;
31
+ } | {
32
+ type: "tool-call-args";
33
+ id: string;
34
+ delta: string;
35
+ } | {
36
+ type: "tool-call-end";
37
+ id: string;
38
+ args: string;
39
+ result?: unknown;
40
+ isError?: boolean;
41
+ } | {
42
+ type: "tool-call-result";
43
+ id: string;
44
+ result: unknown;
45
+ isError: boolean;
46
+ } | {
47
+ type: "streaming-status";
48
+ status: StreamingStatus;
49
+ } | {
50
+ type: "run-finished";
51
+ } | {
52
+ type: "run-error";
53
+ message: string;
54
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Per-service fetch factory.
3
+ *
4
+ * Returns an authenticated `fetch`-shaped function that:
5
+ * 1. Rewrites local-relative URLs to a configured router base in production
6
+ * (Vite proxy in dev → httpRouteConfigs FQDN in prod).
7
+ * 2. Acquires a Bearer token via the supplied `acquireToken` callback and
8
+ * attaches it as the `Authorization` header.
9
+ *
10
+ * `@iloveagents/foundry-agent` stays auth-mechanism-agnostic — `acquireToken` is
11
+ * supplied by the host (apps/web wires it to MSAL via the `/msal` subpath;
12
+ * future shells could plug in a different token source).
13
+ */
14
+ export interface ServiceFetchOptions {
15
+ /**
16
+ * Acquire an access token for outgoing requests. Return `null` to skip
17
+ * token attachment (callers without auth — e.g. local dev — pass through
18
+ * to native fetch).
19
+ *
20
+ * The optional ``{ forceRefresh: true }`` argument is passed by the
21
+ * fetch interceptor on a 401 retry — the auth layer should bypass
22
+ * its local token cache and round-trip the token endpoint so we
23
+ * stop re-sending an access token the resource server has already
24
+ * rejected (canonical MSAL.js fix for tab-open-overnight 401 loops).
25
+ */
26
+ acquireToken: (options?: {
27
+ forceRefresh?: boolean;
28
+ }) => Promise<string | null>;
29
+ /**
30
+ * Force interactive recovery (e.g. ``loginRedirect``) when even a
31
+ * force-refreshed access token gets rejected by the resource server.
32
+ * The fetch interceptor calls this after a SECOND consecutive 401 —
33
+ * at that point we know the silent refresh produced a token the
34
+ * server still won't accept (audience drift, conditional-access
35
+ * re-eval, tenant-policy change), and the only correct UX is to
36
+ * mint a fresh session.
37
+ *
38
+ * Implementations should clear cached auth state and start a redirect
39
+ * to the IdP. They MUST throw rather than return so the fetch caller
40
+ * can stop processing the in-flight request — when this resolves
41
+ * normally the redirect is in flight and the page is about to
42
+ * navigate away.
43
+ *
44
+ * Optional: when omitted, the fetch interceptor lets the second 401
45
+ * propagate as-is. Hosts without an interactive recovery path (e.g.
46
+ * tests, embedded apps) should leave it unset.
47
+ */
48
+ recoverFromHardAuthFailure?: (reason: unknown) => Promise<never>;
49
+ /**
50
+ * Router FQDN for production (e.g. `https://lastspace-prod.eastus2.example.com`).
51
+ * Empty / undefined leaves the URL untouched (Vite proxy handles routing
52
+ * in dev).
53
+ */
54
+ baseUrl?: string;
55
+ /**
56
+ * Resolve `window.location.origin` (or equivalent) for the current runtime.
57
+ * Defaults to a browser-aware lookup; non-browser callers can override.
58
+ */
59
+ originResolver?: () => string;
60
+ }
61
+ export type ServiceFetch = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
62
+ /**
63
+ * Wrapper thrown when ``acquireToken`` rejects inside the fetch
64
+ * interceptor. Lets the outer 401-retry layer distinguish
65
+ * token-acquisition failures (which warrant interactive recovery) from
66
+ * generic ``fetch`` rejections like network drops, aborts, or CORS
67
+ * preflight failures (which do NOT — those would needlessly bounce the
68
+ * user through ``loginRedirect`` on a transient transport error).
69
+ *
70
+ * Exported so hosts that wrap ``serviceFetch`` further can ``instanceof``
71
+ * against the same class without re-declaring it.
72
+ */
73
+ export declare class TokenAcquisitionError extends Error {
74
+ readonly name = "TokenAcquisitionError";
75
+ readonly cause: unknown;
76
+ constructor(cause: unknown);
77
+ }
78
+ /**
79
+ * Build a service-fetch function. Returns a function with the same shape as
80
+ * `fetch` that rewrites URLs + attaches the Bearer token from `acquireToken`.
81
+ */
82
+ export declare function createServiceFetch(options: ServiceFetchOptions): ServiceFetch;
83
+ /**
84
+ * Error class the fetch interceptor raises (and forwards through
85
+ * ``recoverFromHardAuthFailure``) when a 401 needs interactive
86
+ * recovery. Exposed so the auth-layer recovery can pick up a
87
+ * ``claims`` field if one was extracted from the
88
+ * ``WWW-Authenticate`` header.
89
+ */
90
+ export declare class AuthInteractionRequiredError extends Error {
91
+ readonly name = "AuthInteractionRequiredError";
92
+ readonly claims?: string;
93
+ constructor(message: string, options?: {
94
+ claims?: string;
95
+ });
96
+ }
97
+ /**
98
+ * Extract the claims challenge string from a resource server's
99
+ * ``WWW-Authenticate: Bearer ... claims="…"`` header. Returns
100
+ * ``undefined`` when the header is missing, malformed, or carries
101
+ * no claims directive.
102
+ *
103
+ * The value is forwarded VERBATIM to MSAL's
104
+ * ``acquireTokenRedirect({ claims })``; MSAL handles the
105
+ * base64-url decode + JSON parse itself. We don't try to validate
106
+ * the inner shape — letting Entra speak for itself avoids drift if
107
+ * the schema evolves.
108
+ *
109
+ * Spec: RFC 6750 ``WWW-Authenticate`` + CAE claims-challenge
110
+ * supplement (Microsoft Identity Platform docs).
111
+ */
112
+ export declare function parseClaimsChallengeFromWwwAuthenticate(header: string | null | undefined): string | undefined;