modeldispatcher-client 0.6.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 (45) hide show
  1. package/dist/client.d.ts +57 -0
  2. package/dist/client.js +90 -0
  3. package/dist/client.js.map +1 -0
  4. package/dist/events.d.ts +44 -0
  5. package/dist/events.js +48 -0
  6. package/dist/events.js.map +1 -0
  7. package/dist/index.d.ts +23 -0
  8. package/dist/index.js +19 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/interceptors/appcheck.d.ts +20 -0
  11. package/dist/interceptors/appcheck.js +29 -0
  12. package/dist/interceptors/appcheck.js.map +1 -0
  13. package/dist/interceptors/auth.d.ts +21 -0
  14. package/dist/interceptors/auth.js +30 -0
  15. package/dist/interceptors/auth.js.map +1 -0
  16. package/dist/interceptors/handoff.d.ts +22 -0
  17. package/dist/interceptors/handoff.js +73 -0
  18. package/dist/interceptors/handoff.js.map +1 -0
  19. package/dist/interceptors/interceptor.d.ts +44 -0
  20. package/dist/interceptors/interceptor.js +20 -0
  21. package/dist/interceptors/interceptor.js.map +1 -0
  22. package/dist/interceptors/retry.d.ts +46 -0
  23. package/dist/interceptors/retry.js +56 -0
  24. package/dist/interceptors/retry.js.map +1 -0
  25. package/dist/interceptors/timeout.d.ts +16 -0
  26. package/dist/interceptors/timeout.js +48 -0
  27. package/dist/interceptors/timeout.js.map +1 -0
  28. package/dist/react/useGateway.d.ts +34 -0
  29. package/dist/react/useGateway.js +36 -0
  30. package/dist/react/useGateway.js.map +1 -0
  31. package/dist/types.d.ts +109 -0
  32. package/dist/types.js +12 -0
  33. package/dist/types.js.map +1 -0
  34. package/package.json +52 -0
  35. package/src/client.ts +148 -0
  36. package/src/events.ts +77 -0
  37. package/src/index.ts +56 -0
  38. package/src/interceptors/appcheck.ts +33 -0
  39. package/src/interceptors/auth.ts +34 -0
  40. package/src/interceptors/handoff.ts +91 -0
  41. package/src/interceptors/interceptor.ts +53 -0
  42. package/src/interceptors/retry.ts +92 -0
  43. package/src/interceptors/timeout.ts +48 -0
  44. package/src/react/useGateway.ts +67 -0
  45. package/src/types.ts +112 -0
