import type { TokeniteConfig, AuthorizeOptions, PopupOptions, PopupResult, TokenResponse, Provider, ProxyCallOptions, ProxyResponse, AccessContext, InboundFlavor, TopUpOptions, TopUpResult, CallWithRecoveryOptions } from './types.js'; import { type StatusInfo } from './status.js'; import { type AppLlmCatalog } from '@tokenite/shared'; /** * Tokenite client. * * Two ways to obtain an access token: * * **Modal (single-page apps)** — open an iframe consent screen and * receive an OAuth authorization code. The code must be exchanged * server-side because the exchange requires `clientSecret`: * ```typescript * const { code } = await tk.popup({ suggestedBudget: 5 }); * await fetch('/api/auth/exchange', { * method: 'POST', * body: JSON.stringify({ code }), * }); * ``` * * **Redirect (server-side)** — classic OAuth bounce: * ```typescript * res.redirect(tk.getAuthorizeUrl()); * // ...later in /callback: * const { access_token } = await tk.exchangeCode(req.query.code); * ``` * * Once you have the access token, call the LLM through the proxy: * ```typescript * const result = await tk.call({ * accessToken, * provider: 'anthropic', * path: '/v1/messages', * body: { model: 'claude-3-5-sonnet-latest', max_tokens: 1024, messages: [...] }, * }); * if (isProxyError(result)) console.error(result.error); * else console.log(result.data); * ``` * * For streaming responses, use a vendor SDK with `baseURL: tk.proxyUrl(...)` * — streams bypass the unified envelope and are forwarded as-is. */ export declare const Tokenite: (config: TokeniteConfig) => { /** * Build the authorization URL for a full-page redirect. * * Pass `prompt: 'select_account'` on "Sign in with Tokenite" buttons * that follow a sign-out — Tokenite shows a "Continue as ?" * card with a "Use a different account" option instead of silently * dropping the user back into the app. The user's existing spending * limit is preserved (no budget re-entry): * ```typescript * res.redirect(tk.getAuthorizeUrl({ prompt: 'select_account' })); * ``` */ getAuthorizeUrl: (options?: AuthorizeOptions) => string; /** * Open the consent screen and resolve with an OAuth authorization * code when the user approves. The code must then be exchanged * server-side via `tk.exchangeCode(code)` (the exchange requires * `clientSecret`, which must never run in browser code). * * Two presentation modes: * * - `mode: 'iframe'` (default) — overlay an iframe modal in the * current window. Cleaner UX, but the consent screen's host must * allow being framed (no `X-Frame-Options: DENY`). * - `mode: 'window'` — open a separate browser popup window via * `window.open`. Works regardless of frame policy. * * ```typescript * const { code } = await tk.popup({ suggestedBudget: 5 }); * await fetch('/api/auth/exchange', { * method: 'POST', * body: JSON.stringify({ code }), * }); * ``` * * On a "Sign in with Tokenite" button shown after the user signed * out, pass `prompt: 'select_account'` — Tokenite shows a "Continue * as ?" card with a "Use a different account" option instead * of silently re-authorizing. The user's existing spending limit is * preserved: * ```typescript * const { code } = await tk.popup({ prompt: 'select_account' }); * ``` */ popup: (options?: PopupOptions) => Promise; /** * Open the funding picker so a signed-in user can switch where this * app's tokens are funded from — their own provider key, a team * budget, or a campaign they hold — **without leaving your app**. * * Resolves with a fresh authorization `code`; exchange it server-side * via `tk.exchangeCode(code)` to get a new access token funded by the * chosen source, then **replace the token you were using**. The old * token keeps its old funding until you swap it out. * * Thin wrapper over `popup({ prompt: 'select_funding' })` — named so * it's clear it switches *funding*, not the user's login. * * ```typescript * // e.g. behind a "Change funding" button inside your app * const r = await tk.switchFunding(); * if ('code' in r) { * const { access_token } = await exchangeOnYourServer(r.code); * useToken(access_token); // swap to the newly-funded token * } * ``` */ switchFunding: (options?: PopupOptions) => Promise; /** * Exchange an authorization code for an access token. * Call this server-side in your callback handler. * Requires clientSecret to be set in config. */ exchangeCode: (code: string) => Promise; /** * Mint a **service access token** for app-side LLM calls that are not * tied to any end user — billed to the builder key you configured for * this app on the dashboard. Use it for summaries, moderation, agent * loops, or any inference your users shouldn't have to pay for. * * Server-side only (requires `clientSecret`). The returned token is a * normal access token: pass it to `tk.call({ accessToken, ... })`. * If the app has builder keys for more than one provider, pass * `provider` to pick which key to bill. * * ```typescript * const { access_token } = await tk.getServiceToken(); * const res = await tk.call({ * accessToken: access_token, * provider: 'anthropic', * path: '/v1/messages', * body: { model: 'claude-haiku-4-5', max_tokens: 256, messages: [...] }, * }); * ``` */ getServiceToken: (options?: { provider?: Provider; }) => Promise; /** * Make an authenticated, non-streaming request through the proxy. * Returns a unified envelope: `ProxySuccess` on success, `ProxyError` * on failure. Narrow the result with `isProxyError` / `isProxySuccess`. * * For streaming responses, use a vendor SDK with `baseURL: tk.proxyUrl(...)` * — streams bypass the envelope and are forwarded as-is. * * ```typescript * const result = await tk.call({ * accessToken, * provider: 'anthropic', * path: '/v1/messages', * body: { model: 'claude-3-5-sonnet-latest', max_tokens: 1024, messages: [...] }, * }); * ``` */ call: (options: ProxyCallOptions) => Promise; /** * Fetch the full access context for an access token: which app it * belongs to, who holds the token, and which providers it can call. * * The returned `providers` list is exactly the set that will succeed * through `tk.call()` (budget permitting). Use it to render a picker, * gate UI, or detect that the user is missing a required provider. * * The returned `user` identifies the token holder. Use `user.id` as * the stable key for any per-user state in your app — it survives * token refreshes and re-logins, unlike the access token itself. * * ```typescript * const { app, user, providers } = await tk.getAccessContext(accessToken); * console.log(`Signed in as ${user.email}`); * for (const p of providers) { * console.log(p.displayName, p.logoUrl); * } * ``` */ getAccessContext: (accessToken: string) => Promise; /** * Fetch the app's public LLM catalog — the set of models this app is * configured to call, plus the providers that can serve them and the * reachable tier buckets. No user token required. * * "LLM catalog" because that's what the payload describes: it's the * AI surface area of the app, not a generic resource listing. Every * field is a catalog fact (model slug, pricing, provider logo), not * user-private. Per-model `callableNow` is intentionally omitted; use * `getAccessContext(token)` when you need the user-scoped view. * * Defaults to the SDK's configured `clientId`. Pass an explicit * clientId to fetch another app's catalog (the proxy is public, so * any clientId works). */ getAppLlmCatalog: (clientId?: string) => Promise; /** * Get the proxy URL for a specific provider. * Use as `baseURL` in a vendor SDK for streaming requests, which * bypass the unified envelope. */ proxyUrl: (provider: Provider) => string; /** * Base URL for the *agnostic* proxy route — same SDK shape (`flavor`), * but the model named in the body is looked up globally across every * provider. The user's keys decide which one actually runs the request; * the proxy translates the request and response envelopes so your * vendor SDK still sees its own shape. * * Useful when you want to write code once against, say, the OpenAI SDK, * but accept any model the user has access to. Point the SDK at: * * - `agnosticUrl('anthropic')` for `@anthropic-ai/sdk` * - `agnosticUrl('openai') + '/v1'` for `openai` * - `agnosticUrl('gemini')` for `@google/genai` * * Non-streaming only. For provider-bound calls keep using `proxyUrl()`. */ agnosticUrl: (flavor: InboundFlavor) => string; /** * Fetch a compact status snapshot for the access token: budget remaining, * pool state, whether keys are configured, and a CTA the user can click * to fix anything actionable. * * Returns the cached value when one exists; otherwise hits the proxy. * Use `refreshStatus()` to force a fresh fetch. */ getStatus: (accessToken: string) => Promise; /** * Force a fresh fetch of the status, bypassing the cache. Useful after * an out-of-band action (e.g. the user adjusts their budget in another * tab and your app wants to reflect it immediately). */ refreshStatus: (accessToken: string) => Promise; /** * Subscribe to status changes for an access token. The callback fires: * * - immediately with the current value (or after the first fetch), * - after each `tk.call()` (budget shrinks), * - after `tk.topUp()` succeeds (budget grows), * - every 60s in the background, when the page is visible. * * Returns an unsubscribe function. Background polling stops automatically * when the last subscriber for a given access token unsubscribes. * * ```typescript * const off = tk.onStatusChange(accessToken, (status) => { * if (status.level === 'critical' && status.cta?.kind === 'topup') { * showRaiseBudgetButton(() => tk.topUp()); * } * }); * // …later * off(); * ``` */ onStatusChange: (accessToken: string, cb: (status: StatusInfo) => void) => (() => void); /** * Open a Tokenite-hosted "raise budget" popup for this app. * * When `tk.call()` returns `BUDGET_EXCEEDED`, call this to let the * user authorise a higher spending cap without leaving your app. * The popup resolves once the new limit is committed. The access * token does not change — only its budget ceiling. * * The user must be signed in to Tokenite in the same browser (the * popup uses their session cookie). If not, the popup routes them * through sign-in and returns afterwards. * * ```typescript * const r = await tk.call({ accessToken, provider: 'anthropic', ... }); * if (isProxyError(r) && r.error.code === 'BUDGET_EXCEEDED') { * const top = await tk.topUp(); * if (top.ok) return tk.call({ accessToken, ... }); * } * ``` */ topUp: (options?: TopUpOptions) => Promise; /** * Wrap `tk.call()` with automatic recovery when the proxy returns a * recoverable funding error (`BUDGET_EXCEEDED` by default). On a * fundable error, opens the top-up popup and retries the call once * if the user committed a new limit. * * The retry is bounded to one attempt — repeated funding failures * surface back to the caller so misconfigured caps don't loop. */ callWithRecovery: (options: ProxyCallOptions, recovery?: CallWithRecoveryOptions) => Promise; /** The Tokenite dashboard base URL */ baseUrl: string; /** The Tokenite proxy base URL */ proxyBase: string; }; //# sourceMappingURL=client.d.ts.map