@braedonsaunders/appkit-ai 1.0.1 → 1.0.3
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/README.md +5 -0
- package/client.js +1 -1
- package/client.js.map +1 -1
- package/models.js +1 -1
- package/models.js.map +1 -1
- package/package.json +2 -2
- package/react.d.ts +7 -1
- package/react.d.ts.map +1 -1
- package/react.js +70 -15
- package/react.js.map +1 -1
package/README.md
CHANGED
|
@@ -11,6 +11,11 @@ functions already enforce its `RequestContext` and RBAC policy.
|
|
|
11
11
|
`@braedonsaunders/appkit-ai/react` supplies the streaming assistant thread, UI-message decoder,
|
|
12
12
|
markdown renderer, generic tool-use cards, abort control, composer, welcome and
|
|
13
13
|
disabled states. Conversation persistence and the HTTP transport stay app-owned.
|
|
14
|
+
Hosts can place contextual controls in the panel's fixed header with the optional
|
|
15
|
+
`headerActions` prop and replace the stock first-message card with a full-height
|
|
16
|
+
application stage through `emptyContent`. Consecutive tool calls collapse into
|
|
17
|
+
one quiet activity line showing the newest action and total step count; operators can expand it to
|
|
18
|
+
inspect every input and result without letting a long run overwhelm the thread.
|
|
14
19
|
|
|
15
20
|
```ts
|
|
16
21
|
import { runAgentTurn } from '@braedonsaunders/appkit-ai'
|
package/client.js
CHANGED
|
@@ -4,7 +4,7 @@ import { createAnthropic } from '@ai-sdk/anthropic';
|
|
|
4
4
|
import { createGoogleGenerativeAI } from '@ai-sdk/google';
|
|
5
5
|
import { createOpenAI } from '@ai-sdk/openai';
|
|
6
6
|
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
|
|
7
|
-
import { resolvePublicHost, secureFetch, validateOutboundRequestConfiguration, } from '@braedonsaunders/appkit-
|
|
7
|
+
import { resolvePublicHost, secureFetch, validateOutboundRequestConfiguration, } from '@braedonsaunders/appkit-egress-proxy/secure-fetch';
|
|
8
8
|
import { generateText } from 'ai';
|
|
9
9
|
const MAX_AI_REQUEST_BYTES = 16 * 1024 * 1024;
|
|
10
10
|
const MAX_AI_RESPONSE_BYTES = 16 * 1024 * 1024;
|
package/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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","sourcesContent":["// Config-driven AI client. Provider + API key + models (+ base URL) are passed\n// in per call (resolved from per-tenant settings), NOT read from the environment.\n\nimport { createAnthropic } from '@ai-sdk/anthropic'\nimport { createGoogleGenerativeAI } from '@ai-sdk/google'\nimport { createOpenAI } from '@ai-sdk/openai'\nimport { createOpenAICompatible } from '@ai-sdk/openai-compatible'\nimport {\n resolvePublicHost,\n secureFetch,\n validateOutboundRequestConfiguration,\n} from '@braedonsaunders/appkit-sync/egress'\nimport { generateText, type LanguageModel } from 'ai'\n\nconst MAX_AI_REQUEST_BYTES = 16 * 1024 * 1024\nconst MAX_AI_RESPONSE_BYTES = 16 * 1024 * 1024\n/**\n * How long one model call may take before the transport gives up on it.\n *\n * This is not a budget — the caller's own loop governs how much work an agent\n * may do — it is only the point past which a socket is assumed dead. Two\n * minutes turned out to be inside the normal range for a reasoning model\n * answering with tool calls: measured runs on several hosted models spent\n * longer than that on a single step, and each one died on this timeout having\n * produced nothing, billed nothing, and lost every step that came before it.\n *\n * Ten minutes is comfortably past the slowest observed step while still\n * catching a connection that has genuinely gone away.\n */\nconst AI_REQUEST_TIMEOUT_MS = 600_000\n\nexport type AiProvider =\n | 'anthropic'\n | 'openai'\n | 'google'\n | 'openrouter'\n | 'groq'\n | 'xai'\n | 'deepseek'\n | 'mistral'\n | 'custom'\n\nexport type ModelTier = 'fast' | 'smart'\n\n/**\n * Platform-wide AI policy governing per-tenant overrides (mirrors the email/SMS\n * policy modes). 'disabled' is a global kill switch; 'global_only' forces the\n * platform provider for every tenant; 'tenant_optional' lets each tenant use its\n * own provider and falls back to the platform default.\n */\nexport type AiPolicyMode = 'tenant_optional' | 'global_only' | 'disabled'\n\nexport type AiConfig = {\n provider: AiProvider\n apiKey: string\n modelFast?: string | null\n modelSmart?: string | null\n /**\n * Endpoint for OpenAI-compatible providers. Required for `custom`; for the\n * named compatible providers it is an optional override of the built-in URL.\n */\n baseUrl?: string | null\n /**\n * Organization (tenant) identity for prompt grounding, so generated content\n * uses the real org name instead of a placeholder. Populated by\n * `getTenantAiConfig`; content-generation paths inject it into the system\n * prompt, analysis/vision paths ignore it.\n */\n org?: { name: string } | null\n}\n\n// How a provider's language model is constructed. The OpenAI-compatible kind\n// covers OpenRouter, Groq, xAI, DeepSeek, Mistral and any user `custom` endpoint.\ntype ProviderKind = 'anthropic' | 'openai' | 'google' | 'openai-compatible'\n\nexport type ProviderSpec = {\n value: AiProvider\n label: string\n kind: ProviderKind\n /** Built-in endpoint for a named OpenAI-compatible provider (null otherwise). */\n baseUrl: string | null\n /** True when the tenant MUST supply their own base URL (i.e. `custom`). */\n requiresBaseUrl: boolean\n /** Default fast/smart model ids (placeholders + fallbacks). Empty for `custom`. */\n fast: string\n smart: string\n /** Placeholder shown in the API-key field. */\n keyHint: string\n /** Optional note about the model-id format for this provider. */\n modelHint?: string\n /**\n * True when this provider's API accepts IMAGE content inside a tool result\n * (Anthropic does; OpenAI's and Google's function/tool results are text/JSON\n * only). Gates vision tools that return rendered page images to the model —\n * see `providerSupportsImageToolResults`.\n */\n visionToolResults: boolean\n}\n\n/**\n * Provider catalogue — single source of truth for the settings UI, the model\n * factory and config validation. Add a provider here and it lights up everywhere.\n */\nexport const AI_PROVIDER_SPECS: ProviderSpec[] = [\n {\n value: 'anthropic',\n label: 'Anthropic — Claude',\n kind: 'anthropic',\n baseUrl: null,\n requiresBaseUrl: false,\n fast: 'claude-haiku-4-5-20251001',\n smart: 'claude-sonnet-4-6',\n keyHint: 'sk-ant-…',\n visionToolResults: true,\n },\n {\n value: 'openai',\n label: 'OpenAI — GPT',\n kind: 'openai',\n baseUrl: null,\n requiresBaseUrl: false,\n fast: 'gpt-4o-mini',\n smart: 'gpt-4o',\n keyHint: 'sk-…',\n visionToolResults: false,\n },\n {\n value: 'google',\n label: 'Google — Gemini',\n kind: 'google',\n baseUrl: null,\n requiresBaseUrl: false,\n fast: 'gemini-2.5-flash',\n smart: 'gemini-2.5-pro',\n keyHint: 'AIza…',\n visionToolResults: false,\n },\n {\n value: 'openrouter',\n label: 'OpenRouter — any model, one key',\n kind: 'openai-compatible',\n baseUrl: 'https://openrouter.ai/api/v1',\n requiresBaseUrl: false,\n fast: 'anthropic/claude-3.5-haiku',\n smart: 'anthropic/claude-3.5-sonnet',\n keyHint: 'sk-or-…',\n modelHint: 'Use vendor/model slugs, e.g. anthropic/claude-3.5-sonnet or openai/gpt-4o.',\n // OpenRouter proxies many vendors over an OpenAI-compatible surface; image\n // tool-results aren't reliably supported, so keep vision tools off here.\n visionToolResults: false,\n },\n {\n value: 'groq',\n label: 'Groq — fast inference',\n kind: 'openai-compatible',\n baseUrl: 'https://api.groq.com/openai/v1',\n requiresBaseUrl: false,\n fast: 'llama-3.1-8b-instant',\n smart: 'llama-3.3-70b-versatile',\n keyHint: 'gsk_…',\n visionToolResults: false,\n },\n {\n value: 'xai',\n label: 'xAI — Grok',\n kind: 'openai-compatible',\n baseUrl: 'https://api.x.ai/v1',\n requiresBaseUrl: false,\n fast: 'grok-2-1212',\n smart: 'grok-2-vision-1212',\n keyHint: 'xai-…',\n visionToolResults: false,\n },\n {\n value: 'deepseek',\n label: 'DeepSeek',\n kind: 'openai-compatible',\n baseUrl: 'https://api.deepseek.com',\n requiresBaseUrl: false,\n fast: 'deepseek-chat',\n smart: 'deepseek-chat',\n keyHint: 'sk-…',\n visionToolResults: false,\n },\n {\n value: 'mistral',\n label: 'Mistral',\n kind: 'openai-compatible',\n baseUrl: 'https://api.mistral.ai/v1',\n requiresBaseUrl: false,\n fast: 'mistral-small-latest',\n smart: 'mistral-large-latest',\n keyHint: 'Your Mistral API key',\n visionToolResults: false,\n },\n {\n value: 'custom',\n label: 'Custom (OpenAI-compatible)',\n kind: 'openai-compatible',\n baseUrl: null,\n requiresBaseUrl: true,\n fast: '',\n smart: '',\n keyHint: 'Your provider API key',\n modelHint:\n 'Use a public HTTPS OpenAI-compatible endpoint (for example Together, Fireworks, Perplexity, or hosted vLLM) and set explicit model ids.',\n visionToolResults: false,\n },\n]\n\nconst SPEC_BY_VALUE = Object.fromEntries(AI_PROVIDER_SPECS.map((s) => [s.value, s])) as Record<\n AiProvider,\n ProviderSpec\n>\n\nexport function isAiProvider(value: unknown): value is AiProvider {\n return typeof value === 'string' && Object.hasOwn(SPEC_BY_VALUE, value)\n}\n\nexport function providerSpec(provider: AiProvider): ProviderSpec {\n return SPEC_BY_VALUE[provider]\n}\n\nfunction withoutTrailingSlashes(value: string): string {\n let end = value.length\n while (end > 0 && value.charCodeAt(end - 1) === 47) end -= 1\n return value.slice(0, end)\n}\n\n/**\n * Validate and canonicalize a persisted AI endpoint override. Runtime requests\n * repeat the public-DNS check immediately before opening each socket.\n */\nexport async function validateAiBaseUrl(\n provider: AiProvider,\n rawBaseUrl: string | null | undefined,\n): Promise<string | null> {\n const spec = providerSpec(provider)\n const raw = rawBaseUrl?.trim() ?? ''\n if (spec.kind !== 'openai-compatible') {\n if (raw) throw new Error(`${spec.label} does not support a custom base URL.`)\n return null\n }\n if (!raw) {\n if (spec.requiresBaseUrl) throw new Error('A public HTTPS base URL is required.')\n return null\n }\n\n let submitted: URL\n try {\n submitted = new URL(raw)\n } catch {\n throw new Error('The AI base URL is not valid.')\n }\n if (submitted.search || submitted.hash) {\n throw new Error('The AI base URL must not include a query string or fragment.')\n }\n const validated = validateOutboundRequestConfiguration(submitted).url\n await resolvePublicHost(validated.hostname)\n\n const path = withoutTrailingSlashes(validated.pathname)\n validated.pathname = path || '/'\n const canonical = withoutTrailingSlashes(validated.href)\n return canonical || validated.origin\n}\n\nasync function readBoundedRequestBody(request: Request): Promise<Uint8Array | undefined> {\n if (!request.body) return undefined\n const declaredLength = Number(request.headers.get('content-length'))\n if (Number.isFinite(declaredLength) && declaredLength > MAX_AI_REQUEST_BYTES) {\n throw new Error(`AI request body exceeded ${MAX_AI_REQUEST_BYTES} bytes.`)\n }\n\n const reader = request.body.getReader()\n const chunks: Uint8Array[] = []\n let total = 0\n try {\n for (;;) {\n if (request.signal.aborted) throw request.signal.reason\n const { done, value } = await reader.read()\n if (done) break\n total += value.byteLength\n if (total > MAX_AI_REQUEST_BYTES) {\n await reader.cancel()\n throw new Error(`AI request body exceeded ${MAX_AI_REQUEST_BYTES} bytes.`)\n }\n chunks.push(value)\n }\n } finally {\n reader.releaseLock()\n }\n\n const body = new Uint8Array(total)\n let offset = 0\n for (const chunk of chunks) {\n body.set(chunk, offset)\n offset += chunk.byteLength\n }\n return body\n}\n\n/** Socket-pinned transport for tenant-configurable OpenAI-compatible endpoints. */\nexport const secureAiFetch: typeof globalThis.fetch = async (input, init) => {\n const request = new Request(input, init)\n const method = request.method.toUpperCase()\n if (!['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE'].includes(method)) {\n throw new Error(`AI request method ${method} is not supported.`)\n }\n const body = await readBoundedRequestBody(request)\n return secureFetch(request.url, {\n method: method as 'GET' | 'HEAD' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',\n headers: request.headers,\n body,\n timeoutMs: AI_REQUEST_TIMEOUT_MS,\n maxRequestBytes: MAX_AI_REQUEST_BYTES,\n maxResponseBytes: MAX_AI_RESPONSE_BYTES,\n maxRedirects: 2,\n signal: request.signal,\n })\n}\n\nexport function defaultModel(provider: AiProvider, tier: ModelTier): string {\n const spec = SPEC_BY_VALUE[provider] ?? SPEC_BY_VALUE.anthropic\n return tier === 'smart' ? spec.smart : spec.fast\n}\n\n/**\n * Whether the configured provider accepts IMAGE content in a tool result — the\n * capability that lets a tool hand rendered PDF pages back to the model for\n * vision reading. Currently Anthropic only; other providers' tool/function\n * results are text/JSON only, so exposing such a tool to them would break the\n * agent turn. Used to gate the assistant's `view_document_pages` tool.\n */\nexport function providerSupportsImageToolResults(config: AiConfig | null | undefined): boolean {\n if (!config) return false\n return SPEC_BY_VALUE[config.provider]?.visionToolResults ?? false\n}\n\nexport function isAiConfigured(config: AiConfig | null | undefined): config is AiConfig {\n if (!config || !config.apiKey) return false\n const spec = SPEC_BY_VALUE[config.provider]\n if (!spec) return false\n if (spec.requiresBaseUrl && !config.baseUrl?.trim()) return false\n return true\n}\n\n/** Resolve a language model from a tenant's config, or null when not configured. */\nexport function getModel(\n config: AiConfig | null | undefined,\n tier: ModelTier = 'fast',\n): LanguageModel | null {\n if (!isAiConfigured(config)) return null\n const spec = SPEC_BY_VALUE[config.provider]\n const modelId =\n (tier === 'smart' ? config.modelSmart : config.modelFast) || defaultModel(config.provider, tier)\n // `custom` has no default model — without an explicit one, AI stays disabled.\n if (!modelId) return null\n\n switch (spec.kind) {\n case 'anthropic':\n return createAnthropic({ apiKey: config.apiKey })(modelId)\n case 'openai':\n return createOpenAI({ apiKey: config.apiKey })(modelId)\n case 'google':\n return createGoogleGenerativeAI({ apiKey: config.apiKey })(modelId)\n case 'openai-compatible': {\n const baseURL = config.baseUrl?.trim() || spec.baseUrl\n if (!baseURL) return null\n return createOpenAICompatible({\n name: spec.value,\n apiKey: config.apiKey,\n baseURL,\n fetch: secureAiFetch,\n })(modelId)\n }\n }\n}\n\nexport class AIDisabledError extends Error {\n override readonly name = 'AIDisabledError'\n constructor() {\n super('No AI provider is available for this request.')\n }\n}\n\n/** Live test of a config — sends a tiny prompt and reports success/failure. */\nexport async function pingModel(\n config: AiConfig | null | undefined,\n): Promise<{ ok: boolean; message: string }> {\n const model = getModel(config, 'fast')\n if (!model) {\n return {\n ok: false,\n message: 'Not configured yet — check the provider, API key, model and base URL.',\n }\n }\n try {\n const { text } = await generateText({ model, prompt: 'Reply with the single word: ok' })\n return { ok: true, message: `Connected — the model replied “${text.trim().slice(0, 24)}”.` }\n } catch (e) {\n return { ok: false, message: e instanceof Error ? e.message.slice(0, 180) : 'Request failed.' }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,kFAAkF;AAElF,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAA;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC7C,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAA;AAClE,OAAO,EACL,iBAAiB,EACjB,WAAW,EACX,oCAAoC,GACrC,MAAM,mDAAmD,CAAA;AAC1D,OAAO,EAAE,YAAY,EAAsB,MAAM,IAAI,CAAA;AAErD,MAAM,oBAAoB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAA;AAC7C,MAAM,qBAAqB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAA;AAC9C;;;;;;;;;;;;GAYG;AACH,MAAM,qBAAqB,GAAG,OAAO,CAAA;AAsErC;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAmB;IAC/C;QACE,KAAK,EAAE,WAAW;QAClB,KAAK,EAAE,oBAAoB;QAC3B,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,IAAI;QACb,eAAe,EAAE,KAAK;QACtB,IAAI,EAAE,2BAA2B;QACjC,KAAK,EAAE,mBAAmB;QAC1B,OAAO,EAAE,UAAU;QACnB,iBAAiB,EAAE,IAAI;KACxB;IACD;QACE,KAAK,EAAE,QAAQ;QACf,KAAK,EAAE,cAAc;QACrB,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,IAAI;QACb,eAAe,EAAE,KAAK;QACtB,IAAI,EAAE,aAAa;QACnB,KAAK,EAAE,QAAQ;QACf,OAAO,EAAE,MAAM;QACf,iBAAiB,EAAE,KAAK;KACzB;IACD;QACE,KAAK,EAAE,QAAQ;QACf,KAAK,EAAE,iBAAiB;QACxB,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,IAAI;QACb,eAAe,EAAE,KAAK;QACtB,IAAI,EAAE,kBAAkB;QACxB,KAAK,EAAE,gBAAgB;QACvB,OAAO,EAAE,OAAO;QAChB,iBAAiB,EAAE,KAAK;KACzB;IACD;QACE,KAAK,EAAE,YAAY;QACnB,KAAK,EAAE,iCAAiC;QACxC,IAAI,EAAE,mBAAmB;QACzB,OAAO,EAAE,8BAA8B;QACvC,eAAe,EAAE,KAAK;QACtB,IAAI,EAAE,4BAA4B;QAClC,KAAK,EAAE,6BAA6B;QACpC,OAAO,EAAE,SAAS;QAClB,SAAS,EAAE,4EAA4E;QACvF,2EAA2E;QAC3E,yEAAyE;QACzE,iBAAiB,EAAE,KAAK;KACzB;IACD;QACE,KAAK,EAAE,MAAM;QACb,KAAK,EAAE,uBAAuB;QAC9B,IAAI,EAAE,mBAAmB;QACzB,OAAO,EAAE,gCAAgC;QACzC,eAAe,EAAE,KAAK;QACtB,IAAI,EAAE,sBAAsB;QAC5B,KAAK,EAAE,yBAAyB;QAChC,OAAO,EAAE,OAAO;QAChB,iBAAiB,EAAE,KAAK;KACzB;IACD;QACE,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,YAAY;QACnB,IAAI,EAAE,mBAAmB;QACzB,OAAO,EAAE,qBAAqB;QAC9B,eAAe,EAAE,KAAK;QACtB,IAAI,EAAE,aAAa;QACnB,KAAK,EAAE,oBAAoB;QAC3B,OAAO,EAAE,OAAO;QAChB,iBAAiB,EAAE,KAAK;KACzB;IACD;QACE,KAAK,EAAE,UAAU;QACjB,KAAK,EAAE,UAAU;QACjB,IAAI,EAAE,mBAAmB;QACzB,OAAO,EAAE,0BAA0B;QACnC,eAAe,EAAE,KAAK;QACtB,IAAI,EAAE,eAAe;QACrB,KAAK,EAAE,eAAe;QACtB,OAAO,EAAE,MAAM;QACf,iBAAiB,EAAE,KAAK;KACzB;IACD;QACE,KAAK,EAAE,SAAS;QAChB,KAAK,EAAE,SAAS;QAChB,IAAI,EAAE,mBAAmB;QACzB,OAAO,EAAE,2BAA2B;QACpC,eAAe,EAAE,KAAK;QACtB,IAAI,EAAE,sBAAsB;QAC5B,KAAK,EAAE,sBAAsB;QAC7B,OAAO,EAAE,sBAAsB;QAC/B,iBAAiB,EAAE,KAAK;KACzB;IACD;QACE,KAAK,EAAE,QAAQ;QACf,KAAK,EAAE,4BAA4B;QACnC,IAAI,EAAE,mBAAmB;QACzB,OAAO,EAAE,IAAI;QACb,eAAe,EAAE,IAAI;QACrB,IAAI,EAAE,EAAE;QACR,KAAK,EAAE,EAAE;QACT,OAAO,EAAE,uBAAuB;QAChC,SAAS,EACP,yIAAyI;QAC3I,iBAAiB,EAAE,KAAK;KACzB;CACF,CAAA;AAED,MAAM,aAAa,GAAG,MAAM,CAAC,WAAW,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAGlF,CAAA;AAED,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,CAAC,aAAa,EAAE,KAAK,CAAC,CAAA;AACzE,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,QAAoB;IAC/C,OAAO,aAAa,CAAC,QAAQ,CAAC,CAAA;AAChC,CAAC;AAED,SAAS,sBAAsB,CAAC,KAAa;IAC3C,IAAI,GAAG,GAAG,KAAK,CAAC,MAAM,CAAA;IACtB,OAAO,GAAG,GAAG,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,EAAE;QAAE,GAAG,IAAI,CAAC,CAAA;IAC5D,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;AAC5B,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,QAAoB,EACpB,UAAqC;IAErC,MAAM,IAAI,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAA;IACnC,MAAM,GAAG,GAAG,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;IACpC,IAAI,IAAI,CAAC,IAAI,KAAK,mBAAmB,EAAE,CAAC;QACtC,IAAI,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,CAAC,KAAK,sCAAsC,CAAC,CAAA;QAC7E,OAAO,IAAI,CAAA;IACb,CAAC;IACD,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,IAAI,IAAI,CAAC,eAAe;YAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAA;QACjF,OAAO,IAAI,CAAA;IACb,CAAC;IAED,IAAI,SAAc,CAAA;IAClB,IAAI,CAAC;QACH,SAAS,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAA;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAA;IAClD,CAAC;IACD,IAAI,SAAS,CAAC,MAAM,IAAI,SAAS,CAAC,IAAI,EAAE,CAAC;QACvC,MAAM,IAAI,KAAK,CAAC,8DAA8D,CAAC,CAAA;IACjF,CAAC;IACD,MAAM,SAAS,GAAG,oCAAoC,CAAC,SAAS,CAAC,CAAC,GAAG,CAAA;IACrE,MAAM,iBAAiB,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAA;IAE3C,MAAM,IAAI,GAAG,sBAAsB,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAA;IACvD,SAAS,CAAC,QAAQ,GAAG,IAAI,IAAI,GAAG,CAAA;IAChC,MAAM,SAAS,GAAG,sBAAsB,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;IACxD,OAAO,SAAS,IAAI,SAAS,CAAC,MAAM,CAAA;AACtC,CAAC;AAED,KAAK,UAAU,sBAAsB,CAAC,OAAgB;IACpD,IAAI,CAAC,OAAO,CAAC,IAAI;QAAE,OAAO,SAAS,CAAA;IACnC,MAAM,cAAc,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAA;IACpE,IAAI,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC,IAAI,cAAc,GAAG,oBAAoB,EAAE,CAAC;QAC7E,MAAM,IAAI,KAAK,CAAC,4BAA4B,oBAAoB,SAAS,CAAC,CAAA;IAC5E,CAAC;IAED,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,CAAA;IACvC,MAAM,MAAM,GAAiB,EAAE,CAAA;IAC/B,IAAI,KAAK,GAAG,CAAC,CAAA;IACb,IAAI,CAAC;QACH,SAAS,CAAC;YACR,IAAI,OAAO,CAAC,MAAM,CAAC,OAAO;gBAAE,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,CAAA;YACvD,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAA;YAC3C,IAAI,IAAI;gBAAE,MAAK;YACf,KAAK,IAAI,KAAK,CAAC,UAAU,CAAA;YACzB,IAAI,KAAK,GAAG,oBAAoB,EAAE,CAAC;gBACjC,MAAM,MAAM,CAAC,MAAM,EAAE,CAAA;gBACrB,MAAM,IAAI,KAAK,CAAC,4BAA4B,oBAAoB,SAAS,CAAC,CAAA;YAC5E,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACpB,CAAC;IACH,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,WAAW,EAAE,CAAA;IACtB,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,CAAA;IAClC,IAAI,MAAM,GAAG,CAAC,CAAA;IACd,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;QACvB,MAAM,IAAI,KAAK,CAAC,UAAU,CAAA;IAC5B,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED,mFAAmF;AACnF,MAAM,CAAC,MAAM,aAAa,GAA4B,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;IAC1E,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;IACxC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,CAAA;IAC3C,IAAI,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACxE,MAAM,IAAI,KAAK,CAAC,qBAAqB,MAAM,oBAAoB,CAAC,CAAA;IAClE,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,sBAAsB,CAAC,OAAO,CAAC,CAAA;IAClD,OAAO,WAAW,CAAC,OAAO,CAAC,GAAG,EAAE;QAC9B,MAAM,EAAE,MAA8D;QACtE,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,IAAI;QACJ,SAAS,EAAE,qBAAqB;QAChC,eAAe,EAAE,oBAAoB;QACrC,gBAAgB,EAAE,qBAAqB;QACvC,YAAY,EAAE,CAAC;QACf,MAAM,EAAE,OAAO,CAAC,MAAM;KACvB,CAAC,CAAA;AACJ,CAAC,CAAA;AAED,MAAM,UAAU,YAAY,CAAC,QAAoB,EAAE,IAAe;IAChE,MAAM,IAAI,GAAG,aAAa,CAAC,QAAQ,CAAC,IAAI,aAAa,CAAC,SAAS,CAAA;IAC/D,OAAO,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAA;AAClD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gCAAgC,CAAC,MAAmC;IAClF,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IACzB,OAAO,aAAa,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,iBAAiB,IAAI,KAAK,CAAA;AACnE,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,MAAmC;IAChE,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IAC3C,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;IAC3C,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAA;IACvB,IAAI,IAAI,CAAC,eAAe,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE;QAAE,OAAO,KAAK,CAAA;IACjE,OAAO,IAAI,CAAA;AACb,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,QAAQ,CACtB,MAAmC,EACnC,OAAkB,MAAM;IAExB,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAA;IACxC,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;IAC3C,MAAM,OAAO,GACX,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,YAAY,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;IAClG,8EAA8E;IAC9E,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAA;IAEzB,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,KAAK,WAAW;YACd,OAAO,eAAe,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAA;QAC5D,KAAK,QAAQ;YACX,OAAO,YAAY,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAA;QACzD,KAAK,QAAQ;YACX,OAAO,wBAAwB,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAA;QACrE,KAAK,mBAAmB,CAAC,CAAC,CAAC;YACzB,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,OAAO,CAAA;YACtD,IAAI,CAAC,OAAO;gBAAE,OAAO,IAAI,CAAA;YACzB,OAAO,sBAAsB,CAAC;gBAC5B,IAAI,EAAE,IAAI,CAAC,KAAK;gBAChB,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,OAAO;gBACP,KAAK,EAAE,aAAa;aACrB,CAAC,CAAC,OAAO,CAAC,CAAA;QACb,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,OAAO,eAAgB,SAAQ,KAAK;IACtB,IAAI,GAAG,iBAAiB,CAAA;IAC1C;QACE,KAAK,CAAC,+CAA+C,CAAC,CAAA;IACxD,CAAC;CACF;AAED,+EAA+E;AAC/E,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,MAAmC;IAEnC,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACtC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO;YACL,EAAE,EAAE,KAAK;YACT,OAAO,EAAE,uEAAuE;SACjF,CAAA;IACH,CAAC;IACD,IAAI,CAAC;QACH,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,YAAY,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,gCAAgC,EAAE,CAAC,CAAA;QACxF,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,kCAAkC,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,CAAA;IAC9F,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,iBAAiB,EAAE,CAAA;IACjG,CAAC;AACH,CAAC","sourcesContent":["// Config-driven AI client. Provider + API key + models (+ base URL) are passed\n// in per call (resolved from per-tenant settings), NOT read from the environment.\n\nimport { createAnthropic } from '@ai-sdk/anthropic'\nimport { createGoogleGenerativeAI } from '@ai-sdk/google'\nimport { createOpenAI } from '@ai-sdk/openai'\nimport { createOpenAICompatible } from '@ai-sdk/openai-compatible'\nimport {\n resolvePublicHost,\n secureFetch,\n validateOutboundRequestConfiguration,\n} from '@braedonsaunders/appkit-egress-proxy/secure-fetch'\nimport { generateText, type LanguageModel } from 'ai'\n\nconst MAX_AI_REQUEST_BYTES = 16 * 1024 * 1024\nconst MAX_AI_RESPONSE_BYTES = 16 * 1024 * 1024\n/**\n * How long one model call may take before the transport gives up on it.\n *\n * This is not a budget — the caller's own loop governs how much work an agent\n * may do — it is only the point past which a socket is assumed dead. Two\n * minutes turned out to be inside the normal range for a reasoning model\n * answering with tool calls: measured runs on several hosted models spent\n * longer than that on a single step, and each one died on this timeout having\n * produced nothing, billed nothing, and lost every step that came before it.\n *\n * Ten minutes is comfortably past the slowest observed step while still\n * catching a connection that has genuinely gone away.\n */\nconst AI_REQUEST_TIMEOUT_MS = 600_000\n\nexport type AiProvider =\n | 'anthropic'\n | 'openai'\n | 'google'\n | 'openrouter'\n | 'groq'\n | 'xai'\n | 'deepseek'\n | 'mistral'\n | 'custom'\n\nexport type ModelTier = 'fast' | 'smart'\n\n/**\n * Platform-wide AI policy governing per-tenant overrides (mirrors the email/SMS\n * policy modes). 'disabled' is a global kill switch; 'global_only' forces the\n * platform provider for every tenant; 'tenant_optional' lets each tenant use its\n * own provider and falls back to the platform default.\n */\nexport type AiPolicyMode = 'tenant_optional' | 'global_only' | 'disabled'\n\nexport type AiConfig = {\n provider: AiProvider\n apiKey: string\n modelFast?: string | null\n modelSmart?: string | null\n /**\n * Endpoint for OpenAI-compatible providers. Required for `custom`; for the\n * named compatible providers it is an optional override of the built-in URL.\n */\n baseUrl?: string | null\n /**\n * Organization (tenant) identity for prompt grounding, so generated content\n * uses the real org name instead of a placeholder. Populated by\n * `getTenantAiConfig`; content-generation paths inject it into the system\n * prompt, analysis/vision paths ignore it.\n */\n org?: { name: string } | null\n}\n\n// How a provider's language model is constructed. The OpenAI-compatible kind\n// covers OpenRouter, Groq, xAI, DeepSeek, Mistral and any user `custom` endpoint.\ntype ProviderKind = 'anthropic' | 'openai' | 'google' | 'openai-compatible'\n\nexport type ProviderSpec = {\n value: AiProvider\n label: string\n kind: ProviderKind\n /** Built-in endpoint for a named OpenAI-compatible provider (null otherwise). */\n baseUrl: string | null\n /** True when the tenant MUST supply their own base URL (i.e. `custom`). */\n requiresBaseUrl: boolean\n /** Default fast/smart model ids (placeholders + fallbacks). Empty for `custom`. */\n fast: string\n smart: string\n /** Placeholder shown in the API-key field. */\n keyHint: string\n /** Optional note about the model-id format for this provider. */\n modelHint?: string\n /**\n * True when this provider's API accepts IMAGE content inside a tool result\n * (Anthropic does; OpenAI's and Google's function/tool results are text/JSON\n * only). Gates vision tools that return rendered page images to the model —\n * see `providerSupportsImageToolResults`.\n */\n visionToolResults: boolean\n}\n\n/**\n * Provider catalogue — single source of truth for the settings UI, the model\n * factory and config validation. Add a provider here and it lights up everywhere.\n */\nexport const AI_PROVIDER_SPECS: ProviderSpec[] = [\n {\n value: 'anthropic',\n label: 'Anthropic — Claude',\n kind: 'anthropic',\n baseUrl: null,\n requiresBaseUrl: false,\n fast: 'claude-haiku-4-5-20251001',\n smart: 'claude-sonnet-4-6',\n keyHint: 'sk-ant-…',\n visionToolResults: true,\n },\n {\n value: 'openai',\n label: 'OpenAI — GPT',\n kind: 'openai',\n baseUrl: null,\n requiresBaseUrl: false,\n fast: 'gpt-4o-mini',\n smart: 'gpt-4o',\n keyHint: 'sk-…',\n visionToolResults: false,\n },\n {\n value: 'google',\n label: 'Google — Gemini',\n kind: 'google',\n baseUrl: null,\n requiresBaseUrl: false,\n fast: 'gemini-2.5-flash',\n smart: 'gemini-2.5-pro',\n keyHint: 'AIza…',\n visionToolResults: false,\n },\n {\n value: 'openrouter',\n label: 'OpenRouter — any model, one key',\n kind: 'openai-compatible',\n baseUrl: 'https://openrouter.ai/api/v1',\n requiresBaseUrl: false,\n fast: 'anthropic/claude-3.5-haiku',\n smart: 'anthropic/claude-3.5-sonnet',\n keyHint: 'sk-or-…',\n modelHint: 'Use vendor/model slugs, e.g. anthropic/claude-3.5-sonnet or openai/gpt-4o.',\n // OpenRouter proxies many vendors over an OpenAI-compatible surface; image\n // tool-results aren't reliably supported, so keep vision tools off here.\n visionToolResults: false,\n },\n {\n value: 'groq',\n label: 'Groq — fast inference',\n kind: 'openai-compatible',\n baseUrl: 'https://api.groq.com/openai/v1',\n requiresBaseUrl: false,\n fast: 'llama-3.1-8b-instant',\n smart: 'llama-3.3-70b-versatile',\n keyHint: 'gsk_…',\n visionToolResults: false,\n },\n {\n value: 'xai',\n label: 'xAI — Grok',\n kind: 'openai-compatible',\n baseUrl: 'https://api.x.ai/v1',\n requiresBaseUrl: false,\n fast: 'grok-2-1212',\n smart: 'grok-2-vision-1212',\n keyHint: 'xai-…',\n visionToolResults: false,\n },\n {\n value: 'deepseek',\n label: 'DeepSeek',\n kind: 'openai-compatible',\n baseUrl: 'https://api.deepseek.com',\n requiresBaseUrl: false,\n fast: 'deepseek-chat',\n smart: 'deepseek-chat',\n keyHint: 'sk-…',\n visionToolResults: false,\n },\n {\n value: 'mistral',\n label: 'Mistral',\n kind: 'openai-compatible',\n baseUrl: 'https://api.mistral.ai/v1',\n requiresBaseUrl: false,\n fast: 'mistral-small-latest',\n smart: 'mistral-large-latest',\n keyHint: 'Your Mistral API key',\n visionToolResults: false,\n },\n {\n value: 'custom',\n label: 'Custom (OpenAI-compatible)',\n kind: 'openai-compatible',\n baseUrl: null,\n requiresBaseUrl: true,\n fast: '',\n smart: '',\n keyHint: 'Your provider API key',\n modelHint:\n 'Use a public HTTPS OpenAI-compatible endpoint (for example Together, Fireworks, Perplexity, or hosted vLLM) and set explicit model ids.',\n visionToolResults: false,\n },\n]\n\nconst SPEC_BY_VALUE = Object.fromEntries(AI_PROVIDER_SPECS.map((s) => [s.value, s])) as Record<\n AiProvider,\n ProviderSpec\n>\n\nexport function isAiProvider(value: unknown): value is AiProvider {\n return typeof value === 'string' && Object.hasOwn(SPEC_BY_VALUE, value)\n}\n\nexport function providerSpec(provider: AiProvider): ProviderSpec {\n return SPEC_BY_VALUE[provider]\n}\n\nfunction withoutTrailingSlashes(value: string): string {\n let end = value.length\n while (end > 0 && value.charCodeAt(end - 1) === 47) end -= 1\n return value.slice(0, end)\n}\n\n/**\n * Validate and canonicalize a persisted AI endpoint override. Runtime requests\n * repeat the public-DNS check immediately before opening each socket.\n */\nexport async function validateAiBaseUrl(\n provider: AiProvider,\n rawBaseUrl: string | null | undefined,\n): Promise<string | null> {\n const spec = providerSpec(provider)\n const raw = rawBaseUrl?.trim() ?? ''\n if (spec.kind !== 'openai-compatible') {\n if (raw) throw new Error(`${spec.label} does not support a custom base URL.`)\n return null\n }\n if (!raw) {\n if (spec.requiresBaseUrl) throw new Error('A public HTTPS base URL is required.')\n return null\n }\n\n let submitted: URL\n try {\n submitted = new URL(raw)\n } catch {\n throw new Error('The AI base URL is not valid.')\n }\n if (submitted.search || submitted.hash) {\n throw new Error('The AI base URL must not include a query string or fragment.')\n }\n const validated = validateOutboundRequestConfiguration(submitted).url\n await resolvePublicHost(validated.hostname)\n\n const path = withoutTrailingSlashes(validated.pathname)\n validated.pathname = path || '/'\n const canonical = withoutTrailingSlashes(validated.href)\n return canonical || validated.origin\n}\n\nasync function readBoundedRequestBody(request: Request): Promise<Uint8Array | undefined> {\n if (!request.body) return undefined\n const declaredLength = Number(request.headers.get('content-length'))\n if (Number.isFinite(declaredLength) && declaredLength > MAX_AI_REQUEST_BYTES) {\n throw new Error(`AI request body exceeded ${MAX_AI_REQUEST_BYTES} bytes.`)\n }\n\n const reader = request.body.getReader()\n const chunks: Uint8Array[] = []\n let total = 0\n try {\n for (;;) {\n if (request.signal.aborted) throw request.signal.reason\n const { done, value } = await reader.read()\n if (done) break\n total += value.byteLength\n if (total > MAX_AI_REQUEST_BYTES) {\n await reader.cancel()\n throw new Error(`AI request body exceeded ${MAX_AI_REQUEST_BYTES} bytes.`)\n }\n chunks.push(value)\n }\n } finally {\n reader.releaseLock()\n }\n\n const body = new Uint8Array(total)\n let offset = 0\n for (const chunk of chunks) {\n body.set(chunk, offset)\n offset += chunk.byteLength\n }\n return body\n}\n\n/** Socket-pinned transport for tenant-configurable OpenAI-compatible endpoints. */\nexport const secureAiFetch: typeof globalThis.fetch = async (input, init) => {\n const request = new Request(input, init)\n const method = request.method.toUpperCase()\n if (!['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE'].includes(method)) {\n throw new Error(`AI request method ${method} is not supported.`)\n }\n const body = await readBoundedRequestBody(request)\n return secureFetch(request.url, {\n method: method as 'GET' | 'HEAD' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',\n headers: request.headers,\n body,\n timeoutMs: AI_REQUEST_TIMEOUT_MS,\n maxRequestBytes: MAX_AI_REQUEST_BYTES,\n maxResponseBytes: MAX_AI_RESPONSE_BYTES,\n maxRedirects: 2,\n signal: request.signal,\n })\n}\n\nexport function defaultModel(provider: AiProvider, tier: ModelTier): string {\n const spec = SPEC_BY_VALUE[provider] ?? SPEC_BY_VALUE.anthropic\n return tier === 'smart' ? spec.smart : spec.fast\n}\n\n/**\n * Whether the configured provider accepts IMAGE content in a tool result — the\n * capability that lets a tool hand rendered PDF pages back to the model for\n * vision reading. Currently Anthropic only; other providers' tool/function\n * results are text/JSON only, so exposing such a tool to them would break the\n * agent turn. Used to gate the assistant's `view_document_pages` tool.\n */\nexport function providerSupportsImageToolResults(config: AiConfig | null | undefined): boolean {\n if (!config) return false\n return SPEC_BY_VALUE[config.provider]?.visionToolResults ?? false\n}\n\nexport function isAiConfigured(config: AiConfig | null | undefined): config is AiConfig {\n if (!config || !config.apiKey) return false\n const spec = SPEC_BY_VALUE[config.provider]\n if (!spec) return false\n if (spec.requiresBaseUrl && !config.baseUrl?.trim()) return false\n return true\n}\n\n/** Resolve a language model from a tenant's config, or null when not configured. */\nexport function getModel(\n config: AiConfig | null | undefined,\n tier: ModelTier = 'fast',\n): LanguageModel | null {\n if (!isAiConfigured(config)) return null\n const spec = SPEC_BY_VALUE[config.provider]\n const modelId =\n (tier === 'smart' ? config.modelSmart : config.modelFast) || defaultModel(config.provider, tier)\n // `custom` has no default model — without an explicit one, AI stays disabled.\n if (!modelId) return null\n\n switch (spec.kind) {\n case 'anthropic':\n return createAnthropic({ apiKey: config.apiKey })(modelId)\n case 'openai':\n return createOpenAI({ apiKey: config.apiKey })(modelId)\n case 'google':\n return createGoogleGenerativeAI({ apiKey: config.apiKey })(modelId)\n case 'openai-compatible': {\n const baseURL = config.baseUrl?.trim() || spec.baseUrl\n if (!baseURL) return null\n return createOpenAICompatible({\n name: spec.value,\n apiKey: config.apiKey,\n baseURL,\n fetch: secureAiFetch,\n })(modelId)\n }\n }\n}\n\nexport class AIDisabledError extends Error {\n override readonly name = 'AIDisabledError'\n constructor() {\n super('No AI provider is available for this request.')\n }\n}\n\n/** Live test of a config — sends a tiny prompt and reports success/failure. */\nexport async function pingModel(\n config: AiConfig | null | undefined,\n): Promise<{ ok: boolean; message: string }> {\n const model = getModel(config, 'fast')\n if (!model) {\n return {\n ok: false,\n message: 'Not configured yet — check the provider, API key, model and base URL.',\n }\n }\n try {\n const { text } = await generateText({ model, prompt: 'Reply with the single word: ok' })\n return { ok: true, message: `Connected — the model replied “${text.trim().slice(0, 24)}”.` }\n } catch (e) {\n return { ok: false, message: e instanceof Error ? e.message.slice(0, 180) : 'Request failed.' }\n }\n}\n"]}
|
package/models.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// settings UI can offer dynamic dropdowns instead of free-text model ids.
|
|
3
3
|
// Runs server-side only (the API key never leaves the server).
|
|
4
4
|
import { providerSpec, validateAiBaseUrl } from './client.js';
|
|
5
|
-
import { secureFetch } from '@braedonsaunders/appkit-
|
|
5
|
+
import { secureFetch } from '@braedonsaunders/appkit-egress-proxy/secure-fetch';
|
|
6
6
|
function trimSlash(u) {
|
|
7
7
|
let end = u.length;
|
|
8
8
|
while (end > 0 && u.charCodeAt(end - 1) === 47)
|
package/models.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"models.js","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,0EAA0E;AAC1E,+DAA+D;AAE/D,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAiB,MAAM,UAAU,CAAA;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"models.js","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"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","sourcesContent":["// Model discovery — queries each provider's \"list models\" endpoint so the\n// settings UI can offer dynamic dropdowns instead of free-text model ids.\n// Runs server-side only (the API key never leaves the server).\n\nimport { providerSpec, validateAiBaseUrl, type AiConfig } from './client'\nimport { secureFetch } from '@braedonsaunders/appkit-egress-proxy/secure-fetch'\n\nexport type ModelListItem = { id: string; label?: string }\n\nfunction trimSlash(u: string): string {\n let end = u.length\n while (end > 0 && u.charCodeAt(end - 1) === 47) end -= 1\n return u.slice(0, end)\n}\n\nfunction dedupeSort(items: ModelListItem[]): ModelListItem[] {\n const map = new Map<string, ModelListItem>()\n for (const it of items) {\n if (it.id && !map.has(it.id)) map.set(it.id, it)\n }\n return [...map.values()].sort((a, b) => a.id.localeCompare(b.id))\n}\n\nasync function fetchJson(url: string, headers: Record<string, string>): Promise<unknown> {\n // Custom OpenAI-compatible endpoints are tenant-configured. Use the shared\n // HTTPS/public-DNS transport so redirects, DNS rebinding, private networks,\n // response size, and timeouts are enforced at the socket boundary.\n const res = await secureFetch(url, {\n headers,\n timeoutMs: 15_000,\n maxResponseBytes: 2 * 1024 * 1024,\n })\n if (!res.ok) {\n const body = await res.text().catch(() => '')\n throw new Error(`${res.status} ${res.statusText}${body ? ` — ${body.slice(0, 160)}` : ''}`)\n }\n return res.json()\n}\n\nfunction asArray(value: unknown): Record<string, unknown>[] {\n return Array.isArray(value) ? (value as Record<string, unknown>[]) : []\n}\n\nfunction str(value: unknown): string {\n return typeof value === 'string' ? value : value == null ? '' : String(value)\n}\n\n/** OpenAI returns every model (embeddings, tts, image…) — keep chat-capable text ones. */\nfunction isOpenAiChatModel(id: string): boolean {\n if (\n /embedding|whisper|tts|audio|dall-e|image|moderation|realtime|transcribe|search|similarity|edit|babbage|davinci/i.test(\n id,\n )\n ) {\n return false\n }\n return /^(gpt-|o1|o3|o4|chatgpt)/i.test(id)\n}\n\n/**\n * Fetch the available model ids for a provider config. Throws on HTTP / auth\n * errors so the caller can surface the provider's message.\n */\nexport async function listModels(config: AiConfig): Promise<ModelListItem[]> {\n const spec = providerSpec(config.provider)\n const key = config.apiKey\n\n switch (spec.kind) {\n case 'anthropic': {\n const json = await fetchJson('https://api.anthropic.com/v1/models?limit=1000', {\n 'x-api-key': key,\n 'anthropic-version': '2023-06-01',\n })\n const data = asArray((json as Record<string, unknown>)?.data)\n return dedupeSort(\n data.map((m) => ({\n id: str(m.id),\n label: m.display_name ? str(m.display_name) : undefined,\n })),\n )\n }\n case 'openai': {\n const json = await fetchJson('https://api.openai.com/v1/models', {\n Authorization: `Bearer ${key}`,\n })\n const data = asArray((json as Record<string, unknown>)?.data)\n return dedupeSort(data.map((m) => ({ id: str(m.id) })).filter((m) => isOpenAiChatModel(m.id)))\n }\n case 'google': {\n const json = await fetchJson(\n `https://generativelanguage.googleapis.com/v1beta/models?key=${encodeURIComponent(key)}&pageSize=1000`,\n {},\n )\n const models = asArray((json as Record<string, unknown>)?.models)\n return dedupeSort(\n models\n .filter((m) => {\n const methods = m.supportedGenerationMethods\n return Array.isArray(methods) && methods.includes('generateContent')\n })\n .map((m) => ({\n id: str(m.name).replace(/^models\\//, ''),\n label: m.displayName ? str(m.displayName) : undefined,\n })),\n )\n }\n case 'openai-compatible': {\n const baseURL = (await validateAiBaseUrl(config.provider, config.baseUrl)) || spec.baseUrl\n if (!baseURL) throw new Error('A base URL is required to list models.')\n const json = await fetchJson(`${trimSlash(baseURL)}/models`, {\n Authorization: `Bearer ${key}`,\n })\n const root = json as Record<string, unknown>\n const data = asArray(root?.data).length ? asArray(root?.data) : asArray(root?.models)\n return dedupeSort(\n data.map((m) => {\n const id = str(m.id || m.name)\n const name = str(m.name)\n return { id, label: name && name !== id ? name : undefined }\n }),\n )\n }\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@braedonsaunders/appkit-ai",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "App-agnostic bounded agent loop and streaming assistant interface, with provider policy supplied by the host app.",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"type": "module",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"@ai-sdk/google": "^3.0.91",
|
|
25
25
|
"@ai-sdk/openai": "^3.0.84",
|
|
26
26
|
"@ai-sdk/openai-compatible": "^2.0.59",
|
|
27
|
-
"@braedonsaunders/appkit-
|
|
27
|
+
"@braedonsaunders/appkit-egress-proxy": "^0.1.1",
|
|
28
28
|
"ai": "^6.0.224",
|
|
29
29
|
"zod": "^4.4.3"
|
|
30
30
|
},
|
package/react.d.ts
CHANGED
|
@@ -16,12 +16,18 @@ export type AgentPanelLabels = {
|
|
|
16
16
|
failed: string;
|
|
17
17
|
input: string;
|
|
18
18
|
result: string;
|
|
19
|
+
working: string;
|
|
20
|
+
step: string;
|
|
21
|
+
steps: string;
|
|
19
22
|
};
|
|
20
23
|
export type AgentPanelProps = {
|
|
21
24
|
enabled: boolean;
|
|
22
25
|
initialMessages?: AgentMessage[];
|
|
23
26
|
suggestions?: string[];
|
|
24
27
|
labels?: Partial<AgentPanelLabels>;
|
|
28
|
+
headerActions?: React.ReactNode;
|
|
29
|
+
/** Replaces the stock empty-state card while preserving the panel header and composer. */
|
|
30
|
+
emptyContent?: React.ReactNode;
|
|
25
31
|
send?: (prompt: string, signal: AbortSignal) => Promise<Response>;
|
|
26
32
|
maxPromptCharacters?: number;
|
|
27
33
|
toolLabels?: Record<string, string>;
|
|
@@ -31,7 +37,7 @@ export type AgentPanelProps = {
|
|
|
31
37
|
* owns persistence and the HTTP transport; appkit owns UI-message decoding,
|
|
32
38
|
* cancellation, ordered part rendering, and tool cards.
|
|
33
39
|
*/
|
|
34
|
-
export declare function AgentPanel({ enabled, initialMessages, suggestions, labels: labelOverrides, send, maxPromptCharacters, toolLabels, }: AgentPanelProps): React.JSX.Element;
|
|
40
|
+
export declare function AgentPanel({ enabled, initialMessages, suggestions, labels: labelOverrides, headerActions, emptyContent, send, maxPromptCharacters, toolLabels, }: AgentPanelProps): React.JSX.Element;
|
|
35
41
|
export declare function ChatMarkdown({ children }: {
|
|
36
42
|
children: string;
|
|
37
43
|
}): React.JSX.Element;
|
package/react.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAqB9B,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAA;IACrC,KAAK,EAAE,OAAO,EAAE,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,YAAY,EAAE,MAAM,CAAA;IACpB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,aAAa,EAAE,MAAM,CAAA;IACrB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAqB9B,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAA;IACrC,KAAK,EAAE,OAAO,EAAE,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,YAAY,EAAE,MAAM,CAAA;IACpB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,aAAa,EAAE,MAAM,CAAA;IACrB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAmBD,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,OAAO,CAAA;IAChB,eAAe,CAAC,EAAE,YAAY,EAAE,CAAA;IAChC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAA;IAClC,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC/B,0FAA0F;IAC1F,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC9B,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IACjE,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACpC,CAAA;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,eAAoB,EACpB,WAAgB,EAChB,MAAM,EAAE,cAAc,EACtB,aAAa,EACb,YAAY,EACZ,IAAI,EACJ,mBAA4B,EAC5B,UAAU,GACX,EAAE,eAAe,qBA0EjB;AAkFD,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,qBAE9D;AAED,wBAAgB,aAAa,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,UAAoB,EAAE,WAAsB,EAAE,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,qBAK/O"}
|
package/react.js
CHANGED
|
@@ -18,23 +18,36 @@ const DEFAULT_LABELS = {
|
|
|
18
18
|
failed: 'The assistant could not complete that turn. Please try again.',
|
|
19
19
|
input: 'Input',
|
|
20
20
|
result: 'Result',
|
|
21
|
+
working: 'Working',
|
|
22
|
+
step: 'step',
|
|
23
|
+
steps: 'steps',
|
|
21
24
|
};
|
|
22
25
|
/**
|
|
23
26
|
* The streaming thread/composer extracted from the sibling assistant. The app
|
|
24
27
|
* owns persistence and the HTTP transport; appkit owns UI-message decoding,
|
|
25
28
|
* cancellation, ordered part rendering, and tool cards.
|
|
26
29
|
*/
|
|
27
|
-
export function AgentPanel({ enabled, initialMessages = [], suggestions = [], labels: labelOverrides, send, maxPromptCharacters = 32_000, toolLabels, }) {
|
|
30
|
+
export function AgentPanel({ enabled, initialMessages = [], suggestions = [], labels: labelOverrides, headerActions, emptyContent, send, maxPromptCharacters = 32_000, toolLabels, }) {
|
|
28
31
|
const labels = { ...DEFAULT_LABELS, ...labelOverrides };
|
|
29
32
|
const [messages, setMessages] = React.useState(initialMessages);
|
|
30
33
|
const [input, setInput] = React.useState('');
|
|
31
34
|
const [streaming, setStreaming] = React.useState(false);
|
|
32
35
|
const [error, setError] = React.useState(null);
|
|
33
36
|
const abortRef = React.useRef(null);
|
|
34
|
-
const
|
|
37
|
+
const messageViewportRef = React.useRef(null);
|
|
35
38
|
const scrollToBottom = React.useCallback(() => {
|
|
36
|
-
window.requestAnimationFrame(() =>
|
|
39
|
+
window.requestAnimationFrame(() => {
|
|
40
|
+
const viewport = messageViewportRef.current;
|
|
41
|
+
if (viewport)
|
|
42
|
+
viewport.scrollTop = viewport.scrollHeight;
|
|
43
|
+
});
|
|
37
44
|
}, []);
|
|
45
|
+
// An existing conversation opens where work most recently happened. Keep
|
|
46
|
+
// this scoped to the panel's own viewport: scrollIntoView also moves every
|
|
47
|
+
// scrolling ancestor and can make an otherwise app-height page jump.
|
|
48
|
+
React.useLayoutEffect(() => {
|
|
49
|
+
scrollToBottom();
|
|
50
|
+
}, [scrollToBottom]);
|
|
38
51
|
const submit = React.useCallback(async (raw) => {
|
|
39
52
|
const prompt = raw.trim();
|
|
40
53
|
if (!enabled || !send || !prompt || prompt.length > maxPromptCharacters || abortRef.current)
|
|
@@ -77,7 +90,7 @@ export function AgentPanel({ enabled, initialMessages = [], suggestions = [], la
|
|
|
77
90
|
abortRef.current = null;
|
|
78
91
|
}
|
|
79
92
|
}, [enabled, labels.failed, maxPromptCharacters, scrollToBottom, send]);
|
|
80
|
-
return (_jsxs("div", { className: "flex min-h-0 flex-1 flex-col bg-bg-subtle", children: [_jsxs("header", { className: "flex h-12 shrink-0 items-center gap-2 border-b border-border bg-surface px-4", children: [_jsx(Sparkles, { size: 16, className: "text-primary" }), _jsx("span", { className: "text-sm font-medium text-fg", children: labels.title })] }),
|
|
93
|
+
return (_jsxs("div", { className: "flex min-h-0 flex-1 flex-col bg-bg-subtle", children: [_jsxs("header", { className: "flex h-12 shrink-0 items-center gap-2 border-b border-border bg-surface px-4", children: [_jsx(Sparkles, { size: 16, className: "text-primary" }), _jsx("span", { className: "text-sm font-medium text-fg", children: labels.title }), headerActions != null ? _jsx("div", { className: "ml-auto flex items-center gap-2", children: headerActions }) : null] }), _jsxs("div", { ref: messageViewportRef, className: "app-scroll min-h-0 flex-1 overflow-y-auto", children: [messages.length === 0 && emptyContent != null ? (_jsx("div", { className: "flex min-h-full flex-col", children: emptyContent })) : (_jsx("div", { className: "mx-auto w-full max-w-3xl px-4 py-6", children: messages.length === 0 ? _jsx(AgentWelcome, { enabled: enabled, title: enabled ? labels.welcomeTitle : labels.disabledTitle, description: enabled ? labels.welcomeDescription : labels.disabledDescription, suggestions: suggestions, onPick: (value) => void submit(value) }) : _jsx("div", { className: "space-y-6", children: messages.map((message) => message.role === 'system' ? null : _jsx(AgentMessageRow, { message: message, streaming: streaming, labels: labels, toolLabels: toolLabels }, message.id)) }) })), error ? _jsx("div", { role: "alert", className: "mx-auto mb-5 w-[calc(100%-2rem)] max-w-3xl rounded-lg border border-danger/25 bg-danger-subtle px-3 py-2 text-sm text-danger", children: error }) : null] }), enabled ? _jsx("div", { className: "shrink-0 border-t border-border bg-surface px-4 py-3", children: _jsx("div", { className: "mx-auto w-full max-w-3xl", children: _jsxs("div", { className: "flex items-end gap-2 rounded-2xl border border-border-strong bg-surface p-2 shadow-sm focus-within:border-primary focus-within:ring-2 focus-within:ring-ring/20", children: [_jsx("textarea", { value: input, onChange: (event) => setInput(event.target.value), onKeyDown: (event) => { if (event.key === 'Enter' && !event.shiftKey) {
|
|
81
94
|
event.preventDefault();
|
|
82
95
|
void submit(input);
|
|
83
96
|
} }, maxLength: maxPromptCharacters, rows: 1, placeholder: labels.placeholder, className: "max-h-40 flex-1 resize-none overflow-y-auto border-0 bg-transparent px-2 py-1.5 text-base text-fg outline-none placeholder:text-fg-subtle sm:text-sm" }), streaming ? _jsx(Button, { type: "button", variant: "outline", size: "icon", onClick: () => abortRef.current?.abort(), "aria-label": labels.stop, children: _jsx(Square, { size: 16 }) }) : _jsx(Button, { type: "button", size: "icon", onClick: () => void submit(input), disabled: !input.trim() || !send, "aria-label": labels.send, children: _jsx(Send, { size: 16 }) })] }) }) }) : null] }));
|
|
@@ -103,17 +116,59 @@ function AgentMessageRow({ message, streaming, labels, toolLabels }) {
|
|
|
103
116
|
return _jsxs("div", { className: "flex gap-3", children: [_jsx("span", { className: "mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-full bg-primary text-primary-fg shadow-sm", children: _jsx(Sparkles, { size: 16 }) }), _jsx("div", { className: "min-w-0 flex-1 pt-0.5", children: message.parts.length === 0 && streaming ? _jsx("div", { className: "flex items-center gap-1 py-1.5", children: [0, 1, 2].map((index) => _jsx("span", { className: "size-1.5 animate-bounce rounded-full bg-fg-subtle", style: { animationDelay: `${index * 0.15}s` } }, index)) }) : _jsx(AgentMessageParts, { parts: message.parts, labels: labels, toolLabels: toolLabels }) })] });
|
|
104
117
|
}
|
|
105
118
|
function AgentMessageParts({ parts, labels, toolLabels }) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
119
|
+
const rendered = [];
|
|
120
|
+
let tools = [];
|
|
121
|
+
let toolGroupStart = 0;
|
|
122
|
+
const flushTools = () => {
|
|
123
|
+
if (tools.length === 0)
|
|
124
|
+
return;
|
|
125
|
+
rendered.push(_jsx(AgentToolActivity, { parts: tools, labels: labels, toolLabels: toolLabels }, `tools-${toolGroupStart}`));
|
|
126
|
+
tools = [];
|
|
127
|
+
};
|
|
128
|
+
parts.forEach((part, index) => {
|
|
129
|
+
if (isAgentToolPart(part)) {
|
|
130
|
+
if (tools.length === 0)
|
|
131
|
+
toolGroupStart = index;
|
|
132
|
+
tools.push(part);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (part.type === 'step-start' || part.type === 'reasoning')
|
|
136
|
+
return;
|
|
137
|
+
flushTools();
|
|
138
|
+
if (part.type === 'text' && typeof part.text === 'string' && part.text.trim()) {
|
|
139
|
+
rendered.push(_jsx(ChatMarkdown, { children: part.text }, `text-${index}`));
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
flushTools();
|
|
143
|
+
return _jsx("div", { className: "space-y-2.5", children: rendered });
|
|
144
|
+
}
|
|
145
|
+
function isAgentToolPart(part) {
|
|
146
|
+
return part.type === 'dynamic-tool' || part.type.startsWith('tool-');
|
|
147
|
+
}
|
|
148
|
+
function agentToolName(part) {
|
|
149
|
+
return part.type === 'dynamic-tool' ? String(part.toolName ?? 'tool') : part.type.slice(5);
|
|
150
|
+
}
|
|
151
|
+
function agentToolRunning(part) {
|
|
152
|
+
const state = String(part.state ?? 'output-available');
|
|
153
|
+
return state === 'input-streaming' || state === 'input-available';
|
|
154
|
+
}
|
|
155
|
+
function agentToolErrored(part) {
|
|
156
|
+
return String(part.state) === 'output-error' || part.output?.ok === false;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* A multi-step run stays quiet in the transcript: the newest action and total
|
|
160
|
+
* step count occupy one line, while the complete inputs/results remain one
|
|
161
|
+
* click away. Applications keep their full durable audit trail separately.
|
|
162
|
+
*/
|
|
163
|
+
function AgentToolActivity({ parts, labels, toolLabels }) {
|
|
164
|
+
const [open, setOpen] = React.useState(false);
|
|
165
|
+
const latest = parts.at(-1);
|
|
166
|
+
const latestName = agentToolName(latest);
|
|
167
|
+
const running = parts.some(agentToolRunning);
|
|
168
|
+
const errored = parts.some(agentToolErrored);
|
|
169
|
+
const count = parts.length;
|
|
170
|
+
const status = running ? labels.working : `${count} ${count === 1 ? labels.step : labels.steps}`;
|
|
171
|
+
return _jsxs("div", { className: "text-sm", children: [_jsxs("button", { type: "button", onClick: () => setOpen((value) => !value), "aria-expanded": open, className: "flex h-7 max-w-full items-center gap-1.5 rounded-md px-1.5 text-left text-xs text-fg-muted transition-colors hover:bg-surface-hover hover:text-fg", children: [_jsx("span", { className: cn('size-1.5 shrink-0 rounded-full', running ? 'animate-pulse bg-primary' : errored ? 'bg-danger' : 'bg-success') }), _jsx("span", { className: "shrink-0 font-medium", children: status }), _jsx("span", { "aria-hidden": true, className: "text-fg-subtle", children: "\u00B7" }), _jsx("span", { className: "min-w-0 truncate", children: toolLabels?.[latestName] ?? latestName.replaceAll('_', ' ') }), _jsx(ChevronRight, { size: 12, className: cn('shrink-0 text-fg-subtle transition-transform', open && 'rotate-90') })] }), open ? _jsx("div", { className: "mt-1.5 space-y-1.5 border-l border-border pl-2", children: parts.map((part, index) => { const name = agentToolName(part); return _jsx(AgentToolCard, { name: name, label: toolLabels?.[name], state: String(part.state ?? 'output-available'), input: part.input, output: part.output, inputLabel: labels.input, resultLabel: labels.result }, `${name}-${index}`); }) }) : null] });
|
|
117
172
|
}
|
|
118
173
|
export function ChatMarkdown({ children }) {
|
|
119
174
|
return _jsx("div", { className: "space-y-2 text-sm leading-relaxed text-fg", children: _jsx(Markdown, { remarkPlugins: [remarkGfm], components: { p: ({ children: content }) => _jsx("p", { className: "whitespace-pre-wrap", children: content }), h1: ({ children: content }) => _jsx("h1", { className: "text-lg font-semibold", children: content }), h2: ({ children: content }) => _jsx("h2", { className: "text-base font-semibold", children: content }), ul: ({ children: content }) => _jsx("ul", { className: "list-disc space-y-1 pl-5", children: content }), ol: ({ children: content }) => _jsx("ol", { className: "list-decimal space-y-1 pl-5", children: content }), code: ({ children: content }) => _jsx("code", { className: "rounded bg-bg-subtle px-1 py-0.5 font-mono text-[0.85em] text-primary", children: content }), pre: ({ children: content }) => _jsx("pre", { className: "overflow-auto rounded-lg bg-overlay p-3 text-sm whitespace-pre-wrap text-white", children: content }), table: ({ children: content }) => _jsx("div", { className: "overflow-x-auto", children: _jsx("table", { className: "w-full border-collapse text-sm", children: content }) }), th: ({ children: content }) => _jsx("th", { className: "border-b border-border px-2 py-1 text-left", children: content }), td: ({ children: content }) => _jsx("td", { className: "border-b border-border-subtle px-2 py-1", children: content }), a: ({ href, children: content }) => href?.startsWith('/') ? _jsx(UiLink, { href: href, className: "font-medium text-primary underline-offset-2 hover:underline", children: content }) : _jsx("a", { href: href, target: "_blank", rel: "noreferrer", className: "font-medium text-primary underline-offset-2 hover:underline", children: content }) }, children: children }) });
|
package/react.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.js","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client'\n\nimport * as React from 'react'\nimport {\n parseJsonEventStream,\n readUIMessageStream,\n uiMessageChunkSchema,\n type UIMessageChunk,\n} from 'ai'\nimport {\n AlertCircle,\n CheckCircle2,\n ChevronRight,\n Database,\n Loader2,\n Send,\n Sparkles,\n Square,\n} from 'lucide-react'\nimport Markdown from 'react-markdown'\nimport remarkGfm from 'remark-gfm'\nimport { Button, EmptyState, UiLink, cn } from '@braedonsaunders/appkit-ui'\n\nexport type AgentMessage = {\n id: string\n role: 'user' | 'assistant' | 'system'\n parts: unknown[]\n}\n\nexport type AgentPanelLabels = {\n title: string\n welcomeTitle: string\n welcomeDescription: string\n disabledTitle: string\n disabledDescription: string\n placeholder: string\n send: string\n stop: string\n failed: string\n input: string\n result: string\n}\n\nconst DEFAULT_LABELS: AgentPanelLabels = {\n title: 'Assistant',\n welcomeTitle: 'How can I help?',\n welcomeDescription: 'Ask about your workspace or let the assistant use an approved tool.',\n disabledTitle: 'Connect an AI provider',\n disabledDescription: 'Connect an AI provider to enable agent conversations. No provider credentials are included in the demo.',\n placeholder: 'Ask the assistant…',\n send: 'Send',\n stop: 'Stop generating',\n failed: 'The assistant could not complete that turn. Please try again.',\n input: 'Input',\n result: 'Result',\n}\n\nexport type AgentPanelProps = {\n enabled: boolean\n initialMessages?: AgentMessage[]\n suggestions?: string[]\n labels?: Partial<AgentPanelLabels>\n send?: (prompt: string, signal: AbortSignal) => Promise<Response>\n maxPromptCharacters?: number\n toolLabels?: Record<string, string>\n}\n\n/**\n * The streaming thread/composer extracted from the sibling assistant. The app\n * owns persistence and the HTTP transport; appkit owns UI-message decoding,\n * cancellation, ordered part rendering, and tool cards.\n */\nexport function AgentPanel({\n enabled,\n initialMessages = [],\n suggestions = [],\n labels: labelOverrides,\n send,\n maxPromptCharacters = 32_000,\n toolLabels,\n}: AgentPanelProps) {\n const labels = { ...DEFAULT_LABELS, ...labelOverrides }\n const [messages, setMessages] = React.useState(initialMessages)\n const [input, setInput] = React.useState('')\n const [streaming, setStreaming] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const abortRef = React.useRef<AbortController | null>(null)\n const bottomRef = React.useRef<HTMLDivElement>(null)\n\n const scrollToBottom = React.useCallback(() => {\n window.requestAnimationFrame(() => bottomRef.current?.scrollIntoView({ block: 'end' }))\n }, [])\n\n const submit = React.useCallback(async (raw: string) => {\n const prompt = raw.trim()\n if (!enabled || !send || !prompt || prompt.length > maxPromptCharacters || abortRef.current) return\n const controller = new AbortController()\n abortRef.current = controller\n const stamp = Date.now()\n setInput('')\n setError(null)\n setMessages((current) => [...current, { id: `user-${stamp}`, role: 'user', parts: [{ type: 'text', text: prompt }] }, { id: `assistant-${stamp}`, role: 'assistant', parts: [] }])\n setStreaming(true)\n scrollToBottom()\n let producedParts = false\n try {\n const response = await send(prompt, controller.signal)\n if (!response.ok || !response.body) throw new Error('agent request failed')\n const chunks = parseJsonEventStream({ stream: response.body, schema: uiMessageChunkSchema }).pipeThrough(new TransformStream<{ success: boolean; value?: UIMessageChunk }, UIMessageChunk>({ transform(part, stream) { if (part.success && part.value) stream.enqueue(part.value) } }))\n let lastParts: unknown[] = []\n for await (const message of readUIMessageStream({ stream: chunks })) {\n lastParts = message.parts as unknown[]\n producedParts = lastParts.length > 0\n setMessages((current) => replaceLastAssistantParts(current, lastParts))\n scrollToBottom()\n }\n if (lastParts.length === 0 && !controller.signal.aborted) setError(labels.failed)\n } catch (reason) {\n if ((reason as Error).name !== 'AbortError') setError(labels.failed)\n } finally {\n if (!producedParts) {\n setMessages((current) => current.filter((message) => message.id !== `assistant-${stamp}`))\n }\n setStreaming(false)\n if (abortRef.current === controller) abortRef.current = null\n }\n }, [enabled, labels.failed, maxPromptCharacters, scrollToBottom, send])\n\n return (\n <div className=\"flex min-h-0 flex-1 flex-col bg-bg-subtle\">\n <header className=\"flex h-12 shrink-0 items-center gap-2 border-b border-border bg-surface px-4\"><Sparkles size={16} className=\"text-primary\" /><span className=\"text-sm font-medium text-fg\">{labels.title}</span></header>\n <div className=\"app-scroll min-h-0 flex-1 overflow-y-auto\"><div className=\"mx-auto w-full max-w-3xl px-4 py-6\">\n {messages.length === 0 ? <AgentWelcome enabled={enabled} title={enabled ? labels.welcomeTitle : labels.disabledTitle} description={enabled ? labels.welcomeDescription : labels.disabledDescription} suggestions={suggestions} onPick={(value) => void submit(value)} /> : <div className=\"space-y-6\">{messages.map((message) => message.role === 'system' ? null : <AgentMessageRow key={message.id} message={message} streaming={streaming} labels={labels} toolLabels={toolLabels} />)}</div>}\n {error ? <div role=\"alert\" className=\"mt-5 rounded-lg border border-danger/25 bg-danger-subtle px-3 py-2 text-sm text-danger\">{error}</div> : null}<div ref={bottomRef} />\n </div></div>\n {enabled ? <div className=\"shrink-0 border-t border-border bg-surface px-4 py-3\"><div className=\"mx-auto w-full max-w-3xl\"><div className=\"flex items-end gap-2 rounded-2xl border border-border-strong bg-surface p-2 shadow-sm focus-within:border-primary focus-within:ring-2 focus-within:ring-ring/20\"><textarea value={input} onChange={(event) => setInput(event.target.value)} onKeyDown={(event) => { if (event.key === 'Enter' && !event.shiftKey) { event.preventDefault(); void submit(input) } }} maxLength={maxPromptCharacters} rows={1} placeholder={labels.placeholder} className=\"max-h-40 flex-1 resize-none overflow-y-auto border-0 bg-transparent px-2 py-1.5 text-base text-fg outline-none placeholder:text-fg-subtle sm:text-sm\" />{streaming ? <Button type=\"button\" variant=\"outline\" size=\"icon\" onClick={() => abortRef.current?.abort()} aria-label={labels.stop}><Square size={16} /></Button> : <Button type=\"button\" size=\"icon\" onClick={() => void submit(input)} disabled={!input.trim() || !send} aria-label={labels.send}><Send size={16} /></Button>}</div></div></div> : null}\n </div>\n )\n}\n\nfunction replaceLastAssistantParts(messages: AgentMessage[], parts: unknown[]): AgentMessage[] {\n const copy = messages.slice()\n for (let index = copy.length - 1; index >= 0; index -= 1) {\n if (copy[index]?.role === 'assistant') { copy[index] = { ...copy[index]!, parts }; break }\n }\n return copy\n}\n\nfunction AgentWelcome({ enabled, title, description, suggestions, onPick }: { enabled: boolean; title: string; description: string; suggestions: string[]; onPick: (value: string) => void }) {\n return <div className=\"pt-10\"><EmptyState icon={<Sparkles />} title={title} description={description} />{enabled && suggestions.length ? <div className=\"mx-auto mt-6 grid max-w-2xl gap-2 sm:grid-cols-2\">{suggestions.map((suggestion) => <button key={suggestion} type=\"button\" onClick={() => onPick(suggestion)} className=\"rounded-xl border border-border bg-surface px-4 py-3 text-left text-sm text-fg-muted shadow-sm transition-colors hover:border-primary/40 hover:bg-primary-subtle hover:text-fg\">{suggestion}</button>)}</div> : null}</div>\n}\n\nfunction AgentMessageRow({ message, streaming, labels, toolLabels }: { message: AgentMessage; streaming: boolean; labels: AgentPanelLabels; toolLabels?: Record<string, string> }) {\n if (message.role === 'user') {\n const text = (message.parts.find((part) => (part as { type?: string }).type === 'text') as { text?: string } | undefined)?.text\n return <div className=\"flex justify-end\"><div className=\"max-w-[85%] rounded-2xl rounded-br-md bg-primary px-4 py-2 text-sm whitespace-pre-wrap text-primary-fg\">{text}</div></div>\n }\n return <div className=\"flex gap-3\"><span className=\"mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-full bg-primary text-primary-fg shadow-sm\"><Sparkles size={16} /></span><div className=\"min-w-0 flex-1 pt-0.5\">{message.parts.length === 0 && streaming ? <div className=\"flex items-center gap-1 py-1.5\">{[0,1,2].map((index) => <span key={index} className=\"size-1.5 animate-bounce rounded-full bg-fg-subtle\" style={{ animationDelay: `${index * 0.15}s` }} />)}</div> : <AgentMessageParts parts={message.parts} labels={labels} toolLabels={toolLabels} />}</div></div>\n}\n\nfunction AgentMessageParts({ parts, labels, toolLabels }: { parts: unknown[]; labels: AgentPanelLabels; toolLabels?: Record<string, string> }) {\n return <div className=\"space-y-2.5\">{(parts as { type: string; [key: string]: unknown }[]).map((part, index) => {\n if (part.type === 'text') return typeof part.text === 'string' && part.text.trim() ? <ChatMarkdown key={index}>{part.text}</ChatMarkdown> : null\n if (part.type === 'step-start' || part.type === 'reasoning') return null\n if (part.type === 'dynamic-tool' || part.type.startsWith('tool-')) { const name = part.type === 'dynamic-tool' ? String(part.toolName ?? 'tool') : part.type.slice(5); return <AgentToolCard key={index} name={name} label={toolLabels?.[name]} state={String(part.state ?? 'output-available')} input={part.input} output={part.output} inputLabel={labels.input} resultLabel={labels.result} /> }\n return null\n })}</div>\n}\n\nexport function ChatMarkdown({ children }: { children: string }) {\n return <div className=\"space-y-2 text-sm leading-relaxed text-fg\"><Markdown remarkPlugins={[remarkGfm]} components={{ p: ({ children: content }) => <p className=\"whitespace-pre-wrap\">{content}</p>, h1: ({ children: content }) => <h1 className=\"text-lg font-semibold\">{content}</h1>, h2: ({ children: content }) => <h2 className=\"text-base font-semibold\">{content}</h2>, ul: ({ children: content }) => <ul className=\"list-disc space-y-1 pl-5\">{content}</ul>, ol: ({ children: content }) => <ol className=\"list-decimal space-y-1 pl-5\">{content}</ol>, code: ({ children: content }) => <code className=\"rounded bg-bg-subtle px-1 py-0.5 font-mono text-[0.85em] text-primary\">{content}</code>, pre: ({ children: content }) => <pre className=\"overflow-auto rounded-lg bg-overlay p-3 text-sm whitespace-pre-wrap text-white\">{content}</pre>, table: ({ children: content }) => <div className=\"overflow-x-auto\"><table className=\"w-full border-collapse text-sm\">{content}</table></div>, th: ({ children: content }) => <th className=\"border-b border-border px-2 py-1 text-left\">{content}</th>, td: ({ children: content }) => <td className=\"border-b border-border-subtle px-2 py-1\">{content}</td>, a: ({ href, children: content }) => href?.startsWith('/') ? <UiLink href={href} className=\"font-medium text-primary underline-offset-2 hover:underline\">{content}</UiLink> : <a href={href} target=\"_blank\" rel=\"noreferrer\" className=\"font-medium text-primary underline-offset-2 hover:underline\">{content}</a> }}>{children}</Markdown></div>\n}\n\nexport function AgentToolCard({ name, label, state, input, output, inputLabel = 'Input', resultLabel = 'Result' }: { name: string; label?: string; state: string; input?: unknown; output?: unknown; inputLabel?: string; resultLabel?: string }) {\n const [open, setOpen] = React.useState(false)\n const running = state === 'input-streaming' || state === 'input-available'\n const errored = state === 'output-error' || (output as { ok?: boolean } | undefined)?.ok === false\n return <div className=\"overflow-hidden rounded-lg border border-border bg-bg-subtle text-sm\"><button type=\"button\" onClick={() => setOpen((value) => !value)} className=\"flex w-full items-center gap-2.5 px-3 py-2 text-left transition-colors hover:bg-surface-hover\"><span className={cn('flex size-6 shrink-0 items-center justify-center rounded-md', errored ? 'bg-danger-subtle text-danger' : 'bg-primary-subtle text-primary')}><Database size={14} /></span><span className=\"min-w-0 flex-1 truncate font-medium text-fg\">{label ?? name.replaceAll('_', ' ')}</span>{running ? <Loader2 size={14} className=\"animate-spin text-fg-subtle\" /> : errored ? <AlertCircle size={14} className=\"text-danger\" /> : <CheckCircle2 size={14} className=\"text-success\" />}<ChevronRight size={14} className={cn('text-fg-subtle transition-transform', open && 'rotate-90')} /></button>{open ? <div className=\"space-y-2 border-t border-border px-3 py-2\">{input !== undefined ? <AgentToolDetail label={inputLabel} value={input} /> : null}{output !== undefined ? <AgentToolDetail label={resultLabel} value={output} /> : null}</div> : null}</div>\n}\n\nfunction AgentToolDetail({ label, value }: { label: string; value: unknown }) {\n let text: string\n try { text = JSON.stringify(value, null, 2) } catch { text = String(value) }\n return <div><div className=\"mb-1 text-[11px] font-semibold tracking-wide text-fg-subtle uppercase\">{label}</div><pre className=\"max-h-60 overflow-auto rounded-md bg-surface p-2 text-xs leading-relaxed text-fg ring-1 ring-border\">{text}</pre></div>\n}\n"]}
|
|
1
|
+
{"version":3,"file":"react.js","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client'\n\nimport * as React from 'react'\nimport {\n parseJsonEventStream,\n readUIMessageStream,\n uiMessageChunkSchema,\n type UIMessageChunk,\n} from 'ai'\nimport {\n AlertCircle,\n CheckCircle2,\n ChevronRight,\n Database,\n Loader2,\n Send,\n Sparkles,\n Square,\n} from 'lucide-react'\nimport Markdown from 'react-markdown'\nimport remarkGfm from 'remark-gfm'\nimport { Button, EmptyState, UiLink, cn } from '@braedonsaunders/appkit-ui'\n\nexport type AgentMessage = {\n id: string\n role: 'user' | 'assistant' | 'system'\n parts: unknown[]\n}\n\nexport type AgentPanelLabels = {\n title: string\n welcomeTitle: string\n welcomeDescription: string\n disabledTitle: string\n disabledDescription: string\n placeholder: string\n send: string\n stop: string\n failed: string\n input: string\n result: string\n working: string\n step: string\n steps: string\n}\n\nconst DEFAULT_LABELS: AgentPanelLabels = {\n title: 'Assistant',\n welcomeTitle: 'How can I help?',\n welcomeDescription: 'Ask about your workspace or let the assistant use an approved tool.',\n disabledTitle: 'Connect an AI provider',\n disabledDescription: 'Connect an AI provider to enable agent conversations. No provider credentials are included in the demo.',\n placeholder: 'Ask the assistant…',\n send: 'Send',\n stop: 'Stop generating',\n failed: 'The assistant could not complete that turn. Please try again.',\n input: 'Input',\n result: 'Result',\n working: 'Working',\n step: 'step',\n steps: 'steps',\n}\n\nexport type AgentPanelProps = {\n enabled: boolean\n initialMessages?: AgentMessage[]\n suggestions?: string[]\n labels?: Partial<AgentPanelLabels>\n headerActions?: React.ReactNode\n /** Replaces the stock empty-state card while preserving the panel header and composer. */\n emptyContent?: React.ReactNode\n send?: (prompt: string, signal: AbortSignal) => Promise<Response>\n maxPromptCharacters?: number\n toolLabels?: Record<string, string>\n}\n\n/**\n * The streaming thread/composer extracted from the sibling assistant. The app\n * owns persistence and the HTTP transport; appkit owns UI-message decoding,\n * cancellation, ordered part rendering, and tool cards.\n */\nexport function AgentPanel({\n enabled,\n initialMessages = [],\n suggestions = [],\n labels: labelOverrides,\n headerActions,\n emptyContent,\n send,\n maxPromptCharacters = 32_000,\n toolLabels,\n}: AgentPanelProps) {\n const labels = { ...DEFAULT_LABELS, ...labelOverrides }\n const [messages, setMessages] = React.useState(initialMessages)\n const [input, setInput] = React.useState('')\n const [streaming, setStreaming] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const abortRef = React.useRef<AbortController | null>(null)\n const messageViewportRef = React.useRef<HTMLDivElement>(null)\n\n const scrollToBottom = React.useCallback(() => {\n window.requestAnimationFrame(() => {\n const viewport = messageViewportRef.current\n if (viewport) viewport.scrollTop = viewport.scrollHeight\n })\n }, [])\n\n // An existing conversation opens where work most recently happened. Keep\n // this scoped to the panel's own viewport: scrollIntoView also moves every\n // scrolling ancestor and can make an otherwise app-height page jump.\n React.useLayoutEffect(() => {\n scrollToBottom()\n }, [scrollToBottom])\n\n const submit = React.useCallback(async (raw: string) => {\n const prompt = raw.trim()\n if (!enabled || !send || !prompt || prompt.length > maxPromptCharacters || abortRef.current) return\n const controller = new AbortController()\n abortRef.current = controller\n const stamp = Date.now()\n setInput('')\n setError(null)\n setMessages((current) => [...current, { id: `user-${stamp}`, role: 'user', parts: [{ type: 'text', text: prompt }] }, { id: `assistant-${stamp}`, role: 'assistant', parts: [] }])\n setStreaming(true)\n scrollToBottom()\n let producedParts = false\n try {\n const response = await send(prompt, controller.signal)\n if (!response.ok || !response.body) throw new Error('agent request failed')\n const chunks = parseJsonEventStream({ stream: response.body, schema: uiMessageChunkSchema }).pipeThrough(new TransformStream<{ success: boolean; value?: UIMessageChunk }, UIMessageChunk>({ transform(part, stream) { if (part.success && part.value) stream.enqueue(part.value) } }))\n let lastParts: unknown[] = []\n for await (const message of readUIMessageStream({ stream: chunks })) {\n lastParts = message.parts as unknown[]\n producedParts = lastParts.length > 0\n setMessages((current) => replaceLastAssistantParts(current, lastParts))\n scrollToBottom()\n }\n if (lastParts.length === 0 && !controller.signal.aborted) setError(labels.failed)\n } catch (reason) {\n if ((reason as Error).name !== 'AbortError') setError(labels.failed)\n } finally {\n if (!producedParts) {\n setMessages((current) => current.filter((message) => message.id !== `assistant-${stamp}`))\n }\n setStreaming(false)\n if (abortRef.current === controller) abortRef.current = null\n }\n }, [enabled, labels.failed, maxPromptCharacters, scrollToBottom, send])\n\n return (\n <div className=\"flex min-h-0 flex-1 flex-col bg-bg-subtle\">\n <header className=\"flex h-12 shrink-0 items-center gap-2 border-b border-border bg-surface px-4\"><Sparkles size={16} className=\"text-primary\" /><span className=\"text-sm font-medium text-fg\">{labels.title}</span>{headerActions != null ? <div className=\"ml-auto flex items-center gap-2\">{headerActions}</div> : null}</header>\n <div ref={messageViewportRef} className=\"app-scroll min-h-0 flex-1 overflow-y-auto\">\n {messages.length === 0 && emptyContent != null ? (\n <div className=\"flex min-h-full flex-col\">{emptyContent}</div>\n ) : (\n <div className=\"mx-auto w-full max-w-3xl px-4 py-6\">\n {messages.length === 0 ? <AgentWelcome enabled={enabled} title={enabled ? labels.welcomeTitle : labels.disabledTitle} description={enabled ? labels.welcomeDescription : labels.disabledDescription} suggestions={suggestions} onPick={(value) => void submit(value)} /> : <div className=\"space-y-6\">{messages.map((message) => message.role === 'system' ? null : <AgentMessageRow key={message.id} message={message} streaming={streaming} labels={labels} toolLabels={toolLabels} />)}</div>}\n </div>\n )}\n {error ? <div role=\"alert\" className=\"mx-auto mb-5 w-[calc(100%-2rem)] max-w-3xl rounded-lg border border-danger/25 bg-danger-subtle px-3 py-2 text-sm text-danger\">{error}</div> : null}\n </div>\n {enabled ? <div className=\"shrink-0 border-t border-border bg-surface px-4 py-3\"><div className=\"mx-auto w-full max-w-3xl\"><div className=\"flex items-end gap-2 rounded-2xl border border-border-strong bg-surface p-2 shadow-sm focus-within:border-primary focus-within:ring-2 focus-within:ring-ring/20\"><textarea value={input} onChange={(event) => setInput(event.target.value)} onKeyDown={(event) => { if (event.key === 'Enter' && !event.shiftKey) { event.preventDefault(); void submit(input) } }} maxLength={maxPromptCharacters} rows={1} placeholder={labels.placeholder} className=\"max-h-40 flex-1 resize-none overflow-y-auto border-0 bg-transparent px-2 py-1.5 text-base text-fg outline-none placeholder:text-fg-subtle sm:text-sm\" />{streaming ? <Button type=\"button\" variant=\"outline\" size=\"icon\" onClick={() => abortRef.current?.abort()} aria-label={labels.stop}><Square size={16} /></Button> : <Button type=\"button\" size=\"icon\" onClick={() => void submit(input)} disabled={!input.trim() || !send} aria-label={labels.send}><Send size={16} /></Button>}</div></div></div> : null}\n </div>\n )\n}\n\nfunction replaceLastAssistantParts(messages: AgentMessage[], parts: unknown[]): AgentMessage[] {\n const copy = messages.slice()\n for (let index = copy.length - 1; index >= 0; index -= 1) {\n if (copy[index]?.role === 'assistant') { copy[index] = { ...copy[index]!, parts }; break }\n }\n return copy\n}\n\nfunction AgentWelcome({ enabled, title, description, suggestions, onPick }: { enabled: boolean; title: string; description: string; suggestions: string[]; onPick: (value: string) => void }) {\n return <div className=\"pt-10\"><EmptyState icon={<Sparkles />} title={title} description={description} />{enabled && suggestions.length ? <div className=\"mx-auto mt-6 grid max-w-2xl gap-2 sm:grid-cols-2\">{suggestions.map((suggestion) => <button key={suggestion} type=\"button\" onClick={() => onPick(suggestion)} className=\"rounded-xl border border-border bg-surface px-4 py-3 text-left text-sm text-fg-muted shadow-sm transition-colors hover:border-primary/40 hover:bg-primary-subtle hover:text-fg\">{suggestion}</button>)}</div> : null}</div>\n}\n\nfunction AgentMessageRow({ message, streaming, labels, toolLabels }: { message: AgentMessage; streaming: boolean; labels: AgentPanelLabels; toolLabels?: Record<string, string> }) {\n if (message.role === 'user') {\n const text = (message.parts.find((part) => (part as { type?: string }).type === 'text') as { text?: string } | undefined)?.text\n return <div className=\"flex justify-end\"><div className=\"max-w-[85%] rounded-2xl rounded-br-md bg-primary px-4 py-2 text-sm whitespace-pre-wrap text-primary-fg\">{text}</div></div>\n }\n return <div className=\"flex gap-3\"><span className=\"mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-full bg-primary text-primary-fg shadow-sm\"><Sparkles size={16} /></span><div className=\"min-w-0 flex-1 pt-0.5\">{message.parts.length === 0 && streaming ? <div className=\"flex items-center gap-1 py-1.5\">{[0,1,2].map((index) => <span key={index} className=\"size-1.5 animate-bounce rounded-full bg-fg-subtle\" style={{ animationDelay: `${index * 0.15}s` }} />)}</div> : <AgentMessageParts parts={message.parts} labels={labels} toolLabels={toolLabels} />}</div></div>\n}\n\nfunction AgentMessageParts({ parts, labels, toolLabels }: { parts: unknown[]; labels: AgentPanelLabels; toolLabels?: Record<string, string> }) {\n const rendered: React.ReactNode[] = []\n let tools: AgentToolPart[] = []\n let toolGroupStart = 0\n const flushTools = () => {\n if (tools.length === 0) return\n rendered.push(<AgentToolActivity key={`tools-${toolGroupStart}`} parts={tools} labels={labels} toolLabels={toolLabels} />)\n tools = []\n }\n ;(parts as { type: string; [key: string]: unknown }[]).forEach((part, index) => {\n if (isAgentToolPart(part)) {\n if (tools.length === 0) toolGroupStart = index\n tools.push(part)\n return\n }\n if (part.type === 'step-start' || part.type === 'reasoning') return\n flushTools()\n if (part.type === 'text' && typeof part.text === 'string' && part.text.trim()) {\n rendered.push(<ChatMarkdown key={`text-${index}`}>{part.text}</ChatMarkdown>)\n }\n })\n flushTools()\n return <div className=\"space-y-2.5\">{rendered}</div>\n}\n\ntype AgentToolPart = { type: string; toolName?: unknown; state?: unknown; input?: unknown; output?: unknown }\n\nfunction isAgentToolPart(part: { type: string }): part is AgentToolPart {\n return part.type === 'dynamic-tool' || part.type.startsWith('tool-')\n}\n\nfunction agentToolName(part: AgentToolPart): string {\n return part.type === 'dynamic-tool' ? String(part.toolName ?? 'tool') : part.type.slice(5)\n}\n\nfunction agentToolRunning(part: AgentToolPart): boolean {\n const state = String(part.state ?? 'output-available')\n return state === 'input-streaming' || state === 'input-available'\n}\n\nfunction agentToolErrored(part: AgentToolPart): boolean {\n return String(part.state) === 'output-error' || (part.output as { ok?: boolean } | undefined)?.ok === false\n}\n\n/**\n * A multi-step run stays quiet in the transcript: the newest action and total\n * step count occupy one line, while the complete inputs/results remain one\n * click away. Applications keep their full durable audit trail separately.\n */\nfunction AgentToolActivity({ parts, labels, toolLabels }: { parts: AgentToolPart[]; labels: AgentPanelLabels; toolLabels?: Record<string, string> }) {\n const [open, setOpen] = React.useState(false)\n const latest = parts.at(-1)!\n const latestName = agentToolName(latest)\n const running = parts.some(agentToolRunning)\n const errored = parts.some(agentToolErrored)\n const count = parts.length\n const status = running ? labels.working : `${count} ${count === 1 ? labels.step : labels.steps}`\n return <div className=\"text-sm\"><button type=\"button\" onClick={() => setOpen((value) => !value)} aria-expanded={open} className=\"flex h-7 max-w-full items-center gap-1.5 rounded-md px-1.5 text-left text-xs text-fg-muted transition-colors hover:bg-surface-hover hover:text-fg\"><span className={cn('size-1.5 shrink-0 rounded-full', running ? 'animate-pulse bg-primary' : errored ? 'bg-danger' : 'bg-success')} /><span className=\"shrink-0 font-medium\">{status}</span><span aria-hidden className=\"text-fg-subtle\">·</span><span className=\"min-w-0 truncate\">{toolLabels?.[latestName] ?? latestName.replaceAll('_', ' ')}</span><ChevronRight size={12} className={cn('shrink-0 text-fg-subtle transition-transform', open && 'rotate-90')} /></button>{open ? <div className=\"mt-1.5 space-y-1.5 border-l border-border pl-2\">{parts.map((part, index) => { const name = agentToolName(part); return <AgentToolCard key={`${name}-${index}`} name={name} label={toolLabels?.[name]} state={String(part.state ?? 'output-available')} input={part.input} output={part.output} inputLabel={labels.input} resultLabel={labels.result} /> })}</div> : null}</div>\n}\n\nexport function ChatMarkdown({ children }: { children: string }) {\n return <div className=\"space-y-2 text-sm leading-relaxed text-fg\"><Markdown remarkPlugins={[remarkGfm]} components={{ p: ({ children: content }) => <p className=\"whitespace-pre-wrap\">{content}</p>, h1: ({ children: content }) => <h1 className=\"text-lg font-semibold\">{content}</h1>, h2: ({ children: content }) => <h2 className=\"text-base font-semibold\">{content}</h2>, ul: ({ children: content }) => <ul className=\"list-disc space-y-1 pl-5\">{content}</ul>, ol: ({ children: content }) => <ol className=\"list-decimal space-y-1 pl-5\">{content}</ol>, code: ({ children: content }) => <code className=\"rounded bg-bg-subtle px-1 py-0.5 font-mono text-[0.85em] text-primary\">{content}</code>, pre: ({ children: content }) => <pre className=\"overflow-auto rounded-lg bg-overlay p-3 text-sm whitespace-pre-wrap text-white\">{content}</pre>, table: ({ children: content }) => <div className=\"overflow-x-auto\"><table className=\"w-full border-collapse text-sm\">{content}</table></div>, th: ({ children: content }) => <th className=\"border-b border-border px-2 py-1 text-left\">{content}</th>, td: ({ children: content }) => <td className=\"border-b border-border-subtle px-2 py-1\">{content}</td>, a: ({ href, children: content }) => href?.startsWith('/') ? <UiLink href={href} className=\"font-medium text-primary underline-offset-2 hover:underline\">{content}</UiLink> : <a href={href} target=\"_blank\" rel=\"noreferrer\" className=\"font-medium text-primary underline-offset-2 hover:underline\">{content}</a> }}>{children}</Markdown></div>\n}\n\nexport function AgentToolCard({ name, label, state, input, output, inputLabel = 'Input', resultLabel = 'Result' }: { name: string; label?: string; state: string; input?: unknown; output?: unknown; inputLabel?: string; resultLabel?: string }) {\n const [open, setOpen] = React.useState(false)\n const running = state === 'input-streaming' || state === 'input-available'\n const errored = state === 'output-error' || (output as { ok?: boolean } | undefined)?.ok === false\n return <div className=\"overflow-hidden rounded-lg border border-border bg-bg-subtle text-sm\"><button type=\"button\" onClick={() => setOpen((value) => !value)} className=\"flex w-full items-center gap-2.5 px-3 py-2 text-left transition-colors hover:bg-surface-hover\"><span className={cn('flex size-6 shrink-0 items-center justify-center rounded-md', errored ? 'bg-danger-subtle text-danger' : 'bg-primary-subtle text-primary')}><Database size={14} /></span><span className=\"min-w-0 flex-1 truncate font-medium text-fg\">{label ?? name.replaceAll('_', ' ')}</span>{running ? <Loader2 size={14} className=\"animate-spin text-fg-subtle\" /> : errored ? <AlertCircle size={14} className=\"text-danger\" /> : <CheckCircle2 size={14} className=\"text-success\" />}<ChevronRight size={14} className={cn('text-fg-subtle transition-transform', open && 'rotate-90')} /></button>{open ? <div className=\"space-y-2 border-t border-border px-3 py-2\">{input !== undefined ? <AgentToolDetail label={inputLabel} value={input} /> : null}{output !== undefined ? <AgentToolDetail label={resultLabel} value={output} /> : null}</div> : null}</div>\n}\n\nfunction AgentToolDetail({ label, value }: { label: string; value: unknown }) {\n let text: string\n try { text = JSON.stringify(value, null, 2) } catch { text = String(value) }\n return <div><div className=\"mb-1 text-[11px] font-semibold tracking-wide text-fg-subtle uppercase\">{label}</div><pre className=\"max-h-60 overflow-auto rounded-md bg-surface p-2 text-xs leading-relaxed text-fg ring-1 ring-border\">{text}</pre></div>\n}\n"]}
|