@@ -0,0 +1,57 @@
1
+ /**
2
+ * {@link GatewayClient}: the facade a Vercel frontend calls.
3
+ *
4
+ * It composes the interceptor chain once (App Check → Auth → retry → timeout →
5
+ * fetch), then `dispatch` runs a request through it and hands the final
6
+ * response to the {@link decodeOutcome} decoder. Every transport failure is
7
+ * normalised into a `{ kind: "network" }` outcome, so callers only ever
8
+ * `switch` on {@link DispatchOutcome.kind} — no thrown errors escape `dispatch`.
9
+ */
10
+ import { GatewayEventBus } from "./events.js";
11
+ import { type RetryOptions } from "./interceptors/retry.js";
12
+ import type { AppCheckTokenProvider, AuthTokenProvider, DispatchOutcome, GatewayResult } from "./types.js";
13
+ /** Construction options for {@link GatewayClient}. */
14
+ export interface GatewayClientOptions {
15
+ /** Endpoint the wrapper is deployed at. Defaults to `"/api/gateway"`. */
16
+ endpoint?: string;
17
+ /** Per-attempt timeout in milliseconds. Defaults to `15000`. */
18
+ timeoutMs?: number;
19
+ /** Firebase App Check token supplier. Omit to disable the header (dev). */
20
+ appCheckTokenProvider?: AppCheckTokenProvider;
21
+ /**
22
+ * Firebase Auth ID token supplier. Omit to disable the header (dev only —
23
+ * the wrapper's default `MD_AUTH_MODE=enforce` will reject every request
24
+ * with `401` without it; see `_lib/auth.py`).
25
+ */
26
+ authTokenProvider?: AuthTokenProvider;
27
+ /** Retry/backoff tuning and injected clock (for tests). */
28
+ retry?: RetryOptions;
29
+ /** Shared event bus; a fresh one is created when omitted. */
30
+ bus?: GatewayEventBus;
31
+ /** Injectable `fetch` (defaults to the global). */
32
+ fetchImpl?: typeof fetch;
33
+ }
34
+ /** Per-call overrides for {@link GatewayClient.dispatch}. */
35
+ export interface DispatchOptions {
36
+ /** Caller cancellation, merged with the internal timeout. */
37
+ signal?: AbortSignal;
38
+ /** Extra headers to merge onto this request. */
39
+ headers?: Record<string, string>;
40
+ }
41
+ /** Resilient client for the ModelDispatcher gateway endpoint. */
42
+ export declare class GatewayClient {
43
+ #private;
44
+ /** Event bus other subscribers (e.g. the wizard host) can listen on. */
45
+ readonly bus: GatewayEventBus;
46
+ constructor(options?: GatewayClientOptions);
47
+ /**
48
+ * Dispatch a request body to the gateway and return a typed outcome.
49
+ *
50
+ * Never rejects: transport failures surface as `{ kind: "network" }`.
51
+ *
52
+ * @typeParam T - Expected success shape (defaults to {@link GatewayResult}).
53
+ */
54
+ dispatch<T = GatewayResult>(body: unknown, options?: DispatchOptions): Promise<DispatchOutcome<T>>;
55
+ }
56
+ /** Convenience factory mirroring the constructor. */
57
+ export declare function createGatewayClient(options?: GatewayClientOptions): GatewayClient;
package/dist/client.js ADDED
@@ -0,0 +1,90 @@
1
+ /**
2
+ * {@link GatewayClient}: the facade a Vercel frontend calls.
3
+ *
4
+ * It composes the interceptor chain once (App Check → Auth → retry → timeout →
5
+ * fetch), then `dispatch` runs a request through it and hands the final
6
+ * response to the {@link decodeOutcome} decoder. Every transport failure is
7
+ * normalised into a `{ kind: "network" }` outcome, so callers only ever
8
+ * `switch` on {@link DispatchOutcome.kind} — no thrown errors escape `dispatch`.
9
+ */
10
+ import { GatewayEventBus } from "./events.js";
11
+ import { appCheckInterceptor } from "./interceptors/appcheck.js";
12
+ import { authInterceptor } from "./interceptors/auth.js";
13
+ import { decodeOutcome } from "./interceptors/handoff.js";
14
+ import { compose, } from "./interceptors/interceptor.js";
15
+ import { retryInterceptor } from "./interceptors/retry.js";
16
+ import { TimeoutError, timeoutInterceptor } from "./interceptors/timeout.js";
17
+ const DEFAULT_ENDPOINT = "/api/gateway";
18
+ const DEFAULT_TIMEOUT_MS = 15_000;
19
+ function classifyNetworkError(error) {
20
+ if (error instanceof TimeoutError) {
21
+ return { kind: "timeout", message: error.message, cause: error };
22
+ }
23
+ // `fetch` throws a TypeError for DNS/connection failures (offline).
24
+ if (error instanceof TypeError) {
25
+ return { kind: "offline", message: error.message, cause: error };
26
+ }
27
+ const kind = "unknown";
28
+ const message = error instanceof Error ? error.message : String(error);
29
+ return { kind, message, cause: error };
30
+ }
31
+ /** Resilient client for the ModelDispatcher gateway endpoint. */
32
+ export class GatewayClient {
33
+ /** Event bus other subscribers (e.g. the wizard host) can listen on. */
34
+ bus;
35
+ #endpoint;
36
+ #timeoutMs;
37
+ #send;
38
+ constructor(options = {}) {
39
+ this.bus = options.bus ?? new GatewayEventBus();
40
+ this.#endpoint = options.endpoint ?? DEFAULT_ENDPOINT;
41
+ this.#timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
42
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
43
+ const chain = [];
44
+ if (options.appCheckTokenProvider) {
45
+ chain.push(appCheckInterceptor(options.appCheckTokenProvider));
46
+ }
47
+ if (options.authTokenProvider) {
48
+ chain.push(authInterceptor(options.authTokenProvider));
49
+ }
50
+ chain.push(retryInterceptor(options.retry ?? {}));
51
+ chain.push(timeoutInterceptor());
52
+ const terminal = (config) => fetchImpl(config.url, {
53
+ method: config.method,
54
+ headers: config.headers,
55
+ body: JSON.stringify(config.body),
56
+ ...(config.signal ? { signal: config.signal } : {}),
57
+ });
58
+ this.#send = compose(chain, terminal);
59
+ }
60
+ /**
61
+ * Dispatch a request body to the gateway and return a typed outcome.
62
+ *
63
+ * Never rejects: transport failures surface as `{ kind: "network" }`.
64
+ *
65
+ * @typeParam T - Expected success shape (defaults to {@link GatewayResult}).
66
+ */
67
+ async dispatch(body, options = {}) {
68
+ const config = {
69
+ url: this.#endpoint,
70
+ method: "POST",
71
+ headers: { "content-type": "application/json", ...options.headers },
72
+ body,
73
+ timeoutMs: this.#timeoutMs,
74
+ ...(options.signal ? { signal: options.signal } : {}),
75
+ };
76
+ let response;
77
+ try {
78
+ response = await this.#send(config);
79
+ }
80
+ catch (error) {
81
+ return { kind: "network", error: classifyNetworkError(error) };
82
+ }
83
+ return decodeOutcome(response, this.bus);
84
+ }
85
+ }
86
+ /** Convenience factory mirroring the constructor. */
87
+ export function createGatewayClient(options = {}) {
88
+ return new GatewayClient(options);
89
+ }
90
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * A tiny, strongly-typed event bus (Observer pattern).
3
+ *
4
+ * It exists to decouple *detecting* a Stage-2 handoff (deep inside the response
5
+ * pipeline) from *reacting* to it (a wizard component that may live anywhere in
6
+ * the tree). The interceptor publishes; {@link GatewayEventBus} fans the event
7
+ * out to every subscriber without either side knowing about the other.
8
+ */
9
+ import type { Handoff } from "./types.js";
10
+ /** Payload emitted whenever the gateway returns a structured handoff. */
11
+ export interface HandoffEvent {
12
+ /** HTTP status the gateway chose: `402` (budget wall) or `429` (rate window). */
13
+ readonly status: number;
14
+ /** The decoded handoff instruction. */
15
+ readonly handoff: Handoff;
16
+ }
17
+ /** The map of event names to their payload types. */
18
+ export interface GatewayEventMap {
19
+ handoff: HandoffEvent;
20
+ }
21
+ /** Unsubscribe handle returned by {@link GatewayEventBus.on}. */
22
+ export type Unsubscribe = () => void;
23
+ /** Listener signature for a given event. */
24
+ export type Listener<E extends keyof GatewayEventMap> = (event: GatewayEventMap[E]) => void;
25
+ /**
26
+ * Synchronous, in-memory pub/sub for gateway events.
27
+ *
28
+ * Listener exceptions are swallowed (and forwarded to `onListenerError` when
29
+ * provided) so one bad subscriber can never break delivery to the others.
30
+ */
31
+ export declare class GatewayEventBus {
32
+ #private;
33
+ constructor(onListenerError?: (error: unknown) => void);
34
+ /**
35
+ * Subscribe to an event.
36
+ *
37
+ * @returns A function that removes this exact listener when called.
38
+ */
39
+ on<E extends keyof GatewayEventMap>(event: E, listener: Listener<E>): Unsubscribe;
40
+ /** Publish an event to every current subscriber. */
41
+ emit<E extends keyof GatewayEventMap>(event: E, payload: GatewayEventMap[E]): void;
42
+ /** Remove all listeners (useful in test teardown). */
43
+ clear(): void;
44
+ }
package/dist/events.js ADDED
@@ -0,0 +1,48 @@
1
+ /**
2
+ * A tiny, strongly-typed event bus (Observer pattern).
3
+ *
4
+ * It exists to decouple *detecting* a Stage-2 handoff (deep inside the response
5
+ * pipeline) from *reacting* to it (a wizard component that may live anywhere in
6
+ * the tree). The interceptor publishes; {@link GatewayEventBus} fans the event
7
+ * out to every subscriber without either side knowing about the other.
8
+ */
9
+ /**
10
+ * Synchronous, in-memory pub/sub for gateway events.
11
+ *
12
+ * Listener exceptions are swallowed (and forwarded to `onListenerError` when
13
+ * provided) so one bad subscriber can never break delivery to the others.
14
+ */
15
+ export class GatewayEventBus {
16
+ #listeners = { handoff: new Set() };
17
+ #onListenerError;
18
+ constructor(onListenerError = () => { }) {
19
+ this.#onListenerError = onListenerError;
20
+ }
21
+ /**
22
+ * Subscribe to an event.
23
+ *
24
+ * @returns A function that removes this exact listener when called.
25
+ */
26
+ on(event, listener) {
27
+ this.#listeners[event].add(listener);
28
+ return () => {
29
+ this.#listeners[event].delete(listener);
30
+ };
31
+ }
32
+ /** Publish an event to every current subscriber. */
33
+ emit(event, payload) {
34
+ for (const listener of this.#listeners[event]) {
35
+ try {
36
+ listener(payload);
37
+ }
38
+ catch (error) {
39
+ this.#onListenerError(error);
40
+ }
41
+ }
42
+ }
43
+ /** Remove all listeners (useful in test teardown). */
44
+ clear() {
45
+ this.#listeners.handoff.clear();
46
+ }
47
+ }
48
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAyBH;;;;;GAKG;AACH,MAAM,OAAO,eAAe;IACjB,UAAU,GAEf,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,EAAE,CAAC;IAElB,gBAAgB,CAA2B;IAEpD,YAAY,kBAA4C,GAAG,EAAE,GAAE,CAAC;QAC9D,IAAI,CAAC,gBAAgB,GAAG,eAAe,CAAC;IAC1C,CAAC;IAED;;;;OAIG;IACH,EAAE,CAAkC,KAAQ,EAAE,QAAqB;QACjE,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACrC,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC1C,CAAC,CAAC;IACJ,CAAC;IAED,oDAAoD;IACpD,IAAI,CAAkC,KAAQ,EAAE,OAA2B;QACzE,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;YAC9C,IAAI,CAAC;gBACH,QAAQ,CAAC,OAAO,CAAC,CAAC;YACpB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC;YAC/B,CAAC;QACH,CAAC;IACH,CAAC;IAED,sDAAsD;IACtD,KAAK;QACH,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IAClC,CAAC;CACF"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * `modeldispatcher-client` — resilient TypeScript client for the
3
+ * ModelDispatcher AI gateway.
4
+ *
5
+ * Public surface: construct a {@link GatewayClient} with
6
+ * {@link createGatewayClient}, `dispatch` request bodies, and `switch` on the
7
+ * returned {@link DispatchOutcome}. React apps use `useGateway` from the
8
+ * `modeldispatcher-client/react` subpath. All resilience — timeouts, 5xx
9
+ * backoff, and 402/429 key-wizard handoff decoding — is built in.
10
+ */
11
+ export { GatewayClient, createGatewayClient } from "./client.js";
12
+ export type { GatewayClientOptions, DispatchOptions } from "./client.js";
13
+ export { GatewayEventBus } from "./events.js";
14
+ export type { GatewayEventMap, HandoffEvent, Listener, Unsubscribe, } from "./events.js";
15
+ export { compose } from "./interceptors/interceptor.js";
16
+ export type { GatewayRequestConfig, Interceptor, Send } from "./interceptors/interceptor.js";
17
+ export { appCheckInterceptor, APP_CHECK_HEADER } from "./interceptors/appcheck.js";
18
+ export { authInterceptor, AUTHORIZATION_HEADER } from "./interceptors/auth.js";
19
+ export { timeoutInterceptor, TimeoutError } from "./interceptors/timeout.js";
20
+ export { retryInterceptor, backoffDelayMs, DEFAULT_RETRY_POLICY, REAL_SCHEDULER, } from "./interceptors/retry.js";
21
+ export type { RetryPolicy, RetryOptions } from "./interceptors/retry.js";
22
+ export { decodeOutcome, isHandoffPayload, HANDOFF_ACTIONS, } from "./interceptors/handoff.js";
23
+ export type { Attempt, AppCheckTokenProvider, AuthTokenProvider, DispatchOutcome, GatewayError, GatewayResult, Handoff, HandoffAction, NetworkError, NetworkErrorKind, Scheduler, Step, Usage, } from "./types.js";
package/dist/index.js ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * `modeldispatcher-client` — resilient TypeScript client for the
3
+ * ModelDispatcher AI gateway.
4
+ *
5
+ * Public surface: construct a {@link GatewayClient} with
6
+ * {@link createGatewayClient}, `dispatch` request bodies, and `switch` on the
7
+ * returned {@link DispatchOutcome}. React apps use `useGateway` from the
8
+ * `modeldispatcher-client/react` subpath. All resilience — timeouts, 5xx
9
+ * backoff, and 402/429 key-wizard handoff decoding — is built in.
10
+ */
11
+ export { GatewayClient, createGatewayClient } from "./client.js";
12
+ export { GatewayEventBus } from "./events.js";
13
+ export { compose } from "./interceptors/interceptor.js";
14
+ export { appCheckInterceptor, APP_CHECK_HEADER } from "./interceptors/appcheck.js";
15
+ export { authInterceptor, AUTHORIZATION_HEADER } from "./interceptors/auth.js";
16
+ export { timeoutInterceptor, TimeoutError } from "./interceptors/timeout.js";
17
+ export { retryInterceptor, backoffDelayMs, DEFAULT_RETRY_POLICY, REAL_SCHEDULER, } from "./interceptors/retry.js";
18
+ export { decodeOutcome, isHandoffPayload, HANDOFF_ACTIONS, } from "./interceptors/handoff.js";
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAGjE,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAQ9C,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAGxD,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACnF,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC/E,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC7E,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,oBAAoB,EACpB,cAAc,GACf,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,eAAe,GAChB,MAAM,2BAA2B,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Firebase App Check interceptor (request side).
3
+ *
4
+ * Mints/attaches the attestation token the Vercel Python wrapper verifies before
5
+ * it will invoke the gateway. The token *provider* is injected rather than
6
+ * importing the Firebase SDK directly, so this package carries no hard Firebase
7
+ * dependency and stays trivially testable — the app wires
8
+ * `() => getToken(appCheck).then(r => r.token)` at construction time.
9
+ */
10
+ import type { AppCheckTokenProvider } from "../types.js";
11
+ import type { Interceptor } from "./interceptor.js";
12
+ /** HTTP header the wrapper reads (matches `firebase_admin.app_check`). */
13
+ export declare const APP_CHECK_HEADER = "X-Firebase-AppCheck";
14
+ /**
15
+ * Build the interceptor that stamps every request with an App Check token.
16
+ *
17
+ * A `null` token (provider opted out, or dev bypass) is simply not attached;
18
+ * the wrapper then decides whether to reject based on its own environment.
19
+ */
20
+ export declare function appCheckInterceptor(provider: AppCheckTokenProvider): Interceptor;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Firebase App Check interceptor (request side).
3
+ *
4
+ * Mints/attaches the attestation token the Vercel Python wrapper verifies before
5
+ * it will invoke the gateway. The token *provider* is injected rather than
6
+ * importing the Firebase SDK directly, so this package carries no hard Firebase
7
+ * dependency and stays trivially testable — the app wires
8
+ * `() => getToken(appCheck).then(r => r.token)` at construction time.
9
+ */
10
+ /** HTTP header the wrapper reads (matches `firebase_admin.app_check`). */
11
+ export const APP_CHECK_HEADER = "X-Firebase-AppCheck";
12
+ /**
13
+ * Build the interceptor that stamps every request with an App Check token.
14
+ *
15
+ * A `null` token (provider opted out, or dev bypass) is simply not attached;
16
+ * the wrapper then decides whether to reject based on its own environment.
17
+ */
18
+ export function appCheckInterceptor(provider) {
19
+ return {
20
+ async intercept(config, next) {
21
+ const token = await provider();
22
+ if (token) {
23
+ config.headers[APP_CHECK_HEADER] = token;
24
+ }
25
+ return next(config);
26
+ },
27
+ };
28
+ }
29
+ //# sourceMappingURL=appcheck.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"appcheck.js","sourceRoot":"","sources":["../../src/interceptors/appcheck.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,0EAA0E;AAC1E,MAAM,CAAC,MAAM,gBAAgB,GAAG,qBAAqB,CAAC;AAEtD;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,QAA+B;IACjE,OAAO;QACL,KAAK,CAAC,SAAS,CAAC,MAA4B,EAAE,IAAU;YACtD,MAAM,KAAK,GAAG,MAAM,QAAQ,EAAE,CAAC;YAC/B,IAAI,KAAK,EAAE,CAAC;gBACV,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,GAAG,KAAK,CAAC;YAC3C,CAAC;YACD,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC;QACtB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Firebase Auth interceptor (request side).
3
+ *
4
+ * Mints/attaches the ID token the Vercel Python wrapper verifies to derive an
5
+ * *authoritative* tenant id (see `_lib/auth.py`). Mirrors
6
+ * {@link appCheckInterceptor} exactly: the token provider is injected rather
7
+ * than importing the Firebase SDK directly, so this package carries no hard
8
+ * Firebase dependency. Without this header, the wrapper's `MD_AUTH_MODE=enforce`
9
+ * default rejects the request with `401` before the gateway ever runs.
10
+ */
11
+ import type { AuthTokenProvider } from "../types.js";
12
+ import type { Interceptor } from "./interceptor.js";
13
+ /** HTTP header the wrapper reads the bearer token from. */
14
+ export declare const AUTHORIZATION_HEADER = "Authorization";
15
+ /**
16
+ * Build the interceptor that stamps every request with a Firebase Auth ID token.
17
+ *
18
+ * A `null` token (provider opted out, or dev bypass) is simply not attached;
19
+ * the wrapper then decides whether to reject based on its own `MD_AUTH_MODE`.
20
+ */
21
+ export declare function authInterceptor(provider: AuthTokenProvider): Interceptor;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Firebase Auth interceptor (request side).
3
+ *
4
+ * Mints/attaches the ID token the Vercel Python wrapper verifies to derive an
5
+ * *authoritative* tenant id (see `_lib/auth.py`). Mirrors
6
+ * {@link appCheckInterceptor} exactly: the token provider is injected rather
7
+ * than importing the Firebase SDK directly, so this package carries no hard
8
+ * Firebase dependency. Without this header, the wrapper's `MD_AUTH_MODE=enforce`
9
+ * default rejects the request with `401` before the gateway ever runs.
10
+ */
11
+ /** HTTP header the wrapper reads the bearer token from. */
12
+ export const AUTHORIZATION_HEADER = "Authorization";
13
+ /**
14
+ * Build the interceptor that stamps every request with a Firebase Auth ID token.
15
+ *
16
+ * A `null` token (provider opted out, or dev bypass) is simply not attached;
17
+ * the wrapper then decides whether to reject based on its own `MD_AUTH_MODE`.
18
+ */
19
+ export function authInterceptor(provider) {
20
+ return {
21
+ async intercept(config, next) {
22
+ const token = await provider();
23
+ if (token) {
24
+ config.headers[AUTHORIZATION_HEADER] = `Bearer ${token}`;
25
+ }
26
+ return next(config);
27
+ },
28
+ };
29
+ }
30
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/interceptors/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,2DAA2D;AAC3D,MAAM,CAAC,MAAM,oBAAoB,GAAG,eAAe,CAAC;AAEpD;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,QAA2B;IACzD,OAAO;QACL,KAAK,CAAC,SAAS,CAAC,MAA4B,EAAE,IAAU;YACtD,MAAM,KAAK,GAAG,MAAM,QAAQ,EAAE,CAAC;YAC/B,IAAI,KAAK,EAAE,CAAC;gBACV,MAAM,CAAC,OAAO,CAAC,oBAAoB,CAAC,GAAG,UAAU,KAAK,EAAE,CAAC;YAC3D,CAAC;YACD,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC;QACtB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Response decoder: HTTP {@link Response} → {@link DispatchOutcome}.
3
+ *
4
+ * This is where the gateway's `to_payload()` contract is interpreted. A `2xx`
5
+ * body becomes `{ kind: "ok" }`; a non-OK body whose `action` names a known
6
+ * handoff becomes `{ kind: "handoff" }` *and* is published on the event bus so a
7
+ * decoupled wizard host can react; anything else becomes `{ kind: "error" }`.
8
+ * Runs after the retry interceptor, so only *final* responses reach it.
9
+ */
10
+ import type { GatewayEventBus } from "../events.js";
11
+ import type { DispatchOutcome, Handoff, HandoffAction } from "../types.js";
12
+ /** The actions this client knows how to surface. */
13
+ export declare const HANDOFF_ACTIONS: readonly HandoffAction[];
14
+ /** Type guard: does an arbitrary parsed body match the {@link Handoff} shape? */
15
+ export declare function isHandoffPayload(body: unknown): body is Handoff;
16
+ /**
17
+ * Decode a final response into a typed outcome.
18
+ *
19
+ * @param response - The response emerging from the interceptor chain.
20
+ * @param bus - Event bus a handoff is published on before returning.
21
+ */
22
+ export declare function decodeOutcome<T>(response: Response, bus: GatewayEventBus): Promise<DispatchOutcome<T>>;
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Response decoder: HTTP {@link Response} → {@link DispatchOutcome}.
3
+ *
4
+ * This is where the gateway's `to_payload()` contract is interpreted. A `2xx`
5
+ * body becomes `{ kind: "ok" }`; a non-OK body whose `action` names a known
6
+ * handoff becomes `{ kind: "handoff" }` *and* is published on the event bus so a
7
+ * decoupled wizard host can react; anything else becomes `{ kind: "error" }`.
8
+ * Runs after the retry interceptor, so only *final* responses reach it.
9
+ */
10
+ /** The actions this client knows how to surface. */
11
+ export const HANDOFF_ACTIONS = [
12
+ "trigger_key_wizard",
13
+ "upgrade_plan",
14
+ "retry_later",
15
+ ];
16
+ function isRecord(value) {
17
+ return typeof value === "object" && value !== null;
18
+ }
19
+ /** Type guard: does an arbitrary parsed body match the {@link Handoff} shape? */
20
+ export function isHandoffPayload(body) {
21
+ if (!isRecord(body))
22
+ return false;
23
+ return (typeof body["error"] === "string" &&
24
+ typeof body["provider"] === "string" &&
25
+ typeof body["action"] === "string" &&
26
+ HANDOFF_ACTIONS.includes(body["action"]));
27
+ }
28
+ /** Parse a JSON body without throwing; returns `undefined` on empty/invalid. */
29
+ async function readJson(response) {
30
+ const text = await response.text();
31
+ if (!text)
32
+ return undefined;
33
+ try {
34
+ return JSON.parse(text);
35
+ }
36
+ catch {
37
+ return undefined;
38
+ }
39
+ }
40
+ function toGatewayError(body, status) {
41
+ if (isRecord(body) && typeof body["error"] === "string") {
42
+ const detail = body["detail"];
43
+ return {
44
+ error: body["error"],
45
+ ...(typeof detail === "string" ? { detail } : {}),
46
+ };
47
+ }
48
+ return { error: "unexpected_response", detail: `HTTP ${status}` };
49
+ }
50
+ /**
51
+ * Decode a final response into a typed outcome.
52
+ *
53
+ * @param response - The response emerging from the interceptor chain.
54
+ * @param bus - Event bus a handoff is published on before returning.
55
+ */
56
+ export async function decodeOutcome(response, bus) {
57
+ const body = await readJson(response);
58
+ if (response.ok) {
59
+ return { kind: "ok", result: body };
60
+ }
61
+ if (isHandoffPayload(body)) {
62
+ const handoff = {
63
+ error: body.error,
64
+ provider: body.provider,
65
+ action: body.action,
66
+ ...(body.detail !== undefined ? { detail: body.detail } : {}),
67
+ };
68
+ bus.emit("handoff", { status: response.status, handoff });
69
+ return { kind: "handoff", status: response.status, handoff };
70
+ }
71
+ return { kind: "error", status: response.status, error: toGatewayError(body, response.status) };
72
+ }
73
+ //# sourceMappingURL=handoff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handoff.js","sourceRoot":"","sources":["../../src/interceptors/handoff.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAUH,oDAAoD;AACpD,MAAM,CAAC,MAAM,eAAe,GAA6B;IACvD,oBAAoB;IACpB,cAAc;IACd,aAAa;CACd,CAAC;AAEF,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAC;AACrD,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,gBAAgB,CAAC,IAAa;IAC5C,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IAClC,OAAO,CACL,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,QAAQ;QACjC,OAAO,IAAI,CAAC,UAAU,CAAC,KAAK,QAAQ;QACpC,OAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,QAAQ;QACjC,eAAqC,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAChE,CAAC;AACJ,CAAC;AAED,gFAAgF;AAChF,KAAK,UAAU,QAAQ,CAAC,QAAkB;IACxC,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IACnC,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAC5B,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAY,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,IAAa,EAAE,MAAc;IACnD,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;QACxD,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC9B,OAAO;YACL,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC;YACpB,GAAG,CAAC,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAClD,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,qBAAqB,EAAE,MAAM,EAAE,QAAQ,MAAM,EAAE,EAAE,CAAC;AACpE,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,QAAkB,EAClB,GAAoB;IAEpB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAEtC,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;QAChB,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAS,EAAE,CAAC;IAC3C,CAAC;IAED,IAAI,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAY;YACvB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,GAAG,CAAC,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC9D,CAAC;QACF,GAAG,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;QAC1D,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;IAC/D,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,KAAK,EAAE,cAAc,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;AAClG,CAAC"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Interceptor pipeline contracts (Chain of Responsibility).
3
+ *
4
+ * Each {@link Interceptor} receives the outgoing request config and a `next`
5
+ * continuation. It may mutate the config, short-circuit, wrap `next` with
6
+ * cross-cutting behaviour (timeout, retry), and inspect the resulting response
7
+ * before returning it. Interceptors are composed as an onion: the first in the
8
+ * list is the outermost layer.
9
+ */
10
+ /** The in-flight request description handed down the interceptor chain. */
11
+ export interface GatewayRequestConfig {
12
+ /** Absolute or app-relative endpoint, e.g. `"/api/gateway"`. */
13
+ url: string;
14
+ /** HTTP method; the gateway endpoint is `POST`. */
15
+ method: string;
16
+ /** Mutable header bag; interceptors add to it (App Check, content-type). */
17
+ headers: Record<string, string>;
18
+ /** JSON-serialisable request body. */
19
+ body: unknown;
20
+ /** Per-attempt timeout in milliseconds. */
21
+ timeoutMs: number;
22
+ /** Optional caller-supplied cancellation signal, merged with the timeout. */
23
+ signal?: AbortSignal;
24
+ }
25
+ /** Terminal continuation: performs exactly one transport attempt. */
26
+ export type Send = (config: GatewayRequestConfig) => Promise<Response>;
27
+ /** A single layer of the request/response pipeline. */
28
+ export interface Interceptor {
29
+ /**
30
+ * Process the request and return a response.
31
+ *
32
+ * @param config - The (mutable) request configuration.
33
+ * @param next - Continuation invoking the remaining inner layers.
34
+ */
35
+ intercept(config: GatewayRequestConfig, next: Send): Promise<Response>;
36
+ }
37
+ /**
38
+ * Fold a list of interceptors around a terminal {@link Send} into one `Send`.
39
+ *
40
+ * The first interceptor becomes the outermost layer, so a list of
41
+ * `[appcheck, retry, timeout]` yields `appcheck(retry(timeout(fetch)))` — retry
42
+ * re-drives the timeout+transport on each attempt.
43
+ */
44
+ export declare function compose(interceptors: readonly Interceptor[], terminal: Send): Send;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Interceptor pipeline contracts (Chain of Responsibility).
3
+ *
4
+ * Each {@link Interceptor} receives the outgoing request config and a `next`
5
+ * continuation. It may mutate the config, short-circuit, wrap `next` with
6
+ * cross-cutting behaviour (timeout, retry), and inspect the resulting response
7
+ * before returning it. Interceptors are composed as an onion: the first in the
8
+ * list is the outermost layer.
9
+ */
10
+ /**
11
+ * Fold a list of interceptors around a terminal {@link Send} into one `Send`.
12
+ *
13
+ * The first interceptor becomes the outermost layer, so a list of
14
+ * `[appcheck, retry, timeout]` yields `appcheck(retry(timeout(fetch)))` — retry
15
+ * re-drives the timeout+transport on each attempt.
16
+ */
17
+ export function compose(interceptors, terminal) {
18
+ return interceptors.reduceRight((next, interceptor) => (config) => interceptor.intercept(config, next), terminal);
19
+ }
20
+ //# sourceMappingURL=interceptor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interceptor.js","sourceRoot":"","sources":["../../src/interceptors/interceptor.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAgCH;;;;;;GAMG;AACH,MAAM,UAAU,OAAO,CAAC,YAAoC,EAAE,QAAc;IAC1E,OAAO,YAAY,CAAC,WAAW,CAC7B,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,WAAW,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,CAAC,EACtE,QAAQ,CACT,CAAC;AACJ,CAAC"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Retry interceptor with exponential backoff (Policy object pattern).
3
+ *
4
+ * Re-issues the request when the response status is in
5
+ * {@link RetryPolicy.retryableStatuses} — the 5xx family — sleeping
6
+ * `min(maxDelayMs, baseDelayMs * 2 ** attempt)` between tries, optionally with
7
+ * full jitter to avoid a thundering herd. It deliberately does **not** retry
8
+ * `402`/`429`: those are structured quota handoffs that must reach the UI intact,
9
+ * not transient faults to paper over. Transport errors thrown by inner layers
10
+ * (e.g. a timeout) propagate unchanged so the client can classify them.
11
+ */
12
+ import type { Scheduler } from "../types.js";
13
+ import type { Interceptor } from "./interceptor.js";
14
+ /** Tunable backoff parameters. */
15
+ export interface RetryPolicy {
16
+ /** Maximum *additional* attempts after the first (e.g. `3`). */
17
+ readonly maxRetries: number;
18
+ /** Base delay in milliseconds (e.g. `250`). */
19
+ readonly baseDelayMs: number;
20
+ /** Upper bound on any single backoff sleep. */
21
+ readonly maxDelayMs: number;
22
+ /** Apply full jitter (`random(0, delay)`) when `true`. */
23
+ readonly jitter: boolean;
24
+ /** Statuses worth retrying. Never include `402`/`429`. */
25
+ readonly retryableStatuses: readonly number[];
26
+ }
27
+ /** Sensible defaults for a Vercel ↔ serverless-Python round trip. */
28
+ export declare const DEFAULT_RETRY_POLICY: RetryPolicy;
29
+ /** Real-time scheduler backing the default client. */
30
+ export declare const REAL_SCHEDULER: Scheduler;
31
+ /**
32
+ * Compute the backoff for a given zero-based attempt index.
33
+ *
34
+ * @param policy - The active {@link RetryPolicy}.
35
+ * @param attempt - Number of retries already performed (0 for the first retry).
36
+ * @param random - Source of `[0, 1)` randomness (injected for deterministic tests).
37
+ */
38
+ export declare function backoffDelayMs(policy: RetryPolicy, attempt: number, random?: () => number): number;
39
+ /** Options for {@link retryInterceptor}. */
40
+ export interface RetryOptions {
41
+ readonly policy?: RetryPolicy;
42
+ readonly scheduler?: Scheduler;
43
+ readonly random?: () => number;
44
+ }
45
+ /** Build the backoff/retry {@link Interceptor}. */
46
+ export declare function retryInterceptor(options?: RetryOptions): Interceptor;