@tangle-network/agent-app 0.9.1 → 0.10.1
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.
package/dist/platform/index.d.ts
CHANGED
|
@@ -107,6 +107,55 @@ interface TangleSsoSessionCookieArgs {
|
|
|
107
107
|
* serialization, matching better-call's `serializeSignedCookie` byte-exactly.
|
|
108
108
|
*/
|
|
109
109
|
declare function signSessionCookieValue(token: string, secret: string): Promise<string>;
|
|
110
|
+
/** Structural slice of a `betterAuth()` instance — only what cookie minting
|
|
111
|
+
* reads. No better-auth import: the signing contract is implemented by
|
|
112
|
+
* `signSessionCookieValue`, byte-compatible with better-auth's own
|
|
113
|
+
* `makeSignature`. */
|
|
114
|
+
interface BetterAuthSessionCookieSource {
|
|
115
|
+
$context: PromiseLike<{
|
|
116
|
+
secret: string;
|
|
117
|
+
authCookies: {
|
|
118
|
+
sessionToken: {
|
|
119
|
+
/** Final cookie name — better-auth decides the `__Secure-` prefix
|
|
120
|
+
* (and any `advanced.cookiePrefix`) once at `betterAuth()` init. */
|
|
121
|
+
name: string;
|
|
122
|
+
attributes: {
|
|
123
|
+
secure?: boolean;
|
|
124
|
+
sameSite?: string;
|
|
125
|
+
path?: string;
|
|
126
|
+
httpOnly?: boolean;
|
|
127
|
+
domain?: string;
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
}>;
|
|
132
|
+
}
|
|
133
|
+
interface BetterAuthSessionCookieMinterOptions {
|
|
134
|
+
/** Receives the shadowed-cookie-name warning (see below). Default
|
|
135
|
+
* console.warn. */
|
|
136
|
+
warn?: (message: string) => void;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Canonical `setSessionCookie` wiring for better-auth apps: mint the session
|
|
140
|
+
* Set-Cookie exactly as better-auth's own login flows do — name + attributes
|
|
141
|
+
* from `auth.$context.authCookies.sessionToken` (better-auth stays
|
|
142
|
+
* authoritative over prefix/name/attributes) and the value signed to
|
|
143
|
+
* better-call's `getSignedCookie` contract. A raw unprefixed
|
|
144
|
+
* `better-auth.session_token` left by an earlier login is explicitly expired
|
|
145
|
+
* so it cannot shadow the real cookie.
|
|
146
|
+
*
|
|
147
|
+
* Warns when the app's session cookie still has better-auth's DEFAULT name:
|
|
148
|
+
* the Tangle platform (id.tangle.tools) sets a `Domain=.tangle.tools` cookie
|
|
149
|
+
* under that exact name, and equal-path cookies are sent oldest-first — the
|
|
150
|
+
* platform's cookie is always older (the user signs in there before the app's
|
|
151
|
+
* callback runs), so the app reads the platform's token, fails its own
|
|
152
|
+
* signature check, and every fresh login lands logged-out. Per-app
|
|
153
|
+
* `advanced.cookiePrefix` is the fix.
|
|
154
|
+
*
|
|
155
|
+
* Throws on a domain-scoped session cookie for the same reason: a
|
|
156
|
+
* `Domain=`-wide session cookie is exactly the shadowing footgun.
|
|
157
|
+
*/
|
|
158
|
+
declare function createBetterAuthSessionCookieMinter(auth: BetterAuthSessionCookieSource, options?: BetterAuthSessionCookieMinterOptions): (args: TangleSsoSessionCookieArgs) => Promise<string[]>;
|
|
110
159
|
interface TangleSsoHandlerOptions {
|
|
111
160
|
auth: TangleSsoAuthClient;
|
|
112
161
|
store: TangleSsoAccountStore;
|
|
@@ -380,4 +429,4 @@ interface AssertBillableBalanceOptions {
|
|
|
380
429
|
*/
|
|
381
430
|
declare function assertBillableBalance(state: BillableBalanceState, opts?: AssertBillableBalanceOptions): void;
|
|
382
431
|
|
|
383
|
-
export { type AdminGuardOptions, type AssertBillableBalanceOptions, type AuthGuard, type AuthGuardOptions, type BillableBalanceState, DEFAULT_TANGLE_TIER_POLICY, type HubClientLike, type HubProxyContext, type HubProxyRouteArgs, type HubProxyRoutes, type PlatformBalanceSnapshot, type PlatformBillingHttp, PlatformBillingHttpError, type PlatformBillingHttpOptions, type PlatformIdentityStore, type PlatformSubscriptionInfo, type PlatformUsageProductRow, type SsoStateConfig, TangleBearerMissingError, type TanglePlanTier, type TangleSsoAccountStore, type TangleSsoAuthClient, type TangleSsoExchangeResult, type TangleSsoHandlerOptions, type TangleSsoHandlers, type TangleSsoSessionCookieArgs, TangleSsoUserCreateError, type TangleTierPolicy, type TangleTierState, assertBillableBalance, createAdminGuard, createAuthGuard, createHubProxyRoutes, createPlatformBillingHttp, createSignedSsoState, createTanglePlatformBillingClient, createTangleSsoHandlers, isPlatformBillingHttpError, isPlatformHubErrorLike, isTangleBearerMissingError, normalizeTanglePlanTier, parseAdminEmails, readTangleTierState, signSessionCookieValue, verifySignedSsoState };
|
|
432
|
+
export { type AdminGuardOptions, type AssertBillableBalanceOptions, type AuthGuard, type AuthGuardOptions, type BetterAuthSessionCookieMinterOptions, type BetterAuthSessionCookieSource, type BillableBalanceState, DEFAULT_TANGLE_TIER_POLICY, type HubClientLike, type HubProxyContext, type HubProxyRouteArgs, type HubProxyRoutes, type PlatformBalanceSnapshot, type PlatformBillingHttp, PlatformBillingHttpError, type PlatformBillingHttpOptions, type PlatformIdentityStore, type PlatformSubscriptionInfo, type PlatformUsageProductRow, type SsoStateConfig, TangleBearerMissingError, type TanglePlanTier, type TangleSsoAccountStore, type TangleSsoAuthClient, type TangleSsoExchangeResult, type TangleSsoHandlerOptions, type TangleSsoHandlers, type TangleSsoSessionCookieArgs, TangleSsoUserCreateError, type TangleTierPolicy, type TangleTierState, assertBillableBalance, createAdminGuard, createAuthGuard, createBetterAuthSessionCookieMinter, createHubProxyRoutes, createPlatformBillingHttp, createSignedSsoState, createTanglePlatformBillingClient, createTangleSsoHandlers, isPlatformBillingHttpError, isPlatformHubErrorLike, isTangleBearerMissingError, normalizeTanglePlanTier, parseAdminEmails, readTangleTierState, signSessionCookieValue, verifySignedSsoState };
|
package/dist/platform/index.js
CHANGED
|
@@ -70,6 +70,40 @@ async function signSessionCookieValue(token, secret) {
|
|
|
70
70
|
for (const byte of sig) bin += String.fromCharCode(byte);
|
|
71
71
|
return `${token}.${btoa(bin)}`;
|
|
72
72
|
}
|
|
73
|
+
function createBetterAuthSessionCookieMinter(auth, options = {}) {
|
|
74
|
+
const warn = options.warn ?? ((message) => console.warn(message));
|
|
75
|
+
return async ({ token, ttlSeconds }) => {
|
|
76
|
+
const ctx = await auth.$context;
|
|
77
|
+
if (!ctx.secret) {
|
|
78
|
+
throw new Error("createBetterAuthSessionCookieMinter: auth context has no secret");
|
|
79
|
+
}
|
|
80
|
+
const { name, attributes } = ctx.authCookies.sessionToken;
|
|
81
|
+
if (attributes.domain) {
|
|
82
|
+
throw new Error(
|
|
83
|
+
`createBetterAuthSessionCookieMinter: refusing a domain-scoped session cookie (Domain=${attributes.domain}) \u2014 a domain-wide session cookie shadows sibling apps that share the parent domain`
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
if (name === DEFAULT_SESSION_COOKIE || name === `__Secure-${DEFAULT_SESSION_COOKIE}`) {
|
|
87
|
+
warn(
|
|
88
|
+
`[tangle-sso] session cookie is named "${name}" \u2014 better-auth's default. The Tangle platform (id.tangle.tools) sets a Domain=.tangle.tools cookie under the same name, and the platform's (older) cookie wins the Cookie-header order, so this app's sessions read back null. Set a per-app prefix: betterAuth({ advanced: { cookiePrefix: '<app>' } }).`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
const sameSite = typeof attributes.sameSite === "string" ? attributes.sameSite : "lax";
|
|
92
|
+
const cookieOptions = {
|
|
93
|
+
name,
|
|
94
|
+
path: typeof attributes.path === "string" ? attributes.path : "/",
|
|
95
|
+
httpOnly: attributes.httpOnly !== false,
|
|
96
|
+
sameSite: sameSite.charAt(0).toUpperCase() + sameSite.slice(1),
|
|
97
|
+
secure: Boolean(attributes.secure) || name.startsWith("__Secure-"),
|
|
98
|
+
maxAgeSeconds: ttlSeconds
|
|
99
|
+
};
|
|
100
|
+
const cookies = [serializeCookie(await signSessionCookieValue(token, ctx.secret), cookieOptions)];
|
|
101
|
+
if (name !== DEFAULT_SESSION_COOKIE) {
|
|
102
|
+
cookies.push(clearCookieHeader({ ...cookieOptions, name: DEFAULT_SESSION_COOKIE }));
|
|
103
|
+
}
|
|
104
|
+
return cookies;
|
|
105
|
+
};
|
|
106
|
+
}
|
|
73
107
|
function sanitizeRedirectPath(value, fallback) {
|
|
74
108
|
if (value && value.startsWith("/") && !value.startsWith("//")) return value;
|
|
75
109
|
return fallback;
|
|
@@ -457,6 +491,7 @@ export {
|
|
|
457
491
|
assertBillableBalance,
|
|
458
492
|
createAdminGuard,
|
|
459
493
|
createAuthGuard,
|
|
494
|
+
createBetterAuthSessionCookieMinter,
|
|
460
495
|
createHubProxyRoutes,
|
|
461
496
|
createPlatformBillingHttp,
|
|
462
497
|
createSignedSsoState,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/platform/sso.ts","../../src/platform/hub.ts","../../src/platform/billing.ts","../../src/platform/guards.ts"],"sourcesContent":["/**\n * Cross-site Tangle SSO for agent apps: signed-state CSRF cookies plus the\n * full start/callback orchestration against the platform's /cross-site\n * bridge. The platform wire client and account persistence are structural\n * seams (`TangleSsoAuthClient` / `TangleSsoAccountStore`), so this module\n * never imports agent-runtime, an auth framework, or a database driver.\n * WebCrypto only — runs in workerd without node compatibility flags.\n */\n\nimport { clearCookieHeader, readCookieValue, serializeCookie } from '../web/index'\n\nconst DEFAULT_STATE_TTL_SECONDS = 600\nconst DEFAULT_SESSION_TTL_SECONDS = 60 * 60 * 24 * 7\nconst DEFAULT_REDIRECT_PATH = '/app'\nconst DEFAULT_LOGIN_PATH = '/login'\nconst DEFAULT_SESSION_COOKIE = 'better-auth.session_token'\n\n// ── Signed state ────────────────────────────────────────────────────────────\n\nexport interface SsoStateConfig {\n /** HMAC-SHA256 secret (e.g. the app's auth secret). */\n secret: string\n /** State lifetime in ms. Default 600 000. */\n ttlMs?: number\n /** Injectable clock (ms since epoch). Default Date.now. */\n now?: () => number\n}\n\nfunction randomHex(bytes: number): string {\n const buf = new Uint8Array(bytes)\n crypto.getRandomValues(buf)\n return Array.from(buf, (b) => b.toString(16).padStart(2, '0')).join('')\n}\n\nasync function hmacBytes(secret: string, value: string): Promise<Uint8Array> {\n const key = await crypto.subtle.importKey(\n 'raw',\n new TextEncoder().encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign'],\n )\n return new Uint8Array(await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(value)))\n}\n\nasync function hmacHex(secret: string, value: string): Promise<string> {\n return Array.from(await hmacBytes(secret, value), (b) => b.toString(16).padStart(2, '0')).join('')\n}\n\nfunction constantTimeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false\n let diff = 0\n for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i)\n return diff === 0\n}\n\n/** Mint a `<randomHex32>.<timestamp36>.<hmacHex>` state value. The timestamp\n * is inside the signed payload, so expiry survives cookie-attribute tampering. */\nexport async function createSignedSsoState(config: SsoStateConfig): Promise<string> {\n if (!config.secret) throw new Error('SsoStateConfig.secret is required')\n const now = config.now ?? Date.now\n const payload = `${randomHex(16)}.${now().toString(36)}`\n return `${payload}.${await hmacHex(config.secret, payload)}`\n}\n\n/** Verify the MAC (constant-time) and the signed TTL. */\nexport async function verifySignedSsoState(state: string, config: SsoStateConfig): Promise<boolean> {\n if (!config.secret) throw new Error('SsoStateConfig.secret is required')\n const parts = state.split('.')\n if (parts.length !== 3) return false\n const [random, timestamp, mac] = parts\n if (!random || !timestamp || !mac) return false\n const expected = await hmacHex(config.secret, `${random}.${timestamp}`)\n if (!constantTimeEqual(mac, expected)) return false\n const mintedAt = parseInt(timestamp, 36)\n if (!Number.isFinite(mintedAt)) return false\n const now = config.now ?? Date.now\n const ttlMs = config.ttlMs ?? DEFAULT_STATE_TTL_SECONDS * 1000\n return now() - mintedAt <= ttlMs\n}\n\n// ── Seams ───────────────────────────────────────────────────────────────────\n\nexport interface TangleSsoExchangeResult {\n apiKey: string\n user: { id: string; email: string; name?: string | null }\n plan?: { tier: string } | null\n}\n\n/** Structural mirror of the platform auth wire client — any object with these\n * two methods satisfies it without this module importing the concrete class. */\nexport interface TangleSsoAuthClient {\n authorizeUrl(options: { state: string; redirectUri?: string }): string\n exchange(code: string): Promise<TangleSsoExchangeResult>\n}\n\n/** Thrown by `upsertUserByEmail` when the app-local user row cannot be\n * created; the callback handler maps it to `?error=tangle_user_create_failed`.\n * Any other store error propagates. */\nexport class TangleSsoUserCreateError extends Error {\n constructor(message = 'Failed to create local user for Tangle SSO') {\n super(message)\n this.name = 'TangleSsoUserCreateError'\n }\n}\n\n/**\n * Account persistence seam. Covers both storage styles in use: link-table\n * apps (a per-user platform-link row) and session-column apps (the key on the\n * session row) — `saveTangleLink` receives both `userId` and `sessionToken`,\n * and each app persists with the key it needs. `createSession` runs first so\n * the token is always available to `saveTangleLink`.\n */\nexport interface TangleSsoAccountStore {\n /** Find-or-create the app-local user. `tangleUserId` is the platform's\n * stable user id — match on it first when the app stores it (emails are\n * mutable on the platform; the id is not), falling back to email for\n * first-time logins. */\n upsertUserByEmail(input: { email: string; name: string | null; tangleUserId: string }): Promise<{ userId: string }>\n /** Create an app session row; returns the session-cookie token value. */\n createSession(input: {\n userId: string\n expiresAt: Date\n ipAddress: string | null\n userAgent: string | null\n }): Promise<{ token: string }>\n /** Persist the platform link (API key + platform identity). */\n saveTangleLink(input: {\n userId: string\n sessionToken: string\n tangleUserId: string\n email: string\n name: string | null\n apiKey: string\n planTier: string | null\n }): Promise<void>\n}\n\n// ── Session cookie ──────────────────────────────────────────────────────────\n\n/** Successful-login context handed to the `setSessionCookie` seam. */\nexport interface TangleSsoSessionCookieArgs {\n /** Session token returned by `store.createSession`. */\n token: string\n /** Session expiry (now + `sessionTtlSeconds`). */\n expiresAt: Date\n /** Mirrors `sessionTtlSeconds` after defaulting. */\n ttlSeconds: number\n /** Mirrors `TangleSsoHandlerOptions.secureCookies`. */\n secure: boolean\n}\n\n/**\n * Sign a session token to better-call's signed-cookie contract — the value\n * better-auth's `getSignedCookie` verifies: `<token>.<signature>` where the\n * signature is the raw HMAC-SHA256 of the token under `secret`, encoded as\n * STANDARD base64 WITH padding (32 bytes → 44 chars ending `=`; better-call\n * rejects any other length or suffix, so url-safe/unpadded variants read back\n * as a null session). The joined value is percent-encoded once at cookie\n * serialization, matching better-call's `serializeSignedCookie` byte-exactly.\n */\nexport async function signSessionCookieValue(token: string, secret: string): Promise<string> {\n if (!secret) throw new Error('signSessionCookieValue requires a non-empty secret')\n const sig = await hmacBytes(secret, token)\n let bin = ''\n for (const byte of sig) bin += String.fromCharCode(byte)\n return `${token}.${btoa(bin)}`\n}\n\n// ── Handlers ────────────────────────────────────────────────────────────────\n\nexport interface TangleSsoHandlerOptions {\n auth: TangleSsoAuthClient\n store: TangleSsoAccountStore\n /** HMAC secret for the state cookie. */\n stateSecret: string\n /** Absolute callback URL registered with the platform. */\n callbackUrl: string\n stateCookieName: string\n /** Default 'better-auth.session_token'. Ignored when `setSessionCookie` is\n * provided. The default path prepends `__Secure-` iff `secureCookies`. */\n sessionCookieName?: string\n /** Mint the host auth framework's own session cookie(s); return complete\n * Set-Cookie header values (the handler appends them verbatim and sets no\n * session cookie itself). Supply this when the framework should stay\n * authoritative over name/prefix/signing/attributes — e.g. better-auth:\n * `auth.$context.authCookies.sessionToken` + `makeSignature`. */\n setSessionCookie?: (\n args: TangleSsoSessionCookieArgs,\n ) => readonly string[] | Promise<readonly string[]>\n /** HMAC-SHA256 secret the host auth framework verifies session cookies with\n * (better-auth: its `secret`). Required when `setSessionCookie` is absent —\n * the default cookie is minted to better-call's signed contract via\n * `signSessionCookieValue`; an unsigned or mis-signed value reads back as a\n * null session, so there is deliberately no fallback to `stateSecret`\n * (which is not guaranteed to be the auth secret). */\n sessionCookieSecret?: string\n /** Adds `Secure` to every cookie this module sets, and (default session\n * cookie only) the `__Secure-` name prefix. Must match the auth\n * framework's own secure-cookie decision (better-auth: https `baseURL` /\n * `advanced.useSecureCookies`), or it will look up a different cookie name\n * than the one set here. */\n secureCookies: boolean\n /** Default 604 800 (7 days). */\n sessionTtlSeconds?: number\n /** Default 600. Applies to both the cookie Max-Age and the signed TTL. */\n stateTtlSeconds?: number\n /** Default '/app'. */\n defaultRedirectPath?: string\n /** Default '/login'. */\n loginPath?: string\n /** Failure log hook (e.g. console.error). Default no-op. */\n log?: (message: string, error?: unknown) => void\n now?: () => number\n}\n\nexport interface TangleSsoHandlers {\n /** GET start route: mint + sign state, set the state cookie, 302 to the\n * platform authorize URL. `?redirect=` carries the post-login path. */\n start(request: Request): Promise<Response>\n /** GET callback route: verify state, exchange the code, upsert the user,\n * create the session, save the platform link, set the session cookie\n * (via the `setSessionCookie` seam, else signed to better-call's contract\n * with `sessionCookieSecret`), 302 to the saved redirect. Every failure\n * 302s to `loginPath?error=…` with the state cookie cleared. */\n callback(request: Request): Promise<Response>\n}\n\n/** Accept only same-origin absolute paths (rejects `//host` protocol-relative URLs). */\nfunction sanitizeRedirectPath(value: string | null, fallback: string): string {\n if (value && value.startsWith('/') && !value.startsWith('//')) return value\n return fallback\n}\n\nfunction redirectResponse(location: string, headers = new Headers()): Response {\n headers.set('Location', location)\n return new Response(null, { status: 302, headers })\n}\n\n/** Real client IP: `CF-Connecting-IP` behind Cloudflare, else the first\n * `x-forwarded-for` hop (the rest of the list is sender-controlled). */\nfunction clientIp(request: Request): string | null {\n return (\n request.headers.get('CF-Connecting-IP') ??\n request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ??\n null\n )\n}\n\ninterface StateCookiePayload {\n s: string\n r: string\n}\n\nfunction parseStateCookiePayload(raw: string | null): StateCookiePayload | null {\n if (!raw) return null\n try {\n const parsed = JSON.parse(raw) as unknown\n if (parsed === null || typeof parsed !== 'object') return null\n const { s, r } = parsed as Record<string, unknown>\n if (typeof s !== 'string' || typeof r !== 'string') return null\n return { s, r }\n } catch {\n return null\n }\n}\n\nexport function createTangleSsoHandlers(opts: TangleSsoHandlerOptions): TangleSsoHandlers {\n if (!opts.stateSecret) throw new Error('TangleSsoHandlerOptions.stateSecret is required')\n if (!opts.callbackUrl) throw new Error('TangleSsoHandlerOptions.callbackUrl is required')\n if (!opts.stateCookieName) throw new Error('TangleSsoHandlerOptions.stateCookieName is required')\n\n const sessionCookieName = opts.sessionCookieName ?? DEFAULT_SESSION_COOKIE\n\n let mintSessionCookies: (args: TangleSsoSessionCookieArgs) => Promise<readonly string[]>\n if (opts.setSessionCookie) {\n const seam = opts.setSessionCookie\n mintSessionCookies = async (args) => await seam(args)\n } else if (opts.sessionCookieSecret) {\n const secret = opts.sessionCookieSecret\n mintSessionCookies = async ({ token, secure, ttlSeconds }) => [\n serializeCookie(await signSessionCookieValue(token, secret), {\n name: secure ? `__Secure-${sessionCookieName}` : sessionCookieName,\n secure,\n maxAgeSeconds: ttlSeconds,\n }),\n ]\n } else {\n throw new Error(\n 'TangleSsoHandlerOptions requires setSessionCookie or sessionCookieSecret: ' +\n 'better-auth only accepts HMAC-signed (and, on https, __Secure--prefixed) session cookies, ' +\n 'so an unsigned default would mint sessions that read back null',\n )\n }\n const sessionTtlSeconds = opts.sessionTtlSeconds ?? DEFAULT_SESSION_TTL_SECONDS\n const stateTtlSeconds = opts.stateTtlSeconds ?? DEFAULT_STATE_TTL_SECONDS\n const defaultRedirectPath = opts.defaultRedirectPath ?? DEFAULT_REDIRECT_PATH\n const loginPath = opts.loginPath ?? DEFAULT_LOGIN_PATH\n const log = opts.log ?? (() => {})\n const now = opts.now ?? Date.now\n const stateConfig: SsoStateConfig = { secret: opts.stateSecret, ttlMs: stateTtlSeconds * 1000, now }\n\n const stateCookieOpts = { name: opts.stateCookieName, secure: opts.secureCookies }\n\n function loginErrorRedirect(code: string): Response {\n const headers = new Headers()\n headers.append('Set-Cookie', clearCookieHeader(stateCookieOpts))\n return redirectResponse(`${loginPath}?error=${code}`, headers)\n }\n\n return {\n async start(request) {\n const url = new URL(request.url)\n const redirectPath = sanitizeRedirectPath(url.searchParams.get('redirect'), defaultRedirectPath)\n const state = await createSignedSsoState(stateConfig)\n const cookie = serializeCookie(JSON.stringify({ s: state, r: redirectPath }), {\n ...stateCookieOpts,\n maxAgeSeconds: stateTtlSeconds,\n })\n const headers = new Headers()\n headers.append('Set-Cookie', cookie)\n return redirectResponse(opts.auth.authorizeUrl({ state, redirectUri: opts.callbackUrl }), headers)\n },\n\n async callback(request) {\n const url = new URL(request.url)\n const code = url.searchParams.get('code')\n const stateFromPlatform = url.searchParams.get('state')\n if (!code || !stateFromPlatform) return loginErrorRedirect('tangle_callback_missing')\n\n const payload = parseStateCookiePayload(readCookieValue(request.headers.get('cookie'), opts.stateCookieName))\n if (!payload || payload.s !== stateFromPlatform) return loginErrorRedirect('tangle_state_mismatch')\n if (!(await verifySignedSsoState(payload.s, stateConfig))) return loginErrorRedirect('tangle_state_mismatch')\n\n let exchanged: TangleSsoExchangeResult\n try {\n exchanged = await opts.auth.exchange(code)\n } catch (err) {\n log('[tangle-sso] exchange failed', err)\n return loginErrorRedirect('tangle_exchange_failed')\n }\n\n let userId: string\n try {\n ;({ userId } = await opts.store.upsertUserByEmail({\n email: exchanged.user.email,\n name: exchanged.user.name ?? null,\n tangleUserId: exchanged.user.id,\n }))\n } catch (err) {\n if (err instanceof TangleSsoUserCreateError) return loginErrorRedirect('tangle_user_create_failed')\n throw err\n }\n\n const expiresAt = new Date(now() + sessionTtlSeconds * 1000)\n const { token } = await opts.store.createSession({\n userId,\n expiresAt,\n ipAddress: clientIp(request),\n userAgent: request.headers.get('user-agent'),\n })\n\n await opts.store.saveTangleLink({\n userId,\n sessionToken: token,\n tangleUserId: exchanged.user.id,\n email: exchanged.user.email,\n name: exchanged.user.name ?? null,\n apiKey: exchanged.apiKey,\n planTier: exchanged.plan?.tier ?? null,\n })\n\n const headers = new Headers()\n headers.append('Set-Cookie', clearCookieHeader(stateCookieOpts))\n const sessionCookies = await mintSessionCookies({\n token,\n expiresAt,\n ttlSeconds: sessionTtlSeconds,\n secure: opts.secureCookies,\n })\n for (const cookie of sessionCookies) headers.append('Set-Cookie', cookie)\n return redirectResponse(sanitizeRedirectPath(payload.r, defaultRedirectPath), headers)\n },\n }\n}\n","/**\n * Integrations-hub proxy routes: the app-side surface that forwards an\n * authenticated user's requests to the platform's `/v1/integrations/*` API\n * using their stored platform key. Auth, key lookup, and the wire client are\n * structural seams (`HubProxyContext`); error detection is by name + shape so\n * it survives bundlers duplicating module instances.\n */\n\nexport class TangleBearerMissingError extends Error {\n constructor(readonly userId: string) {\n super(`No Tangle platform link for user ${userId}`)\n this.name = 'TangleBearerMissingError'\n }\n}\n\n/** Structural guard (name + userId shape) — robust when the error class is\n * constructed in a different module instance than the one checking it. */\nexport function isTangleBearerMissingError(error: unknown): error is TangleBearerMissingError {\n return (\n error instanceof Error &&\n error.name === 'TangleBearerMissingError' &&\n typeof (error as { userId?: unknown }).userId === 'string'\n )\n}\n\n/** Structural detection of the platform hub wire error (name + numeric status). */\nexport function isPlatformHubErrorLike(error: unknown): error is Error & { status: number; code?: string } {\n return (\n error instanceof Error &&\n error.name === 'PlatformHubError' &&\n typeof (error as { status?: unknown }).status === 'number'\n )\n}\n\n/** Structural subset of the platform hub wire client — extra methods are fine. */\nexport interface HubClientLike {\n catalog(): Promise<unknown>\n listConnections(): Promise<unknown>\n revokeConnection(connectionId: string): Promise<unknown>\n startAuth(input: {\n providerId: string\n connectorId: string\n returnUrl: string\n requestedScopes?: string[]\n }): Promise<{ authorizationUrl: string; state: string }>\n listHealthchecks(): Promise<unknown>\n}\n\nexport interface HubProxyContext {\n /** Resolve the authenticated user id. Throw the app's own auth Response /\n * redirect to reject — it propagates untouched. */\n requireUserId(request: Request): Promise<string>\n /** The user's platform bearer; throw `TangleBearerMissingError` when unlinked. */\n getBearer(userId: string): Promise<string>\n /** A hub client bound to the bearer. */\n createHubClient(bearer: string): HubClientLike\n}\n\nexport interface HubProxyRouteArgs {\n request: Request\n params?: Record<string, string | undefined>\n}\n\nexport interface HubProxyRoutes {\n /** GET → `{ catalog }`. */\n catalog(args: HubProxyRouteArgs): Promise<Response>\n /** GET → `{ connections }`. */\n connections(args: HubProxyRouteArgs): Promise<Response>\n /** DELETE → the platform revocation result verbatim; 405 otherwise. */\n connectionDelete(args: { request: Request; params: { connectionId: string } }): Promise<Response>\n /** GET → `{ healthchecks }`. */\n healthchecks(args: HubProxyRouteArgs): Promise<Response>\n /** POST `{ providerId, connectorId, returnUrl, requestedScopes? }` →\n * `{ authorizationUrl, state }`; 405 non-POST; 400 on bad JSON / missing fields. */\n authStart(args: HubProxyRouteArgs): Promise<Response>\n}\n\ninterface StartAuthBody {\n providerId?: string\n connectorId?: string\n returnUrl?: string\n requestedScopes?: string[]\n}\n\nexport function createHubProxyRoutes(ctx: HubProxyContext): HubProxyRoutes {\n /** Auth runs OUTSIDE the proxy try/catch so the app's auth throw (redirect\n * Response etc.) is never swallowed; bearer + platform errors are mapped. */\n async function proxy(request: Request, call: (hub: HubClientLike) => Promise<Response>): Promise<Response> {\n const userId = await ctx.requireUserId(request)\n try {\n const bearer = await ctx.getBearer(userId)\n return await call(ctx.createHubClient(bearer))\n } catch (err) {\n if (isTangleBearerMissingError(err)) {\n return Response.json({ error: 'tangle_link_required' }, { status: 412 })\n }\n if (isPlatformHubErrorLike(err)) {\n return Response.json({ error: err.message, code: err.code }, { status: err.status })\n }\n throw err\n }\n }\n\n return {\n catalog: ({ request }) => proxy(request, async (hub) => Response.json({ catalog: await hub.catalog() })),\n\n connections: ({ request }) =>\n proxy(request, async (hub) => Response.json({ connections: await hub.listConnections() })),\n\n connectionDelete: async ({ request, params }) => {\n if (request.method !== 'DELETE') {\n return Response.json({ error: 'Method not allowed' }, { status: 405 })\n }\n return proxy(request, async (hub) => Response.json(await hub.revokeConnection(params.connectionId)))\n },\n\n healthchecks: ({ request }) =>\n proxy(request, async (hub) => Response.json({ healthchecks: await hub.listHealthchecks() })),\n\n authStart: async ({ request }) => {\n if (request.method !== 'POST') {\n return Response.json({ error: 'Method not allowed' }, { status: 405 })\n }\n const userId = await ctx.requireUserId(request)\n let body: StartAuthBody\n try {\n body = (await request.json()) as StartAuthBody\n } catch {\n return Response.json({ error: 'Invalid JSON body' }, { status: 400 })\n }\n if (!body.providerId || !body.connectorId || !body.returnUrl) {\n return Response.json({ error: 'providerId, connectorId, and returnUrl are required' }, { status: 400 })\n }\n try {\n const bearer = await ctx.getBearer(userId)\n const result = await ctx.createHubClient(bearer).startAuth({\n providerId: body.providerId,\n connectorId: body.connectorId,\n returnUrl: body.returnUrl,\n requestedScopes: body.requestedScopes,\n })\n return Response.json({ authorizationUrl: result.authorizationUrl, state: result.state })\n } catch (err) {\n if (isTangleBearerMissingError(err)) {\n return Response.json({ error: 'tangle_link_required' }, { status: 412 })\n }\n if (isPlatformHubErrorLike(err)) {\n return Response.json({ error: err.message, code: err.code }, { status: err.status })\n }\n throw err\n }\n },\n }\n}\n","/**\n * Platform billing HTTP transport + tier state for apps on the shared\n * Tangle balance model (id.tangle.tools). Reads authenticate as the user via\n * their per-user platform key (the platform resolves the caller from the\n * key; service or impersonation headers on read routes are rejected). The\n * deduct write authenticates as the product service (`Bearer <serviceToken>`\n * + `X-Service-Name`) and names the target user in the body. Also provides a\n * fetch-backed implementation of the `/billing` module's\n * `PlatformBillingClient` seam (type-only import — no runtime coupling).\n */\n\nimport type { PlatformBillingClient, PlatformIdentity } from '../billing/index'\n\nexport type TanglePlanTier = 'free' | 'pro' | 'enterprise'\n\n/** 'pro' | 'enterprise' pass through; anything else (null, unknown) → 'free'. */\nexport function normalizeTanglePlanTier(plan: string | null | undefined): TanglePlanTier {\n return plan === 'pro' || plan === 'enterprise' ? plan : 'free'\n}\n\nexport class PlatformBillingHttpError extends Error {\n constructor(\n readonly status: number,\n detail: string,\n ) {\n super(`Platform request failed (${status}): ${detail}`)\n this.name = 'PlatformBillingHttpError'\n }\n}\n\n/** Structural guard (name + numeric status) — robust across module instances. */\nexport function isPlatformBillingHttpError(error: unknown): error is PlatformBillingHttpError {\n return (\n error instanceof Error &&\n error.name === 'PlatformBillingHttpError' &&\n typeof (error as { status?: unknown }).status === 'number'\n )\n}\n\nexport interface PlatformBillingHttpOptions {\n /** Platform root, e.g. https://id.tangle.tools (trailing slashes stripped). */\n baseUrl: string\n /** Used only by `deduct()`; resolved lazily so reads never require it.\n * Throws at call time when empty. */\n serviceToken: string | (() => string)\n /** Product slug — the `X-Service-Name` header and the deduct `product` field. */\n productSlug: string\n fetchImpl?: typeof fetch\n /** Default 10 000. */\n timeoutMs?: number\n}\n\nexport interface PlatformSubscriptionInfo {\n tier: TanglePlanTier\n status: string | null\n}\n\nexport interface PlatformBalanceSnapshot {\n balance: number\n lifetimeSpent: number\n updatedAt?: string\n}\n\nexport interface PlatformUsageProductRow {\n product: string | null\n totalSpent: number\n count: number\n}\n\nexport interface PlatformBillingHttp {\n /** GET /v1/plans/current (user bearer). */\n getSubscription(userApiKey: string): Promise<PlatformSubscriptionInfo>\n /** GET /v1/billing/balance (user bearer). */\n getBalance(userApiKey: string): Promise<PlatformBalanceSnapshot>\n /** GET /v1/billing/usage (user bearer). */\n getUsageByProduct(userApiKey: string): Promise<PlatformUsageProductRow[]>\n /** POST /v1/billing/deduct (service token). */\n deduct(input: {\n platformUserId: string\n amountUsd: number\n type: string\n description: string\n referenceId: string\n }): Promise<void>\n /** Absolute URL of the platform's billing-management surface. */\n billingUrl(): string\n}\n\nexport function createPlatformBillingHttp(opts: PlatformBillingHttpOptions): PlatformBillingHttp {\n const baseUrl = opts.baseUrl.replace(/\\/+$/, '')\n if (!baseUrl) throw new Error('PlatformBillingHttpOptions.baseUrl is required')\n if (!opts.productSlug) throw new Error('PlatformBillingHttpOptions.productSlug is required')\n const fetchImpl = opts.fetchImpl ?? fetch\n const timeoutMs = opts.timeoutMs ?? 10_000\n\n function resolveServiceToken(): string {\n const token = typeof opts.serviceToken === 'function' ? opts.serviceToken() : opts.serviceToken\n if (!token) throw new Error('A platform service token is required for deduct')\n return token\n }\n\n async function request<T>(path: string, init: RequestInit, headers: Headers): Promise<T> {\n const res = await fetchImpl(`${baseUrl}${path}`, {\n ...init,\n headers,\n signal: AbortSignal.timeout(timeoutMs),\n })\n if (!res.ok) {\n const body = (await res.json().catch(() => null)) as { error?: { message?: string } } | null\n throw new PlatformBillingHttpError(res.status, body?.error?.message ?? res.statusText)\n }\n return res.json() as Promise<T>\n }\n\n function userRead<T>(userApiKey: string, path: string): Promise<T> {\n const headers = new Headers()\n headers.set('Authorization', `Bearer ${userApiKey}`)\n return request<T>(path, {}, headers)\n }\n\n return {\n async getSubscription(userApiKey) {\n const body = await userRead<{\n success: boolean\n data?: { subscription?: { plan?: string | null; status?: string | null } | null }\n }>(userApiKey, '/v1/plans/current')\n const sub = body.data?.subscription ?? null\n return { tier: normalizeTanglePlanTier(sub?.plan), status: sub?.status ?? null }\n },\n\n async getBalance(userApiKey) {\n const body = await userRead<{\n success: boolean\n data?: { balance?: number; lifetimeSpent?: number; updatedAt?: string }\n }>(userApiKey, '/v1/billing/balance')\n return {\n balance: body.data?.balance ?? 0,\n lifetimeSpent: body.data?.lifetimeSpent ?? 0,\n updatedAt: body.data?.updatedAt,\n }\n },\n\n async getUsageByProduct(userApiKey) {\n const body = await userRead<{\n success: boolean\n data?: Array<{ product?: string | null; totalSpent?: number; count?: number }>\n }>(userApiKey, '/v1/billing/usage')\n return (body.data ?? []).map((row) => ({\n product: row.product ?? null,\n totalSpent: row.totalSpent ?? 0,\n count: row.count ?? 0,\n }))\n },\n\n async deduct(input) {\n const headers = new Headers()\n headers.set('Authorization', `Bearer ${resolveServiceToken()}`)\n headers.set('X-Service-Name', opts.productSlug)\n headers.set('Content-Type', 'application/json')\n await request('/v1/billing/deduct', {\n method: 'POST',\n body: JSON.stringify({\n userId: input.platformUserId,\n amount: input.amountUsd,\n type: input.type,\n product: opts.productSlug,\n description: input.description,\n referenceId: input.referenceId,\n }),\n }, headers)\n },\n\n billingUrl() {\n return `${baseUrl}/app/billing`\n },\n }\n}\n\n// ── Tier policy + composed state ────────────────────────────────────────────\n\nexport interface TangleTierPolicy {\n concurrency: number\n overageAllowed: boolean\n}\n\nexport const DEFAULT_TANGLE_TIER_POLICY: Record<TanglePlanTier, TangleTierPolicy> = {\n free: { concurrency: 1, overageAllowed: false },\n pro: { concurrency: Number.POSITIVE_INFINITY, overageAllowed: true },\n enterprise: { concurrency: Number.POSITIVE_INFINITY, overageAllowed: true },\n}\n\nexport interface TangleTierState {\n tier: TanglePlanTier\n subscriptionStatus: string | null\n remainingBalanceUsd: number\n lifetimeSpentUsd: number\n concurrency: number\n overageAllowed: boolean\n}\n\n/**\n * Read subscription + balance and project them onto the tier policy. A\n * null/absent key fails CLOSED (free tier, zero balance) — a billable run is\n * never started against an unknown balance. Platform errors throw; callers\n * on the billable path choose their posture explicitly.\n */\nexport async function readTangleTierState(\n http: PlatformBillingHttp,\n userApiKey: string | null | undefined,\n policy: Record<TanglePlanTier, TangleTierPolicy> = DEFAULT_TANGLE_TIER_POLICY,\n): Promise<TangleTierState> {\n if (!userApiKey) {\n return {\n tier: 'free',\n subscriptionStatus: null,\n remainingBalanceUsd: 0,\n lifetimeSpentUsd: 0,\n ...policy.free,\n }\n }\n const [subscription, balance] = await Promise.all([\n http.getSubscription(userApiKey),\n http.getBalance(userApiKey),\n ])\n return {\n tier: subscription.tier,\n subscriptionStatus: subscription.status,\n remainingBalanceUsd: balance.balance,\n lifetimeSpentUsd: balance.lifetimeSpent,\n ...policy[subscription.tier],\n }\n}\n\n// ── Bridge onto the /billing seam ───────────────────────────────────────────\n\nexport interface PlatformIdentityStore {\n resolveIdentity(userId: string): Promise<PlatformIdentity | null>\n}\n\n/** Concrete fetch-backed `PlatformBillingClient<TanglePlanTier>` for\n * `createPlatformBalanceManager` (from `/billing`). */\nexport function createTanglePlatformBillingClient(\n http: PlatformBillingHttp,\n identity: PlatformIdentityStore,\n): PlatformBillingClient<TanglePlanTier> {\n return {\n resolveIdentity: (userId) => identity.resolveIdentity(userId),\n getPlan: async (apiKey) => (await http.getSubscription(apiKey)).tier,\n getBalance: async (apiKey) => {\n const snapshot = await http.getBalance(apiKey)\n return { balance: snapshot.balance, lifetimeSpent: snapshot.lifetimeSpent }\n },\n getUsageByProduct: (apiKey) => http.getUsageByProduct(apiKey),\n deduct: (input) => http.deduct(input),\n }\n}\n","/**\n * Request guards for agent-app routes: session auth (302 redirect for pages,\n * JSON 401 for APIs), admin allowlisting (404 — the route stays invisible to\n * non-admins), and the billable-balance gate (402 with a stable code).\n * Session resolution is a seam; thrown Responses follow the router convention\n * of surfacing a thrown Response as the route result.\n */\n\nimport { isTangleBillingEnforcementDisabled } from '../runtime/model'\n\nexport interface AuthGuardOptions<Session> {\n /** e.g. a better-auth `auth.api.getSession` wrapped by the app. */\n getSession(request: Request): Promise<Session | null | undefined>\n /** Default '/login'. */\n loginPath?: string\n}\n\nexport interface AuthGuard<Session> {\n /** Page guard — throws a 302 redirect Response to `loginPath`. */\n requireUser(request: Request): Promise<Session>\n /** API guard — throws JSON 401 `{ error: 'Unauthorized', code: 'auth.unauthenticated' }`. */\n requireApiUser(request: Request): Promise<Session>\n /** `apiResponse` selects the 401 JSON path over the redirect. */\n requireSession(request: Request, opts?: { apiResponse?: boolean }): Promise<Session>\n getOptionalSession(request: Request): Promise<Session | null>\n}\n\nexport function createAuthGuard<Session>(opts: AuthGuardOptions<Session>): AuthGuard<Session> {\n const loginPath = opts.loginPath ?? '/login'\n\n async function requireSession(request: Request, o: { apiResponse?: boolean } = {}): Promise<Session> {\n const session = await opts.getSession(request)\n if (!session) {\n if (o.apiResponse) {\n throw Response.json({ error: 'Unauthorized', code: 'auth.unauthenticated' }, { status: 401 })\n }\n throw new Response(null, { status: 302, headers: { Location: loginPath } })\n }\n return session\n }\n\n return {\n requireSession,\n requireUser: (request) => requireSession(request),\n requireApiUser: (request) => requireSession(request, { apiResponse: true }),\n getOptionalSession: async (request) => (await opts.getSession(request)) ?? null,\n }\n}\n\n/** Comma/whitespace separated → trimmed, lowercased, empties dropped. */\nexport function parseAdminEmails(raw: string | null | undefined): string[] {\n return (raw ?? '')\n .split(/[,\\s]+/)\n .map((e) => e.trim().toLowerCase())\n .filter(Boolean)\n}\n\nexport interface AdminGuardOptions<Session> {\n requireUser(request: Request): Promise<Session>\n emailOf(session: Session): string | null | undefined\n /** Resolved per request; an EMPTY allowlist refuses everyone. */\n allowedEmails(): string[]\n}\n\n/** Non-admins (and empty allowlists) get 404, keeping the route invisible —\n * better than a \"forbidden\" footprint that advertises its existence. */\nexport function createAdminGuard<Session>(opts: AdminGuardOptions<Session>): (request: Request) => Promise<Session> {\n return async (request) => {\n const session = await opts.requireUser(request)\n const allowed = opts.allowedEmails()\n if (allowed.length === 0) throw new Response('Not found', { status: 404 })\n const email = (opts.emailOf(session) ?? '').toLowerCase()\n if (!allowed.includes(email)) throw new Response('Not found', { status: 404 })\n return session\n }\n}\n\nexport interface BillableBalanceState {\n overageAllowed: boolean\n remainingBalanceUsd: number\n}\n\nexport interface AssertBillableBalanceOptions {\n env?: Record<string, string | undefined>\n /** App-specific enforcement override flag (e.g. 'GTM_BILLING_ENFORCEMENT'),\n * fed to `isTangleBillingEnforcementDisabled`. */\n enforcementEnvVar?: string\n /** Default 'Add balance or upgrade your plan to invoke this agent.'. */\n errorMessage?: string\n /** Merged into the 402 JSON body (e.g. `{ organizationId }`). */\n errorBody?: Record<string, unknown>\n}\n\n/**\n * Gate a billable turn: passes when enforcement is disabled (dev default),\n * the tier allows overage, or remaining balance is positive. Otherwise throws\n * a 402 Response with the stable `billing.balance_required` code so clients\n * can route to the billing screen.\n */\nexport function assertBillableBalance(state: BillableBalanceState, opts: AssertBillableBalanceOptions = {}): void {\n if (isTangleBillingEnforcementDisabled({ env: opts.env, enforcementEnvVar: opts.enforcementEnvVar })) return\n if (state.overageAllowed || state.remainingBalanceUsd > 0) return\n // errorBody first: the stable error/code contract always wins over caller extras.\n throw Response.json(\n {\n ...opts.errorBody,\n error: opts.errorMessage ?? 'Add balance or upgrade your plan to invoke this agent.',\n code: 'billing.balance_required',\n },\n { status: 402 },\n )\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/platform/sso.ts","../../src/platform/hub.ts","../../src/platform/billing.ts","../../src/platform/guards.ts"],"sourcesContent":["/**\n * Cross-site Tangle SSO for agent apps: signed-state CSRF cookies plus the\n * full start/callback orchestration against the platform's /cross-site\n * bridge. The platform wire client and account persistence are structural\n * seams (`TangleSsoAuthClient` / `TangleSsoAccountStore`), so this module\n * never imports agent-runtime, an auth framework, or a database driver.\n * WebCrypto only — runs in workerd without node compatibility flags.\n */\n\nimport { clearCookieHeader, readCookieValue, serializeCookie } from '../web/index'\n\nconst DEFAULT_STATE_TTL_SECONDS = 600\nconst DEFAULT_SESSION_TTL_SECONDS = 60 * 60 * 24 * 7\nconst DEFAULT_REDIRECT_PATH = '/app'\nconst DEFAULT_LOGIN_PATH = '/login'\nconst DEFAULT_SESSION_COOKIE = 'better-auth.session_token'\n\n// ── Signed state ────────────────────────────────────────────────────────────\n\nexport interface SsoStateConfig {\n /** HMAC-SHA256 secret (e.g. the app's auth secret). */\n secret: string\n /** State lifetime in ms. Default 600 000. */\n ttlMs?: number\n /** Injectable clock (ms since epoch). Default Date.now. */\n now?: () => number\n}\n\nfunction randomHex(bytes: number): string {\n const buf = new Uint8Array(bytes)\n crypto.getRandomValues(buf)\n return Array.from(buf, (b) => b.toString(16).padStart(2, '0')).join('')\n}\n\nasync function hmacBytes(secret: string, value: string): Promise<Uint8Array> {\n const key = await crypto.subtle.importKey(\n 'raw',\n new TextEncoder().encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign'],\n )\n return new Uint8Array(await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(value)))\n}\n\nasync function hmacHex(secret: string, value: string): Promise<string> {\n return Array.from(await hmacBytes(secret, value), (b) => b.toString(16).padStart(2, '0')).join('')\n}\n\nfunction constantTimeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false\n let diff = 0\n for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i)\n return diff === 0\n}\n\n/** Mint a `<randomHex32>.<timestamp36>.<hmacHex>` state value. The timestamp\n * is inside the signed payload, so expiry survives cookie-attribute tampering. */\nexport async function createSignedSsoState(config: SsoStateConfig): Promise<string> {\n if (!config.secret) throw new Error('SsoStateConfig.secret is required')\n const now = config.now ?? Date.now\n const payload = `${randomHex(16)}.${now().toString(36)}`\n return `${payload}.${await hmacHex(config.secret, payload)}`\n}\n\n/** Verify the MAC (constant-time) and the signed TTL. */\nexport async function verifySignedSsoState(state: string, config: SsoStateConfig): Promise<boolean> {\n if (!config.secret) throw new Error('SsoStateConfig.secret is required')\n const parts = state.split('.')\n if (parts.length !== 3) return false\n const [random, timestamp, mac] = parts\n if (!random || !timestamp || !mac) return false\n const expected = await hmacHex(config.secret, `${random}.${timestamp}`)\n if (!constantTimeEqual(mac, expected)) return false\n const mintedAt = parseInt(timestamp, 36)\n if (!Number.isFinite(mintedAt)) return false\n const now = config.now ?? Date.now\n const ttlMs = config.ttlMs ?? DEFAULT_STATE_TTL_SECONDS * 1000\n return now() - mintedAt <= ttlMs\n}\n\n// ── Seams ───────────────────────────────────────────────────────────────────\n\nexport interface TangleSsoExchangeResult {\n apiKey: string\n user: { id: string; email: string; name?: string | null }\n plan?: { tier: string } | null\n}\n\n/** Structural mirror of the platform auth wire client — any object with these\n * two methods satisfies it without this module importing the concrete class. */\nexport interface TangleSsoAuthClient {\n authorizeUrl(options: { state: string; redirectUri?: string }): string\n exchange(code: string): Promise<TangleSsoExchangeResult>\n}\n\n/** Thrown by `upsertUserByEmail` when the app-local user row cannot be\n * created; the callback handler maps it to `?error=tangle_user_create_failed`.\n * Any other store error propagates. */\nexport class TangleSsoUserCreateError extends Error {\n constructor(message = 'Failed to create local user for Tangle SSO') {\n super(message)\n this.name = 'TangleSsoUserCreateError'\n }\n}\n\n/**\n * Account persistence seam. Covers both storage styles in use: link-table\n * apps (a per-user platform-link row) and session-column apps (the key on the\n * session row) — `saveTangleLink` receives both `userId` and `sessionToken`,\n * and each app persists with the key it needs. `createSession` runs first so\n * the token is always available to `saveTangleLink`.\n */\nexport interface TangleSsoAccountStore {\n /** Find-or-create the app-local user. `tangleUserId` is the platform's\n * stable user id — match on it first when the app stores it (emails are\n * mutable on the platform; the id is not), falling back to email for\n * first-time logins. */\n upsertUserByEmail(input: { email: string; name: string | null; tangleUserId: string }): Promise<{ userId: string }>\n /** Create an app session row; returns the session-cookie token value. */\n createSession(input: {\n userId: string\n expiresAt: Date\n ipAddress: string | null\n userAgent: string | null\n }): Promise<{ token: string }>\n /** Persist the platform link (API key + platform identity). */\n saveTangleLink(input: {\n userId: string\n sessionToken: string\n tangleUserId: string\n email: string\n name: string | null\n apiKey: string\n planTier: string | null\n }): Promise<void>\n}\n\n// ── Session cookie ──────────────────────────────────────────────────────────\n\n/** Successful-login context handed to the `setSessionCookie` seam. */\nexport interface TangleSsoSessionCookieArgs {\n /** Session token returned by `store.createSession`. */\n token: string\n /** Session expiry (now + `sessionTtlSeconds`). */\n expiresAt: Date\n /** Mirrors `sessionTtlSeconds` after defaulting. */\n ttlSeconds: number\n /** Mirrors `TangleSsoHandlerOptions.secureCookies`. */\n secure: boolean\n}\n\n/**\n * Sign a session token to better-call's signed-cookie contract — the value\n * better-auth's `getSignedCookie` verifies: `<token>.<signature>` where the\n * signature is the raw HMAC-SHA256 of the token under `secret`, encoded as\n * STANDARD base64 WITH padding (32 bytes → 44 chars ending `=`; better-call\n * rejects any other length or suffix, so url-safe/unpadded variants read back\n * as a null session). The joined value is percent-encoded once at cookie\n * serialization, matching better-call's `serializeSignedCookie` byte-exactly.\n */\nexport async function signSessionCookieValue(token: string, secret: string): Promise<string> {\n if (!secret) throw new Error('signSessionCookieValue requires a non-empty secret')\n const sig = await hmacBytes(secret, token)\n let bin = ''\n for (const byte of sig) bin += String.fromCharCode(byte)\n return `${token}.${btoa(bin)}`\n}\n\n/** Structural slice of a `betterAuth()` instance — only what cookie minting\n * reads. No better-auth import: the signing contract is implemented by\n * `signSessionCookieValue`, byte-compatible with better-auth's own\n * `makeSignature`. */\nexport interface BetterAuthSessionCookieSource {\n $context: PromiseLike<{\n secret: string\n authCookies: {\n sessionToken: {\n /** Final cookie name — better-auth decides the `__Secure-` prefix\n * (and any `advanced.cookiePrefix`) once at `betterAuth()` init. */\n name: string\n attributes: {\n secure?: boolean\n sameSite?: string\n path?: string\n httpOnly?: boolean\n domain?: string\n }\n }\n }\n }>\n}\n\nexport interface BetterAuthSessionCookieMinterOptions {\n /** Receives the shadowed-cookie-name warning (see below). Default\n * console.warn. */\n warn?: (message: string) => void\n}\n\n/**\n * Canonical `setSessionCookie` wiring for better-auth apps: mint the session\n * Set-Cookie exactly as better-auth's own login flows do — name + attributes\n * from `auth.$context.authCookies.sessionToken` (better-auth stays\n * authoritative over prefix/name/attributes) and the value signed to\n * better-call's `getSignedCookie` contract. A raw unprefixed\n * `better-auth.session_token` left by an earlier login is explicitly expired\n * so it cannot shadow the real cookie.\n *\n * Warns when the app's session cookie still has better-auth's DEFAULT name:\n * the Tangle platform (id.tangle.tools) sets a `Domain=.tangle.tools` cookie\n * under that exact name, and equal-path cookies are sent oldest-first — the\n * platform's cookie is always older (the user signs in there before the app's\n * callback runs), so the app reads the platform's token, fails its own\n * signature check, and every fresh login lands logged-out. Per-app\n * `advanced.cookiePrefix` is the fix.\n *\n * Throws on a domain-scoped session cookie for the same reason: a\n * `Domain=`-wide session cookie is exactly the shadowing footgun.\n */\nexport function createBetterAuthSessionCookieMinter(\n auth: BetterAuthSessionCookieSource,\n options: BetterAuthSessionCookieMinterOptions = {},\n): (args: TangleSsoSessionCookieArgs) => Promise<string[]> {\n const warn = options.warn ?? ((message: string) => console.warn(message))\n return async ({ token, ttlSeconds }) => {\n const ctx = await auth.$context\n if (!ctx.secret) {\n throw new Error('createBetterAuthSessionCookieMinter: auth context has no secret')\n }\n const { name, attributes } = ctx.authCookies.sessionToken\n if (attributes.domain) {\n throw new Error(\n `createBetterAuthSessionCookieMinter: refusing a domain-scoped session cookie (Domain=${attributes.domain}) — ` +\n 'a domain-wide session cookie shadows sibling apps that share the parent domain',\n )\n }\n if (name === DEFAULT_SESSION_COOKIE || name === `__Secure-${DEFAULT_SESSION_COOKIE}`) {\n warn(\n `[tangle-sso] session cookie is named \"${name}\" — better-auth's default. ` +\n 'The Tangle platform (id.tangle.tools) sets a Domain=.tangle.tools cookie under the same name, ' +\n \"and the platform's (older) cookie wins the Cookie-header order, so this app's sessions read back null. \" +\n \"Set a per-app prefix: betterAuth({ advanced: { cookiePrefix: '<app>' } }).\",\n )\n }\n const sameSite = typeof attributes.sameSite === 'string' ? attributes.sameSite : 'lax'\n const cookieOptions = {\n name,\n path: typeof attributes.path === 'string' ? attributes.path : '/',\n httpOnly: attributes.httpOnly !== false,\n sameSite: (sameSite.charAt(0).toUpperCase() + sameSite.slice(1)) as 'Lax' | 'Strict' | 'None',\n secure: Boolean(attributes.secure) || name.startsWith('__Secure-'),\n maxAgeSeconds: ttlSeconds,\n }\n const cookies = [serializeCookie(await signSessionCookieValue(token, ctx.secret), cookieOptions)]\n if (name !== DEFAULT_SESSION_COOKIE) {\n cookies.push(clearCookieHeader({ ...cookieOptions, name: DEFAULT_SESSION_COOKIE }))\n }\n return cookies\n }\n}\n\n// ── Handlers ────────────────────────────────────────────────────────────────\n\nexport interface TangleSsoHandlerOptions {\n auth: TangleSsoAuthClient\n store: TangleSsoAccountStore\n /** HMAC secret for the state cookie. */\n stateSecret: string\n /** Absolute callback URL registered with the platform. */\n callbackUrl: string\n stateCookieName: string\n /** Default 'better-auth.session_token'. Ignored when `setSessionCookie` is\n * provided. The default path prepends `__Secure-` iff `secureCookies`. */\n sessionCookieName?: string\n /** Mint the host auth framework's own session cookie(s); return complete\n * Set-Cookie header values (the handler appends them verbatim and sets no\n * session cookie itself). Supply this when the framework should stay\n * authoritative over name/prefix/signing/attributes — e.g. better-auth:\n * `auth.$context.authCookies.sessionToken` + `makeSignature`. */\n setSessionCookie?: (\n args: TangleSsoSessionCookieArgs,\n ) => readonly string[] | Promise<readonly string[]>\n /** HMAC-SHA256 secret the host auth framework verifies session cookies with\n * (better-auth: its `secret`). Required when `setSessionCookie` is absent —\n * the default cookie is minted to better-call's signed contract via\n * `signSessionCookieValue`; an unsigned or mis-signed value reads back as a\n * null session, so there is deliberately no fallback to `stateSecret`\n * (which is not guaranteed to be the auth secret). */\n sessionCookieSecret?: string\n /** Adds `Secure` to every cookie this module sets, and (default session\n * cookie only) the `__Secure-` name prefix. Must match the auth\n * framework's own secure-cookie decision (better-auth: https `baseURL` /\n * `advanced.useSecureCookies`), or it will look up a different cookie name\n * than the one set here. */\n secureCookies: boolean\n /** Default 604 800 (7 days). */\n sessionTtlSeconds?: number\n /** Default 600. Applies to both the cookie Max-Age and the signed TTL. */\n stateTtlSeconds?: number\n /** Default '/app'. */\n defaultRedirectPath?: string\n /** Default '/login'. */\n loginPath?: string\n /** Failure log hook (e.g. console.error). Default no-op. */\n log?: (message: string, error?: unknown) => void\n now?: () => number\n}\n\nexport interface TangleSsoHandlers {\n /** GET start route: mint + sign state, set the state cookie, 302 to the\n * platform authorize URL. `?redirect=` carries the post-login path. */\n start(request: Request): Promise<Response>\n /** GET callback route: verify state, exchange the code, upsert the user,\n * create the session, save the platform link, set the session cookie\n * (via the `setSessionCookie` seam, else signed to better-call's contract\n * with `sessionCookieSecret`), 302 to the saved redirect. Every failure\n * 302s to `loginPath?error=…` with the state cookie cleared. */\n callback(request: Request): Promise<Response>\n}\n\n/** Accept only same-origin absolute paths (rejects `//host` protocol-relative URLs). */\nfunction sanitizeRedirectPath(value: string | null, fallback: string): string {\n if (value && value.startsWith('/') && !value.startsWith('//')) return value\n return fallback\n}\n\nfunction redirectResponse(location: string, headers = new Headers()): Response {\n headers.set('Location', location)\n return new Response(null, { status: 302, headers })\n}\n\n/** Real client IP: `CF-Connecting-IP` behind Cloudflare, else the first\n * `x-forwarded-for` hop (the rest of the list is sender-controlled). */\nfunction clientIp(request: Request): string | null {\n return (\n request.headers.get('CF-Connecting-IP') ??\n request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ??\n null\n )\n}\n\ninterface StateCookiePayload {\n s: string\n r: string\n}\n\nfunction parseStateCookiePayload(raw: string | null): StateCookiePayload | null {\n if (!raw) return null\n try {\n const parsed = JSON.parse(raw) as unknown\n if (parsed === null || typeof parsed !== 'object') return null\n const { s, r } = parsed as Record<string, unknown>\n if (typeof s !== 'string' || typeof r !== 'string') return null\n return { s, r }\n } catch {\n return null\n }\n}\n\nexport function createTangleSsoHandlers(opts: TangleSsoHandlerOptions): TangleSsoHandlers {\n if (!opts.stateSecret) throw new Error('TangleSsoHandlerOptions.stateSecret is required')\n if (!opts.callbackUrl) throw new Error('TangleSsoHandlerOptions.callbackUrl is required')\n if (!opts.stateCookieName) throw new Error('TangleSsoHandlerOptions.stateCookieName is required')\n\n const sessionCookieName = opts.sessionCookieName ?? DEFAULT_SESSION_COOKIE\n\n let mintSessionCookies: (args: TangleSsoSessionCookieArgs) => Promise<readonly string[]>\n if (opts.setSessionCookie) {\n const seam = opts.setSessionCookie\n mintSessionCookies = async (args) => await seam(args)\n } else if (opts.sessionCookieSecret) {\n const secret = opts.sessionCookieSecret\n mintSessionCookies = async ({ token, secure, ttlSeconds }) => [\n serializeCookie(await signSessionCookieValue(token, secret), {\n name: secure ? `__Secure-${sessionCookieName}` : sessionCookieName,\n secure,\n maxAgeSeconds: ttlSeconds,\n }),\n ]\n } else {\n throw new Error(\n 'TangleSsoHandlerOptions requires setSessionCookie or sessionCookieSecret: ' +\n 'better-auth only accepts HMAC-signed (and, on https, __Secure--prefixed) session cookies, ' +\n 'so an unsigned default would mint sessions that read back null',\n )\n }\n const sessionTtlSeconds = opts.sessionTtlSeconds ?? DEFAULT_SESSION_TTL_SECONDS\n const stateTtlSeconds = opts.stateTtlSeconds ?? DEFAULT_STATE_TTL_SECONDS\n const defaultRedirectPath = opts.defaultRedirectPath ?? DEFAULT_REDIRECT_PATH\n const loginPath = opts.loginPath ?? DEFAULT_LOGIN_PATH\n const log = opts.log ?? (() => {})\n const now = opts.now ?? Date.now\n const stateConfig: SsoStateConfig = { secret: opts.stateSecret, ttlMs: stateTtlSeconds * 1000, now }\n\n const stateCookieOpts = { name: opts.stateCookieName, secure: opts.secureCookies }\n\n function loginErrorRedirect(code: string): Response {\n const headers = new Headers()\n headers.append('Set-Cookie', clearCookieHeader(stateCookieOpts))\n return redirectResponse(`${loginPath}?error=${code}`, headers)\n }\n\n return {\n async start(request) {\n const url = new URL(request.url)\n const redirectPath = sanitizeRedirectPath(url.searchParams.get('redirect'), defaultRedirectPath)\n const state = await createSignedSsoState(stateConfig)\n const cookie = serializeCookie(JSON.stringify({ s: state, r: redirectPath }), {\n ...stateCookieOpts,\n maxAgeSeconds: stateTtlSeconds,\n })\n const headers = new Headers()\n headers.append('Set-Cookie', cookie)\n return redirectResponse(opts.auth.authorizeUrl({ state, redirectUri: opts.callbackUrl }), headers)\n },\n\n async callback(request) {\n const url = new URL(request.url)\n const code = url.searchParams.get('code')\n const stateFromPlatform = url.searchParams.get('state')\n if (!code || !stateFromPlatform) return loginErrorRedirect('tangle_callback_missing')\n\n const payload = parseStateCookiePayload(readCookieValue(request.headers.get('cookie'), opts.stateCookieName))\n if (!payload || payload.s !== stateFromPlatform) return loginErrorRedirect('tangle_state_mismatch')\n if (!(await verifySignedSsoState(payload.s, stateConfig))) return loginErrorRedirect('tangle_state_mismatch')\n\n let exchanged: TangleSsoExchangeResult\n try {\n exchanged = await opts.auth.exchange(code)\n } catch (err) {\n log('[tangle-sso] exchange failed', err)\n return loginErrorRedirect('tangle_exchange_failed')\n }\n\n let userId: string\n try {\n ;({ userId } = await opts.store.upsertUserByEmail({\n email: exchanged.user.email,\n name: exchanged.user.name ?? null,\n tangleUserId: exchanged.user.id,\n }))\n } catch (err) {\n if (err instanceof TangleSsoUserCreateError) return loginErrorRedirect('tangle_user_create_failed')\n throw err\n }\n\n const expiresAt = new Date(now() + sessionTtlSeconds * 1000)\n const { token } = await opts.store.createSession({\n userId,\n expiresAt,\n ipAddress: clientIp(request),\n userAgent: request.headers.get('user-agent'),\n })\n\n await opts.store.saveTangleLink({\n userId,\n sessionToken: token,\n tangleUserId: exchanged.user.id,\n email: exchanged.user.email,\n name: exchanged.user.name ?? null,\n apiKey: exchanged.apiKey,\n planTier: exchanged.plan?.tier ?? null,\n })\n\n const headers = new Headers()\n headers.append('Set-Cookie', clearCookieHeader(stateCookieOpts))\n const sessionCookies = await mintSessionCookies({\n token,\n expiresAt,\n ttlSeconds: sessionTtlSeconds,\n secure: opts.secureCookies,\n })\n for (const cookie of sessionCookies) headers.append('Set-Cookie', cookie)\n return redirectResponse(sanitizeRedirectPath(payload.r, defaultRedirectPath), headers)\n },\n }\n}\n","/**\n * Integrations-hub proxy routes: the app-side surface that forwards an\n * authenticated user's requests to the platform's `/v1/integrations/*` API\n * using their stored platform key. Auth, key lookup, and the wire client are\n * structural seams (`HubProxyContext`); error detection is by name + shape so\n * it survives bundlers duplicating module instances.\n */\n\nexport class TangleBearerMissingError extends Error {\n constructor(readonly userId: string) {\n super(`No Tangle platform link for user ${userId}`)\n this.name = 'TangleBearerMissingError'\n }\n}\n\n/** Structural guard (name + userId shape) — robust when the error class is\n * constructed in a different module instance than the one checking it. */\nexport function isTangleBearerMissingError(error: unknown): error is TangleBearerMissingError {\n return (\n error instanceof Error &&\n error.name === 'TangleBearerMissingError' &&\n typeof (error as { userId?: unknown }).userId === 'string'\n )\n}\n\n/** Structural detection of the platform hub wire error (name + numeric status). */\nexport function isPlatformHubErrorLike(error: unknown): error is Error & { status: number; code?: string } {\n return (\n error instanceof Error &&\n error.name === 'PlatformHubError' &&\n typeof (error as { status?: unknown }).status === 'number'\n )\n}\n\n/** Structural subset of the platform hub wire client — extra methods are fine. */\nexport interface HubClientLike {\n catalog(): Promise<unknown>\n listConnections(): Promise<unknown>\n revokeConnection(connectionId: string): Promise<unknown>\n startAuth(input: {\n providerId: string\n connectorId: string\n returnUrl: string\n requestedScopes?: string[]\n }): Promise<{ authorizationUrl: string; state: string }>\n listHealthchecks(): Promise<unknown>\n}\n\nexport interface HubProxyContext {\n /** Resolve the authenticated user id. Throw the app's own auth Response /\n * redirect to reject — it propagates untouched. */\n requireUserId(request: Request): Promise<string>\n /** The user's platform bearer; throw `TangleBearerMissingError` when unlinked. */\n getBearer(userId: string): Promise<string>\n /** A hub client bound to the bearer. */\n createHubClient(bearer: string): HubClientLike\n}\n\nexport interface HubProxyRouteArgs {\n request: Request\n params?: Record<string, string | undefined>\n}\n\nexport interface HubProxyRoutes {\n /** GET → `{ catalog }`. */\n catalog(args: HubProxyRouteArgs): Promise<Response>\n /** GET → `{ connections }`. */\n connections(args: HubProxyRouteArgs): Promise<Response>\n /** DELETE → the platform revocation result verbatim; 405 otherwise. */\n connectionDelete(args: { request: Request; params: { connectionId: string } }): Promise<Response>\n /** GET → `{ healthchecks }`. */\n healthchecks(args: HubProxyRouteArgs): Promise<Response>\n /** POST `{ providerId, connectorId, returnUrl, requestedScopes? }` →\n * `{ authorizationUrl, state }`; 405 non-POST; 400 on bad JSON / missing fields. */\n authStart(args: HubProxyRouteArgs): Promise<Response>\n}\n\ninterface StartAuthBody {\n providerId?: string\n connectorId?: string\n returnUrl?: string\n requestedScopes?: string[]\n}\n\nexport function createHubProxyRoutes(ctx: HubProxyContext): HubProxyRoutes {\n /** Auth runs OUTSIDE the proxy try/catch so the app's auth throw (redirect\n * Response etc.) is never swallowed; bearer + platform errors are mapped. */\n async function proxy(request: Request, call: (hub: HubClientLike) => Promise<Response>): Promise<Response> {\n const userId = await ctx.requireUserId(request)\n try {\n const bearer = await ctx.getBearer(userId)\n return await call(ctx.createHubClient(bearer))\n } catch (err) {\n if (isTangleBearerMissingError(err)) {\n return Response.json({ error: 'tangle_link_required' }, { status: 412 })\n }\n if (isPlatformHubErrorLike(err)) {\n return Response.json({ error: err.message, code: err.code }, { status: err.status })\n }\n throw err\n }\n }\n\n return {\n catalog: ({ request }) => proxy(request, async (hub) => Response.json({ catalog: await hub.catalog() })),\n\n connections: ({ request }) =>\n proxy(request, async (hub) => Response.json({ connections: await hub.listConnections() })),\n\n connectionDelete: async ({ request, params }) => {\n if (request.method !== 'DELETE') {\n return Response.json({ error: 'Method not allowed' }, { status: 405 })\n }\n return proxy(request, async (hub) => Response.json(await hub.revokeConnection(params.connectionId)))\n },\n\n healthchecks: ({ request }) =>\n proxy(request, async (hub) => Response.json({ healthchecks: await hub.listHealthchecks() })),\n\n authStart: async ({ request }) => {\n if (request.method !== 'POST') {\n return Response.json({ error: 'Method not allowed' }, { status: 405 })\n }\n const userId = await ctx.requireUserId(request)\n let body: StartAuthBody\n try {\n body = (await request.json()) as StartAuthBody\n } catch {\n return Response.json({ error: 'Invalid JSON body' }, { status: 400 })\n }\n if (!body.providerId || !body.connectorId || !body.returnUrl) {\n return Response.json({ error: 'providerId, connectorId, and returnUrl are required' }, { status: 400 })\n }\n try {\n const bearer = await ctx.getBearer(userId)\n const result = await ctx.createHubClient(bearer).startAuth({\n providerId: body.providerId,\n connectorId: body.connectorId,\n returnUrl: body.returnUrl,\n requestedScopes: body.requestedScopes,\n })\n return Response.json({ authorizationUrl: result.authorizationUrl, state: result.state })\n } catch (err) {\n if (isTangleBearerMissingError(err)) {\n return Response.json({ error: 'tangle_link_required' }, { status: 412 })\n }\n if (isPlatformHubErrorLike(err)) {\n return Response.json({ error: err.message, code: err.code }, { status: err.status })\n }\n throw err\n }\n },\n }\n}\n","/**\n * Platform billing HTTP transport + tier state for apps on the shared\n * Tangle balance model (id.tangle.tools). Reads authenticate as the user via\n * their per-user platform key (the platform resolves the caller from the\n * key; service or impersonation headers on read routes are rejected). The\n * deduct write authenticates as the product service (`Bearer <serviceToken>`\n * + `X-Service-Name`) and names the target user in the body. Also provides a\n * fetch-backed implementation of the `/billing` module's\n * `PlatformBillingClient` seam (type-only import — no runtime coupling).\n */\n\nimport type { PlatformBillingClient, PlatformIdentity } from '../billing/index'\n\nexport type TanglePlanTier = 'free' | 'pro' | 'enterprise'\n\n/** 'pro' | 'enterprise' pass through; anything else (null, unknown) → 'free'. */\nexport function normalizeTanglePlanTier(plan: string | null | undefined): TanglePlanTier {\n return plan === 'pro' || plan === 'enterprise' ? plan : 'free'\n}\n\nexport class PlatformBillingHttpError extends Error {\n constructor(\n readonly status: number,\n detail: string,\n ) {\n super(`Platform request failed (${status}): ${detail}`)\n this.name = 'PlatformBillingHttpError'\n }\n}\n\n/** Structural guard (name + numeric status) — robust across module instances. */\nexport function isPlatformBillingHttpError(error: unknown): error is PlatformBillingHttpError {\n return (\n error instanceof Error &&\n error.name === 'PlatformBillingHttpError' &&\n typeof (error as { status?: unknown }).status === 'number'\n )\n}\n\nexport interface PlatformBillingHttpOptions {\n /** Platform root, e.g. https://id.tangle.tools (trailing slashes stripped). */\n baseUrl: string\n /** Used only by `deduct()`; resolved lazily so reads never require it.\n * Throws at call time when empty. */\n serviceToken: string | (() => string)\n /** Product slug — the `X-Service-Name` header and the deduct `product` field. */\n productSlug: string\n fetchImpl?: typeof fetch\n /** Default 10 000. */\n timeoutMs?: number\n}\n\nexport interface PlatformSubscriptionInfo {\n tier: TanglePlanTier\n status: string | null\n}\n\nexport interface PlatformBalanceSnapshot {\n balance: number\n lifetimeSpent: number\n updatedAt?: string\n}\n\nexport interface PlatformUsageProductRow {\n product: string | null\n totalSpent: number\n count: number\n}\n\nexport interface PlatformBillingHttp {\n /** GET /v1/plans/current (user bearer). */\n getSubscription(userApiKey: string): Promise<PlatformSubscriptionInfo>\n /** GET /v1/billing/balance (user bearer). */\n getBalance(userApiKey: string): Promise<PlatformBalanceSnapshot>\n /** GET /v1/billing/usage (user bearer). */\n getUsageByProduct(userApiKey: string): Promise<PlatformUsageProductRow[]>\n /** POST /v1/billing/deduct (service token). */\n deduct(input: {\n platformUserId: string\n amountUsd: number\n type: string\n description: string\n referenceId: string\n }): Promise<void>\n /** Absolute URL of the platform's billing-management surface. */\n billingUrl(): string\n}\n\nexport function createPlatformBillingHttp(opts: PlatformBillingHttpOptions): PlatformBillingHttp {\n const baseUrl = opts.baseUrl.replace(/\\/+$/, '')\n if (!baseUrl) throw new Error('PlatformBillingHttpOptions.baseUrl is required')\n if (!opts.productSlug) throw new Error('PlatformBillingHttpOptions.productSlug is required')\n const fetchImpl = opts.fetchImpl ?? fetch\n const timeoutMs = opts.timeoutMs ?? 10_000\n\n function resolveServiceToken(): string {\n const token = typeof opts.serviceToken === 'function' ? opts.serviceToken() : opts.serviceToken\n if (!token) throw new Error('A platform service token is required for deduct')\n return token\n }\n\n async function request<T>(path: string, init: RequestInit, headers: Headers): Promise<T> {\n const res = await fetchImpl(`${baseUrl}${path}`, {\n ...init,\n headers,\n signal: AbortSignal.timeout(timeoutMs),\n })\n if (!res.ok) {\n const body = (await res.json().catch(() => null)) as { error?: { message?: string } } | null\n throw new PlatformBillingHttpError(res.status, body?.error?.message ?? res.statusText)\n }\n return res.json() as Promise<T>\n }\n\n function userRead<T>(userApiKey: string, path: string): Promise<T> {\n const headers = new Headers()\n headers.set('Authorization', `Bearer ${userApiKey}`)\n return request<T>(path, {}, headers)\n }\n\n return {\n async getSubscription(userApiKey) {\n const body = await userRead<{\n success: boolean\n data?: { subscription?: { plan?: string | null; status?: string | null } | null }\n }>(userApiKey, '/v1/plans/current')\n const sub = body.data?.subscription ?? null\n return { tier: normalizeTanglePlanTier(sub?.plan), status: sub?.status ?? null }\n },\n\n async getBalance(userApiKey) {\n const body = await userRead<{\n success: boolean\n data?: { balance?: number; lifetimeSpent?: number; updatedAt?: string }\n }>(userApiKey, '/v1/billing/balance')\n return {\n balance: body.data?.balance ?? 0,\n lifetimeSpent: body.data?.lifetimeSpent ?? 0,\n updatedAt: body.data?.updatedAt,\n }\n },\n\n async getUsageByProduct(userApiKey) {\n const body = await userRead<{\n success: boolean\n data?: Array<{ product?: string | null; totalSpent?: number; count?: number }>\n }>(userApiKey, '/v1/billing/usage')\n return (body.data ?? []).map((row) => ({\n product: row.product ?? null,\n totalSpent: row.totalSpent ?? 0,\n count: row.count ?? 0,\n }))\n },\n\n async deduct(input) {\n const headers = new Headers()\n headers.set('Authorization', `Bearer ${resolveServiceToken()}`)\n headers.set('X-Service-Name', opts.productSlug)\n headers.set('Content-Type', 'application/json')\n await request('/v1/billing/deduct', {\n method: 'POST',\n body: JSON.stringify({\n userId: input.platformUserId,\n amount: input.amountUsd,\n type: input.type,\n product: opts.productSlug,\n description: input.description,\n referenceId: input.referenceId,\n }),\n }, headers)\n },\n\n billingUrl() {\n return `${baseUrl}/app/billing`\n },\n }\n}\n\n// ── Tier policy + composed state ────────────────────────────────────────────\n\nexport interface TangleTierPolicy {\n concurrency: number\n overageAllowed: boolean\n}\n\nexport const DEFAULT_TANGLE_TIER_POLICY: Record<TanglePlanTier, TangleTierPolicy> = {\n free: { concurrency: 1, overageAllowed: false },\n pro: { concurrency: Number.POSITIVE_INFINITY, overageAllowed: true },\n enterprise: { concurrency: Number.POSITIVE_INFINITY, overageAllowed: true },\n}\n\nexport interface TangleTierState {\n tier: TanglePlanTier\n subscriptionStatus: string | null\n remainingBalanceUsd: number\n lifetimeSpentUsd: number\n concurrency: number\n overageAllowed: boolean\n}\n\n/**\n * Read subscription + balance and project them onto the tier policy. A\n * null/absent key fails CLOSED (free tier, zero balance) — a billable run is\n * never started against an unknown balance. Platform errors throw; callers\n * on the billable path choose their posture explicitly.\n */\nexport async function readTangleTierState(\n http: PlatformBillingHttp,\n userApiKey: string | null | undefined,\n policy: Record<TanglePlanTier, TangleTierPolicy> = DEFAULT_TANGLE_TIER_POLICY,\n): Promise<TangleTierState> {\n if (!userApiKey) {\n return {\n tier: 'free',\n subscriptionStatus: null,\n remainingBalanceUsd: 0,\n lifetimeSpentUsd: 0,\n ...policy.free,\n }\n }\n const [subscription, balance] = await Promise.all([\n http.getSubscription(userApiKey),\n http.getBalance(userApiKey),\n ])\n return {\n tier: subscription.tier,\n subscriptionStatus: subscription.status,\n remainingBalanceUsd: balance.balance,\n lifetimeSpentUsd: balance.lifetimeSpent,\n ...policy[subscription.tier],\n }\n}\n\n// ── Bridge onto the /billing seam ───────────────────────────────────────────\n\nexport interface PlatformIdentityStore {\n resolveIdentity(userId: string): Promise<PlatformIdentity | null>\n}\n\n/** Concrete fetch-backed `PlatformBillingClient<TanglePlanTier>` for\n * `createPlatformBalanceManager` (from `/billing`). */\nexport function createTanglePlatformBillingClient(\n http: PlatformBillingHttp,\n identity: PlatformIdentityStore,\n): PlatformBillingClient<TanglePlanTier> {\n return {\n resolveIdentity: (userId) => identity.resolveIdentity(userId),\n getPlan: async (apiKey) => (await http.getSubscription(apiKey)).tier,\n getBalance: async (apiKey) => {\n const snapshot = await http.getBalance(apiKey)\n return { balance: snapshot.balance, lifetimeSpent: snapshot.lifetimeSpent }\n },\n getUsageByProduct: (apiKey) => http.getUsageByProduct(apiKey),\n deduct: (input) => http.deduct(input),\n }\n}\n","/**\n * Request guards for agent-app routes: session auth (302 redirect for pages,\n * JSON 401 for APIs), admin allowlisting (404 — the route stays invisible to\n * non-admins), and the billable-balance gate (402 with a stable code).\n * Session resolution is a seam; thrown Responses follow the router convention\n * of surfacing a thrown Response as the route result.\n */\n\nimport { isTangleBillingEnforcementDisabled } from '../runtime/model'\n\nexport interface AuthGuardOptions<Session> {\n /** e.g. a better-auth `auth.api.getSession` wrapped by the app. */\n getSession(request: Request): Promise<Session | null | undefined>\n /** Default '/login'. */\n loginPath?: string\n}\n\nexport interface AuthGuard<Session> {\n /** Page guard — throws a 302 redirect Response to `loginPath`. */\n requireUser(request: Request): Promise<Session>\n /** API guard — throws JSON 401 `{ error: 'Unauthorized', code: 'auth.unauthenticated' }`. */\n requireApiUser(request: Request): Promise<Session>\n /** `apiResponse` selects the 401 JSON path over the redirect. */\n requireSession(request: Request, opts?: { apiResponse?: boolean }): Promise<Session>\n getOptionalSession(request: Request): Promise<Session | null>\n}\n\nexport function createAuthGuard<Session>(opts: AuthGuardOptions<Session>): AuthGuard<Session> {\n const loginPath = opts.loginPath ?? '/login'\n\n async function requireSession(request: Request, o: { apiResponse?: boolean } = {}): Promise<Session> {\n const session = await opts.getSession(request)\n if (!session) {\n if (o.apiResponse) {\n throw Response.json({ error: 'Unauthorized', code: 'auth.unauthenticated' }, { status: 401 })\n }\n throw new Response(null, { status: 302, headers: { Location: loginPath } })\n }\n return session\n }\n\n return {\n requireSession,\n requireUser: (request) => requireSession(request),\n requireApiUser: (request) => requireSession(request, { apiResponse: true }),\n getOptionalSession: async (request) => (await opts.getSession(request)) ?? null,\n }\n}\n\n/** Comma/whitespace separated → trimmed, lowercased, empties dropped. */\nexport function parseAdminEmails(raw: string | null | undefined): string[] {\n return (raw ?? '')\n .split(/[,\\s]+/)\n .map((e) => e.trim().toLowerCase())\n .filter(Boolean)\n}\n\nexport interface AdminGuardOptions<Session> {\n requireUser(request: Request): Promise<Session>\n emailOf(session: Session): string | null | undefined\n /** Resolved per request; an EMPTY allowlist refuses everyone. */\n allowedEmails(): string[]\n}\n\n/** Non-admins (and empty allowlists) get 404, keeping the route invisible —\n * better than a \"forbidden\" footprint that advertises its existence. */\nexport function createAdminGuard<Session>(opts: AdminGuardOptions<Session>): (request: Request) => Promise<Session> {\n return async (request) => {\n const session = await opts.requireUser(request)\n const allowed = opts.allowedEmails()\n if (allowed.length === 0) throw new Response('Not found', { status: 404 })\n const email = (opts.emailOf(session) ?? '').toLowerCase()\n if (!allowed.includes(email)) throw new Response('Not found', { status: 404 })\n return session\n }\n}\n\nexport interface BillableBalanceState {\n overageAllowed: boolean\n remainingBalanceUsd: number\n}\n\nexport interface AssertBillableBalanceOptions {\n env?: Record<string, string | undefined>\n /** App-specific enforcement override flag (e.g. 'GTM_BILLING_ENFORCEMENT'),\n * fed to `isTangleBillingEnforcementDisabled`. */\n enforcementEnvVar?: string\n /** Default 'Add balance or upgrade your plan to invoke this agent.'. */\n errorMessage?: string\n /** Merged into the 402 JSON body (e.g. `{ organizationId }`). */\n errorBody?: Record<string, unknown>\n}\n\n/**\n * Gate a billable turn: passes when enforcement is disabled (dev default),\n * the tier allows overage, or remaining balance is positive. Otherwise throws\n * a 402 Response with the stable `billing.balance_required` code so clients\n * can route to the billing screen.\n */\nexport function assertBillableBalance(state: BillableBalanceState, opts: AssertBillableBalanceOptions = {}): void {\n if (isTangleBillingEnforcementDisabled({ env: opts.env, enforcementEnvVar: opts.enforcementEnvVar })) return\n if (state.overageAllowed || state.remainingBalanceUsd > 0) return\n // errorBody first: the stable error/code contract always wins over caller extras.\n throw Response.json(\n {\n ...opts.errorBody,\n error: opts.errorMessage ?? 'Add balance or upgrade your plan to invoke this agent.',\n code: 'billing.balance_required',\n },\n { status: 402 },\n )\n}\n"],"mappings":"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","names":[]}
|
package/dist/web-react/index.js
CHANGED
|
@@ -449,6 +449,30 @@ function pendingApprovalOf(call) {
|
|
|
449
449
|
if (!outcome?.ok || outcome.result?.status !== "queued_for_approval" || !outcome.result.proposalId) return null;
|
|
450
450
|
return { proposalId: outcome.result.proposalId };
|
|
451
451
|
}
|
|
452
|
+
function ToolGlyph({ name, className }) {
|
|
453
|
+
if (name.startsWith("sandbox_")) {
|
|
454
|
+
return /* @__PURE__ */ jsxs2("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
|
|
455
|
+
/* @__PURE__ */ jsx2("polyline", { points: "4 17 10 11 4 5" }),
|
|
456
|
+
/* @__PURE__ */ jsx2("line", { x1: "12", y1: "19", x2: "20", y2: "19" })
|
|
457
|
+
] });
|
|
458
|
+
}
|
|
459
|
+
if (name === "submit_proposal") {
|
|
460
|
+
return /* @__PURE__ */ jsxs2("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
|
|
461
|
+
/* @__PURE__ */ jsx2("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
|
|
462
|
+
/* @__PURE__ */ jsx2("path", { d: "M14 2v6h6M9 15l2 2 4-4" })
|
|
463
|
+
] });
|
|
464
|
+
}
|
|
465
|
+
if (name === "schedule_followup") {
|
|
466
|
+
return /* @__PURE__ */ jsxs2("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": true, children: [
|
|
467
|
+
/* @__PURE__ */ jsx2("circle", { cx: "12", cy: "12", r: "9" }),
|
|
468
|
+
/* @__PURE__ */ jsx2("path", { d: "M12 7v5l3 3" })
|
|
469
|
+
] });
|
|
470
|
+
}
|
|
471
|
+
return /* @__PURE__ */ jsxs2("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
|
|
472
|
+
/* @__PURE__ */ jsx2("path", { d: "M12 3v3m0 12v3M3 12h3m12 0h3" }),
|
|
473
|
+
/* @__PURE__ */ jsx2("circle", { cx: "12", cy: "12", r: "4" })
|
|
474
|
+
] });
|
|
475
|
+
}
|
|
452
476
|
function toolOutcomeOf(call) {
|
|
453
477
|
return call.result;
|
|
454
478
|
}
|
|
@@ -485,6 +509,21 @@ function KvRows({ data }) {
|
|
|
485
509
|
/* @__PURE__ */ jsx2("dd", { className: "min-w-0 whitespace-pre-wrap break-words font-mono text-[11px] text-muted-foreground", children: truncate(v) })
|
|
486
510
|
] }, k)) });
|
|
487
511
|
}
|
|
512
|
+
function ShellDetail({ call }) {
|
|
513
|
+
const outcome = toolOutcomeOf(call);
|
|
514
|
+
const r = outcome?.result ?? {};
|
|
515
|
+
return /* @__PURE__ */ jsxs2("div", { className: "overflow-hidden rounded-md bg-zinc-900 font-mono text-[11px] leading-relaxed", children: [
|
|
516
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 px-3 pt-2 text-zinc-400", children: [
|
|
517
|
+
/* @__PURE__ */ jsx2("span", { className: "select-none text-zinc-500", children: "$" }),
|
|
518
|
+
/* @__PURE__ */ jsx2("span", { className: "min-w-0 flex-1 truncate text-zinc-200", children: String(call.args?.command ?? "") }),
|
|
519
|
+
r.exitCode != null && /* @__PURE__ */ jsxs2("span", { className: r.exitCode === 0 ? "text-emerald-400" : "text-red-400", children: [
|
|
520
|
+
"exit ",
|
|
521
|
+
r.exitCode
|
|
522
|
+
] })
|
|
523
|
+
] }),
|
|
524
|
+
/* @__PURE__ */ jsx2("pre", { className: "max-h-56 overflow-auto whitespace-pre-wrap px-3 pb-2.5 pt-1.5 text-zinc-300", children: outcome?.ok === false ? outcome.message ?? "failed" : [r.stdout, r.stderr].filter(Boolean).join("\n") || "(no output)" })
|
|
525
|
+
] });
|
|
526
|
+
}
|
|
488
527
|
function DefaultToolDetail({ call }) {
|
|
489
528
|
const outcome = toolOutcomeOf(call);
|
|
490
529
|
return /* @__PURE__ */ jsxs2("div", { className: "space-y-2", children: [
|
|
@@ -527,6 +566,7 @@ function ToolCallCard({
|
|
|
527
566
|
className: `h-2 w-2 shrink-0 rounded-full ${call.status === "running" ? "animate-pulse bg-yellow-500" : pending ? "bg-amber-500" : failed ? "bg-red-500" : "bg-green-500"}`
|
|
528
567
|
}
|
|
529
568
|
),
|
|
569
|
+
/* @__PURE__ */ jsx2(ToolGlyph, { name: call.name, className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }),
|
|
530
570
|
/* @__PURE__ */ jsx2("span", { className: "min-w-0 flex-1 truncate font-medium", children: friendlyToolTitle(call) }),
|
|
531
571
|
pending && approval && /* @__PURE__ */ jsxs2("span", { className: "flex shrink-0 items-center gap-1", onClick: (e) => e.stopPropagation(), children: [
|
|
532
572
|
/* @__PURE__ */ jsx2(
|
|
@@ -554,7 +594,7 @@ function ToolCallCard({
|
|
|
554
594
|
}
|
|
555
595
|
),
|
|
556
596
|
expanded && /* @__PURE__ */ jsxs2("div", { className: "border-t border-border/40 px-3 py-2.5", children: [
|
|
557
|
-
custom ?? /* @__PURE__ */ jsx2(DefaultToolDetail, { call }),
|
|
597
|
+
custom ?? (call.name === "sandbox_run_command" ? /* @__PURE__ */ jsx2(ShellDetail, { call }) : /* @__PURE__ */ jsx2(DefaultToolDetail, { call })),
|
|
558
598
|
onOpenRun && call.name.startsWith("sandbox_") && /* @__PURE__ */ jsx2(
|
|
559
599
|
"button",
|
|
560
600
|
{
|
|
@@ -583,6 +623,14 @@ function AssistantMessage({
|
|
|
583
623
|
const content = useSmoothText(msg.content, streaming);
|
|
584
624
|
const reasoning = useSmoothText(msg.reasoning ?? "", streaming);
|
|
585
625
|
const reasoningScrollRef = useRef2(null);
|
|
626
|
+
const thinkStartRef = useRef2(null);
|
|
627
|
+
const thinkMsRef = useRef2(null);
|
|
628
|
+
if (streaming && reasoning && !content && thinkStartRef.current === null) {
|
|
629
|
+
thinkStartRef.current = performance.now();
|
|
630
|
+
}
|
|
631
|
+
if (content && thinkStartRef.current !== null && thinkMsRef.current === null) {
|
|
632
|
+
thinkMsRef.current = performance.now() - thinkStartRef.current;
|
|
633
|
+
}
|
|
586
634
|
useEffect2(() => {
|
|
587
635
|
const el = reasoningScrollRef.current;
|
|
588
636
|
if (el && streaming && !content) el.scrollTop = el.scrollHeight;
|
|
@@ -594,14 +642,14 @@ function AssistantMessage({
|
|
|
594
642
|
formatTokensPerSecond(msg) && /* @__PURE__ */ jsx2("span", { children: formatTokensPerSecond(msg) }),
|
|
595
643
|
formatModelCost(msg, models) && /* @__PURE__ */ jsx2("span", { children: formatModelCost(msg, models) })
|
|
596
644
|
] }),
|
|
597
|
-
reasoning && /* @__PURE__ */ jsxs2("details", { className: "mb-2 rounded-
|
|
598
|
-
/* @__PURE__ */
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
645
|
+
reasoning && /* @__PURE__ */ jsxs2("details", { className: "mb-2 rounded-lg border-l-2 border-border/70 bg-muted/20 px-3 py-2", open: !content, children: [
|
|
646
|
+
/* @__PURE__ */ jsx2("summary", { className: "cursor-pointer select-none text-xs font-medium text-muted-foreground", children: !content ? /* @__PURE__ */ jsx2("span", { className: "animate-pulse", children: "Thinking\u2026" }) : thinkMsRef.current != null ? `Thought for ${Math.max(1, Math.round(thinkMsRef.current / 1e3))}s` : "Thought process" }),
|
|
647
|
+
/* @__PURE__ */ jsx2("div", { ref: reasoningScrollRef, className: "mt-2 max-h-48 overflow-y-auto whitespace-pre-wrap text-[13px] leading-relaxed text-muted-foreground/70", children: reasoning })
|
|
648
|
+
] }),
|
|
649
|
+
/* @__PURE__ */ jsxs2("div", { className: "text-base leading-[1.75]", children: [
|
|
650
|
+
renderBody(content),
|
|
651
|
+
streaming && content && !msg.toolCalls?.length && /* @__PURE__ */ jsx2("span", { className: "ml-0.5 inline-block h-[1.1em] w-[3px] translate-y-[2px] animate-pulse rounded-sm bg-foreground/70", "aria-hidden": true })
|
|
603
652
|
] }),
|
|
604
|
-
/* @__PURE__ */ jsx2("div", { className: "text-base leading-[1.75]", children: renderBody(content) }),
|
|
605
653
|
msg.toolCalls && msg.toolCalls.length > 0 && /* @__PURE__ */ jsx2("div", { className: "mt-2 flex flex-col gap-1.5", children: msg.toolCalls.map((tc) => /* @__PURE__ */ jsx2(
|
|
606
654
|
ToolCallCard,
|
|
607
655
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/web-react/index.tsx","../../src/web-react/provider-logo.tsx","../../src/web-react/smooth-text.ts","../../src/web-react/chat-stream.ts"],"sourcesContent":["/**\n * `@tangle-network/agent-app/web-react` — the shared chat-shell components\n * every agent app's web UI hand-rolls: a model picker over the runtime's\n * model catalogue, a reasoning-effort selector, and a message thread with\n * User/Agent identity, per-message model + cost + tokens/sec metrics, tool\n * chips, and a collapsible thinking section.\n *\n * Works for BOTH chat shapes: router-backed copilots (LoopEvents from\n * `runtime/openai-stream`) and sandbox-backed chats — the thread renders\n * `ChatUiMessage`s; how they're produced is the app's business.\n *\n * Styling contract: Tailwind classes against the shared design tokens\n * (`bg-card`, `border-border`, `text-muted-foreground`, `bg-primary`, …) that\n * Tangle app shells define. No icon library — the few glyphs are inline SVGs.\n * Markdown and provider logos are injected (`renderMarkdown`,\n * `renderProviderBadge`) so this package stays dependency-free beyond React.\n */\n\nimport { useEffect, useMemo, useRef, useState, type ReactNode } from 'react'\nimport { ProviderLogo } from './provider-logo'\nimport { useSmoothText } from './smooth-text'\n\nexport * from './chat-stream'\nexport * from './provider-logo'\nexport * from './smooth-text'\nimport type { CatalogModel } from '../runtime/model-catalog'\n\n// ── shared glyphs (no icon-library dependency) ────────────────────────────\n\nfunction ChevronDown({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n )\n}\n\nfunction SearchGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <circle cx=\"11\" cy=\"11\" r=\"8\" />\n <path d=\"m21 21-4.3-4.3\" />\n </svg>\n )\n}\n\nfunction SparkleGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M12 3v3m0 12v3M3 12h3m12 0h3M5.6 5.6l2.1 2.1m8.6 8.6 2.1 2.1m0-12.8-2.1 2.1M7.7 16.3l-2.1 2.1\" />\n </svg>\n )\n}\n\n/** Close an absolutely-positioned popover on outside mousedown. */\nfunction useClickOutside(onOutside: () => void) {\n const ref = useRef<HTMLDivElement>(null)\n useEffect(() => {\n function handler(e: MouseEvent) {\n if (ref.current && !ref.current.contains(e.target as Node)) onOutside()\n }\n document.addEventListener('mousedown', handler)\n return () => document.removeEventListener('mousedown', handler)\n })\n return ref\n}\n\n// ── metrics helpers ───────────────────────────────────────────────────────\n\nexport interface ChatMessageMetrics {\n modelUsed?: string\n promptTokens?: number\n completionTokens?: number\n durationMs?: number\n}\n\n/** \"$0.0042\" from token counts × catalogue per-token pricing; null when unknown. */\nexport function formatModelCost(msg: ChatMessageMetrics, models: CatalogModel[]): string | null {\n if (msg.promptTokens == null && msg.completionTokens == null) return null\n const pricing = models.find((m) => m.id === msg.modelUsed)?.pricing\n if (!pricing) return null\n const cost =\n (msg.promptTokens ?? 0) * Number(pricing.prompt ?? 0) +\n (msg.completionTokens ?? 0) * Number(pricing.completion ?? 0)\n if (!isFinite(cost) || cost <= 0) return null\n return cost < 0.01 ? `$${cost.toFixed(4)}` : `$${cost.toFixed(2)}`\n}\n\n/** \"38 tok/s\" from completion tokens over first-token→end duration; null when unknown. */\nexport function formatTokensPerSecond(msg: ChatMessageMetrics): string | null {\n if (msg.completionTokens == null || !msg.durationMs) return null\n return `${Math.round(msg.completionTokens / (msg.durationMs / 1000))} tok/s`\n}\n\n// ── ModelPicker ───────────────────────────────────────────────────────────\n\nexport interface ModelPickerProps {\n value: string\n onChange: (id: string) => void\n /** Catalogue models — from `GET`ing the app's catalogue route (see\n * `runtime/model-catalog`), plus any product-specific entries appended. */\n models: CatalogModel[]\n loading?: boolean\n /** Render a provider logo/badge; default is a generic sparkle. */\n renderProviderBadge?: (provider: string) => ReactNode\n /** Section label for `featured` models. */\n recommendedLabel?: string\n}\n\nfunction formatPrice(p?: string): string | undefined {\n if (!p) return undefined\n const n = Number(p)\n if (isNaN(n) || n === 0) return undefined\n const perM = n * 1_000_000\n return perM >= 1 ? `$${perM.toFixed(0)}/M` : `$${perM.toFixed(2)}/M`\n}\n\nfunction formatContext(len?: number): string | undefined {\n if (!len) return undefined\n if (len >= 1_000_000) return `${(len / 1_000_000).toFixed(1)}M ctx`\n if (len >= 1_000) return `${Math.round(len / 1_000)}K ctx`\n return `${len} ctx`\n}\n\nfunction SectionHeader({ children }: { children: ReactNode }) {\n return (\n <div className=\"px-3 pb-1 pt-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground/70\">\n {children}\n </div>\n )\n}\n\nfunction ModelRow({\n model,\n selected,\n onSelect,\n renderProviderBadge,\n}: {\n model: CatalogModel\n selected: boolean\n onSelect: () => void\n renderProviderBadge?: (provider: string) => ReactNode\n}) {\n const price = formatPrice(model.pricing?.prompt)\n const ctx = formatContext(model.contextLength)\n return (\n <button\n type=\"button\"\n onClick={onSelect}\n className={`flex w-full items-center gap-2.5 rounded-md px-3 py-2.5 text-left text-sm transition ${\n selected ? 'bg-primary/10 font-medium' : 'hover:bg-accent/30'\n }`}\n >\n {renderProviderBadge ? renderProviderBadge(model.provider) : <ProviderLogo provider={model.provider} size={16} />}\n <span className=\"truncate\">{model.name}</span>\n {!model.supportsTools && (\n <span className=\"shrink-0 rounded bg-muted/60 px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground\">\n no tools\n </span>\n )}\n <span className=\"ml-auto flex shrink-0 items-center gap-2 text-xs text-muted-foreground\">\n {ctx && <span>{ctx}</span>}\n {price && <span>{price}</span>}\n </span>\n </button>\n )\n}\n\n/**\n * Searchable model picker pill + popover: a featured/recommended section\n * first, then per-provider groups in catalogue order (the server already\n * sorts providers by tier).\n */\nexport function ModelPicker({ value, onChange, models, loading, renderProviderBadge, recommendedLabel = 'Recommended' }: ModelPickerProps) {\n const [open, setOpen] = useState(false)\n const [query, setQuery] = useState('')\n const containerRef = useClickOutside(() => setOpen(false))\n const inputRef = useRef<HTMLInputElement>(null)\n\n useEffect(() => {\n if (open) inputRef.current?.focus()\n }, [open])\n\n const selected = models.find((m) => m.id === value)\n\n const filtered = useMemo(() => {\n const q = query.trim().toLowerCase()\n if (!q) return null\n return models.filter(\n (m) =>\n m.id.toLowerCase().includes(q) ||\n m.name.toLowerCase().includes(q) ||\n (m.description?.toLowerCase() ?? '').includes(q) ||\n m.provider.toLowerCase().includes(q),\n )\n }, [models, query])\n\n const sections = useMemo(() => {\n const recommended = models.filter((m) => m.featured)\n const byProvider: Array<{ provider: string; items: CatalogModel[] }> = []\n for (const m of models) {\n if (m.featured) continue\n const last = byProvider[byProvider.length - 1]\n if (last && last.provider === m.provider) last.items.push(m)\n else byProvider.push({ provider: m.provider, items: [m] })\n }\n return { recommended, byProvider }\n }, [models])\n\n const select = (id: string) => {\n onChange(id)\n setOpen(false)\n setQuery('')\n }\n\n return (\n <div ref={containerRef} className=\"relative inline-flex\">\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n className=\"inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground transition hover:bg-accent/30\"\n >\n {selected ? (renderProviderBadge ? renderProviderBadge(selected.provider) : <ProviderLogo provider={selected.provider} size={16} />) : <SparkleGlyph className=\"h-3.5 w-3.5 text-muted-foreground\" />}\n <span className=\"max-w-[160px] truncate\">{selected?.name ?? value}</span>\n <ChevronDown className=\"h-3.5 w-3.5 text-muted-foreground\" />\n </button>\n\n {open && (\n <div className=\"absolute bottom-full left-0 z-50 mb-2 w-[420px] overflow-hidden rounded-xl border border-border bg-card shadow-lg\">\n <div className=\"border-b border-border px-3 py-2\">\n <div className=\"flex items-center gap-2 rounded-lg border border-border bg-background px-3 py-2\">\n <SearchGlyph className=\"h-3.5 w-3.5 text-muted-foreground\" />\n <input\n ref={inputRef}\n type=\"text\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Search models...\"\n className=\"flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground\"\n />\n </div>\n </div>\n <div className=\"max-h-[400px] overflow-y-auto p-1 pb-2\">\n {loading && <div className=\"px-3 py-4 text-center text-sm text-muted-foreground\">Loading models...</div>}\n {!loading && filtered && (\n <>\n {filtered.length === 0 && (\n <div className=\"px-3 py-4 text-center text-sm text-muted-foreground\">No models match your search</div>\n )}\n {filtered.map((m) => (\n <ModelRow key={m.id} model={m} selected={m.id === value} onSelect={() => select(m.id)} renderProviderBadge={renderProviderBadge} />\n ))}\n </>\n )}\n {!loading && !filtered && (\n <>\n {sections.recommended.length > 0 && (\n <>\n <SectionHeader>{recommendedLabel}</SectionHeader>\n {sections.recommended.map((m) => (\n <ModelRow key={m.id} model={m} selected={m.id === value} onSelect={() => select(m.id)} renderProviderBadge={renderProviderBadge} />\n ))}\n </>\n )}\n {sections.byProvider.map((g) => (\n <div key={g.provider}>\n <SectionHeader>{g.provider}</SectionHeader>\n {g.items.map((m) => (\n <ModelRow key={m.id} model={m} selected={m.id === value} onSelect={() => select(m.id)} renderProviderBadge={renderProviderBadge} />\n ))}\n </div>\n ))}\n </>\n )}\n </div>\n </div>\n )}\n </div>\n )\n}\n\n// ── EffortPicker ──────────────────────────────────────────────────────────\n\nconst EFFORT_LEVELS = [\n { id: 'off', label: 'Off' },\n { id: 'low', label: 'Low' },\n { id: 'medium', label: 'Medium' },\n { id: 'high', label: 'High' },\n] as const\n\nexport interface EffortPickerProps {\n value: string\n onChange: (id: string) => void\n}\n\n/** Reasoning-effort selector pill, styled to match {@link ModelPicker}. Show\n * it only when the selected model `supportsReasoning`. */\nexport function EffortPicker({ value, onChange }: EffortPickerProps) {\n const [open, setOpen] = useState(false)\n const containerRef = useClickOutside(() => setOpen(false))\n const selected = EFFORT_LEVELS.find((l) => l.id === value) ?? EFFORT_LEVELS[2]\n\n return (\n <div ref={containerRef} className=\"relative inline-flex\">\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n title=\"Reasoning effort\"\n className=\"inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground transition hover:bg-accent/30\"\n >\n <SparkleGlyph className=\"h-3.5 w-3.5 text-muted-foreground\" />\n <span>{selected.label}</span>\n <ChevronDown className=\"h-3.5 w-3.5 text-muted-foreground\" />\n </button>\n {open && (\n <div className=\"absolute bottom-full left-0 z-50 mb-2 w-36 overflow-hidden rounded-xl border border-border bg-card p-1 shadow-lg\">\n {EFFORT_LEVELS.map((l) => (\n <button\n key={l.id}\n type=\"button\"\n onClick={() => {\n onChange(l.id)\n setOpen(false)\n }}\n className={`flex w-full items-center rounded-md px-3 py-2 text-left text-sm transition ${\n l.id === value ? 'bg-primary/10 font-medium' : 'hover:bg-accent/30'\n }`}\n >\n {l.label}\n </button>\n ))}\n </div>\n )}\n </div>\n )\n}\n\n// ── Tool run drill-in (retained runs) ─────────────────────────────────────\n\n/** One step of a retained tool run (e.g. a sandbox command + its output). */\nexport interface ToolRunStep {\n at: string\n label: string\n detail?: string\n status?: 'ok' | 'error'\n}\n\n/** A retained tool run keyed by the parent message's toolCallId. The product\n * persists these server-side (fail-closed: only ids its own loop created)\n * and serves them to the drill-in panel. */\nexport interface ToolRunRecord {\n toolCallId: string\n toolName: string\n title: string\n status: 'running' | 'complete' | 'error'\n steps: ToolRunStep[]\n}\n\nexport interface RunDrillInProps {\n run: ToolRunRecord\n onClose: () => void\n}\n\n/**\n * Readonly side panel showing a retained tool run's transcript — the\n * \"drill into what the sandbox actually did\" view. Follow-ups happen in the\n * main chat, never here.\n */\nexport function RunDrillIn({ run, onClose }: RunDrillInProps) {\n return (\n <div className=\"fixed inset-y-0 right-0 z-50 flex w-[480px] max-w-full flex-col border-l border-border bg-card shadow-xl\">\n <div className=\"flex items-center gap-2 border-b border-border px-4 py-3\">\n <span\n className={`h-2 w-2 shrink-0 rounded-full ${\n run.status === 'running' ? 'bg-yellow-500' : run.status === 'error' ? 'bg-red-500' : 'bg-green-500'\n }`}\n />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-semibold\">{run.title}</p>\n <p className=\"truncate font-mono text-[11px] text-muted-foreground\">{run.toolName}</p>\n </div>\n <button\n type=\"button\"\n onClick={onClose}\n aria-label=\"Close\"\n className=\"rounded-md p-1.5 text-muted-foreground transition hover:bg-accent/30 hover:text-foreground\"\n >\n <svg className=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n </button>\n </div>\n <div className=\"flex-1 space-y-3 overflow-y-auto p-4\">\n {run.steps.length === 0 && (\n <p className=\"text-sm text-muted-foreground\">No steps recorded yet.</p>\n )}\n {run.steps.map((step, i) => (\n <div key={i} className=\"rounded-lg border border-border/60 bg-background\">\n <div className=\"flex items-baseline gap-2 border-b border-border/40 px-3 py-1.5\">\n <span className={`font-mono text-[11px] ${step.status === 'error' ? 'text-red-600' : 'text-muted-foreground'}`}>\n {step.status === 'error' ? '✗' : '$'}\n </span>\n <code className=\"min-w-0 flex-1 truncate font-mono text-xs\">{step.label}</code>\n <span className=\"shrink-0 text-[10px] text-muted-foreground/60\">\n {new Date(step.at).toLocaleTimeString()}\n </span>\n </div>\n {step.detail && (\n <pre className=\"max-h-48 overflow-auto whitespace-pre-wrap px-3 py-2 font-mono text-[11px] leading-relaxed text-muted-foreground\">\n {step.detail}\n </pre>\n )}\n </div>\n ))}\n </div>\n <p className=\"border-t border-border px-4 py-2 text-[11px] text-muted-foreground/60\">\n Readonly drill-in. Follow up in the main chat.\n </p>\n </div>\n )\n}\n\n// ── ChatMessages ──────────────────────────────────────────────────────────\n\nexport interface ChatToolCallInfo {\n id: string\n name: string\n status: 'running' | 'done' | 'error'\n /** The call arguments, captured from the tool_call event — shown in the\n * expanded card so users see exactly what the agent invoked. */\n args?: Record<string, unknown>\n /** The tool outcome (`{ok, result}` shape). When `result.status` is\n * 'queued_for_approval' the card renders the approval state. */\n result?: unknown\n}\n\n/** Extract `{proposalId, status}` from a tool outcome when it is a proposal\n * awaiting human approval; null otherwise. */\nexport function pendingApprovalOf(call: ChatToolCallInfo): { proposalId: string } | null {\n const outcome = call.result as { ok?: boolean; result?: { status?: string; proposalId?: string } } | undefined\n if (!outcome?.ok || outcome.result?.status !== 'queued_for_approval' || !outcome.result.proposalId) return null\n return { proposalId: outcome.result.proposalId }\n}\n\nexport interface ChatUiMessage extends ChatMessageMetrics {\n id: string\n role: 'user' | 'assistant' | 'system'\n content: string\n reasoning?: string\n toolCalls?: ChatToolCallInfo[]\n}\n\nexport interface ChatMessagesProps {\n messages: ChatUiMessage[]\n /** Catalogue models, for per-message cost from pricing. Pass [] to skip cost. */\n models?: CatalogModel[]\n /** Markdown renderer for assistant content; default renders pre-wrapped text. */\n renderMarkdown?: (content: string) => ReactNode\n /** Extra per-message content (artifacts, custom panels) appended after the body. */\n renderExtras?: (message: ChatUiMessage) => ReactNode\n userLabel?: string\n agentLabel?: string\n /** Render the trailing \"agent is thinking\" row. */\n loading?: boolean\n /** Approve/Reject handlers for proposals awaiting approval. When omitted the\n * chip still shows \"awaiting approval\" but without action buttons. */\n approval?: ProposalApprovalHandlers\n /** Open a full-transcript view (e.g. {@link RunDrillIn}) from a tool card. */\n onToolCallClick?: (call: ChatToolCallInfo, message: ChatUiMessage) => void\n /** Per-tool custom detail renderers for expanded tool cards. */\n toolRenderers?: ToolDetailRenderers\n}\n\nexport interface ProposalApprovalHandlers {\n onApprove: (proposalId: string, toolCallId: string) => void | Promise<void>\n onReject: (proposalId: string, toolCallId: string) => void | Promise<void>\n}\n\n/** Per-tool custom detail renderers for the expanded card body — keyed by\n * tool name. Return null to fall back to the generic detail view. */\nexport type ToolDetailRenderers = Record<\n string,\n (call: ChatToolCallInfo, message: ChatUiMessage) => ReactNode\n>\n\nfunction toolOutcomeOf(call: ChatToolCallInfo): { ok?: boolean; result?: Record<string, unknown>; message?: string } | undefined {\n return call.result as { ok?: boolean; result?: Record<string, unknown>; message?: string } | undefined\n}\n\n/** Human title for a call, derived from its real arguments. */\nfunction friendlyToolTitle(call: ChatToolCallInfo): string {\n const a = call.args ?? {}\n switch (call.name) {\n case 'submit_proposal':\n return `Proposal · ${String(a.type ?? '')}${a.title ? `: ${String(a.title)}` : ''}`\n case 'sandbox_create':\n return `Create sandbox (${String(a.environment ?? 'universal')})`\n case 'sandbox_run_command':\n return `$ ${String(a.command ?? '')}`\n case 'sandbox_destroy':\n return `Destroy sandbox ${String(a.sandbox_id ?? '')}`\n case 'schedule_followup':\n return `Follow-up · ${String(a.title ?? '')}`\n case 'render_ui':\n return `Render view · ${String(a.title ?? '')}`\n case 'add_citation':\n return `Citation · ${String(a.path ?? '')}`\n default:\n return call.name\n }\n}\n\nfunction truncate(v: unknown, max = 240): string {\n const s = typeof v === 'string' ? v : JSON.stringify(v)\n return s.length > max ? `${s.slice(0, max)}…` : s\n}\n\nfunction KvRows({ data }: { data: Record<string, unknown> }) {\n const entries = Object.entries(data).filter(([, v]) => v !== undefined && v !== null && v !== '')\n if (!entries.length) return null\n return (\n <dl className=\"grid grid-cols-[auto_1fr] gap-x-3 gap-y-1\">\n {entries.map(([k, v]) => (\n <div key={k} className=\"contents\">\n <dt className=\"font-mono text-[11px] text-muted-foreground/70\">{k}</dt>\n <dd className=\"min-w-0 whitespace-pre-wrap break-words font-mono text-[11px] text-muted-foreground\">\n {truncate(v)}\n </dd>\n </div>\n ))}\n </dl>\n )\n}\n\n/** Generic expanded detail: what was called, and what actually happened. */\nfunction DefaultToolDetail({ call }: { call: ChatToolCallInfo }) {\n const outcome = toolOutcomeOf(call)\n return (\n <div className=\"space-y-2\">\n {call.args && Object.keys(call.args).length > 0 && (\n <div>\n <p className=\"mb-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground/50\">Called with</p>\n <KvRows data={call.args} />\n </div>\n )}\n {outcome && (\n <div>\n <p className=\"mb-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground/50\">\n {outcome.ok === false ? 'Failed' : 'Result'}\n </p>\n {outcome.ok === false ? (\n <p className=\"text-xs text-red-600\">{outcome.message ?? 'Tool failed'}</p>\n ) : outcome.result && typeof outcome.result === 'object' ? (\n <KvRows data={outcome.result} />\n ) : (\n <p className=\"font-mono text-[11px] text-muted-foreground\">{truncate(outcome.result)}</p>\n )}\n </div>\n )}\n </div>\n )\n}\n\nfunction ToolCallCard({\n call,\n message,\n approval,\n onOpenRun,\n renderers,\n}: {\n call: ChatToolCallInfo\n message: ChatUiMessage\n approval?: ProposalApprovalHandlers\n onOpenRun?: (call: ChatToolCallInfo, message: ChatUiMessage) => void\n renderers?: ToolDetailRenderers\n}) {\n const [expanded, setExpanded] = useState(false)\n const pending = call.status === 'done' ? pendingApprovalOf(call) : null\n const failed = call.status === 'error' || toolOutcomeOf(call)?.ok === false\n const custom = renderers?.[call.name]?.(call, message)\n\n return (\n <div\n className={`w-fit min-w-[280px] max-w-full rounded-lg border text-xs transition ${\n pending\n ? 'border-amber-300/60 bg-amber-500/5'\n : failed\n ? 'border-red-300/60 bg-red-500/5'\n : 'border-border/60 bg-muted/20'\n }`}\n >\n <button\n type=\"button\"\n onClick={() => setExpanded((v) => !v)}\n className=\"flex w-full items-center gap-2 px-3 py-2 text-left\"\n >\n <span\n className={`h-2 w-2 shrink-0 rounded-full ${\n call.status === 'running'\n ? 'animate-pulse bg-yellow-500'\n : pending\n ? 'bg-amber-500'\n : failed\n ? 'bg-red-500'\n : 'bg-green-500'\n }`}\n />\n <span className=\"min-w-0 flex-1 truncate font-medium\">{friendlyToolTitle(call)}</span>\n {pending && approval && (\n <span className=\"flex shrink-0 items-center gap-1\" onClick={(e) => e.stopPropagation()}>\n <button\n type=\"button\"\n onClick={() => approval.onApprove(pending.proposalId, call.id)}\n className=\"rounded bg-green-600/90 px-2 py-0.5 text-[11px] font-semibold text-white transition hover:bg-green-600\"\n >\n Approve\n </button>\n <button\n type=\"button\"\n onClick={() => approval.onReject(pending.proposalId, call.id)}\n className=\"rounded border border-border bg-card px-2 py-0.5 text-[11px] font-medium text-foreground transition hover:bg-accent/30\"\n >\n Reject\n </button>\n </span>\n )}\n <span className=\"shrink-0 text-[11px] text-muted-foreground/70\">\n {call.status === 'running' ? 'running…' : pending ? 'awaiting approval' : failed ? 'failed' : 'done'}\n </span>\n <ChevronDown className={`h-3 w-3 shrink-0 text-muted-foreground transition-transform ${expanded ? 'rotate-180' : ''}`} />\n </button>\n {expanded && (\n <div className=\"border-t border-border/40 px-3 py-2.5\">\n {custom ?? <DefaultToolDetail call={call} />}\n {onOpenRun && call.name.startsWith('sandbox_') && (\n <button\n type=\"button\"\n onClick={() => onOpenRun(call, message)}\n className=\"mt-2 rounded border border-border bg-card px-2 py-1 text-[11px] font-medium transition hover:bg-accent/30\"\n >\n Open full transcript →\n </button>\n )}\n </div>\n )}\n </div>\n )\n}\n\nfunction AssistantMessage({\n msg,\n streaming,\n models,\n agentLabel,\n renderBody,\n approval,\n onToolCallClick,\n toolRenderers,\n renderExtras,\n}: {\n msg: ChatUiMessage\n streaming: boolean\n models: CatalogModel[]\n agentLabel: string\n renderBody: (content: string) => ReactNode\n approval?: ProposalApprovalHandlers\n onToolCallClick?: (call: ChatToolCallInfo, message: ChatUiMessage) => void\n toolRenderers?: ToolDetailRenderers\n renderExtras?: (message: ChatUiMessage) => ReactNode\n}) {\n // Smooth reveal: chunky network slabs (model bursts, flush windows, replay\n // polls) paint as a continuous typewriter. Reasoning often arrives as one\n // burst right before the answer — smoothing makes it visibly type out in\n // the open thinking box instead of popping in and collapsing.\n const content = useSmoothText(msg.content, streaming)\n const reasoning = useSmoothText(msg.reasoning ?? '', streaming)\n const reasoningScrollRef = useRef<HTMLDivElement>(null)\n useEffect(() => {\n const el = reasoningScrollRef.current\n if (el && streaming && !content) el.scrollTop = el.scrollHeight\n }, [reasoning, streaming, content])\n\n return (\n <div className=\"mx-auto w-full max-w-3xl px-6 py-3\">\n <div className=\"mb-1 flex items-baseline gap-2 text-[11px] tracking-wide text-muted-foreground/60\">\n <span className=\"font-semibold uppercase\">{agentLabel}</span>\n {msg.modelUsed && <span className=\"font-mono normal-case\">{msg.modelUsed}</span>}\n {formatTokensPerSecond(msg) && <span>{formatTokensPerSecond(msg)}</span>}\n {formatModelCost(msg, models) && <span>{formatModelCost(msg, models)}</span>}\n </div>\n {reasoning && (\n <details className=\"mb-2 rounded-md border border-border/40 bg-muted/30 px-3 py-2\" open={!content}>\n <summary className=\"cursor-pointer select-none text-xs font-medium text-muted-foreground\">\n {content ? 'Thinking…' : 'Thinking'}\n {!content && <span className=\"ml-1 inline-block animate-pulse\">●</span>}\n </summary>\n <div ref={reasoningScrollRef} className=\"mt-2 max-h-48 overflow-y-auto whitespace-pre-wrap text-sm text-muted-foreground/80\">\n {reasoning}\n </div>\n </details>\n )}\n <div className=\"text-base leading-[1.75]\">{renderBody(content)}</div>\n {msg.toolCalls && msg.toolCalls.length > 0 && (\n <div className=\"mt-2 flex flex-col gap-1.5\">\n {msg.toolCalls.map((tc) => (\n <ToolCallCard\n key={tc.id}\n call={tc}\n message={msg}\n approval={approval}\n onOpenRun={onToolCallClick}\n renderers={toolRenderers}\n />\n ))}\n </div>\n )}\n {renderExtras?.(msg)}\n </div>\n )\n}\n\nfunction ThinkingRow({ agentLabel }: { agentLabel: string }) {\n const [seconds, setSeconds] = useState(0)\n useEffect(() => {\n const id = setInterval(() => setSeconds((s) => s + 1), 1000)\n return () => clearInterval(id)\n }, [])\n return (\n <div className=\"mx-auto w-full max-w-3xl px-6 py-3\">\n <p className=\"mb-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground/60\">{agentLabel}</p>\n <div className=\"flex items-center gap-2 text-base text-muted-foreground\">\n <svg className=\"h-4 w-4 animate-spin\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" aria-hidden>\n <path d=\"M21 12a9 9 0 1 1-6.219-8.56\" strokeLinecap=\"round\" />\n </svg>\n Thinking{seconds >= 3 ? ` · ${seconds}s` : '...'}\n </div>\n </div>\n )\n}\n\n/**\n * The message thread: one centered column; user messages are right-aligned\n * bubbles with a User label; agent messages carry an Agent meta line with\n * model id, tokens/sec, and cost, plus a collapsible thinking section and\n * tool-call chips.\n */\nexport function ChatMessages({\n messages,\n models = [],\n renderMarkdown,\n renderExtras,\n userLabel = 'User',\n agentLabel = 'Agent',\n loading,\n approval,\n onToolCallClick,\n toolRenderers,\n}: ChatMessagesProps) {\n const renderBody = renderMarkdown ?? ((content: string) => <p className=\"whitespace-pre-wrap\">{content}</p>)\n const lastIsUser = messages[messages.length - 1]?.role === 'user'\n return (\n <>\n {messages.map((msg) =>\n msg.role === 'user' ? (\n <div key={msg.id} className=\"mx-auto w-full max-w-3xl px-6 py-3\">\n <div className=\"ml-auto w-fit max-w-[85%]\">\n <p className=\"mb-1 text-right text-[11px] font-semibold uppercase tracking-wide text-muted-foreground/60\">\n {userLabel}\n </p>\n <div className=\"rounded-2xl rounded-tr-md bg-primary/10 px-4 py-2.5 text-base leading-relaxed\">\n <p className=\"whitespace-pre-wrap\">{msg.content}</p>\n </div>\n </div>\n </div>\n ) : (\n <AssistantMessage\n key={msg.id}\n msg={msg}\n streaming={!!loading && msg.id === messages[messages.length - 1]?.id}\n models={models}\n agentLabel={agentLabel}\n renderBody={renderBody}\n approval={approval}\n onToolCallClick={onToolCallClick}\n toolRenderers={toolRenderers}\n renderExtras={renderExtras}\n />\n ),\n )}\n {loading && lastIsUser && <ThinkingRow agentLabel={agentLabel} />}\n </>\n )\n}\n","/**\n * Provider brand marks — real logo path data (simple-icons / SVG Logos, both\n * CC0) inlined so the picker shows actual provider identity instead of\n * colored-initial monograms. Providers without a usable mark fall back to a\n * tinted monogram chip. Aliases (z-ai/zai, moonshot/moonshotai, deepseek_ai)\n * normalize to one entry.\n */\n\nimport type { ReactNode } from 'react'\n\ninterface LogoDef {\n viewBox: string\n fill: string\n paths: string[]\n}\n\nconst LOGOS: Record<string, LogoDef> = {\n anthropic: { viewBox: '0 0 24 24', fill: '#D97757', paths: [\"M17.3041 3.541h-3.6718l6.696 16.918H24Zm-10.6082 0L0 20.459h3.7442l1.3693-3.5527h7.0052l1.3693 3.5528h3.7442L10.5363 3.5409Zm-.3712 10.2232 2.2914-5.9456 2.2914 5.9456Z\"] },\n google: { viewBox: '0 0 24 24', fill: '#8E75B2', paths: [\"M11.04 19.32Q12 21.51 12 24q0-2.49.93-4.68.96-2.19 2.58-3.81t3.81-2.55Q21.51 12 24 12q-2.49 0-4.68-.93a12.3 12.3 0 0 1-3.81-2.58 12.3 12.3 0 0 1-2.58-3.81Q12 2.49 12 0q0 2.49-.96 4.68-.93 2.19-2.55 3.81a12.3 12.3 0 0 1-3.81 2.58Q2.49 12 0 12q2.49 0 4.68.96 2.19.93 3.81 2.55t2.55 3.81\"] },\n deepseek: { viewBox: '0 0 24 24', fill: '#4D6BFE', paths: [\"M23.748 4.651c-.254-.124-.364.113-.512.233-.051.04-.094.09-.137.137-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.155-.708-.311-.955-.65-.172-.24-.219-.509-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.094.172.187.129.323-.082.28-.18.553-.266.833-.055.179-.137.218-.328.14a5.5 5.5 0 0 1-1.737-1.179c-.857-.828-1.631-1.743-2.597-2.46a12 12 0 0 0-.689-.47c-.985-.957.13-1.743.387-1.836.27-.098.094-.433-.778-.428-.872.003-1.67.295-2.687.685a3 3 0 0 1-.465.136 9.6 9.6 0 0 0-2.883-.101c-1.885.21-3.39 1.1-4.497 2.622C.082 8.776-.231 10.854.152 13.02c.403 2.284 1.568 4.175 3.36 5.653 1.857 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.132-.284 4.994-1.86.47.234.962.328 1.78.398.629.058 1.235-.031 1.705-.129.735-.155.684-.836.418-.961-2.155-1.004-1.682-.595-2.112-.926 1.095-1.295 2.768-3.598 3.284-6.733.05-.346.115-.834.108-1.114-.004-.171.035-.238.23-.257a4.2 4.2 0 0 0 1.545-.475c1.397-.763 1.96-2.016 2.093-3.517.02-.23-.004-.467-.247-.588M11.58 18.168c-2.088-1.642-3.101-2.183-3.52-2.16-.39.024-.32.472-.234.763.09.288.207.487.371.74.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.168-1.361-.801-2.5-1.86-3.301-3.306-.775-1.393-1.225-2.888-1.299-4.482-.02-.385.094-.522.477-.592a4.7 4.7 0 0 1 1.53-.038c2.131.311 3.946 1.264 5.467 2.774.868.86 1.525 1.887 2.202 2.89.72 1.066 1.494 2.082 2.48 2.915.348.291.626.513.892.677-.802.09-2.14.109-3.055-.615zm1.001-6.44a.306.306 0 0 1 .415-.287.3.3 0 0 1 .113.074.3.3 0 0 1 .086.214c0 .17-.136.307-.308.307a.303.303 0 0 1-.306-.307m3.11 1.596c-.2.081-.4.151-.591.16a1.25 1.25 0 0 1-.798-.254c-.274-.23-.47-.358-.551-.758a1.7 1.7 0 0 1 .015-.588c.07-.327-.007-.537-.238-.727-.188-.156-.426-.199-.689-.199a.6.6 0 0 1-.254-.078.253.253 0 0 1-.114-.358 1 1 0 0 1 .192-.21c.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.392.451.462.576.685.915.176.264.336.536.446.848.066.194-.02.353-.25.45\"] },\n mistral: { viewBox: '0 0 24 24', fill: '#FA520F', paths: [\"M17.143 3.429v3.428h-3.429v3.429h-3.428V6.857H6.857V3.43H3.43v13.714H0v3.428h10.286v-3.428H6.857v-3.429h3.429v3.429h3.429v-3.429h3.428v3.429h-3.428v3.428H24v-3.428h-3.43V3.429z\"] },\n xai: { viewBox: '0 0 24 24', fill: '#000000', paths: [\"M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z\"] },\n nvidia: { viewBox: '0 0 24 24', fill: '#76B900', paths: [\"M8.948 8.798v-1.43a6.7 6.7 0 0 1 .424-.018c3.922-.124 6.493 3.374 6.493 3.374s-2.774 3.851-5.75 3.851c-.398 0-.787-.062-1.158-.185v-4.346c1.528.185 1.837.857 2.747 2.385l2.04-1.714s-1.492-1.952-4-1.952a6.016 6.016 0 0 0-.796.035m0-4.735v2.138l.424-.027c5.45-.185 9.01 4.47 9.01 4.47s-4.08 4.964-8.33 4.964c-.37 0-.733-.035-1.095-.097v1.325c.3.035.61.062.91.062 3.957 0 6.82-2.023 9.593-4.408.459.371 2.34 1.263 2.73 1.652-2.633 2.208-8.772 3.984-12.253 3.984-.335 0-.653-.018-.971-.053v1.864H24V4.063zm0 10.326v1.131c-3.657-.654-4.673-4.46-4.673-4.46s1.758-1.944 4.673-2.262v1.237H8.94c-1.528-.186-2.73 1.245-2.73 1.245s.68 2.412 2.739 3.11M2.456 10.9s2.164-3.197 6.5-3.533V6.201C4.153 6.59 0 10.653 0 10.653s2.35 6.802 8.948 7.42v-1.237c-4.84-.6-6.492-5.936-6.492-5.936z\"] },\n meta: { viewBox: '0 0 24 24', fill: '#0467DF', paths: [\"M6.915 4.03c-1.968 0-3.683 1.28-4.871 3.113C.704 9.208 0 11.883 0 14.449c0 .706.07 1.369.21 1.973a6.624 6.624 0 0 0 .265.86 5.297 5.297 0 0 0 .371.761c.696 1.159 1.818 1.927 3.593 1.927 1.497 0 2.633-.671 3.965-2.444.76-1.012 1.144-1.626 2.663-4.32l.756-1.339.186-.325c.061.1.121.196.183.3l2.152 3.595c.724 1.21 1.665 2.556 2.47 3.314 1.046.987 1.992 1.22 3.06 1.22 1.075 0 1.876-.355 2.455-.843a3.743 3.743 0 0 0 .81-.973c.542-.939.861-2.127.861-3.745 0-2.72-.681-5.357-2.084-7.45-1.282-1.912-2.957-2.93-4.716-2.93-1.047 0-2.088.467-3.053 1.308-.652.57-1.257 1.29-1.82 2.05-.69-.875-1.335-1.547-1.958-2.056-1.182-.966-2.315-1.303-3.454-1.303zm10.16 2.053c1.147 0 2.188.758 2.992 1.999 1.132 1.748 1.647 4.195 1.647 6.4 0 1.548-.368 2.9-1.839 2.9-.58 0-1.027-.23-1.664-1.004-.496-.601-1.343-1.878-2.832-4.358l-.617-1.028a44.908 44.908 0 0 0-1.255-1.98c.07-.109.141-.224.211-.327 1.12-1.667 2.118-2.602 3.358-2.602zm-10.201.553c1.265 0 2.058.791 2.675 1.446.307.327.737.871 1.234 1.579l-1.02 1.566c-.757 1.163-1.882 3.017-2.837 4.338-1.191 1.649-1.81 1.817-2.486 1.817-.524 0-1.038-.237-1.383-.794-.263-.426-.464-1.13-.464-2.046 0-2.221.63-4.535 1.66-6.088.454-.687.964-1.226 1.533-1.533a2.264 2.264 0 0 1 1.088-.285z\"] },\n moonshotai: { viewBox: '0 0 24 24', fill: '#16191E', paths: [\"m1.053 16.91 9.538 2.55a21 20.981 0 0 0 .06 2.031l5.956 1.592a12 11.99 0 0 1-15.554-6.172m-1.02-5.79 11.352 3.035a21 20.981 0 0 0-.469 2.01l10.817 2.89a12 11.99 0 0 1-1.845 2.004L.658 15.918a12 11.99 0 0 1-.625-4.796m1.593-5.146L13.573 9.17a21 20.981 0 0 0-1.01 1.874l11.297 3.02a21 20.981 0 0 1-.67 2.362l-11.55-3.087L.125 10.26a12 11.99 0 0 1 1.499-4.285ZM6.067 1.58l11.285 3.016a21 20.981 0 0 0-1.688 1.719l7.824 2.091a21 20.981 0 0 1 .513 2.664L2.107 5.218a12 11.99 0 0 1 3.96-3.638M21.68 4.866 7.222 1.003A12 11.99 0 0 1 21.68 4.866\"] },\n openai: { viewBox: '0 0 256 260', fill: '#10A37F', paths: [\"M239.184 106.203a64.72 64.72 0 0 0-5.576-53.103C219.452 28.459 191 15.784 163.213 21.74A65.586 65.586 0 0 0 52.096 45.22a64.72 64.72 0 0 0-43.23 31.36c-14.31 24.602-11.061 55.634 8.033 76.74a64.67 64.67 0 0 0 5.525 53.102c14.174 24.65 42.644 37.324 70.446 31.36a64.72 64.72 0 0 0 48.754 21.744c28.481.025 53.714-18.361 62.414-45.481a64.77 64.77 0 0 0 43.229-31.36c14.137-24.558 10.875-55.423-8.083-76.483m-97.56 136.338a48.4 48.4 0 0 1-31.105-11.255l1.535-.87l51.67-29.825a8.6 8.6 0 0 0 4.247-7.367v-72.85l21.845 12.636c.218.111.37.32.409.563v60.367c-.056 26.818-21.783 48.545-48.601 48.601M37.158 197.93a48.35 48.35 0 0 1-5.781-32.589l1.534.921l51.722 29.826a8.34 8.34 0 0 0 8.441 0l63.181-36.425v25.221a.87.87 0 0 1-.358.665l-52.335 30.184c-23.257 13.398-52.97 5.431-66.404-17.803M23.549 85.38a48.5 48.5 0 0 1 25.58-21.333v61.39a8.29 8.29 0 0 0 4.195 7.316l62.874 36.272l-21.845 12.636a.82.82 0 0 1-.767 0L41.353 151.53c-23.211-13.454-31.171-43.144-17.804-66.405zm179.466 41.695l-63.08-36.63L161.73 77.86a.82.82 0 0 1 .768 0l52.233 30.184a48.6 48.6 0 0 1-7.316 87.635v-61.391a8.54 8.54 0 0 0-4.4-7.213m21.742-32.69l-1.535-.922l-51.619-30.081a8.39 8.39 0 0 0-8.492 0L99.98 99.808V74.587a.72.72 0 0 1 .307-.665l52.233-30.133a48.652 48.652 0 0 1 72.236 50.391zM88.061 139.097l-21.845-12.585a.87.87 0 0 1-.41-.614V65.685a48.652 48.652 0 0 1 79.757-37.346l-1.535.87l-51.67 29.825a8.6 8.6 0 0 0-4.246 7.367zm11.868-25.58L128.067 97.3l28.188 16.218v32.434l-28.086 16.218l-28.188-16.218z\"] },\n}\n\nconst ALIASES: Record<string, string> = {\n moonshot: 'moonshotai',\n deepseek_ai: 'deepseek',\n 'x-ai': 'xai',\n 'meta-llama': 'meta',\n}\n\nconst MONOGRAM: Record<string, { bg: string; fg: string }> = {\n cohere: { bg: '#fae8ff', fg: '#c026d3' },\n groq: { bg: '#fce7f3', fg: '#db2777' },\n cerebras: { bg: '#ccfbf1', fg: '#0d9488' },\n zai: { bg: '#ede9fe', fg: '#7c3aed' },\n 'z-ai': { bg: '#ede9fe', fg: '#7c3aed' },\n tuner: { bg: '#dbeafe', fg: '#2563eb' },\n}\n\nexport interface ProviderLogoProps {\n provider?: string\n size?: number\n}\n\n/** Real brand mark when we have one; tinted monogram otherwise. */\nexport function ProviderLogo({ provider, size = 16 }: ProviderLogoProps): ReactNode {\n const key = ALIASES[provider ?? ''] ?? provider ?? ''\n const logo = LOGOS[key]\n if (logo) {\n return (\n <svg width={size} height={size} viewBox={logo.viewBox} role=\"img\" aria-label={key}>\n {logo.paths.map((d, i) => (\n <path key={i} d={d} fill={logo.fill} />\n ))}\n </svg>\n )\n }\n const mono = MONOGRAM[key] ?? { bg: '#f3f4f6', fg: '#6b7280' }\n return (\n <svg width={size} height={size} viewBox=\"0 0 16 16\" role=\"img\" aria-label={key || 'model'}>\n <rect width=\"16\" height=\"16\" rx=\"4\" fill={mono.bg} />\n <text x=\"8\" y=\"11.6\" textAnchor=\"middle\" fill={mono.fg} fontSize=\"9\" fontWeight=\"700\" fontFamily=\"system-ui, sans-serif\">\n {(key || '?').charAt(0).toUpperCase()}\n </text>\n </svg>\n )\n}\n","/**\n * Smooth text reveal — turns chunky network deltas into a continuous\n * typewriter paint. Streamed turns arrive in 100-500ms slabs (model burst,\n * flush windows, replay polls); revealing characters at an adaptive rate\n * makes the same bytes read as top-tier streaming. The rate scales with the\n * backlog so the reveal never falls behind the stream — it crawls when caught\n * up and sprints when a burst lands (e.g. a reasoning summary arriving all at\n * once still *types out* instead of popping in).\n */\n\nimport { useEffect, useRef, useState } from 'react'\n\nexport interface SmoothRevealOptions {\n /** Baseline reveal rate when nearly caught up. Default 90 chars/s. */\n baseCharsPerSecond?: number\n /** Extra chars/s per backlog character — the catch-up pressure. Default 5. */\n catchUpPerChar?: number\n /** Hard ceiling so giant bursts still animate. Default 2400 chars/s. */\n maxCharsPerSecond?: number\n}\n\n/** Pure reveal step: how many characters should be visible after `dtMs`.\n * Exposed for tests; the hook is a thin rAF wrapper around it. */\nexport function nextRevealCount(\n shown: number,\n targetLength: number,\n dtMs: number,\n opts: SmoothRevealOptions = {},\n): number {\n if (shown >= targetLength) return targetLength\n const base = opts.baseCharsPerSecond ?? 90\n const catchUp = opts.catchUpPerChar ?? 5\n const max = opts.maxCharsPerSecond ?? 2400\n const backlog = targetLength - shown\n const rate = Math.min(max, base + backlog * catchUp)\n return Math.min(targetLength, shown + (rate * dtMs) / 1000)\n}\n\n/**\n * Animate `target` text into view. While `enabled`, the returned string grows\n * smoothly toward `target` (which may itself keep growing); when `enabled` is\n * false the full text returns immediately (history, completed turns). A\n * target that is not an extension of the revealed prefix (new message) resets\n * the reveal.\n */\nexport function useSmoothText(target: string, enabled: boolean, opts?: SmoothRevealOptions): string {\n const [, force] = useState(0)\n const shownRef = useRef(0)\n const lastTargetRef = useRef('')\n\n // New message / rewritten prefix → restart the reveal from zero.\n if (!target.startsWith(lastTargetRef.current.slice(0, Math.floor(shownRef.current)))) {\n shownRef.current = 0\n }\n lastTargetRef.current = target\n if (!enabled) shownRef.current = target.length\n\n useEffect(() => {\n if (!enabled) return\n let raf = 0\n let last: number | null = null\n const tick = (t: number) => {\n const dt = last == null ? 16 : Math.min(t - last, 100)\n last = t\n const targetLen = lastTargetRef.current.length\n if (shownRef.current < targetLen) {\n shownRef.current = nextRevealCount(shownRef.current, targetLen, dt, opts)\n force((n) => n + 1)\n }\n raf = requestAnimationFrame(tick)\n }\n raf = requestAnimationFrame(tick)\n return () => cancelAnimationFrame(raf)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [enabled])\n\n return target.slice(0, Math.floor(shownRef.current))\n}\n","/**\n * Client-side chat-stream consumption — the NDJSON parse loop every agent\n * app's chat UI hand-rolls (and breaks). Normalizes the three line shapes the\n * agent-app chat routes emit:\n *\n * {kind:'event', event:{type:'text'|'reasoning'|'tool_call'|'usage', ...}}\n * {kind:'tool_result', toolCallId, toolName, label, outcome}\n * {type:'turn'|'metadata'|'error'|'turn_status', ...} (route-level)\n *\n * Replayed lines carry an extra `seq` — transparently ignored. Works for\n * router-backed and sandbox-backed chats alike: anything producing these\n * lines (live pump, queued follow, resume replay) feeds the same callbacks.\n */\n\nexport interface ChatStreamToolCall {\n toolCallId?: string\n toolName: string\n args: Record<string, unknown>\n}\n\nexport interface ChatStreamToolResult {\n toolCallId?: string\n toolName?: string\n label?: string\n outcome: { ok: boolean; result?: unknown; code?: string; message?: string }\n}\n\nexport interface ChatStreamCallbacks {\n onTurnId?: (turnId: string) => void\n onText?: (delta: string) => void\n onReasoning?: (delta: string) => void\n onToolCall?: (call: ChatStreamToolCall) => void\n onToolResult?: (result: ChatStreamToolResult) => void\n onUsage?: (usage: { promptTokens: number; completionTokens: number }) => void\n onMetadata?: (data: Record<string, unknown>) => void\n /** A loop-level error event (the turn failed server-side). */\n onErrorEvent?: (message: string) => void\n}\n\nexport interface ConsumeChatStreamResult {\n turnId: string | null\n /** True when any text/reasoning/tool activity was received. */\n receivedContent: boolean\n}\n\n/** Parse one NDJSON line into the callbacks. Exposed for tests. */\nexport function dispatchChatStreamLine(line: string, cb: ChatStreamCallbacks): {\n turnId?: string\n receivedContent: boolean\n} {\n let receivedContent = false\n let turnId: string | undefined\n if (!line.trim()) return { receivedContent }\n let parsed: Record<string, unknown>\n try {\n parsed = JSON.parse(line) as Record<string, unknown>\n } catch {\n return { receivedContent } // tolerate a torn line\n }\n\n if (parsed.kind === 'tool_result') {\n cb.onToolResult?.({\n toolCallId: parsed.toolCallId as string | undefined,\n toolName: parsed.toolName as string | undefined,\n label: parsed.label as string | undefined,\n outcome: (parsed.outcome ?? parsed.result) as ChatStreamToolResult['outcome'],\n })\n return { receivedContent: true }\n }\n\n const evt = (parsed.kind === 'event' ? parsed.event : parsed) as Record<string, unknown>\n if (!evt || typeof evt !== 'object') return { receivedContent }\n\n switch (evt.type) {\n case 'turn':\n if (typeof evt.turnId === 'string') turnId = evt.turnId\n break\n case 'text':\n if (typeof evt.text === 'string') {\n cb.onText?.(evt.text)\n receivedContent = true\n }\n break\n case 'reasoning':\n if (typeof evt.text === 'string') {\n cb.onReasoning?.(evt.text)\n receivedContent = true\n }\n break\n case 'tool_call': {\n const call = (evt.call ?? evt) as Record<string, unknown>\n cb.onToolCall?.({\n toolCallId: (call.toolCallId ?? call.id) as string | undefined,\n toolName: String(call.toolName ?? call.name ?? 'unknown'),\n args: (call.args ?? {}) as Record<string, unknown>,\n })\n receivedContent = true\n break\n }\n case 'tool_result':\n cb.onToolResult?.({\n toolCallId: evt.toolCallId as string | undefined,\n toolName: evt.toolName as string | undefined,\n label: evt.label as string | undefined,\n outcome: (evt.outcome ?? evt.result) as ChatStreamToolResult['outcome'],\n })\n receivedContent = true\n break\n case 'usage': {\n const u = evt.usage as { promptTokens?: number; completionTokens?: number } | undefined\n if (u) cb.onUsage?.({ promptTokens: u.promptTokens ?? 0, completionTokens: u.completionTokens ?? 0 })\n break\n }\n case 'metadata':\n cb.onMetadata?.((evt.data ?? {}) as Record<string, unknown>)\n break\n case 'error':\n cb.onErrorEvent?.(String(evt.details ?? evt.error ?? 'Unknown stream error'))\n break\n default:\n break // turn_status and unknown line types are non-content\n }\n return { turnId, receivedContent }\n}\n\n/** Drain one NDJSON body into the callbacks. Throws on transport failure\n * (caller decides whether to resume). */\nexport async function consumeChatStream(\n body: ReadableStream<Uint8Array>,\n cb: ChatStreamCallbacks,\n): Promise<ConsumeChatStreamResult> {\n const reader = body.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n let turnId: string | null = null\n let receivedContent = false\n\n const handle = (line: string) => {\n const r = dispatchChatStreamLine(line, cb)\n if (r.turnId) {\n turnId = r.turnId\n cb.onTurnId?.(r.turnId)\n }\n if (r.receivedContent) receivedContent = true\n }\n\n for (;;) {\n const { done, value } = await reader.read()\n if (done) {\n if (buffer.trim()) handle(buffer)\n break\n }\n buffer += decoder.decode(value, { stream: true })\n const lines = buffer.split('\\n')\n buffer = lines.pop() ?? ''\n for (const line of lines) handle(line)\n }\n return { turnId, receivedContent }\n}\n\nexport interface StreamChatOptions {\n /** Start the turn (POST the chat request); must return a streaming Response. */\n start: () => Promise<Response>\n /** Re-attach to a turn after a transport drop (GET the resume route). */\n resume?: (turnId: string, fromSeq: number) => Promise<Response>\n callbacks: ChatStreamCallbacks\n /** Called before a resume replays from 0 so the UI can reset accumulated\n * turn state (text, reasoning, tool chips). */\n onResetForResume?: () => void\n}\n\n/**\n * Run one chat turn with automatic single-shot resume: if the transport drops\n * mid-turn and the server announced a turnId, reset and replay the buffered\n * turn. Server-side the turn keeps running either way (queued runner).\n */\nexport async function streamChatTurn(opts: StreamChatOptions): Promise<ConsumeChatStreamResult> {\n const res = await opts.start()\n if (!res.ok || !res.body) {\n const err = await res.json().catch(() => ({ error: `HTTP ${res.status}` })) as { error?: string }\n throw new Error(err.error ?? `HTTP ${res.status}`)\n }\n let turnId: string | null = null\n const cb: ChatStreamCallbacks = {\n ...opts.callbacks,\n onTurnId: (id) => {\n turnId = id\n opts.callbacks.onTurnId?.(id)\n },\n }\n try {\n return await consumeChatStream(res.body, cb)\n } catch (transportErr) {\n if (!turnId || !opts.resume) throw transportErr\n opts.onResetForResume?.()\n const resumed = await opts.resume(turnId, 0)\n if (!resumed.ok || !resumed.body) throw transportErr\n return await consumeChatStream(resumed.body, cb)\n }\n}\n"],"mappings":"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","names":["useEffect","useRef","useState","jsx","jsxs","useRef","useEffect","useState"]}
|
|
1
|
+
{"version":3,"sources":["../../src/web-react/index.tsx","../../src/web-react/provider-logo.tsx","../../src/web-react/smooth-text.ts","../../src/web-react/chat-stream.ts"],"sourcesContent":["/**\n * `@tangle-network/agent-app/web-react` — the shared chat-shell components\n * every agent app's web UI hand-rolls: a model picker over the runtime's\n * model catalogue, a reasoning-effort selector, and a message thread with\n * User/Agent identity, per-message model + cost + tokens/sec metrics, tool\n * chips, and a collapsible thinking section.\n *\n * Works for BOTH chat shapes: router-backed copilots (LoopEvents from\n * `runtime/openai-stream`) and sandbox-backed chats — the thread renders\n * `ChatUiMessage`s; how they're produced is the app's business.\n *\n * Styling contract: Tailwind classes against the shared design tokens\n * (`bg-card`, `border-border`, `text-muted-foreground`, `bg-primary`, …) that\n * Tangle app shells define. No icon library — the few glyphs are inline SVGs.\n * Markdown and provider logos are injected (`renderMarkdown`,\n * `renderProviderBadge`) so this package stays dependency-free beyond React.\n */\n\nimport { useEffect, useMemo, useRef, useState, type ReactNode } from 'react'\nimport { ProviderLogo } from './provider-logo'\nimport { useSmoothText } from './smooth-text'\n\nexport * from './chat-stream'\nexport * from './provider-logo'\nexport * from './smooth-text'\nimport type { CatalogModel } from '../runtime/model-catalog'\n\n// ── shared glyphs (no icon-library dependency) ────────────────────────────\n\nfunction ChevronDown({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n )\n}\n\nfunction SearchGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <circle cx=\"11\" cy=\"11\" r=\"8\" />\n <path d=\"m21 21-4.3-4.3\" />\n </svg>\n )\n}\n\nfunction SparkleGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M12 3v3m0 12v3M3 12h3m12 0h3M5.6 5.6l2.1 2.1m8.6 8.6 2.1 2.1m0-12.8-2.1 2.1M7.7 16.3l-2.1 2.1\" />\n </svg>\n )\n}\n\n/** Close an absolutely-positioned popover on outside mousedown. */\nfunction useClickOutside(onOutside: () => void) {\n const ref = useRef<HTMLDivElement>(null)\n useEffect(() => {\n function handler(e: MouseEvent) {\n if (ref.current && !ref.current.contains(e.target as Node)) onOutside()\n }\n document.addEventListener('mousedown', handler)\n return () => document.removeEventListener('mousedown', handler)\n })\n return ref\n}\n\n// ── metrics helpers ───────────────────────────────────────────────────────\n\nexport interface ChatMessageMetrics {\n modelUsed?: string\n promptTokens?: number\n completionTokens?: number\n durationMs?: number\n}\n\n/** \"$0.0042\" from token counts × catalogue per-token pricing; null when unknown. */\nexport function formatModelCost(msg: ChatMessageMetrics, models: CatalogModel[]): string | null {\n if (msg.promptTokens == null && msg.completionTokens == null) return null\n const pricing = models.find((m) => m.id === msg.modelUsed)?.pricing\n if (!pricing) return null\n const cost =\n (msg.promptTokens ?? 0) * Number(pricing.prompt ?? 0) +\n (msg.completionTokens ?? 0) * Number(pricing.completion ?? 0)\n if (!isFinite(cost) || cost <= 0) return null\n return cost < 0.01 ? `$${cost.toFixed(4)}` : `$${cost.toFixed(2)}`\n}\n\n/** \"38 tok/s\" from completion tokens over first-token→end duration; null when unknown. */\nexport function formatTokensPerSecond(msg: ChatMessageMetrics): string | null {\n if (msg.completionTokens == null || !msg.durationMs) return null\n return `${Math.round(msg.completionTokens / (msg.durationMs / 1000))} tok/s`\n}\n\n// ── ModelPicker ───────────────────────────────────────────────────────────\n\nexport interface ModelPickerProps {\n value: string\n onChange: (id: string) => void\n /** Catalogue models — from `GET`ing the app's catalogue route (see\n * `runtime/model-catalog`), plus any product-specific entries appended. */\n models: CatalogModel[]\n loading?: boolean\n /** Render a provider logo/badge; default is a generic sparkle. */\n renderProviderBadge?: (provider: string) => ReactNode\n /** Section label for `featured` models. */\n recommendedLabel?: string\n}\n\nfunction formatPrice(p?: string): string | undefined {\n if (!p) return undefined\n const n = Number(p)\n if (isNaN(n) || n === 0) return undefined\n const perM = n * 1_000_000\n return perM >= 1 ? `$${perM.toFixed(0)}/M` : `$${perM.toFixed(2)}/M`\n}\n\nfunction formatContext(len?: number): string | undefined {\n if (!len) return undefined\n if (len >= 1_000_000) return `${(len / 1_000_000).toFixed(1)}M ctx`\n if (len >= 1_000) return `${Math.round(len / 1_000)}K ctx`\n return `${len} ctx`\n}\n\nfunction SectionHeader({ children }: { children: ReactNode }) {\n return (\n <div className=\"px-3 pb-1 pt-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground/70\">\n {children}\n </div>\n )\n}\n\nfunction ModelRow({\n model,\n selected,\n onSelect,\n renderProviderBadge,\n}: {\n model: CatalogModel\n selected: boolean\n onSelect: () => void\n renderProviderBadge?: (provider: string) => ReactNode\n}) {\n const price = formatPrice(model.pricing?.prompt)\n const ctx = formatContext(model.contextLength)\n return (\n <button\n type=\"button\"\n onClick={onSelect}\n className={`flex w-full items-center gap-2.5 rounded-md px-3 py-2.5 text-left text-sm transition ${\n selected ? 'bg-primary/10 font-medium' : 'hover:bg-accent/30'\n }`}\n >\n {renderProviderBadge ? renderProviderBadge(model.provider) : <ProviderLogo provider={model.provider} size={16} />}\n <span className=\"truncate\">{model.name}</span>\n {!model.supportsTools && (\n <span className=\"shrink-0 rounded bg-muted/60 px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground\">\n no tools\n </span>\n )}\n <span className=\"ml-auto flex shrink-0 items-center gap-2 text-xs text-muted-foreground\">\n {ctx && <span>{ctx}</span>}\n {price && <span>{price}</span>}\n </span>\n </button>\n )\n}\n\n/**\n * Searchable model picker pill + popover: a featured/recommended section\n * first, then per-provider groups in catalogue order (the server already\n * sorts providers by tier).\n */\nexport function ModelPicker({ value, onChange, models, loading, renderProviderBadge, recommendedLabel = 'Recommended' }: ModelPickerProps) {\n const [open, setOpen] = useState(false)\n const [query, setQuery] = useState('')\n const containerRef = useClickOutside(() => setOpen(false))\n const inputRef = useRef<HTMLInputElement>(null)\n\n useEffect(() => {\n if (open) inputRef.current?.focus()\n }, [open])\n\n const selected = models.find((m) => m.id === value)\n\n const filtered = useMemo(() => {\n const q = query.trim().toLowerCase()\n if (!q) return null\n return models.filter(\n (m) =>\n m.id.toLowerCase().includes(q) ||\n m.name.toLowerCase().includes(q) ||\n (m.description?.toLowerCase() ?? '').includes(q) ||\n m.provider.toLowerCase().includes(q),\n )\n }, [models, query])\n\n const sections = useMemo(() => {\n const recommended = models.filter((m) => m.featured)\n const byProvider: Array<{ provider: string; items: CatalogModel[] }> = []\n for (const m of models) {\n if (m.featured) continue\n const last = byProvider[byProvider.length - 1]\n if (last && last.provider === m.provider) last.items.push(m)\n else byProvider.push({ provider: m.provider, items: [m] })\n }\n return { recommended, byProvider }\n }, [models])\n\n const select = (id: string) => {\n onChange(id)\n setOpen(false)\n setQuery('')\n }\n\n return (\n <div ref={containerRef} className=\"relative inline-flex\">\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n className=\"inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground transition hover:bg-accent/30\"\n >\n {selected ? (renderProviderBadge ? renderProviderBadge(selected.provider) : <ProviderLogo provider={selected.provider} size={16} />) : <SparkleGlyph className=\"h-3.5 w-3.5 text-muted-foreground\" />}\n <span className=\"max-w-[160px] truncate\">{selected?.name ?? value}</span>\n <ChevronDown className=\"h-3.5 w-3.5 text-muted-foreground\" />\n </button>\n\n {open && (\n <div className=\"absolute bottom-full left-0 z-50 mb-2 w-[420px] overflow-hidden rounded-xl border border-border bg-card shadow-lg\">\n <div className=\"border-b border-border px-3 py-2\">\n <div className=\"flex items-center gap-2 rounded-lg border border-border bg-background px-3 py-2\">\n <SearchGlyph className=\"h-3.5 w-3.5 text-muted-foreground\" />\n <input\n ref={inputRef}\n type=\"text\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Search models...\"\n className=\"flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground\"\n />\n </div>\n </div>\n <div className=\"max-h-[400px] overflow-y-auto p-1 pb-2\">\n {loading && <div className=\"px-3 py-4 text-center text-sm text-muted-foreground\">Loading models...</div>}\n {!loading && filtered && (\n <>\n {filtered.length === 0 && (\n <div className=\"px-3 py-4 text-center text-sm text-muted-foreground\">No models match your search</div>\n )}\n {filtered.map((m) => (\n <ModelRow key={m.id} model={m} selected={m.id === value} onSelect={() => select(m.id)} renderProviderBadge={renderProviderBadge} />\n ))}\n </>\n )}\n {!loading && !filtered && (\n <>\n {sections.recommended.length > 0 && (\n <>\n <SectionHeader>{recommendedLabel}</SectionHeader>\n {sections.recommended.map((m) => (\n <ModelRow key={m.id} model={m} selected={m.id === value} onSelect={() => select(m.id)} renderProviderBadge={renderProviderBadge} />\n ))}\n </>\n )}\n {sections.byProvider.map((g) => (\n <div key={g.provider}>\n <SectionHeader>{g.provider}</SectionHeader>\n {g.items.map((m) => (\n <ModelRow key={m.id} model={m} selected={m.id === value} onSelect={() => select(m.id)} renderProviderBadge={renderProviderBadge} />\n ))}\n </div>\n ))}\n </>\n )}\n </div>\n </div>\n )}\n </div>\n )\n}\n\n// ── EffortPicker ──────────────────────────────────────────────────────────\n\nconst EFFORT_LEVELS = [\n { id: 'off', label: 'Off' },\n { id: 'low', label: 'Low' },\n { id: 'medium', label: 'Medium' },\n { id: 'high', label: 'High' },\n] as const\n\nexport interface EffortPickerProps {\n value: string\n onChange: (id: string) => void\n}\n\n/** Reasoning-effort selector pill, styled to match {@link ModelPicker}. Show\n * it only when the selected model `supportsReasoning`. */\nexport function EffortPicker({ value, onChange }: EffortPickerProps) {\n const [open, setOpen] = useState(false)\n const containerRef = useClickOutside(() => setOpen(false))\n const selected = EFFORT_LEVELS.find((l) => l.id === value) ?? EFFORT_LEVELS[2]\n\n return (\n <div ref={containerRef} className=\"relative inline-flex\">\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n title=\"Reasoning effort\"\n className=\"inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground transition hover:bg-accent/30\"\n >\n <SparkleGlyph className=\"h-3.5 w-3.5 text-muted-foreground\" />\n <span>{selected.label}</span>\n <ChevronDown className=\"h-3.5 w-3.5 text-muted-foreground\" />\n </button>\n {open && (\n <div className=\"absolute bottom-full left-0 z-50 mb-2 w-36 overflow-hidden rounded-xl border border-border bg-card p-1 shadow-lg\">\n {EFFORT_LEVELS.map((l) => (\n <button\n key={l.id}\n type=\"button\"\n onClick={() => {\n onChange(l.id)\n setOpen(false)\n }}\n className={`flex w-full items-center rounded-md px-3 py-2 text-left text-sm transition ${\n l.id === value ? 'bg-primary/10 font-medium' : 'hover:bg-accent/30'\n }`}\n >\n {l.label}\n </button>\n ))}\n </div>\n )}\n </div>\n )\n}\n\n// ── Tool run drill-in (retained runs) ─────────────────────────────────────\n\n/** One step of a retained tool run (e.g. a sandbox command + its output). */\nexport interface ToolRunStep {\n at: string\n label: string\n detail?: string\n status?: 'ok' | 'error'\n}\n\n/** A retained tool run keyed by the parent message's toolCallId. The product\n * persists these server-side (fail-closed: only ids its own loop created)\n * and serves them to the drill-in panel. */\nexport interface ToolRunRecord {\n toolCallId: string\n toolName: string\n title: string\n status: 'running' | 'complete' | 'error'\n steps: ToolRunStep[]\n}\n\nexport interface RunDrillInProps {\n run: ToolRunRecord\n onClose: () => void\n}\n\n/**\n * Readonly side panel showing a retained tool run's transcript — the\n * \"drill into what the sandbox actually did\" view. Follow-ups happen in the\n * main chat, never here.\n */\nexport function RunDrillIn({ run, onClose }: RunDrillInProps) {\n return (\n <div className=\"fixed inset-y-0 right-0 z-50 flex w-[480px] max-w-full flex-col border-l border-border bg-card shadow-xl\">\n <div className=\"flex items-center gap-2 border-b border-border px-4 py-3\">\n <span\n className={`h-2 w-2 shrink-0 rounded-full ${\n run.status === 'running' ? 'bg-yellow-500' : run.status === 'error' ? 'bg-red-500' : 'bg-green-500'\n }`}\n />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-semibold\">{run.title}</p>\n <p className=\"truncate font-mono text-[11px] text-muted-foreground\">{run.toolName}</p>\n </div>\n <button\n type=\"button\"\n onClick={onClose}\n aria-label=\"Close\"\n className=\"rounded-md p-1.5 text-muted-foreground transition hover:bg-accent/30 hover:text-foreground\"\n >\n <svg className=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n </button>\n </div>\n <div className=\"flex-1 space-y-3 overflow-y-auto p-4\">\n {run.steps.length === 0 && (\n <p className=\"text-sm text-muted-foreground\">No steps recorded yet.</p>\n )}\n {run.steps.map((step, i) => (\n <div key={i} className=\"rounded-lg border border-border/60 bg-background\">\n <div className=\"flex items-baseline gap-2 border-b border-border/40 px-3 py-1.5\">\n <span className={`font-mono text-[11px] ${step.status === 'error' ? 'text-red-600' : 'text-muted-foreground'}`}>\n {step.status === 'error' ? '✗' : '$'}\n </span>\n <code className=\"min-w-0 flex-1 truncate font-mono text-xs\">{step.label}</code>\n <span className=\"shrink-0 text-[10px] text-muted-foreground/60\">\n {new Date(step.at).toLocaleTimeString()}\n </span>\n </div>\n {step.detail && (\n <pre className=\"max-h-48 overflow-auto whitespace-pre-wrap px-3 py-2 font-mono text-[11px] leading-relaxed text-muted-foreground\">\n {step.detail}\n </pre>\n )}\n </div>\n ))}\n </div>\n <p className=\"border-t border-border px-4 py-2 text-[11px] text-muted-foreground/60\">\n Readonly drill-in. Follow up in the main chat.\n </p>\n </div>\n )\n}\n\n// ── ChatMessages ──────────────────────────────────────────────────────────\n\nexport interface ChatToolCallInfo {\n id: string\n name: string\n status: 'running' | 'done' | 'error'\n /** The call arguments, captured from the tool_call event — shown in the\n * expanded card so users see exactly what the agent invoked. */\n args?: Record<string, unknown>\n /** The tool outcome (`{ok, result}` shape). When `result.status` is\n * 'queued_for_approval' the card renders the approval state. */\n result?: unknown\n}\n\n/** Extract `{proposalId, status}` from a tool outcome when it is a proposal\n * awaiting human approval; null otherwise. */\nexport function pendingApprovalOf(call: ChatToolCallInfo): { proposalId: string } | null {\n const outcome = call.result as { ok?: boolean; result?: { status?: string; proposalId?: string } } | undefined\n if (!outcome?.ok || outcome.result?.status !== 'queued_for_approval' || !outcome.result.proposalId) return null\n return { proposalId: outcome.result.proposalId }\n}\n\nexport interface ChatUiMessage extends ChatMessageMetrics {\n id: string\n role: 'user' | 'assistant' | 'system'\n content: string\n reasoning?: string\n toolCalls?: ChatToolCallInfo[]\n}\n\nexport interface ChatMessagesProps {\n messages: ChatUiMessage[]\n /** Catalogue models, for per-message cost from pricing. Pass [] to skip cost. */\n models?: CatalogModel[]\n /** Markdown renderer for assistant content; default renders pre-wrapped text. */\n renderMarkdown?: (content: string) => ReactNode\n /** Extra per-message content (artifacts, custom panels) appended after the body. */\n renderExtras?: (message: ChatUiMessage) => ReactNode\n userLabel?: string\n agentLabel?: string\n /** Render the trailing \"agent is thinking\" row. */\n loading?: boolean\n /** Approve/Reject handlers for proposals awaiting approval. When omitted the\n * chip still shows \"awaiting approval\" but without action buttons. */\n approval?: ProposalApprovalHandlers\n /** Open a full-transcript view (e.g. {@link RunDrillIn}) from a tool card. */\n onToolCallClick?: (call: ChatToolCallInfo, message: ChatUiMessage) => void\n /** Per-tool custom detail renderers for expanded tool cards. */\n toolRenderers?: ToolDetailRenderers\n}\n\nexport interface ProposalApprovalHandlers {\n onApprove: (proposalId: string, toolCallId: string) => void | Promise<void>\n onReject: (proposalId: string, toolCallId: string) => void | Promise<void>\n}\n\n/** Per-tool custom detail renderers for the expanded card body — keyed by\n * tool name. Return null to fall back to the generic detail view. */\nexport type ToolDetailRenderers = Record<\n string,\n (call: ChatToolCallInfo, message: ChatUiMessage) => ReactNode\n>\n\nfunction ToolGlyph({ name, className }: { name: string; className?: string }) {\n if (name.startsWith('sandbox_')) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <polyline points=\"4 17 10 11 4 5\" />\n <line x1=\"12\" y1=\"19\" x2=\"20\" y2=\"19\" />\n </svg>\n )\n }\n if (name === 'submit_proposal') {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\" />\n <path d=\"M14 2v6h6M9 15l2 2 4-4\" />\n </svg>\n )\n }\n if (name === 'schedule_followup') {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <path d=\"M12 7v5l3 3\" />\n </svg>\n )\n }\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M12 3v3m0 12v3M3 12h3m12 0h3\" />\n <circle cx=\"12\" cy=\"12\" r=\"4\" />\n </svg>\n )\n}\n\nfunction toolOutcomeOf(call: ChatToolCallInfo): { ok?: boolean; result?: Record<string, unknown>; message?: string } | undefined {\n return call.result as { ok?: boolean; result?: Record<string, unknown>; message?: string } | undefined\n}\n\n/** Human title for a call, derived from its real arguments. */\nfunction friendlyToolTitle(call: ChatToolCallInfo): string {\n const a = call.args ?? {}\n switch (call.name) {\n case 'submit_proposal':\n return `Proposal · ${String(a.type ?? '')}${a.title ? `: ${String(a.title)}` : ''}`\n case 'sandbox_create':\n return `Create sandbox (${String(a.environment ?? 'universal')})`\n case 'sandbox_run_command':\n return `$ ${String(a.command ?? '')}`\n case 'sandbox_destroy':\n return `Destroy sandbox ${String(a.sandbox_id ?? '')}`\n case 'schedule_followup':\n return `Follow-up · ${String(a.title ?? '')}`\n case 'render_ui':\n return `Render view · ${String(a.title ?? '')}`\n case 'add_citation':\n return `Citation · ${String(a.path ?? '')}`\n default:\n return call.name\n }\n}\n\nfunction truncate(v: unknown, max = 240): string {\n const s = typeof v === 'string' ? v : JSON.stringify(v)\n return s.length > max ? `${s.slice(0, max)}…` : s\n}\n\nfunction KvRows({ data }: { data: Record<string, unknown> }) {\n const entries = Object.entries(data).filter(([, v]) => v !== undefined && v !== null && v !== '')\n if (!entries.length) return null\n return (\n <dl className=\"grid grid-cols-[auto_1fr] gap-x-3 gap-y-1\">\n {entries.map(([k, v]) => (\n <div key={k} className=\"contents\">\n <dt className=\"font-mono text-[11px] text-muted-foreground/70\">{k}</dt>\n <dd className=\"min-w-0 whitespace-pre-wrap break-words font-mono text-[11px] text-muted-foreground\">\n {truncate(v)}\n </dd>\n </div>\n ))}\n </dl>\n )\n}\n\n/** Terminal-styled rendering for shell executions. */\nfunction ShellDetail({ call }: { call: ChatToolCallInfo }) {\n const outcome = toolOutcomeOf(call)\n const r = (outcome?.result ?? {}) as { stdout?: string; stderr?: string; exitCode?: number }\n return (\n <div className=\"overflow-hidden rounded-md bg-zinc-900 font-mono text-[11px] leading-relaxed\">\n <div className=\"flex items-center gap-2 px-3 pt-2 text-zinc-400\">\n <span className=\"select-none text-zinc-500\">$</span>\n <span className=\"min-w-0 flex-1 truncate text-zinc-200\">{String(call.args?.command ?? '')}</span>\n {r.exitCode != null && (\n <span className={r.exitCode === 0 ? 'text-emerald-400' : 'text-red-400'}>exit {r.exitCode}</span>\n )}\n </div>\n <pre className=\"max-h-56 overflow-auto whitespace-pre-wrap px-3 pb-2.5 pt-1.5 text-zinc-300\">\n {outcome?.ok === false ? (outcome.message ?? 'failed') : [r.stdout, r.stderr].filter(Boolean).join('\\n') || '(no output)'}\n </pre>\n </div>\n )\n}\n\n/** Generic expanded detail: what was called, and what actually happened. */\nfunction DefaultToolDetail({ call }: { call: ChatToolCallInfo }) {\n const outcome = toolOutcomeOf(call)\n return (\n <div className=\"space-y-2\">\n {call.args && Object.keys(call.args).length > 0 && (\n <div>\n <p className=\"mb-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground/50\">Called with</p>\n <KvRows data={call.args} />\n </div>\n )}\n {outcome && (\n <div>\n <p className=\"mb-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground/50\">\n {outcome.ok === false ? 'Failed' : 'Result'}\n </p>\n {outcome.ok === false ? (\n <p className=\"text-xs text-red-600\">{outcome.message ?? 'Tool failed'}</p>\n ) : outcome.result && typeof outcome.result === 'object' ? (\n <KvRows data={outcome.result} />\n ) : (\n <p className=\"font-mono text-[11px] text-muted-foreground\">{truncate(outcome.result)}</p>\n )}\n </div>\n )}\n </div>\n )\n}\n\nfunction ToolCallCard({\n call,\n message,\n approval,\n onOpenRun,\n renderers,\n}: {\n call: ChatToolCallInfo\n message: ChatUiMessage\n approval?: ProposalApprovalHandlers\n onOpenRun?: (call: ChatToolCallInfo, message: ChatUiMessage) => void\n renderers?: ToolDetailRenderers\n}) {\n const [expanded, setExpanded] = useState(false)\n const pending = call.status === 'done' ? pendingApprovalOf(call) : null\n const failed = call.status === 'error' || toolOutcomeOf(call)?.ok === false\n const custom = renderers?.[call.name]?.(call, message)\n\n return (\n <div\n className={`w-fit min-w-[280px] max-w-full rounded-lg border text-xs transition ${\n pending\n ? 'border-amber-300/60 bg-amber-500/5'\n : failed\n ? 'border-red-300/60 bg-red-500/5'\n : 'border-border/60 bg-muted/20'\n }`}\n >\n <button\n type=\"button\"\n onClick={() => setExpanded((v) => !v)}\n className=\"flex w-full items-center gap-2 px-3 py-2 text-left\"\n >\n <span\n className={`h-2 w-2 shrink-0 rounded-full ${\n call.status === 'running'\n ? 'animate-pulse bg-yellow-500'\n : pending\n ? 'bg-amber-500'\n : failed\n ? 'bg-red-500'\n : 'bg-green-500'\n }`}\n />\n <ToolGlyph name={call.name} className=\"h-3.5 w-3.5 shrink-0 text-muted-foreground\" />\n <span className=\"min-w-0 flex-1 truncate font-medium\">{friendlyToolTitle(call)}</span>\n {pending && approval && (\n <span className=\"flex shrink-0 items-center gap-1\" onClick={(e) => e.stopPropagation()}>\n <button\n type=\"button\"\n onClick={() => approval.onApprove(pending.proposalId, call.id)}\n className=\"rounded bg-green-600/90 px-2 py-0.5 text-[11px] font-semibold text-white transition hover:bg-green-600\"\n >\n Approve\n </button>\n <button\n type=\"button\"\n onClick={() => approval.onReject(pending.proposalId, call.id)}\n className=\"rounded border border-border bg-card px-2 py-0.5 text-[11px] font-medium text-foreground transition hover:bg-accent/30\"\n >\n Reject\n </button>\n </span>\n )}\n <span className=\"shrink-0 text-[11px] text-muted-foreground/70\">\n {call.status === 'running' ? 'running…' : pending ? 'awaiting approval' : failed ? 'failed' : 'done'}\n </span>\n <ChevronDown className={`h-3 w-3 shrink-0 text-muted-foreground transition-transform ${expanded ? 'rotate-180' : ''}`} />\n </button>\n {expanded && (\n <div className=\"border-t border-border/40 px-3 py-2.5\">\n {custom ?? (call.name === 'sandbox_run_command' ? <ShellDetail call={call} /> : <DefaultToolDetail call={call} />)}\n {onOpenRun && call.name.startsWith('sandbox_') && (\n <button\n type=\"button\"\n onClick={() => onOpenRun(call, message)}\n className=\"mt-2 rounded border border-border bg-card px-2 py-1 text-[11px] font-medium transition hover:bg-accent/30\"\n >\n Open full transcript →\n </button>\n )}\n </div>\n )}\n </div>\n )\n}\n\nfunction AssistantMessage({\n msg,\n streaming,\n models,\n agentLabel,\n renderBody,\n approval,\n onToolCallClick,\n toolRenderers,\n renderExtras,\n}: {\n msg: ChatUiMessage\n streaming: boolean\n models: CatalogModel[]\n agentLabel: string\n renderBody: (content: string) => ReactNode\n approval?: ProposalApprovalHandlers\n onToolCallClick?: (call: ChatToolCallInfo, message: ChatUiMessage) => void\n toolRenderers?: ToolDetailRenderers\n renderExtras?: (message: ChatUiMessage) => ReactNode\n}) {\n // Smooth reveal: chunky network slabs (model bursts, flush windows, replay\n // polls) paint as a continuous typewriter. Reasoning often arrives as one\n // burst right before the answer — smoothing makes it visibly type out in\n // the open thinking box instead of popping in and collapsing.\n const content = useSmoothText(msg.content, streaming)\n const reasoning = useSmoothText(msg.reasoning ?? '', streaming)\n const reasoningScrollRef = useRef<HTMLDivElement>(null)\n // Measure visible thinking time: first reasoning reveal → first answer text.\n const thinkStartRef = useRef<number | null>(null)\n const thinkMsRef = useRef<number | null>(null)\n if (streaming && reasoning && !content && thinkStartRef.current === null) {\n thinkStartRef.current = performance.now()\n }\n if (content && thinkStartRef.current !== null && thinkMsRef.current === null) {\n thinkMsRef.current = performance.now() - thinkStartRef.current\n }\n useEffect(() => {\n const el = reasoningScrollRef.current\n if (el && streaming && !content) el.scrollTop = el.scrollHeight\n }, [reasoning, streaming, content])\n\n return (\n <div className=\"mx-auto w-full max-w-3xl px-6 py-3\">\n <div className=\"mb-1 flex items-baseline gap-2 text-[11px] tracking-wide text-muted-foreground/60\">\n <span className=\"font-semibold uppercase\">{agentLabel}</span>\n {msg.modelUsed && <span className=\"font-mono normal-case\">{msg.modelUsed}</span>}\n {formatTokensPerSecond(msg) && <span>{formatTokensPerSecond(msg)}</span>}\n {formatModelCost(msg, models) && <span>{formatModelCost(msg, models)}</span>}\n </div>\n {reasoning && (\n <details className=\"mb-2 rounded-lg border-l-2 border-border/70 bg-muted/20 px-3 py-2\" open={!content}>\n <summary className=\"cursor-pointer select-none text-xs font-medium text-muted-foreground\">\n {!content ? (\n <span className=\"animate-pulse\">Thinking…</span>\n ) : thinkMsRef.current != null ? (\n `Thought for ${Math.max(1, Math.round(thinkMsRef.current / 1000))}s`\n ) : (\n 'Thought process'\n )}\n </summary>\n <div ref={reasoningScrollRef} className=\"mt-2 max-h-48 overflow-y-auto whitespace-pre-wrap text-[13px] leading-relaxed text-muted-foreground/70\">\n {reasoning}\n </div>\n </details>\n )}\n <div className=\"text-base leading-[1.75]\">\n {renderBody(content)}\n {streaming && content && !msg.toolCalls?.length && (\n <span className=\"ml-0.5 inline-block h-[1.1em] w-[3px] translate-y-[2px] animate-pulse rounded-sm bg-foreground/70\" aria-hidden />\n )}\n </div>\n {msg.toolCalls && msg.toolCalls.length > 0 && (\n <div className=\"mt-2 flex flex-col gap-1.5\">\n {msg.toolCalls.map((tc) => (\n <ToolCallCard\n key={tc.id}\n call={tc}\n message={msg}\n approval={approval}\n onOpenRun={onToolCallClick}\n renderers={toolRenderers}\n />\n ))}\n </div>\n )}\n {renderExtras?.(msg)}\n </div>\n )\n}\n\nfunction ThinkingRow({ agentLabel }: { agentLabel: string }) {\n const [seconds, setSeconds] = useState(0)\n useEffect(() => {\n const id = setInterval(() => setSeconds((s) => s + 1), 1000)\n return () => clearInterval(id)\n }, [])\n return (\n <div className=\"mx-auto w-full max-w-3xl px-6 py-3\">\n <p className=\"mb-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground/60\">{agentLabel}</p>\n <div className=\"flex items-center gap-2 text-base text-muted-foreground\">\n <svg className=\"h-4 w-4 animate-spin\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" aria-hidden>\n <path d=\"M21 12a9 9 0 1 1-6.219-8.56\" strokeLinecap=\"round\" />\n </svg>\n Thinking{seconds >= 3 ? ` · ${seconds}s` : '...'}\n </div>\n </div>\n )\n}\n\n/**\n * The message thread: one centered column; user messages are right-aligned\n * bubbles with a User label; agent messages carry an Agent meta line with\n * model id, tokens/sec, and cost, plus a collapsible thinking section and\n * tool-call chips.\n */\nexport function ChatMessages({\n messages,\n models = [],\n renderMarkdown,\n renderExtras,\n userLabel = 'User',\n agentLabel = 'Agent',\n loading,\n approval,\n onToolCallClick,\n toolRenderers,\n}: ChatMessagesProps) {\n const renderBody = renderMarkdown ?? ((content: string) => <p className=\"whitespace-pre-wrap\">{content}</p>)\n const lastIsUser = messages[messages.length - 1]?.role === 'user'\n return (\n <>\n {messages.map((msg) =>\n msg.role === 'user' ? (\n <div key={msg.id} className=\"mx-auto w-full max-w-3xl px-6 py-3\">\n <div className=\"ml-auto w-fit max-w-[85%]\">\n <p className=\"mb-1 text-right text-[11px] font-semibold uppercase tracking-wide text-muted-foreground/60\">\n {userLabel}\n </p>\n <div className=\"rounded-2xl rounded-tr-md bg-primary/10 px-4 py-2.5 text-base leading-relaxed\">\n <p className=\"whitespace-pre-wrap\">{msg.content}</p>\n </div>\n </div>\n </div>\n ) : (\n <AssistantMessage\n key={msg.id}\n msg={msg}\n streaming={!!loading && msg.id === messages[messages.length - 1]?.id}\n models={models}\n agentLabel={agentLabel}\n renderBody={renderBody}\n approval={approval}\n onToolCallClick={onToolCallClick}\n toolRenderers={toolRenderers}\n renderExtras={renderExtras}\n />\n ),\n )}\n {loading && lastIsUser && <ThinkingRow agentLabel={agentLabel} />}\n </>\n )\n}\n","/**\n * Provider brand marks — real logo path data (simple-icons / SVG Logos, both\n * CC0) inlined so the picker shows actual provider identity instead of\n * colored-initial monograms. Providers without a usable mark fall back to a\n * tinted monogram chip. Aliases (z-ai/zai, moonshot/moonshotai, deepseek_ai)\n * normalize to one entry.\n */\n\nimport type { ReactNode } from 'react'\n\ninterface LogoDef {\n viewBox: string\n fill: string\n paths: string[]\n}\n\nconst LOGOS: Record<string, LogoDef> = {\n anthropic: { viewBox: '0 0 24 24', fill: '#D97757', paths: [\"M17.3041 3.541h-3.6718l6.696 16.918H24Zm-10.6082 0L0 20.459h3.7442l1.3693-3.5527h7.0052l1.3693 3.5528h3.7442L10.5363 3.5409Zm-.3712 10.2232 2.2914-5.9456 2.2914 5.9456Z\"] },\n google: { viewBox: '0 0 24 24', fill: '#8E75B2', paths: [\"M11.04 19.32Q12 21.51 12 24q0-2.49.93-4.68.96-2.19 2.58-3.81t3.81-2.55Q21.51 12 24 12q-2.49 0-4.68-.93a12.3 12.3 0 0 1-3.81-2.58 12.3 12.3 0 0 1-2.58-3.81Q12 2.49 12 0q0 2.49-.96 4.68-.93 2.19-2.55 3.81a12.3 12.3 0 0 1-3.81 2.58Q2.49 12 0 12q2.49 0 4.68.96 2.19.93 3.81 2.55t2.55 3.81\"] },\n deepseek: { viewBox: '0 0 24 24', fill: '#4D6BFE', paths: [\"M23.748 4.651c-.254-.124-.364.113-.512.233-.051.04-.094.09-.137.137-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.155-.708-.311-.955-.65-.172-.24-.219-.509-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.094.172.187.129.323-.082.28-.18.553-.266.833-.055.179-.137.218-.328.14a5.5 5.5 0 0 1-1.737-1.179c-.857-.828-1.631-1.743-2.597-2.46a12 12 0 0 0-.689-.47c-.985-.957.13-1.743.387-1.836.27-.098.094-.433-.778-.428-.872.003-1.67.295-2.687.685a3 3 0 0 1-.465.136 9.6 9.6 0 0 0-2.883-.101c-1.885.21-3.39 1.1-4.497 2.622C.082 8.776-.231 10.854.152 13.02c.403 2.284 1.568 4.175 3.36 5.653 1.857 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.132-.284 4.994-1.86.47.234.962.328 1.78.398.629.058 1.235-.031 1.705-.129.735-.155.684-.836.418-.961-2.155-1.004-1.682-.595-2.112-.926 1.095-1.295 2.768-3.598 3.284-6.733.05-.346.115-.834.108-1.114-.004-.171.035-.238.23-.257a4.2 4.2 0 0 0 1.545-.475c1.397-.763 1.96-2.016 2.093-3.517.02-.23-.004-.467-.247-.588M11.58 18.168c-2.088-1.642-3.101-2.183-3.52-2.16-.39.024-.32.472-.234.763.09.288.207.487.371.74.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.168-1.361-.801-2.5-1.86-3.301-3.306-.775-1.393-1.225-2.888-1.299-4.482-.02-.385.094-.522.477-.592a4.7 4.7 0 0 1 1.53-.038c2.131.311 3.946 1.264 5.467 2.774.868.86 1.525 1.887 2.202 2.89.72 1.066 1.494 2.082 2.48 2.915.348.291.626.513.892.677-.802.09-2.14.109-3.055-.615zm1.001-6.44a.306.306 0 0 1 .415-.287.3.3 0 0 1 .113.074.3.3 0 0 1 .086.214c0 .17-.136.307-.308.307a.303.303 0 0 1-.306-.307m3.11 1.596c-.2.081-.4.151-.591.16a1.25 1.25 0 0 1-.798-.254c-.274-.23-.47-.358-.551-.758a1.7 1.7 0 0 1 .015-.588c.07-.327-.007-.537-.238-.727-.188-.156-.426-.199-.689-.199a.6.6 0 0 1-.254-.078.253.253 0 0 1-.114-.358 1 1 0 0 1 .192-.21c.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.392.451.462.576.685.915.176.264.336.536.446.848.066.194-.02.353-.25.45\"] },\n mistral: { viewBox: '0 0 24 24', fill: '#FA520F', paths: [\"M17.143 3.429v3.428h-3.429v3.429h-3.428V6.857H6.857V3.43H3.43v13.714H0v3.428h10.286v-3.428H6.857v-3.429h3.429v3.429h3.429v-3.429h3.428v3.429h-3.428v3.428H24v-3.428h-3.43V3.429z\"] },\n xai: { viewBox: '0 0 24 24', fill: '#000000', paths: [\"M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z\"] },\n nvidia: { viewBox: '0 0 24 24', fill: '#76B900', paths: [\"M8.948 8.798v-1.43a6.7 6.7 0 0 1 .424-.018c3.922-.124 6.493 3.374 6.493 3.374s-2.774 3.851-5.75 3.851c-.398 0-.787-.062-1.158-.185v-4.346c1.528.185 1.837.857 2.747 2.385l2.04-1.714s-1.492-1.952-4-1.952a6.016 6.016 0 0 0-.796.035m0-4.735v2.138l.424-.027c5.45-.185 9.01 4.47 9.01 4.47s-4.08 4.964-8.33 4.964c-.37 0-.733-.035-1.095-.097v1.325c.3.035.61.062.91.062 3.957 0 6.82-2.023 9.593-4.408.459.371 2.34 1.263 2.73 1.652-2.633 2.208-8.772 3.984-12.253 3.984-.335 0-.653-.018-.971-.053v1.864H24V4.063zm0 10.326v1.131c-3.657-.654-4.673-4.46-4.673-4.46s1.758-1.944 4.673-2.262v1.237H8.94c-1.528-.186-2.73 1.245-2.73 1.245s.68 2.412 2.739 3.11M2.456 10.9s2.164-3.197 6.5-3.533V6.201C4.153 6.59 0 10.653 0 10.653s2.35 6.802 8.948 7.42v-1.237c-4.84-.6-6.492-5.936-6.492-5.936z\"] },\n meta: { viewBox: '0 0 24 24', fill: '#0467DF', paths: [\"M6.915 4.03c-1.968 0-3.683 1.28-4.871 3.113C.704 9.208 0 11.883 0 14.449c0 .706.07 1.369.21 1.973a6.624 6.624 0 0 0 .265.86 5.297 5.297 0 0 0 .371.761c.696 1.159 1.818 1.927 3.593 1.927 1.497 0 2.633-.671 3.965-2.444.76-1.012 1.144-1.626 2.663-4.32l.756-1.339.186-.325c.061.1.121.196.183.3l2.152 3.595c.724 1.21 1.665 2.556 2.47 3.314 1.046.987 1.992 1.22 3.06 1.22 1.075 0 1.876-.355 2.455-.843a3.743 3.743 0 0 0 .81-.973c.542-.939.861-2.127.861-3.745 0-2.72-.681-5.357-2.084-7.45-1.282-1.912-2.957-2.93-4.716-2.93-1.047 0-2.088.467-3.053 1.308-.652.57-1.257 1.29-1.82 2.05-.69-.875-1.335-1.547-1.958-2.056-1.182-.966-2.315-1.303-3.454-1.303zm10.16 2.053c1.147 0 2.188.758 2.992 1.999 1.132 1.748 1.647 4.195 1.647 6.4 0 1.548-.368 2.9-1.839 2.9-.58 0-1.027-.23-1.664-1.004-.496-.601-1.343-1.878-2.832-4.358l-.617-1.028a44.908 44.908 0 0 0-1.255-1.98c.07-.109.141-.224.211-.327 1.12-1.667 2.118-2.602 3.358-2.602zm-10.201.553c1.265 0 2.058.791 2.675 1.446.307.327.737.871 1.234 1.579l-1.02 1.566c-.757 1.163-1.882 3.017-2.837 4.338-1.191 1.649-1.81 1.817-2.486 1.817-.524 0-1.038-.237-1.383-.794-.263-.426-.464-1.13-.464-2.046 0-2.221.63-4.535 1.66-6.088.454-.687.964-1.226 1.533-1.533a2.264 2.264 0 0 1 1.088-.285z\"] },\n moonshotai: { viewBox: '0 0 24 24', fill: '#16191E', paths: [\"m1.053 16.91 9.538 2.55a21 20.981 0 0 0 .06 2.031l5.956 1.592a12 11.99 0 0 1-15.554-6.172m-1.02-5.79 11.352 3.035a21 20.981 0 0 0-.469 2.01l10.817 2.89a12 11.99 0 0 1-1.845 2.004L.658 15.918a12 11.99 0 0 1-.625-4.796m1.593-5.146L13.573 9.17a21 20.981 0 0 0-1.01 1.874l11.297 3.02a21 20.981 0 0 1-.67 2.362l-11.55-3.087L.125 10.26a12 11.99 0 0 1 1.499-4.285ZM6.067 1.58l11.285 3.016a21 20.981 0 0 0-1.688 1.719l7.824 2.091a21 20.981 0 0 1 .513 2.664L2.107 5.218a12 11.99 0 0 1 3.96-3.638M21.68 4.866 7.222 1.003A12 11.99 0 0 1 21.68 4.866\"] },\n openai: { viewBox: '0 0 256 260', fill: '#10A37F', paths: [\"M239.184 106.203a64.72 64.72 0 0 0-5.576-53.103C219.452 28.459 191 15.784 163.213 21.74A65.586 65.586 0 0 0 52.096 45.22a64.72 64.72 0 0 0-43.23 31.36c-14.31 24.602-11.061 55.634 8.033 76.74a64.67 64.67 0 0 0 5.525 53.102c14.174 24.65 42.644 37.324 70.446 31.36a64.72 64.72 0 0 0 48.754 21.744c28.481.025 53.714-18.361 62.414-45.481a64.77 64.77 0 0 0 43.229-31.36c14.137-24.558 10.875-55.423-8.083-76.483m-97.56 136.338a48.4 48.4 0 0 1-31.105-11.255l1.535-.87l51.67-29.825a8.6 8.6 0 0 0 4.247-7.367v-72.85l21.845 12.636c.218.111.37.32.409.563v60.367c-.056 26.818-21.783 48.545-48.601 48.601M37.158 197.93a48.35 48.35 0 0 1-5.781-32.589l1.534.921l51.722 29.826a8.34 8.34 0 0 0 8.441 0l63.181-36.425v25.221a.87.87 0 0 1-.358.665l-52.335 30.184c-23.257 13.398-52.97 5.431-66.404-17.803M23.549 85.38a48.5 48.5 0 0 1 25.58-21.333v61.39a8.29 8.29 0 0 0 4.195 7.316l62.874 36.272l-21.845 12.636a.82.82 0 0 1-.767 0L41.353 151.53c-23.211-13.454-31.171-43.144-17.804-66.405zm179.466 41.695l-63.08-36.63L161.73 77.86a.82.82 0 0 1 .768 0l52.233 30.184a48.6 48.6 0 0 1-7.316 87.635v-61.391a8.54 8.54 0 0 0-4.4-7.213m21.742-32.69l-1.535-.922l-51.619-30.081a8.39 8.39 0 0 0-8.492 0L99.98 99.808V74.587a.72.72 0 0 1 .307-.665l52.233-30.133a48.652 48.652 0 0 1 72.236 50.391zM88.061 139.097l-21.845-12.585a.87.87 0 0 1-.41-.614V65.685a48.652 48.652 0 0 1 79.757-37.346l-1.535.87l-51.67 29.825a8.6 8.6 0 0 0-4.246 7.367zm11.868-25.58L128.067 97.3l28.188 16.218v32.434l-28.086 16.218l-28.188-16.218z\"] },\n}\n\nconst ALIASES: Record<string, string> = {\n moonshot: 'moonshotai',\n deepseek_ai: 'deepseek',\n 'x-ai': 'xai',\n 'meta-llama': 'meta',\n}\n\nconst MONOGRAM: Record<string, { bg: string; fg: string }> = {\n cohere: { bg: '#fae8ff', fg: '#c026d3' },\n groq: { bg: '#fce7f3', fg: '#db2777' },\n cerebras: { bg: '#ccfbf1', fg: '#0d9488' },\n zai: { bg: '#ede9fe', fg: '#7c3aed' },\n 'z-ai': { bg: '#ede9fe', fg: '#7c3aed' },\n tuner: { bg: '#dbeafe', fg: '#2563eb' },\n}\n\nexport interface ProviderLogoProps {\n provider?: string\n size?: number\n}\n\n/** Real brand mark when we have one; tinted monogram otherwise. */\nexport function ProviderLogo({ provider, size = 16 }: ProviderLogoProps): ReactNode {\n const key = ALIASES[provider ?? ''] ?? provider ?? ''\n const logo = LOGOS[key]\n if (logo) {\n return (\n <svg width={size} height={size} viewBox={logo.viewBox} role=\"img\" aria-label={key}>\n {logo.paths.map((d, i) => (\n <path key={i} d={d} fill={logo.fill} />\n ))}\n </svg>\n )\n }\n const mono = MONOGRAM[key] ?? { bg: '#f3f4f6', fg: '#6b7280' }\n return (\n <svg width={size} height={size} viewBox=\"0 0 16 16\" role=\"img\" aria-label={key || 'model'}>\n <rect width=\"16\" height=\"16\" rx=\"4\" fill={mono.bg} />\n <text x=\"8\" y=\"11.6\" textAnchor=\"middle\" fill={mono.fg} fontSize=\"9\" fontWeight=\"700\" fontFamily=\"system-ui, sans-serif\">\n {(key || '?').charAt(0).toUpperCase()}\n </text>\n </svg>\n )\n}\n","/**\n * Smooth text reveal — turns chunky network deltas into a continuous\n * typewriter paint. Streamed turns arrive in 100-500ms slabs (model burst,\n * flush windows, replay polls); revealing characters at an adaptive rate\n * makes the same bytes read as top-tier streaming. The rate scales with the\n * backlog so the reveal never falls behind the stream — it crawls when caught\n * up and sprints when a burst lands (e.g. a reasoning summary arriving all at\n * once still *types out* instead of popping in).\n */\n\nimport { useEffect, useRef, useState } from 'react'\n\nexport interface SmoothRevealOptions {\n /** Baseline reveal rate when nearly caught up. Default 90 chars/s. */\n baseCharsPerSecond?: number\n /** Extra chars/s per backlog character — the catch-up pressure. Default 5. */\n catchUpPerChar?: number\n /** Hard ceiling so giant bursts still animate. Default 2400 chars/s. */\n maxCharsPerSecond?: number\n}\n\n/** Pure reveal step: how many characters should be visible after `dtMs`.\n * Exposed for tests; the hook is a thin rAF wrapper around it. */\nexport function nextRevealCount(\n shown: number,\n targetLength: number,\n dtMs: number,\n opts: SmoothRevealOptions = {},\n): number {\n if (shown >= targetLength) return targetLength\n const base = opts.baseCharsPerSecond ?? 90\n const catchUp = opts.catchUpPerChar ?? 5\n const max = opts.maxCharsPerSecond ?? 2400\n const backlog = targetLength - shown\n const rate = Math.min(max, base + backlog * catchUp)\n return Math.min(targetLength, shown + (rate * dtMs) / 1000)\n}\n\n/**\n * Animate `target` text into view. While `enabled`, the returned string grows\n * smoothly toward `target` (which may itself keep growing); when `enabled` is\n * false the full text returns immediately (history, completed turns). A\n * target that is not an extension of the revealed prefix (new message) resets\n * the reveal.\n */\nexport function useSmoothText(target: string, enabled: boolean, opts?: SmoothRevealOptions): string {\n const [, force] = useState(0)\n const shownRef = useRef(0)\n const lastTargetRef = useRef('')\n\n // New message / rewritten prefix → restart the reveal from zero.\n if (!target.startsWith(lastTargetRef.current.slice(0, Math.floor(shownRef.current)))) {\n shownRef.current = 0\n }\n lastTargetRef.current = target\n if (!enabled) shownRef.current = target.length\n\n useEffect(() => {\n if (!enabled) return\n let raf = 0\n let last: number | null = null\n const tick = (t: number) => {\n const dt = last == null ? 16 : Math.min(t - last, 100)\n last = t\n const targetLen = lastTargetRef.current.length\n if (shownRef.current < targetLen) {\n shownRef.current = nextRevealCount(shownRef.current, targetLen, dt, opts)\n force((n) => n + 1)\n }\n raf = requestAnimationFrame(tick)\n }\n raf = requestAnimationFrame(tick)\n return () => cancelAnimationFrame(raf)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [enabled])\n\n return target.slice(0, Math.floor(shownRef.current))\n}\n","/**\n * Client-side chat-stream consumption — the NDJSON parse loop every agent\n * app's chat UI hand-rolls (and breaks). Normalizes the three line shapes the\n * agent-app chat routes emit:\n *\n * {kind:'event', event:{type:'text'|'reasoning'|'tool_call'|'usage', ...}}\n * {kind:'tool_result', toolCallId, toolName, label, outcome}\n * {type:'turn'|'metadata'|'error'|'turn_status', ...} (route-level)\n *\n * Replayed lines carry an extra `seq` — transparently ignored. Works for\n * router-backed and sandbox-backed chats alike: anything producing these\n * lines (live pump, queued follow, resume replay) feeds the same callbacks.\n */\n\nexport interface ChatStreamToolCall {\n toolCallId?: string\n toolName: string\n args: Record<string, unknown>\n}\n\nexport interface ChatStreamToolResult {\n toolCallId?: string\n toolName?: string\n label?: string\n outcome: { ok: boolean; result?: unknown; code?: string; message?: string }\n}\n\nexport interface ChatStreamCallbacks {\n onTurnId?: (turnId: string) => void\n onText?: (delta: string) => void\n onReasoning?: (delta: string) => void\n onToolCall?: (call: ChatStreamToolCall) => void\n onToolResult?: (result: ChatStreamToolResult) => void\n onUsage?: (usage: { promptTokens: number; completionTokens: number }) => void\n onMetadata?: (data: Record<string, unknown>) => void\n /** A loop-level error event (the turn failed server-side). */\n onErrorEvent?: (message: string) => void\n}\n\nexport interface ConsumeChatStreamResult {\n turnId: string | null\n /** True when any text/reasoning/tool activity was received. */\n receivedContent: boolean\n}\n\n/** Parse one NDJSON line into the callbacks. Exposed for tests. */\nexport function dispatchChatStreamLine(line: string, cb: ChatStreamCallbacks): {\n turnId?: string\n receivedContent: boolean\n} {\n let receivedContent = false\n let turnId: string | undefined\n if (!line.trim()) return { receivedContent }\n let parsed: Record<string, unknown>\n try {\n parsed = JSON.parse(line) as Record<string, unknown>\n } catch {\n return { receivedContent } // tolerate a torn line\n }\n\n if (parsed.kind === 'tool_result') {\n cb.onToolResult?.({\n toolCallId: parsed.toolCallId as string | undefined,\n toolName: parsed.toolName as string | undefined,\n label: parsed.label as string | undefined,\n outcome: (parsed.outcome ?? parsed.result) as ChatStreamToolResult['outcome'],\n })\n return { receivedContent: true }\n }\n\n const evt = (parsed.kind === 'event' ? parsed.event : parsed) as Record<string, unknown>\n if (!evt || typeof evt !== 'object') return { receivedContent }\n\n switch (evt.type) {\n case 'turn':\n if (typeof evt.turnId === 'string') turnId = evt.turnId\n break\n case 'text':\n if (typeof evt.text === 'string') {\n cb.onText?.(evt.text)\n receivedContent = true\n }\n break\n case 'reasoning':\n if (typeof evt.text === 'string') {\n cb.onReasoning?.(evt.text)\n receivedContent = true\n }\n break\n case 'tool_call': {\n const call = (evt.call ?? evt) as Record<string, unknown>\n cb.onToolCall?.({\n toolCallId: (call.toolCallId ?? call.id) as string | undefined,\n toolName: String(call.toolName ?? call.name ?? 'unknown'),\n args: (call.args ?? {}) as Record<string, unknown>,\n })\n receivedContent = true\n break\n }\n case 'tool_result':\n cb.onToolResult?.({\n toolCallId: evt.toolCallId as string | undefined,\n toolName: evt.toolName as string | undefined,\n label: evt.label as string | undefined,\n outcome: (evt.outcome ?? evt.result) as ChatStreamToolResult['outcome'],\n })\n receivedContent = true\n break\n case 'usage': {\n const u = evt.usage as { promptTokens?: number; completionTokens?: number } | undefined\n if (u) cb.onUsage?.({ promptTokens: u.promptTokens ?? 0, completionTokens: u.completionTokens ?? 0 })\n break\n }\n case 'metadata':\n cb.onMetadata?.((evt.data ?? {}) as Record<string, unknown>)\n break\n case 'error':\n cb.onErrorEvent?.(String(evt.details ?? evt.error ?? 'Unknown stream error'))\n break\n default:\n break // turn_status and unknown line types are non-content\n }\n return { turnId, receivedContent }\n}\n\n/** Drain one NDJSON body into the callbacks. Throws on transport failure\n * (caller decides whether to resume). */\nexport async function consumeChatStream(\n body: ReadableStream<Uint8Array>,\n cb: ChatStreamCallbacks,\n): Promise<ConsumeChatStreamResult> {\n const reader = body.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n let turnId: string | null = null\n let receivedContent = false\n\n const handle = (line: string) => {\n const r = dispatchChatStreamLine(line, cb)\n if (r.turnId) {\n turnId = r.turnId\n cb.onTurnId?.(r.turnId)\n }\n if (r.receivedContent) receivedContent = true\n }\n\n for (;;) {\n const { done, value } = await reader.read()\n if (done) {\n if (buffer.trim()) handle(buffer)\n break\n }\n buffer += decoder.decode(value, { stream: true })\n const lines = buffer.split('\\n')\n buffer = lines.pop() ?? ''\n for (const line of lines) handle(line)\n }\n return { turnId, receivedContent }\n}\n\nexport interface StreamChatOptions {\n /** Start the turn (POST the chat request); must return a streaming Response. */\n start: () => Promise<Response>\n /** Re-attach to a turn after a transport drop (GET the resume route). */\n resume?: (turnId: string, fromSeq: number) => Promise<Response>\n callbacks: ChatStreamCallbacks\n /** Called before a resume replays from 0 so the UI can reset accumulated\n * turn state (text, reasoning, tool chips). */\n onResetForResume?: () => void\n}\n\n/**\n * Run one chat turn with automatic single-shot resume: if the transport drops\n * mid-turn and the server announced a turnId, reset and replay the buffered\n * turn. Server-side the turn keeps running either way (queued runner).\n */\nexport async function streamChatTurn(opts: StreamChatOptions): Promise<ConsumeChatStreamResult> {\n const res = await opts.start()\n if (!res.ok || !res.body) {\n const err = await res.json().catch(() => ({ error: `HTTP ${res.status}` })) as { error?: string }\n throw new Error(err.error ?? `HTTP ${res.status}`)\n }\n let turnId: string | null = null\n const cb: ChatStreamCallbacks = {\n ...opts.callbacks,\n onTurnId: (id) => {\n turnId = id\n opts.callbacks.onTurnId?.(id)\n },\n }\n try {\n return await consumeChatStream(res.body, cb)\n } catch (transportErr) {\n if (!turnId || !opts.resume) throw transportErr\n opts.onResetForResume?.()\n const resumed = await opts.resume(turnId, 0)\n if (!resumed.ok || !resumed.body) throw transportErr\n return await consumeChatStream(resumed.body, cb)\n }\n}\n"],"mappings":"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","names":["useEffect","useRef","useState","jsx","jsxs","useRef","useEffect","useState"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"packageManager": "pnpm@10.33.4",
|
|
5
5
|
"description": "Application-shell framework for Tangle agent products: a bounded tool loop, the structured agent\u2192app tool side channel, integration-hub client, per-workspace billing, and crypto \u2014 composed over the Tangle agent substrate through typed seams.",
|
|
6
6
|
"keywords": [
|