@frankieai/server 0.2.1 → 0.2.2
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/chunk-JOADNB5J.js +2918 -0
- package/dist/chunk-JOADNB5J.js.map +1 -0
- package/dist/chunk-NF6IJVES.js +75 -0
- package/dist/chunk-NF6IJVES.js.map +1 -0
- package/dist/{chunk-UZSHFMJN.js → chunk-NSOMFNTK.js} +208 -13
- package/dist/chunk-NSOMFNTK.js.map +1 -0
- package/dist/control-server.js +7 -3
- package/dist/control-server.js.map +1 -1
- package/dist/index.d.ts +46 -8
- package/dist/index.js +12 -12
- package/dist/server.js +3 -3
- package/package.json +5 -2
- package/dist/chunk-UZSHFMJN.js.map +0 -1
- package/dist/chunk-WFENEYOV.js +0 -1266
- package/dist/chunk-WFENEYOV.js.map +0 -1
- package/dist/chunk-XCIM765D.js +0 -1551
- package/dist/chunk-XCIM765D.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/rate-limit.ts","../src/agent/models.ts","../src/agent/headers.ts","../src/agent/openapi.ts","../src/connector-fetch.ts","../src/agent/mcp.ts","../src/auth.ts","../src/control/store.ts","../src/control/postgres-store.ts","../src/sessions.ts","../src/postgres-sessions.ts","../src/control/resolve-store.ts"],"sourcesContent":["/**\n * Sliding-window counter. In-memory by default; swap in Redis for multi-instance\n * deployments by implementing `RateLimiter`.\n */\nexport interface RateLimiter {\n /** Returns false when the caller is over budget. */\n take(key: string, limit: number, windowSeconds: number): Promise<boolean>;\n}\n\nexport class MemoryRateLimiter implements RateLimiter {\n private readonly hits = new Map<string, number[]>();\n private lastSweep = Date.now();\n /**\n * The longest window any caller has asked for. The sweep horizon has to cover\n * it, or sweeping would discard hits the window still counts.\n */\n private maxWindowMs = 0;\n\n async take(key: string, limit: number, windowSeconds: number): Promise<boolean> {\n const now = Date.now();\n const windowMs = windowSeconds * 1000;\n if (windowMs > this.maxWindowMs) this.maxWindowMs = windowMs;\n const cutoff = now - windowMs;\n this.sweep(now);\n\n const timestamps = (this.hits.get(key) ?? []).filter((t) => t > cutoff);\n if (timestamps.length >= limit) {\n this.hits.set(key, timestamps);\n return false;\n }\n timestamps.push(now);\n this.hits.set(key, timestamps);\n return true;\n }\n\n /**\n * Trim stale hits and drop empty buckets occasionally, so a long-lived process\n * does not grow. The horizon tracks the longest window in use rather than a\n * fixed hour: a shorter one would drop timestamps that window still counts,\n * silently handing the caller a fresh budget mid-window.\n */\n private sweep(now: number): void {\n if (now - this.lastSweep < 60_000) return;\n this.lastSweep = now;\n const horizon = now - this.maxWindowMs;\n for (const [key, timestamps] of this.hits) {\n const live = timestamps.filter((t) => t > horizon);\n if (live.length === 0) this.hits.delete(key);\n else this.hits.set(key, live);\n }\n }\n}\n","import { builtinModels } from \"@earendil-works/pi-ai/providers/all\";\nimport { hasApi, type Api, type Model, type Models } from \"@earendil-works/pi-ai\";\nimport type { FrankieConfig } from \"@frankieai/core\";\n\nexport class ModelResolutionError extends Error {}\n\nexport interface ResolvedModel {\n models: Models;\n model: Model<Api>;\n}\n\n/**\n * Resolve the tenant's configured model against the provider catalogue.\n *\n * The catalogue already knows which wire API each model speaks — Kimi K2.6 and\n * K2.7 on Fireworks answer the Anthropic Messages API, K3 the OpenAI\n * completions API — so nothing here has to care, and swapping providers stays a\n * config change.\n */\nexport function resolveModel(config: FrankieConfig): ResolvedModel {\n const models = builtinModels();\n const provider = models.getProvider(config.model.provider);\n if (!provider) {\n const known = models\n .getProviders()\n .map((entry) => entry.id)\n .sort()\n .join(\", \");\n throw new ModelResolutionError(\n `Unknown model provider \"${config.model.provider}\". Known providers: ${known}.`,\n );\n }\n\n const found = models.getModel(config.model.provider, config.model.model);\n if (!found) {\n throw new ModelResolutionError(\n `Provider \"${config.model.provider}\" has no model \"${config.model.model}\". ` +\n `Available: ${provider.getModels().map((entry) => entry.id).slice(0, 10).join(\", \")}…`,\n );\n }\n\n // Per-model overrides: a base URL for a self-hosted gateway or a test double,\n // and a max-tokens ceiling that never exceeds what the model actually allows.\n const model: Model<Api> = {\n ...found,\n ...(config.model.baseUrl ? { baseUrl: config.model.baseUrl } : {}),\n maxTokens: Math.min(config.model.maxTokens, found.maxTokens),\n };\n\n return { models, model };\n}\n\n/**\n * Resolve a per-request model the end user chose, but only if it is allowed:\n * it must be the workspace default or appear in the curated picker list. Returns\n * null for anything else, so an unknown or forbidden id falls back to the\n * default instead of letting a client pick an arbitrary model.\n */\nexport function resolveModelChoice(catalogue: Models, config: FrankieConfig, requestedId: string): Model<Api> | null {\n const choice = config.features.modelPicker.models.find((entry) => entry.id === requestedId);\n const isDefault = requestedId === config.model.model;\n if (!choice && !isDefault) return null;\n\n const provider = choice?.provider ?? config.model.provider;\n const found = catalogue.getModel(provider, requestedId);\n if (!found) return null;\n\n return {\n ...found,\n ...(config.model.baseUrl ? { baseUrl: config.model.baseUrl } : {}),\n maxTokens: Math.min(config.model.maxTokens, found.maxTokens),\n };\n}\n\n/** Narrow a resolved model to a specific wire API, for callers that need it. */\nexport function modelHasApi<TApi extends Api>(model: Model<Api>, api: TApi): model is Model<TApi> {\n return hasApi(model, api);\n}\n","import type { ChatSession } from \"../sessions.js\";\n\n/** The forwarding fields MCP servers and API sources have in common. */\nexport interface ForwardingConfig {\n headers: Record<string, string>;\n forwardUserTokenAs?: string | undefined;\n forwardUserClaims: readonly string[];\n}\n\n/**\n * Headers sent to a tenant's own backend, whether that is an MCP server or an\n * HTTP API.\n *\n * `forwardUserTokenAs` is how the tenant's tools act *as* the signed-in user:\n * their API authorises the call exactly as it would any other request from that\n * person. It is opt-in per source precisely because it hands that source the\n * user's identity token.\n */\nexport function buildForwardedHeaders(\n source: ForwardingConfig,\n session: ChatSession,\n): Record<string, string> {\n const headers: Record<string, string> = { ...source.headers };\n\n if (source.forwardUserTokenAs && session.userToken) {\n headers[source.forwardUserTokenAs] =\n source.forwardUserTokenAs.toLowerCase() === \"authorization\"\n ? `Bearer ${session.userToken}`\n : session.userToken;\n }\n\n headers[\"x-frankie-user-id\"] = session.user.id;\n headers[\"x-frankie-session-id\"] = session.id;\n headers[\"x-frankie-tenant-id\"] = session.tenantId;\n\n for (const claim of source.forwardUserClaims) {\n const value = session.user.claims[claim];\n if (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n headers[`x-frankie-${claim.toLowerCase()}`] = String(value);\n }\n }\n\n return headers;\n}\n","import { readFile } from \"node:fs/promises\";\nimport { connectorFetch, trustedLocalConnectors } from \"../connector-fetch.js\";\nimport type { AgentTool, AgentToolResult } from \"@earendil-works/pi-agent-core\";\nimport { Type } from \"@earendil-works/pi-ai\";\nimport { describeTool, evaluateToolPolicy, type ApiSourceConfig, type ToolPolicy } from \"@frankieai/core\";\nimport { parse as parseYaml } from \"yaml\";\nimport { buildForwardedHeaders } from \"./headers.js\";\nimport type { ChatSession } from \"../sessions.js\";\n\n/** Generated API tools share the namespace shape of MCP tools. */\nexport const API_TOOL_PREFIX = \"api__\";\n\nexport class OpenApiError extends Error {}\n\nexport interface BuildApiToolsOptions {\n sources: readonly ApiSourceConfig[];\n session: ChatSession;\n policy: ToolPolicy;\n fetchImpl?: typeof fetch;\n onWarning?: (message: string, detail: Record<string, unknown>) => void;\n}\n\nexport interface ApiToolSet {\n tools: AgentTool[];\n failed: Array<{ name: string; error: string }>;\n}\n\n/** A concrete, approved operation the dashboard can show as chat access. */\nexport interface ApiCapability {\n operationId: string;\n method: \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\";\n path: string;\n summary?: string;\n}\n\n/** Read a source using the same filters as the agent; never execute an API operation. */\nexport async function inspectApiCapabilities(\n source: ApiSourceConfig,\n options: { fetchImpl?: typeof fetch; policy?: ToolPolicy } = {},\n): Promise<ApiCapability[]> {\n const doc = await loadSpec(source.spec, options.fetchImpl);\n const allowedMethods = new Set(source.methods);\n const capabilities: ApiCapability[] = [];\n for (const [path, item] of Object.entries(doc.paths ?? {})) {\n if (!isRecord(item)) continue;\n if (source.includePaths.length > 0 && !source.includePaths.some((glob) => matchPath(glob, path))) continue;\n for (const method of [\"get\", \"post\", \"put\", \"patch\", \"delete\"] as const) {\n const operation = item[method];\n if (!isRecord(operation)) continue;\n const httpMethod = method.toUpperCase() as ApiCapability[\"method\"];\n if (!allowedMethods.has(httpMethod)) continue;\n const operationId = typeof operation[\"operationId\"] === \"string\" ? operation[\"operationId\"] : `${method}_${slug(path)}`;\n if (source.operations.length > 0 && !source.operations.includes(operationId)) continue;\n const toolName = `${API_TOOL_PREFIX}${source.name}__${sanitize(operationId)}`;\n if (options.policy && !evaluateToolPolicy(toolName, options.policy).allowed) continue;\n const summary = typeof operation[\"summary\"] === \"string\" ? operation[\"summary\"] : undefined;\n capabilities.push({ operationId, method: httpMethod, path, ...(summary ? { summary } : {}) });\n }\n }\n return capabilities;\n}\n\n/**\n * Turn a tenant's OpenAPI document into agent tools.\n *\n * This is the third onboarding path: a customer who has no MCP server and does\n * not want to write one points Frankie at their spec instead. The tools it\n * produces are indistinguishable to the agent from MCP ones, and go through the\n * same deny-by-default policy and the same identity forwarding.\n */\nexport async function buildApiTools(options: BuildApiToolsOptions): Promise<ApiToolSet> {\n const tools: AgentTool[] = [];\n const failed: Array<{ name: string; error: string }> = [];\n\n for (const source of options.sources.filter((source) => source.enabled)) {\n try {\n tools.push(...(await buildOne(source, options)));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n options.onWarning?.(\"api_spec_failed\", { api: source.name, error: message });\n failed.push({ name: source.name, error: message });\n }\n }\n\n return { tools, failed };\n}\n\nasync function buildOne(source: ApiSourceConfig, options: BuildApiToolsOptions): Promise<AgentTool[]> {\n const doc = await loadSpec(source.spec, options.fetchImpl);\n const baseUrl = (source.baseUrl ?? specBaseUrl(doc))?.replace(/\\/+$/, \"\");\n if (!baseUrl) {\n throw new OpenApiError(\n `API \"${source.name}\" needs a baseUrl: its spec declares no \\`servers\\` entry to fall back to.`,\n );\n }\n\n const headers = buildForwardedHeaders(source, options.session);\n const doFetch = options.fetchImpl ?? connectorFetch;\n const allowedMethods = new Set(source.methods);\n const tools: AgentTool[] = [];\n\n for (const [path, item] of Object.entries(doc.paths ?? {})) {\n if (!isRecord(item)) continue;\n if (source.includePaths.length > 0 && !source.includePaths.some((glob) => matchPath(glob, path))) continue;\n\n for (const method of [\"get\", \"post\", \"put\", \"patch\", \"delete\"] as const) {\n const operation = item[method];\n if (!isRecord(operation)) continue;\n if (!allowedMethods.has(method.toUpperCase() as \"GET\")) continue;\n\n const operationId =\n typeof operation[\"operationId\"] === \"string\" ? operation[\"operationId\"] : `${method}_${slug(path)}`;\n if (source.operations.length > 0 && !source.operations.includes(operationId)) continue;\n\n if (source.selectedTools && !source.selectedTools.includes(operationId)) continue;\n const name = `${API_TOOL_PREFIX}${source.name}__${sanitize(operationId)}`;\n // Filter at registration, exactly as MCP tools are: a tool the policy\n // forbids should never reach the prompt.\n if (!evaluateToolPolicy(name, options.policy).allowed) continue;\n\n const params = collectParameters(doc, item, operation);\n const body = collectRequestBody(doc, operation);\n\n tools.push({\n name,\n label: describeTool(name, {}),\n description: describeOperation(operation, method, path),\n parameters: Type.Unsafe(toolSchema(params, body)),\n async execute(_id, args, signal): Promise<AgentToolResult<unknown>> {\n const input = isRecord(args) ? args : {};\n const url = buildUrl(baseUrl, path, params, input);\n const requestBody = body ? input[\"body\"] : undefined;\n\n const response = await doFetch(url, {\n method: method.toUpperCase(),\n headers: {\n ...headers,\n accept: \"application/json, text/plain;q=0.9, */*;q=0.8\",\n ...(requestBody !== undefined ? { \"content-type\": \"application/json\" } : {}),\n },\n ...(requestBody !== undefined ? { body: JSON.stringify(requestBody) } : {}),\n signal: signal ?? AbortSignal.timeout(source.timeoutMs),\n });\n\n const text = truncate(await response.text().catch(() => \"\"), source.maxResponseChars);\n if (!response.ok) {\n // Throwing is how the agent loop books an error tool result, which\n // is what lets the model recover instead of reporting success.\n throw new Error(`${operationId} failed with HTTP ${response.status}: ${truncate(text, 400)}`);\n }\n return { content: [{ type: \"text\", text: text || \"(empty response)\" }], details: null };\n },\n });\n }\n }\n\n if (tools.length === 0) {\n options.onWarning?.(\"api_no_tools\", { api: source.name, baseUrl });\n }\n return tools;\n}\n\n/* ------------------------------ spec loading ------------------------------ */\n\ninterface OpenApiDoc {\n servers?: Array<{ url?: string }>;\n paths?: Record<string, unknown>;\n components?: Record<string, unknown>;\n}\n\nexport async function loadSpec(spec: string, fetchImpl?: typeof fetch): Promise<OpenApiDoc> {\n let raw: string;\n if (spec.trimStart().startsWith(\"{\")) {\n raw = spec;\n } else if (/^https?:\\/\\//i.test(spec)) {\n const response = await (fetchImpl ?? connectorFetch)(spec, {\n headers: { accept: \"application/json, text/yaml, */*\" },\n });\n if (!response.ok) throw new OpenApiError(`Could not fetch ${spec}: HTTP ${response.status}.`);\n raw = await response.text();\n } else {\n if (!trustedLocalConnectors()) throw new OpenApiError(\"Local OpenAPI files are disabled. Paste JSON or use a public HTTPS document.\");\n raw = await readFile(spec, \"utf8\").catch(() => {\n throw new OpenApiError(`Could not read the OpenAPI document at ${spec}.`);\n });\n }\n\n try {\n // YAML is a superset of JSON, so one parser covers both shapes.\n const parsed: unknown = raw.trimStart().startsWith(\"{\") ? JSON.parse(raw) : parseYaml(raw);\n if (!isRecord(parsed)) throw new Error(\"the document is not an object\");\n return parsed as OpenApiDoc;\n } catch (error) {\n throw new OpenApiError(`Could not parse the OpenAPI document at ${spec}: ${(error as Error).message}`);\n }\n}\n\nfunction specBaseUrl(doc: OpenApiDoc): string | undefined {\n const url = doc.servers?.[0]?.url;\n return typeof url === \"string\" && /^https?:\\/\\//i.test(url) ? url : undefined;\n}\n\n/* ------------------------------- parameters ------------------------------- */\n\ninterface Param {\n name: string;\n location: \"path\" | \"query\";\n required: boolean;\n schema: Record<string, unknown>;\n description?: string;\n}\n\nfunction collectParameters(\n doc: OpenApiDoc,\n item: Record<string, unknown>,\n operation: Record<string, unknown>,\n): Param[] {\n const raw = [...asArray(item[\"parameters\"]), ...asArray(operation[\"parameters\"])];\n const params: Param[] = [];\n\n for (const entry of raw) {\n const resolved = resolveRef(doc, entry);\n if (!isRecord(resolved)) continue;\n const name = resolved[\"name\"];\n const location = resolved[\"in\"];\n if (typeof name !== \"string\") continue;\n // Header and cookie parameters belong to the operator, not the model.\n if (location !== \"path\" && location !== \"query\") continue;\n\n const schema = resolveRef(doc, resolved[\"schema\"]);\n params.push({\n name,\n location,\n required: resolved[\"required\"] === true || location === \"path\",\n schema: isRecord(schema) ? (schema as Record<string, unknown>) : { type: \"string\" },\n ...(typeof resolved[\"description\"] === \"string\" ? { description: resolved[\"description\"] } : {}),\n });\n }\n\n return params;\n}\n\nfunction collectRequestBody(\n doc: OpenApiDoc,\n operation: Record<string, unknown>,\n): Record<string, unknown> | undefined {\n const body = resolveRef(doc, operation[\"requestBody\"]);\n if (!isRecord(body)) return undefined;\n const content = body[\"content\"];\n if (!isRecord(content)) return undefined;\n const json = content[\"application/json\"];\n if (!isRecord(json)) return undefined;\n const schema = resolveRef(doc, json[\"schema\"]);\n return isRecord(schema) ? (schema as Record<string, unknown>) : undefined;\n}\n\n/**\n * The request body is nested under `body` rather than flattened alongside the\n * path and query parameters: a spec is free to name a body field `id` and a\n * query parameter `id`, and flattening would silently drop one.\n */\nfunction toolSchema(params: Param[], body: Record<string, unknown> | undefined): Record<string, unknown> {\n const properties: Record<string, unknown> = {};\n const required: string[] = [];\n\n for (const param of params) {\n properties[param.name] = param.description\n ? { ...param.schema, description: param.description }\n : param.schema;\n if (param.required) required.push(param.name);\n }\n\n if (body) {\n properties[\"body\"] = body;\n required.push(\"body\");\n }\n\n return {\n type: \"object\",\n properties,\n ...(required.length ? { required } : {}),\n additionalProperties: false,\n };\n}\n\nfunction buildUrl(baseUrl: string, path: string, params: Param[], input: Record<string, unknown>): string {\n let resolved = path;\n for (const param of params) {\n if (param.location !== \"path\") continue;\n const value = input[param.name];\n resolved = resolved.replace(`{${param.name}}`, encodeURIComponent(String(value ?? \"\")));\n }\n\n const url = new URL(`${baseUrl}${resolved.startsWith(\"/\") ? \"\" : \"/\"}${resolved}`);\n for (const param of params) {\n if (param.location !== \"query\") continue;\n const value = input[param.name];\n if (value === undefined || value === null) continue;\n if (Array.isArray(value)) {\n for (const entry of value) url.searchParams.append(param.name, String(entry));\n } else {\n url.searchParams.set(param.name, String(value));\n }\n }\n return url.toString();\n}\n\nfunction describeOperation(operation: Record<string, unknown>, method: string, path: string): string {\n const summary = typeof operation[\"summary\"] === \"string\" ? operation[\"summary\"] : \"\";\n const description = typeof operation[\"description\"] === \"string\" ? operation[\"description\"] : \"\";\n const text = [summary, description].filter(Boolean).join(\" - \");\n return text || `${method.toUpperCase()} ${path}`;\n}\n\n/* -------------------------------- helpers -------------------------------- */\n\n/** Resolves local `#/components/...` references; remote refs are unsupported. */\nfunction resolveRef(doc: OpenApiDoc, node: unknown, depth = 0): unknown {\n if (!isRecord(node) || depth > 10) return node;\n const ref = node[\"$ref\"];\n if (typeof ref !== \"string\" || !ref.startsWith(\"#/\")) return node;\n\n let current: unknown = doc;\n for (const segment of ref.slice(2).split(\"/\")) {\n if (!isRecord(current)) return undefined;\n current = current[segment.replace(/~1/g, \"/\").replace(/~0/g, \"~\")];\n }\n return resolveRef(doc, current, depth + 1);\n}\n\n/**\n * `/projects/*` matches one path segment; `/projects/**` matches any depth and\n * the base path itself, since a tenant writing that plainly means \"this part of\n * the API\" rather than \"everything under it but not it\".\n */\nexport function matchPath(glob: string, path: string): boolean {\n const escaped = glob.replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const trailingDeep = escaped.endsWith(\"/**\");\n const body = trailingDeep ? escaped.slice(0, -3) : escaped;\n const pattern =\n body\n .split(\"**\")\n .map((chunk) => chunk.replace(/\\*/g, \"[^/]*\"))\n .join(\".*\") + (trailingDeep ? \"(?:/.*)?\" : \"\");\n return new RegExp(`^${pattern}$`).test(path);\n}\n\nfunction sanitize(value: string): string {\n return value.replace(/[^a-zA-Z0-9_-]/g, \"_\").slice(0, 48);\n}\n\nfunction slug(path: string): string {\n return path\n .replace(/[{}]/g, \"\")\n .replace(/[^a-zA-Z0-9]+/g, \"_\")\n .replace(/^_|_$/g, \"\");\n}\n\nfunction truncate(value: string, max: number): string {\n return value.length <= max ? value : `${value.slice(0, max)}\\n[truncated]`;\n}\n\nfunction asArray(value: unknown): unknown[] {\n return Array.isArray(value) ? value : [];\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n","import { lookup } from \"node:dns/promises\";\nimport { isIP } from \"node:net\";\nimport { request as httpsRequest } from \"node:https\";\nimport { Readable, Transform } from \"node:stream\";\n\n/** Public unicast only. Conservative IPv6 policy excludes transition networks. */\nexport function isPublicAddress(address: string): boolean {\n if (isIP(address) === 4) {\n const [a, b] = address.split(\".\").map(Number) as [number, number];\n return !(a === 0 || a === 10 || a === 127 || a >= 224 ||\n (a === 100 && b >= 64 && b <= 127) || (a === 169 && b === 254) ||\n (a === 172 && b >= 16 && b <= 31) || (a === 192 && [0, 168].includes(b)) ||\n (a === 198 && [18, 19, 51].includes(b)) || (a === 203 && b === 0));\n }\n if (isIP(address) !== 6) return false;\n const first = Number.parseInt(address.split(\":\")[0]!, 16);\n return first >= 0x2000 && first <= 0x3fff && first !== 0x2002 &&\n !(first === 0x2001 && Number.parseInt(address.split(\":\")[1] || \"0\", 16) <= 0x1ff) &&\n !address.toLowerCase().startsWith(\"2001:db8:\") && !address.toLowerCase().startsWith(\"3fff:\");\n}\n\nexport function assertConnectorUrl(value: string): URL {\n const url = new URL(value);\n if (url.protocol !== \"https:\" || url.username || url.password) throw new Error(\"Connectors require HTTPS URLs without embedded credentials.\");\n const host = url.hostname.replace(/^\\[|\\]$/g, \"\");\n if (host === \"localhost\" || host.endsWith(\".localhost\") || host.endsWith(\".local\") || (isIP(host) && !isPublicAddress(host))) {\n throw new Error(\"Connectors cannot access local or private networks.\");\n }\n return url;\n}\n\n/** Validated answers are passed directly to the socket: no second DNS lookup. */\nexport async function resolvePublicHost(host: string, resolve = lookup): Promise<{ address: string; family: number }> {\n const addresses = await resolve(host, { all: true, verbatim: true });\n if (!addresses.length || addresses.some(entry => !isPublicAddress(entry.address))) throw new Error(\"Connector DNS must resolve only to public addresses.\");\n return addresses[0]!;\n}\n\n/** HTTPS transport with socket-bound DNS validation, no redirects, and bounded IO. */\nexport const publicConnectorFetch: typeof fetch = async (input, init) => {\n const source = new Request(input, init);\n const url = assertConnectorUrl(source.url);\n const headers = Object.fromEntries(source.headers);\n for (const key of [\"host\", \"connection\", \"content-length\", \"transfer-encoding\", \"proxy-authorization\", \"upgrade\"]) delete headers[key];\n const outgoing = source.body ? await readLimited(source.body, 2_000_000) : undefined;\n return new Promise<Response>((resolve, reject) => {\n const req = httpsRequest(url, {\n method: source.method, headers, signal: source.signal,\n lookup(host, options, callback) {\n const done = callback as (...args: unknown[]) => void;\n void resolvePublicHost(host).then(entry => options.all ? done(null, [entry]) : done(null, entry.address, entry.family), error => done(error));\n },\n }, response => {\n const status = response.statusCode ?? 502;\n if (status >= 300 && status < 400) {\n response.destroy(); reject(new Error(\"Connector redirects are disabled. Configure the final HTTPS endpoint.\")); return;\n }\n const responseHeaders = new Headers();\n for (const [name, value] of Object.entries(response.headers)) if (value) responseHeaders.set(name, Array.isArray(value) ? value.join(\", \") : value);\n let bytes = 0;\n const limit = new Transform({ transform(chunk: Buffer, _encoding, done) {\n bytes += chunk.length;\n done(bytes > 8_000_000 ? new Error(\"Connector response exceeds the size limit.\") : null, chunk);\n } });\n response.on(\"error\", error => limit.destroy(error));\n limit.on(\"error\", () => response.destroy());\n const body = [204, 205, 304].includes(status) || source.method === \"HEAD\" ? null : Readable.toWeb(response.pipe(limit)) as ReadableStream<Uint8Array>;\n if (!body) response.resume();\n resolve(new Response(body, { status, headers: responseHeaders }));\n });\n const timer = setTimeout(() => req.destroy(new Error(\"Connector request timed out.\")), 60_000);\n timer.unref();\n req.on(\"close\", () => clearTimeout(timer));\n req.on(\"error\", reject);\n req.end(outgoing);\n });\n};\n\nasync function readLimited(stream: ReadableStream<Uint8Array>, max: number): Promise<Buffer> {\n const reader = stream.getReader(); const chunks: Uint8Array[] = []; let bytes = 0;\n try {\n for (;;) { const { done, value } = await reader.read(); if (done) break; bytes += value.length; if (bytes > max) { await reader.cancel(); throw new Error(\"Connector request exceeds the size limit.\"); } chunks.push(value); }\n return Buffer.concat(chunks);\n } finally { reader.releaseLock(); }\n}\n\n/** Only the server operator can relax the boundary for trusted self-hosting. */\nexport const trustedLocalConnectors = () => process.env[\"FRANKIE_TRUST_LOCAL_CONNECTORS\"] === \"true\";\nexport const connectorFetch: typeof fetch = (input, init) => trustedLocalConnectors() ? fetch(input, init) : publicConnectorFetch(input, init);\n","import { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { connectorFetch } from \"../connector-fetch.js\";\nimport { SSEClientTransport } from \"@modelcontextprotocol/sdk/client/sse.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport type { AgentTool, AgentToolResult } from \"@earendil-works/pi-agent-core\";\nimport { Type, type ImageContent, type TextContent } from \"@earendil-works/pi-ai\";\nimport { describeTool, evaluateToolPolicy, type McpServerConfig, type ToolPolicy } from \"@frankieai/core\";\nimport { buildForwardedHeaders } from \"./headers.js\";\nimport type { ChatSession } from \"../sessions.js\";\n\n/** Tools are namespaced so one tenant's policy can name a server or a tool. */\nexport const MCP_TOOL_PREFIX = \"mcp__\";\n\nexport interface McpConnection {\n tools: AgentTool[];\n /** Servers that could not be reached, by name. */\n failed: Array<{ name: string; error: string }>;\n close(): Promise<void>;\n}\n\nexport interface ConnectMcpOptions {\n servers: readonly McpServerConfig[];\n session: ChatSession;\n policy: ToolPolicy;\n connectTimeoutMs?: number;\n callTimeoutMs?: number;\n onWarning?: (message: string, detail: Record<string, unknown>) => void;\n}\n\n/**\n * Connect to the tenant's MCP servers and expose their tools to the agent.\n *\n * Connections are per turn rather than pooled. That costs one handshake per\n * message, and buys three things worth more for an onboarding agent: a server\n * restarting mid-conversation heals on the next turn, tool lists stay current,\n * and no connection outlives the request that authorised it.\n */\nexport async function connectMcpTools(options: ConnectMcpOptions): Promise<McpConnection> {\n const clients: Client[] = [];\n const tools: AgentTool[] = [];\n const failed: Array<{ name: string; error: string }> = [];\n\n const results = await Promise.all(\n options.servers.filter((server) => server.enabled).map(async (server) => {\n try {\n return { server, client: await connectOne(server, options) };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n options.onWarning?.(\"mcp_connect_failed\", { server: server.name, error: message });\n failed.push({ name: server.name, error: message });\n return null;\n }\n }),\n );\n\n for (const result of results) {\n if (!result) continue;\n clients.push(result.client);\n try {\n const listed = await withTimeout(result.client.listTools(), options.connectTimeoutMs ?? 10_000, \"MCP tool discovery timed out.\");\n for (const tool of listed.tools) {\n if (result.server.selectedTools && !result.server.selectedTools.includes(tool.name)) continue;\n const name = `${MCP_TOOL_PREFIX}${result.server.name}__${tool.name}`;\n // Filter at registration: a tool the policy forbids should not be in\n // the prompt at all. `beforeToolCall` still re-checks as a backstop.\n if (!evaluateToolPolicy(name, options.policy).allowed) continue;\n tools.push(adaptTool(name, tool, result.client, options.callTimeoutMs));\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n options.onWarning?.(\"mcp_list_tools_failed\", { server: result.server.name, error: message });\n failed.push({ name: result.server.name, error: message });\n }\n }\n\n return {\n tools,\n failed,\n async close() {\n await Promise.all(clients.map((client) => client.close().catch(() => undefined)));\n },\n };\n}\n\nasync function connectOne(server: McpServerConfig, options: ConnectMcpOptions): Promise<Client> {\n const headers = buildForwardedHeaders(server, options.session);\n const url = new URL(server.url);\n const transport =\n server.transport === \"sse\"\n ? new SSEClientTransport(url, { requestInit: { headers }, fetch: connectorFetch, eventSourceInit: { fetch: connectorFetch } })\n : new StreamableHTTPClientTransport(url, { requestInit: { headers }, fetch: connectorFetch });\n\n const client = new Client({ name: \"frankie\", version: \"0.1.0\" }, { capabilities: {} });\n try { await withTimeout(\n client.connect(transport),\n options.connectTimeoutMs ?? 10_000,\n `MCP server \"${server.name}\" did not complete the handshake in time.`,\n ); } catch (error) { await client.close().catch(() => undefined); throw error; }\n return client;\n}\n\ninterface ListedTool {\n name: string;\n description?: string;\n inputSchema?: unknown;\n}\n\nfunction adaptTool(name: string, tool: ListedTool, client: Client, callTimeoutMs = 60_000): AgentTool {\n return {\n name,\n label: describeTool(name, {}),\n description: tool.description ?? `The ${tool.name} tool.`,\n // MCP gives us JSON Schema; `Type.Unsafe` passes it through untouched\n // rather than round-tripping it into a typebox equivalent and losing\n // whatever the server actually declared.\n parameters: Type.Unsafe(normaliseSchema(tool.inputSchema)),\n async execute(_toolCallId, params, signal): Promise<AgentToolResult<unknown>> {\n const response = await withTimeout(\n client.callTool(\n { name: tool.name, arguments: (params ?? {}) as Record<string, unknown> },\n undefined,\n signal ? { signal } : undefined,\n ),\n callTimeoutMs,\n `The ${tool.name} tool did not respond in time.`,\n );\n\n const content = toAgentContent(response.content);\n // The loop encodes thrown errors as error tool results; MCP reports them\n // in-band, so translate rather than passing a success through.\n if (response.isError) {\n throw new Error(textOf(content) || `The ${tool.name} tool reported an error.`);\n }\n return { content, details: response.structuredContent ?? null };\n },\n };\n}\n\nfunction normaliseSchema(schema: unknown): Record<string, unknown> {\n if (typeof schema !== \"object\" || schema === null || Array.isArray(schema)) {\n return { type: \"object\", properties: {} };\n }\n const copy = { ...(schema as Record<string, unknown>) };\n if (copy[\"type\"] !== \"object\") copy[\"type\"] = \"object\";\n if (typeof copy[\"properties\"] !== \"object\" || copy[\"properties\"] === null) copy[\"properties\"] = {};\n return copy;\n}\n\nfunction toAgentContent(content: unknown): (TextContent | ImageContent)[] {\n if (!Array.isArray(content)) return [{ type: \"text\", text: \"\" }];\n const out: (TextContent | ImageContent)[] = [];\n for (const part of content) {\n if (typeof part !== \"object\" || part === null) continue;\n const block = part as Record<string, unknown>;\n if (block[\"type\"] === \"text\" && typeof block[\"text\"] === \"string\") {\n out.push({ type: \"text\", text: block[\"text\"] });\n } else if (\n block[\"type\"] === \"image\" &&\n typeof block[\"data\"] === \"string\" &&\n typeof block[\"mimeType\"] === \"string\"\n ) {\n out.push({ type: \"image\", data: block[\"data\"], mimeType: block[\"mimeType\"] });\n } else if (block[\"type\"] === \"resource\") {\n // Embedded resources vary by server; render the readable part.\n const resource = block[\"resource\"];\n const text =\n typeof resource === \"object\" && resource !== null && typeof (resource as Record<string, unknown>)[\"text\"] === \"string\"\n ? String((resource as Record<string, unknown>)[\"text\"])\n : \"[resource omitted]\";\n out.push({ type: \"text\", text });\n }\n }\n return out.length > 0 ? out : [{ type: \"text\", text: \"\" }];\n}\n\nfunction textOf(content: (TextContent | ImageContent)[]): string {\n return content\n .map((part) => (part.type === \"text\" ? part.text : \"\"))\n .filter(Boolean)\n .join(\"\\n\")\n .trim();\n}\n\nasync function withTimeout<T>(promise: Promise<T>, ms: number, message: string): Promise<T> {\n let timer: NodeJS.Timeout | undefined;\n try {\n return await Promise.race([\n promise,\n new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => reject(new Error(message)), ms);\n }),\n ]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n","import { createRemoteJWKSet, jwtVerify, SignJWT, type JWTPayload } from \"jose\";\nimport type { AuthConfig } from \"@frankieai/core\";\n\nexport interface VerifiedUser {\n id: string;\n displayName?: string;\n email?: string;\n anonymous: boolean;\n /** Every claim from the host's token, for MCP forwarding and prompt context. */\n claims: JWTPayload;\n}\n\nexport class AuthError extends Error {\n constructor(\n message: string,\n readonly code: \"unauthorized\" | \"forbidden_origin\" = \"unauthorized\",\n ) {\n super(message);\n }\n}\n\ntype JwksResolver = ReturnType<typeof createRemoteJWKSet>;\n\n/**\n * Verifies the JWT the customer's backend mints for a signed-in end user, and\n * issues/verifies Frankie's own short-lived session tokens.\n */\nexport class TokenService {\n private readonly sessionKey: Uint8Array;\n private readonly userKey?: Uint8Array;\n private jwks?: JwksResolver;\n\n constructor(\n private readonly auth: AuthConfig,\n secrets: { sessionSecret: string; jwtSecret?: string },\n ) {\n this.sessionKey = new TextEncoder().encode(secrets.sessionSecret);\n this.userKey = secrets.jwtSecret ? new TextEncoder().encode(secrets.jwtSecret) : undefined;\n if (!this.userKey && !auth.jwksUrl && !auth.allowAnonymous) {\n throw new Error(\"Frankie auth needs either a JWT secret, a JWKS URL, or allowAnonymous.\");\n }\n }\n\n /** Verify a host-signed user token. Throws `AuthError` on any failure. */\n async verifyUserToken(token: string): Promise<VerifiedUser> {\n const options = {\n ...(this.auth.jwtIssuer ? { issuer: this.auth.jwtIssuer } : {}),\n ...(this.auth.jwtAudience ? { audience: this.auth.jwtAudience } : {}),\n clockTolerance: 30,\n };\n\n let payload: JWTPayload;\n try {\n if (this.auth.jwksUrl) {\n this.jwks ??= createRemoteJWKSet(new URL(this.auth.jwksUrl));\n ({ payload } = await jwtVerify(token, this.jwks, options));\n } else if (this.userKey) {\n ({ payload } = await jwtVerify(token, this.userKey, { ...options, algorithms: [\"HS256\"] }));\n } else {\n throw new AuthError(\"This tenant is not configured to accept user tokens.\");\n }\n } catch (error) {\n if (error instanceof AuthError) throw error;\n throw new AuthError(`Could not verify the user token: ${(error as Error).message}`);\n }\n\n const subject = typeof payload.sub === \"string\" ? payload.sub : undefined;\n if (!subject) throw new AuthError(\"The user token has no `sub` claim.\");\n\n return {\n id: subject,\n displayName: stringClaim(payload, \"name\") ?? stringClaim(payload, \"preferred_username\"),\n email: stringClaim(payload, \"email\"),\n anonymous: false,\n claims: payload,\n };\n }\n\n /** Mint the bearer token the widget uses for the rest of the conversation. */\n async issueSessionToken(input: { sessionId: string; user: VerifiedUser; tenantId: string }): Promise<{\n token: string;\n expiresAt: Date;\n }> {\n const expiresAt = new Date(Date.now() + this.auth.sessionTtlSeconds * 1000);\n const token = await new SignJWT({\n sid: input.sessionId,\n uid: input.user.id,\n anon: input.user.anonymous,\n tid: input.tenantId,\n })\n .setProtectedHeader({ alg: \"HS256\", typ: \"JWT\" })\n .setIssuedAt()\n .setIssuer(\"frankie\")\n .setAudience(\"frankie-widget\")\n .setExpirationTime(Math.floor(expiresAt.getTime() / 1000))\n .sign(this.sessionKey);\n\n return { token, expiresAt };\n }\n\n async verifySessionToken(token: string): Promise<{ sessionId: string; userId: string; tenantId: string }> {\n try {\n const { payload } = await jwtVerify(token, this.sessionKey, {\n algorithms: [\"HS256\"],\n issuer: \"frankie\",\n audience: \"frankie-widget\",\n });\n const sessionId = stringClaim(payload, \"sid\");\n const userId = stringClaim(payload, \"uid\");\n const tenantId = stringClaim(payload, \"tid\");\n if (!sessionId || !userId || !tenantId) throw new AuthError(\"Malformed session token.\");\n return { sessionId, userId, tenantId };\n } catch (error) {\n if (error instanceof AuthError) throw error;\n throw new AuthError(\"Your chat session has expired. Reload the page to start a new one.\");\n }\n }\n}\n\nfunction stringClaim(payload: JWTPayload, key: string): string | undefined {\n const value = payload[key];\n return typeof value === \"string\" ? value : undefined;\n}\n\n/**\n * Exact-origin allowlist. Wildcards are deliberately unsupported: the widget\n * runs on a page the customer controls, so the list is short and known.\n * An empty list means \"same-origin only\", which is what a server mounted\n * inside the host app wants.\n */\nexport function isOriginAllowed(origin: string | undefined, allowed: readonly string[]): boolean {\n if (allowed.length === 0) return origin === undefined;\n if (!origin) return false;\n return allowed.some((entry) => normaliseOrigin(entry) === normaliseOrigin(origin));\n}\n\nfunction normaliseOrigin(value: string): string {\n try {\n const url = new URL(value);\n return url.origin.toLowerCase();\n } catch {\n return value.trim().replace(/\\/+$/, \"\").toLowerCase();\n }\n}\n\n/** Constant-time string comparison for the publishable key check. */\nexport function safeEqual(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/** Stable pseudonymous id for an anonymous visitor. */\nexport function anonymousUser(seed: string): VerifiedUser {\n return { id: `anon_${seed}`, anonymous: true, claims: {} };\n}\n","import { mkdir, readFile, readdir, rename, rm, writeFile } from \"node:fs/promises\";\nimport { randomUUID } from \"node:crypto\";\nimport { join, resolve } from \"node:path\";\nimport { parseFrankieConfig, type FrankieConfig } from \"@frankieai/core\";\nimport type { ResolvedConfig } from \"../config.js\";\nimport type { TenantStore } from \"../tenants.js\";\n\n/** Filename for the org-membership table in a FileTenantStore directory. */\nconst MEMBERS_FILE = \"__members.json\";\n\n/**\n * What the control plane stores per tenant. The model provider key is *not*\n * here — in the hosted shape that key is Frankie's own, one shared key read\n * from the environment at resolve time, so a tenant record never carries it.\n * The per-tenant secrets it does hold (the JWT verification secret the\n * customer's backend signs with, and the session-token secret) live here for a\n * self-hosted start; a hosted deployment swaps this file store for a vault.\n */\nexport interface RepositoryInstallRecord {\n repository: string;\n status: \"installing\" | \"submitted\" | \"failed\";\n runId?: string;\n pullRequestUrl?: string;\n requiresJwtSecret: boolean;\n failureMessage?: string;\n updatedAt: string;\n}\n\nexport interface TenantRecord {\n apiSpecSync?: Record<string, { repository: string; path: string; commitSha: string; syncedAt: string }>;\n skillHistory?: FrankieConfig[\"skills\"];\n id: string;\n /** Who owns this tenant — the dashboard user's stable id (e.g. \"github:42\"). */\n ownerId?: string;\n config: FrankieConfig;\n jwtSecret?: string;\n sessionSecret: string;\n /** When true, the workspace is protected: it cannot be deleted until unlocked. */\n locked?: boolean;\n /** Durable repository-install state surfaced in the dashboard and setup chat. */\n repositoryInstall?: RepositoryInstallRecord;\n createdAt: string;\n updatedAt: string;\n}\n\n/** A tenant record with its secrets stripped — safe to return from the API. */\nexport interface PublicTenant {\n apiSpecSync?: TenantRecord[\"apiSpecSync\"];\n skillHistory: FrankieConfig[\"skills\"];\n skills: TenantRecord[\"config\"][\"skills\"];\n id: string;\n ownerId?: string;\n name: string;\n publishableKey: string;\n allowedOrigins: string[];\n allowAnonymous: boolean;\n connection: \"knowledge\" | \"mcp\" | \"openapi\";\n mcpServers: string[];\n apis: string[];\n /** Full connection detail so the dashboard can show and pre-fill what's configured. */\n connections: Array<{\n kind: \"mcp\" | \"openapi\";\n name: string;\n /** OpenAPI spec URL/path. */\n spec?: string;\n /** MCP server URL. */\n url?: string;\n baseUrl?: string;\n methods?: string[];\n capabilities?: Array<{\n operationId: string;\n method: \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\";\n path: string;\n summary?: string;\n }>;\n discoveredTools?: Array<{ name: string; description?: string }>;\n selectedTools?: string[];\n exposeAsMcp?: boolean;\n forwardsToken: boolean;\n /** Whether this source is currently enabled for chat. */\n enabled: boolean;\n /** Configuration health, not a simulated runtime probe. */\n status: \"ready\" | \"configured\" | \"needs_attention\";\n }>;\n /** Protected from deletion until unlocked. */\n locked: boolean;\n /** A submitted or in-progress SDK installation for this workspace. */\n repositoryInstall?: RepositoryInstallRecord;\n /** The customer's brand theme for the widget, so the dashboard can edit it. */\n theme: TenantRecord[\"config\"][\"theme\"];\n /** Opt-in chat capabilities (attachments, model picker) for the dashboard to edit. */\n features: TenantRecord[\"config\"][\"features\"];\n createdAt: string;\n updatedAt: string;\n}\n\nexport function toPublicTenant(record: TenantRecord): PublicTenant {\n const { config } = record;\n return {\n skillHistory: record.skillHistory ?? [],\n ...(record.apiSpecSync ? { apiSpecSync: record.apiSpecSync } : {}),\n skills: config.skills,\n id: record.id,\n ...(record.ownerId ? { ownerId: record.ownerId } : {}),\n name: config.branding.name,\n publishableKey: config.auth.publishableKey,\n allowedOrigins: config.auth.allowedOrigins,\n allowAnonymous: config.auth.allowAnonymous,\n connection: config.apis.length > 0 ? \"openapi\" : config.mcpServers.length > 0 ? \"mcp\" : \"knowledge\",\n mcpServers: config.mcpServers.map((s) => s.name),\n apis: config.apis.map((a) => a.name),\n connections: [\n ...config.mcpServers.map((s) => ({\n kind: \"mcp\" as const,\n name: s.name,\n url: s.url,\n discoveredTools: s.discoveredTools,\n ...(s.selectedTools ? { selectedTools: s.selectedTools } : {}),\n forwardsToken: Boolean(s.forwardUserTokenAs),\n enabled: s.enabled && config.agent.tools.allow.some((entry) => entry === `mcp__${s.name}` || entry.startsWith(`mcp__${s.name}__`)),\n status: \"configured\" as const,\n })),\n ...config.apis.map((a) => ({\n kind: \"openapi\" as const,\n name: a.name,\n spec: a.spec,\n ...(a.baseUrl ? { baseUrl: a.baseUrl } : {}),\n methods: a.methods,\n ...(a.selectedTools ? { selectedTools: a.selectedTools } : {}),\n exposeAsMcp: a.exposeAsMcp,\n capabilities: a.capabilities.map((capability) => ({ ...capability })),\n forwardsToken: Boolean(a.forwardUserTokenAs),\n enabled: a.enabled && config.agent.tools.allow.some((entry) => entry === `api__${a.name}` || entry.startsWith(`api__${a.name}__`)),\n status: a.capabilities.length ? \"ready\" as const : \"needs_attention\" as const,\n })),\n ],\n locked: record.locked ?? false,\n ...(record.repositoryInstall ? { repositoryInstall: record.repositoryInstall } : {}),\n theme: config.theme,\n features: config.features,\n createdAt: record.createdAt,\n updatedAt: record.updatedAt,\n };\n}\n\n/**\n * A member of an org. The org is identified by its owner's id (e.g. \"github:42\"),\n * and members are named by their GitHub login (lowercased) so they can be invited\n * before they've ever signed in. `admin`s can manage members; `member`s can\n * manage the org's workspaces but not its people. The owner is always an admin\n * and is never stored here.\n */\nexport interface MembershipRecord {\n /** The org's owner id, e.g. \"github:42\". */\n org: string;\n /** The member's GitHub login, lowercased. */\n login: string;\n role: \"admin\" | \"member\";\n invitedBy?: string;\n createdAt: string;\n}\n\n/**\n * A `TenantStore` the control plane can also write to and list. The data plane\n * only needs `byPublishableKey`; the control plane needs the rest. Both the\n * file and Postgres stores implement this, so the admin API is written once\n * against the interface.\n */\nexport interface PlatformAdminRecord {\n ownerId: string;\n login?: string;\n grantedBy: string;\n createdAt: string;\n}\n\nexport interface ControlStore extends TenantStore {\n listOrganizations(): Promise<string[]>;\n listPlatformAdmins(): Promise<PlatformAdminRecord[]>;\n getPlatformAdmin(ownerId: string): Promise<PlatformAdminRecord | null>;\n putPlatformAdmin(admin: PlatformAdminRecord): Promise<void>;\n deletePlatformAdmin(ownerId: string): Promise<void>;\n compareAndSwap(previous: TenantRecord, next: TenantRecord): Promise<boolean>;\n list(ownerId?: string): Promise<TenantRecord[]>;\n get(id: string): Promise<TenantRecord | null>;\n put(record: TenantRecord): Promise<void>;\n /** Remove a tenant for good. A no-op if it does not exist. */\n delete(id: string): Promise<void>;\n /** All members of the org owned by `org`. */\n listMembers(org: string): Promise<MembershipRecord[]>;\n /** Add or update a member. */\n putMember(member: MembershipRecord): Promise<void>;\n /** Remove a member from an org. A no-op if absent. */\n deleteMember(org: string, login: string): Promise<void>;\n /** Every org membership held by a given login, across all orgs. */\n membershipsForLogin(login: string): Promise<MembershipRecord[]>;\n}\n\n/**\n * The control plane's persistence, and at the same time a `TenantStore` the\n * data plane can serve from — so what the dashboard writes, the agent reads.\n *\n * `MemoryTenantStore` is fine for tests; this one survives a restart by writing\n * one JSON file per tenant under a directory. The provider key is injected from\n * the environment at read time, keeping it out of the tenant files entirely.\n */\nexport class FileTenantStore implements ControlStore {\n async listOrganizations(): Promise<string[]> {\n await this.load();\n return [...new Set([...this.byId.values()].flatMap(record => record.ownerId ? [record.ownerId] : []).concat([...this.members.values()].map(member => member.org)))].sort();\n }\n private adminPath(ownerId: string): string {\n if (!/^github:[1-9]\\d*$/.test(ownerId)) throw new Error(\"Invalid platform user ID.\");\n return join(this.dir, \"platform-admins\", ownerId.slice(7) + \".json\");\n }\n\n async getPlatformAdmin(ownerId: string): Promise<PlatformAdminRecord | null> {\n if (!/^github:[1-9]\\d*$/.test(ownerId)) return null;\n try { return JSON.parse(await readFile(this.adminPath(ownerId), \"utf8\")) as PlatformAdminRecord; }\n catch (error) { if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return null; throw error; }\n }\n\n async listPlatformAdmins(): Promise<PlatformAdminRecord[]> {\n const dir = join(this.dir, \"platform-admins\");\n await mkdir(dir, { recursive: true });\n const files = (await readdir(dir)).filter(name => /^[1-9]\\d*\\.json$/.test(name));\n const records = await Promise.all(files.map(file => this.getPlatformAdmin(\"github:\" + file.slice(0, -5))));\n return records.filter((record): record is PlatformAdminRecord => Boolean(record));\n }\n\n async putPlatformAdmin(admin: PlatformAdminRecord): Promise<void> {\n const path = this.adminPath(admin.ownerId);\n await mkdir(join(this.dir, \"platform-admins\"), { recursive: true });\n const temporary = path + \".\" + randomUUID() + \".tmp\";\n await writeFile(temporary, JSON.stringify(admin), { mode: 0o600 });\n await rename(temporary, path);\n }\n\n async deletePlatformAdmin(ownerId: string): Promise<void> {\n await rm(this.adminPath(ownerId), { force: true });\n }\n\n private readonly dir: string;\n private readonly providerKeyEnv: string;\n private readonly env: NodeJS.ProcessEnv;\n private index = new Map<string, TenantRecord>(); // by publishable key\n private byId = new Map<string, TenantRecord>();\n private members = new Map<string, MembershipRecord>(); // by `${org}\\n${login}`\n private loaded = false;\n\n private static memberKey(org: string, login: string): string {\n return `${org}\\n${login.toLowerCase()}`;\n }\n\n constructor(options: { dir: string; providerKeyEnv?: string; env?: NodeJS.ProcessEnv }) {\n this.dir = resolve(options.dir);\n this.providerKeyEnv = options.providerKeyEnv ?? \"FIREWORKS_API_KEY\";\n this.env = options.env ?? process.env;\n }\n\n private async load(): Promise<void> {\n if (this.loaded) return;\n await mkdir(this.dir, { recursive: true });\n const files = await readdir(this.dir).catch(() => [] as string[]);\n for (const file of files) {\n if (!file.endsWith(\".json\")) continue;\n if (file === MEMBERS_FILE) continue;\n try {\n const record = JSON.parse(await readFile(join(this.dir, file), \"utf8\")) as TenantRecord;\n record.config = parseFrankieConfig(record.config);\n this.remember(record);\n } catch {\n // A malformed tenant file must not take the whole fleet down.\n }\n }\n try {\n const raw = await readFile(join(this.dir, MEMBERS_FILE), \"utf8\");\n for (const member of JSON.parse(raw) as MembershipRecord[]) {\n this.members.set(FileTenantStore.memberKey(member.org, member.login), member);\n }\n } catch {\n // No members file yet, or malformed — start with none.\n }\n this.loaded = true;\n }\n\n private async persistMembers(): Promise<void> {\n await mkdir(this.dir, { recursive: true });\n await writeFile(join(this.dir, MEMBERS_FILE), JSON.stringify([...this.members.values()], null, 2), \"utf8\");\n }\n\n async listMembers(org: string): Promise<MembershipRecord[]> {\n await this.load();\n return [...this.members.values()].filter((m) => m.org === org).sort((a, b) => a.login.localeCompare(b.login));\n }\n\n async putMember(member: MembershipRecord): Promise<void> {\n await this.load();\n this.members.set(FileTenantStore.memberKey(member.org, member.login), { ...member, login: member.login.toLowerCase() });\n await this.persistMembers();\n }\n\n async deleteMember(org: string, login: string): Promise<void> {\n await this.load();\n if (this.members.delete(FileTenantStore.memberKey(org, login))) await this.persistMembers();\n }\n\n async membershipsForLogin(login: string): Promise<MembershipRecord[]> {\n await this.load();\n const lower = login.toLowerCase();\n return [...this.members.values()].filter((m) => m.login === lower);\n }\n\n private remember(record: TenantRecord): void {\n this.byId.set(record.id, record);\n this.index.set(record.config.auth.publishableKey, record);\n }\n\n async byPublishableKey(key: string): Promise<ResolvedConfig | null> {\n await this.load();\n const record = this.index.get(key);\n if (!record) return null;\n const providerApiKey = this.env[record.config.model.apiKeyEnv] ?? this.env[this.providerKeyEnv];\n if (!providerApiKey) return null;\n return {\n config: record.config,\n secrets: {\n ...(record.jwtSecret ? { jwtSecret: record.jwtSecret } : {}),\n sessionSecret: record.sessionSecret,\n providerApiKey,\n },\n };\n }\n\n /** All tenants, or — when an owner is given — only that owner's. */\n async list(ownerId?: string): Promise<TenantRecord[]> {\n await this.load();\n return [...this.byId.values()]\n .filter((record) => ownerId === undefined || record.ownerId === ownerId)\n .sort((a, b) => a.createdAt.localeCompare(b.createdAt));\n }\n\n async get(id: string): Promise<TenantRecord | null> {\n await this.load();\n return this.byId.get(id) ?? null;\n }\n\n async put(record: TenantRecord): Promise<void> {\n await this.load();\n // A publishable key may have changed owners only if it's not another tenant's.\n const existing = this.index.get(record.config.auth.publishableKey);\n if (existing && existing.id !== record.id) {\n throw new Error(`Publishable key already belongs to tenant \"${existing.id}\".`);\n }\n this.remember(record);\n await mkdir(this.dir, { recursive: true });\n await writeFile(join(this.dir, `${record.id}.json`), JSON.stringify(record, null, 2), \"utf8\");\n }\n\n async compareAndSwap(previous: TenantRecord, next: TenantRecord): Promise<boolean> {\n await this.load();\n if (this.byId.get(previous.id) !== previous) return false;\n this.remember(next); // reserve synchronously before file IO\n await mkdir(this.dir, { recursive: true });\n await writeFile(join(this.dir, `${next.id}.json`), JSON.stringify(next, null, 2), \"utf8\");\n return true;\n }\n\n async delete(id: string): Promise<void> {\n await this.load();\n const record = this.byId.get(id);\n if (!record) return;\n this.byId.delete(id);\n this.index.delete(record.config.auth.publishableKey);\n await rm(join(this.dir, `${id}.json`), { force: true });\n }\n}\n","import { parseFrankieConfig } from \"@frankieai/core\";\nimport type { ResolvedConfig } from \"../config.js\";\nimport type { ControlStore, MembershipRecord, PlatformAdminRecord, TenantRecord } from \"./store.js\";\n\n/**\n * The minimal surface of a Postgres client (node-postgres `Pool` or a test\n * double). Keeping the store to this interface means it never imports `pg`\n * directly, so it runs against an in-memory Postgres in tests and a real one in\n * production without changing a line.\n */\nexport interface Queryable {\n query(text: string, params?: unknown[]): Promise<{ rows: Array<Record<string, unknown>> }>;\n}\n\n/**\n * A `TenantStore` backed by Postgres — the production shape. The web app, the\n * control plane and the data plane run as separate services (Vercel + Cloud\n * Run) that share no filesystem, so tenants live in a database they all reach.\n *\n * One table, the full record as JSON with the two lookup keys pulled out as\n * columns. The model provider key is still injected from the environment at\n * read time and never written to the row.\n */\nexport class PostgresTenantStore implements ControlStore {\n private readonly snapshots = new WeakMap<TenantRecord, string>();\n private readonly db: Queryable;\n private readonly providerKeyEnv: string;\n private readonly env: NodeJS.ProcessEnv;\n private ready?: Promise<void>;\n\n constructor(options: { db: Queryable; providerKeyEnv?: string; env?: NodeJS.ProcessEnv }) {\n this.db = options.db;\n this.providerKeyEnv = options.providerKeyEnv ?? \"FIREWORKS_API_KEY\";\n this.env = options.env ?? process.env;\n }\n\n /**\n * Create the table on first use, once. Lazy so the server can start listening\n * before the database is reachable — a failed connection then surfaces on a\n * request (logged, a 502) instead of crashing boot on Cloud Run.\n */\n private ensureReady(): Promise<void> {\n return (this.ready ??= this.init());\n }\n\n /** Create the table if it isn't there yet. Safe to call repeatedly. */\n async init(): Promise<void> {\n await this.db.query(`CREATE TABLE IF NOT EXISTS frankie_platform_admins (\n owner_id text PRIMARY KEY, login text, granted_by text NOT NULL, created_at text NOT NULL\n )`);\n await this.db.query(\n `CREATE TABLE IF NOT EXISTS frankie_tenants (\n id text PRIMARY KEY,\n owner_id text,\n publishable_key text NOT NULL UNIQUE,\n record text NOT NULL,\n created_at text NOT NULL,\n updated_at text NOT NULL\n )`,\n );\n await this.db.query(`CREATE INDEX IF NOT EXISTS frankie_tenants_owner ON frankie_tenants (owner_id)`);\n await this.db.query(\n `CREATE TABLE IF NOT EXISTS frankie_org_members (\n org text NOT NULL,\n login text NOT NULL,\n role text NOT NULL,\n invited_by text,\n created_at text NOT NULL,\n PRIMARY KEY (org, login)\n )`,\n );\n await this.db.query(`CREATE INDEX IF NOT EXISTS frankie_org_members_login ON frankie_org_members (login)`);\n }\n\n private memberRow(row: Record<string, unknown>): MembershipRecord {\n return {\n org: String(row.org),\n login: String(row.login),\n role: row.role === \"admin\" ? \"admin\" : \"member\",\n ...(row.invited_by ? { invitedBy: String(row.invited_by) } : {}),\n createdAt: String(row.created_at),\n };\n }\n\n private adminRow(row: Record<string, unknown>): PlatformAdminRecord {\n return { ownerId: String(row.owner_id), ...(row.login ? { login: String(row.login) } : {}), grantedBy: String(row.granted_by), createdAt: String(row.created_at) };\n }\n\n async listOrganizations(): Promise<string[]> {\n await this.ensureReady();\n const { rows } = await this.db.query(\"SELECT owner_id AS org FROM frankie_tenants WHERE owner_id IS NOT NULL UNION SELECT org FROM frankie_org_members\");\n return rows.map(row => String(row.org)).sort();\n }\n\n async listPlatformAdmins(): Promise<PlatformAdminRecord[]> {\n await this.ensureReady();\n const { rows } = await this.db.query(\"SELECT * FROM frankie_platform_admins ORDER BY created_at ASC\");\n return rows.map(row => this.adminRow(row));\n }\n\n async getPlatformAdmin(ownerId: string): Promise<PlatformAdminRecord | null> {\n await this.ensureReady();\n const { rows } = await this.db.query(\"SELECT * FROM frankie_platform_admins WHERE owner_id = $1\", [ownerId]);\n return rows[0] ? this.adminRow(rows[0]) : null;\n }\n\n async putPlatformAdmin(admin: PlatformAdminRecord): Promise<void> {\n await this.ensureReady();\n await this.db.query(`INSERT INTO frankie_platform_admins (owner_id, login, granted_by, created_at)\n VALUES ($1, $2, $3, $4) ON CONFLICT (owner_id) DO NOTHING`,\n [admin.ownerId, admin.login ?? null, admin.grantedBy, admin.createdAt]);\n }\n\n async deletePlatformAdmin(ownerId: string): Promise<void> {\n await this.ensureReady();\n await this.db.query(\"DELETE FROM frankie_platform_admins WHERE owner_id = $1\", [ownerId]);\n }\n\n async listMembers(org: string): Promise<MembershipRecord[]> {\n await this.ensureReady();\n const { rows } = await this.db.query(\n `SELECT * FROM frankie_org_members WHERE org = $1 ORDER BY login ASC`,\n [org],\n );\n return rows.map((row) => this.memberRow(row));\n }\n\n async putMember(member: MembershipRecord): Promise<void> {\n await this.ensureReady();\n await this.db.query(\n `INSERT INTO frankie_org_members (org, login, role, invited_by, created_at)\n VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (org, login) DO UPDATE SET role = EXCLUDED.role`,\n [member.org, member.login.toLowerCase(), member.role, member.invitedBy ?? null, member.createdAt],\n );\n }\n\n async deleteMember(org: string, login: string): Promise<void> {\n await this.ensureReady();\n await this.db.query(`DELETE FROM frankie_org_members WHERE org = $1 AND login = $2`, [org, login.toLowerCase()]);\n }\n\n async membershipsForLogin(login: string): Promise<MembershipRecord[]> {\n await this.ensureReady();\n const { rows } = await this.db.query(`SELECT * FROM frankie_org_members WHERE login = $1`, [login.toLowerCase()]);\n return rows.map((row) => this.memberRow(row));\n }\n\n private parse(row: Record<string, unknown>): TenantRecord {\n const record = JSON.parse(String(row.record)) as TenantRecord;\n this.snapshots.set(record, String(row.record));\n record.config = parseFrankieConfig(record.config);\n return record;\n }\n\n async byPublishableKey(key: string): Promise<ResolvedConfig | null> {\n await this.ensureReady();\n const { rows } = await this.db.query(`SELECT record FROM frankie_tenants WHERE publishable_key = $1`, [key]);\n if (rows.length === 0 || !rows[0]) return null;\n const record = this.parse(rows[0]);\n const providerApiKey = this.env[record.config.model.apiKeyEnv] ?? this.env[this.providerKeyEnv];\n if (!providerApiKey) return null;\n return {\n config: record.config,\n secrets: {\n ...(record.jwtSecret ? { jwtSecret: record.jwtSecret } : {}),\n sessionSecret: record.sessionSecret,\n providerApiKey,\n },\n };\n }\n\n async list(ownerId?: string): Promise<TenantRecord[]> {\n await this.ensureReady();\n const { rows } = await this.db.query(\n `SELECT record FROM frankie_tenants\n WHERE $1::text IS NULL OR owner_id = $1\n ORDER BY created_at ASC`,\n [ownerId ?? null],\n );\n return rows.map((row) => this.parse(row));\n }\n\n async get(id: string): Promise<TenantRecord | null> {\n await this.ensureReady();\n const { rows } = await this.db.query(`SELECT record FROM frankie_tenants WHERE id = $1`, [id]);\n return rows.length && rows[0] ? this.parse(rows[0]) : null;\n }\n\n async put(record: TenantRecord): Promise<void> {\n await this.ensureReady();\n const key = record.config.auth.publishableKey;\n const clash = await this.db.query(`SELECT id FROM frankie_tenants WHERE publishable_key = $1 AND id <> $2`, [\n key,\n record.id,\n ]);\n if (clash.rows.length > 0) {\n throw new Error(`Publishable key already belongs to tenant \"${String(clash.rows[0]?.id)}\".`);\n }\n await this.db.query(\n `INSERT INTO frankie_tenants (id, owner_id, publishable_key, record, created_at, updated_at)\n VALUES ($1, $2, $3, $4, $5, $6)\n ON CONFLICT (id) DO UPDATE SET\n owner_id = EXCLUDED.owner_id,\n publishable_key = EXCLUDED.publishable_key,\n record = EXCLUDED.record,\n updated_at = EXCLUDED.updated_at`,\n [record.id, record.ownerId ?? null, key, JSON.stringify(record), record.createdAt, record.updatedAt],\n );\n }\n\n async delete(id: string): Promise<void> {\n await this.ensureReady();\n await this.db.query(`DELETE FROM frankie_tenants WHERE id = $1`, [id]);\n }\n\n async compareAndSwap(previous: TenantRecord, next: TenantRecord): Promise<boolean> {\n await this.ensureReady();\n const snapshot = this.snapshots.get(previous);\n if (!snapshot || next.id !== previous.id || next.config.auth.publishableKey !== previous.config.auth.publishableKey) return false;\n const { rows } = await this.db.query(\"UPDATE frankie_tenants SET record = $1, updated_at = $2 WHERE id = $3 AND record = $4 RETURNING id\", [JSON.stringify(next), next.updatedAt, previous.id, snapshot]);\n return rows.length === 1;\n }\n}\n","import { randomUUID } from \"node:crypto\";\nimport type { AgentMessage } from \"@earendil-works/pi-agent-core\";\nimport type { VerifiedUser } from \"./auth.js\";\n\nexport interface ChatSession {\n id: string;\n tenantId: string;\n user: VerifiedUser;\n /** Raw host-signed token, forwarded only to tenant tools that explicitly opt in. */\n userToken?: string;\n origin?: string;\n context: Record<string, string | number | boolean | null>;\n /** Agent transcript used to continue the model conversation on the server. */\n messages: AgentMessage[];\n /** UI transcript kept separately so the exact rendered chat can sync across devices. */\n clientMessages?: unknown[];\n /** User-editable title, otherwise derived from the first user message. */\n title?: string;\n createdAt: number;\n lastActiveAt: number;\n messageCount: number;\n costUsd: number;\n busy: boolean;\n}\n\nexport interface SessionStore {\n create(session: ChatSession): Promise<void>;\n get(id: string): Promise<ChatSession | undefined>;\n /** List one user's conversations for a tenant, newest first. */\n listByUser(tenantId: string, userId: string): Promise<ChatSession[]>;\n /** Workspace-owner history view. This is only exposed through the authenticated control plane. */\n listByTenant(tenantId: string): Promise<ChatSession[]>;\n update(id: string, patch: Partial<ChatSession>): Promise<void>;\n delete(id: string): Promise<void>;\n}\n\nexport class MemorySessionStore implements SessionStore {\n private readonly sessions = new Map<string, ChatSession>();\n\n constructor(private readonly ttlMs = 6 * 60 * 60 * 1000) {}\n\n async create(session: ChatSession): Promise<void> {\n this.evictExpired();\n this.sessions.set(session.id, session);\n }\n\n async get(id: string): Promise<ChatSession | undefined> {\n const session = this.sessions.get(id);\n if (!session) return undefined;\n if (Date.now() - session.lastActiveAt > this.ttlMs) {\n this.sessions.delete(id);\n return undefined;\n }\n return session;\n }\n\n async listByUser(tenantId: string, userId: string): Promise<ChatSession[]> {\n this.evictExpired();\n return [...this.sessions.values()]\n .filter((session) => session.tenantId === tenantId && session.user.id === userId)\n .sort((a, b) => b.lastActiveAt - a.lastActiveAt);\n }\n\n async listByTenant(tenantId: string): Promise<ChatSession[]> {\n this.evictExpired();\n return [...this.sessions.values()]\n .filter((session) => session.tenantId === tenantId)\n .sort((a, b) => b.lastActiveAt - a.lastActiveAt);\n }\n\n async update(id: string, patch: Partial<ChatSession>): Promise<void> {\n const session = this.sessions.get(id);\n if (!session) return;\n this.sessions.set(id, { ...session, ...patch, lastActiveAt: Date.now() });\n }\n\n async delete(id: string): Promise<void> {\n this.sessions.delete(id);\n }\n\n private evictExpired(): void {\n const cutoff = Date.now() - this.ttlMs;\n for (const [id, session] of this.sessions) if (session.lastActiveAt < cutoff) this.sessions.delete(id);\n }\n}\n\nexport function newSessionId(): string {\n return randomUUID();\n}\n","import type { ChatSession, SessionStore } from \"./sessions.js\";\nimport type { Queryable } from \"./control/postgres-store.js\";\n\n/**\n * Hosted session + conversation store. Transcript and UI metadata live together\n * in Postgres, making a signed-in user's chats available from any device.\n */\nexport class PostgresSessionStore implements SessionStore {\n private readonly db: Queryable;\n private readonly ttlMs: number;\n private ready?: Promise<void>;\n\n constructor(options: { db: Queryable; ttlMs?: number }) {\n this.db = options.db;\n // Conversation history should outlive short-lived session JWTs. A resumed\n // conversation receives a fresh session token after the user signs in.\n this.ttlMs = options.ttlMs ?? 90 * 24 * 60 * 60 * 1000;\n }\n\n private ensureReady(): Promise<void> {\n return (this.ready ??= this.init());\n }\n\n async init(): Promise<void> {\n await this.db.query(\n `CREATE TABLE IF NOT EXISTS frankie_sessions (\n id text PRIMARY KEY,\n data text NOT NULL,\n last_active bigint NOT NULL\n )`,\n );\n }\n\n async create(session: ChatSession): Promise<void> {\n await this.ensureReady();\n await this.db.query(\n `INSERT INTO frankie_sessions (id, data, last_active) VALUES ($1, $2, $3)\n ON CONFLICT (id) DO UPDATE SET data = EXCLUDED.data, last_active = EXCLUDED.last_active`,\n [session.id, JSON.stringify(session), session.lastActiveAt],\n );\n }\n\n async get(id: string): Promise<ChatSession | undefined> {\n await this.ensureReady();\n const { rows } = await this.db.query(`SELECT data, last_active FROM frankie_sessions WHERE id = $1`, [id]);\n if (rows.length === 0 || !rows[0]) return undefined;\n if (Date.now() - Number(rows[0].last_active) > this.ttlMs) {\n await this.delete(id);\n return undefined;\n }\n return JSON.parse(String(rows[0].data)) as ChatSession;\n }\n\n async listByUser(tenantId: string, userId: string): Promise<ChatSession[]> {\n await this.ensureReady();\n const { rows } = await this.db.query(`SELECT data, last_active FROM frankie_sessions ORDER BY last_active DESC`);\n const sessions: ChatSession[] = [];\n for (const row of rows) {\n if (Date.now() - Number(row.last_active) > this.ttlMs) continue;\n const session = JSON.parse(String(row.data)) as ChatSession;\n if (session.tenantId === tenantId && session.user.id === userId) sessions.push(session);\n }\n return sessions;\n }\n\n async listByTenant(tenantId: string): Promise<ChatSession[]> {\n await this.ensureReady();\n const { rows } = await this.db.query(`SELECT data, last_active FROM frankie_sessions ORDER BY last_active DESC`);\n const sessions: ChatSession[] = [];\n for (const row of rows) {\n if (Date.now() - Number(row.last_active) > this.ttlMs) continue;\n const session = JSON.parse(String(row.data)) as ChatSession;\n if (session.tenantId === tenantId) sessions.push(session);\n }\n return sessions;\n }\n\n async update(id: string, patch: Partial<ChatSession>): Promise<void> {\n const existing = await this.get(id);\n if (!existing) return;\n const next: ChatSession = { ...existing, ...patch, lastActiveAt: Date.now() };\n await this.db.query(`UPDATE frankie_sessions SET data = $2, last_active = $3 WHERE id = $1`, [\n id,\n JSON.stringify(next),\n next.lastActiveAt,\n ]);\n }\n\n async delete(id: string): Promise<void> {\n await this.ensureReady();\n await this.db.query(`DELETE FROM frankie_sessions WHERE id = $1`, [id]);\n }\n}\n","import { FileTenantStore, type ControlStore } from \"./store.js\";\nimport { PostgresTenantStore } from \"./postgres-store.js\";\nimport { MemorySessionStore, type SessionStore } from \"../sessions.js\";\nimport { PostgresSessionStore } from \"../postgres-sessions.js\";\n\nexport interface ResolvedStore {\n store: ControlStore;\n describe: string;\n}\n\nexport interface ResolvedStores {\n tenants: ControlStore;\n sessions: SessionStore;\n describe: string;\n}\n\n/**\n * Both stores the data plane needs, sharing one Postgres pool when\n * DATABASE_URL is set (so tenant config and chat transcripts live in the same\n * database), or a file tenant store + in-memory sessions otherwise.\n */\nexport async function resolveStores(env: NodeJS.ProcessEnv = process.env): Promise<ResolvedStores> {\n const url = env[\"DATABASE_URL\"];\n if (url) {\n const { Pool } = await import(\"pg\");\n const pool = new Pool({\n connectionString: url,\n max: 4,\n ...(env[\"DATABASE_SSL\"] === \"disable\" ? {} : { ssl: { rejectUnauthorized: false } }),\n });\n // Stores connect lazily on first use, so the server can start listening\n // before the database is reachable (Cloud Run kills a slow-to-listen boot).\n const tenants = new PostgresTenantStore({ db: pool, env });\n const sessions = new PostgresSessionStore({ db: pool });\n return { tenants, sessions, describe: \"postgres\" };\n }\n\n const dir = env[\"FRANKIE_TENANTS_DIR\"] ?? \"./.frankie/tenants\";\n return { tenants: new FileTenantStore({ dir, env }), sessions: new MemorySessionStore(), describe: `files:${dir}` };\n}\n\n/**\n * Pick the tenant store from the environment, so the same binary runs in every\n * shape:\n *\n * - DATABASE_URL set → Postgres, the production shape. The web app (Vercel),\n * the control plane and the data plane (Cloud Run) share no filesystem, so\n * tenants live in a database all three reach.\n * - otherwise → a file under FRANKIE_TENANTS_DIR, for a single host or local\n * development, where one persistent disk is shared.\n */\nexport async function resolveTenantStore(env: NodeJS.ProcessEnv = process.env): Promise<ResolvedStore> {\n const url = env[\"DATABASE_URL\"];\n if (url) {\n // Imported lazily so a file-store deployment never has to install `pg`.\n const { Pool } = await import(\"pg\");\n const pool = new Pool({\n connectionString: url,\n // Managed Postgres (Neon, Supabase, Cloud SQL over the proxy) expects TLS;\n // opt out only for a local database via DATABASE_SSL=disable.\n ...(env[\"DATABASE_SSL\"] === \"disable\" ? {} : { ssl: { rejectUnauthorized: false } }),\n });\n const store = new PostgresTenantStore({ db: pool, env }); // lazy init on first use\n return { store, describe: \"postgres\" };\n }\n\n const dir = env[\"FRANKIE_TENANTS_DIR\"] ?? \"./.frankie/tenants\";\n return { store: new FileTenantStore({ dir, env }), describe: `files:${dir}` };\n}\n"],"mappings":"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","names":["resolve","source","Type","describeTool","evaluateToolPolicy","evaluateToolPolicy","describeTool","Type","readFile","parseFrankieConfig","randomUUID"]}
|