@aprovan/chat-backend 0.1.0-dev.879ed82 → 0.1.0-dev.93c7b6a
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/.turbo/turbo-build.log +6 -6
- package/dist/index.js +6 -35
- package/dist/index.js.map +1 -1
- package/dist/lambda.js +6 -35
- package/dist/lambda.js.map +1 -1
- package/package.json +1 -1
- package/src/gateway-session.ts +1 -38
- package/src/routes/chat.ts +6 -18
- package/test/gateway-session.test.ts +0 -79
- package/test/routes/chat.test.ts +0 -50
package/.turbo/turbo-build.log
CHANGED
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
[34mCLI[39m Target: node20
|
|
10
10
|
[34mCLI[39m Cleaning output folder
|
|
11
11
|
[34mESM[39m Build start
|
|
12
|
-
[32mESM[39m [1mdist/index.js [22m[
|
|
13
|
-
[32mESM[39m [1mdist/lambda.js [22m[
|
|
14
|
-
[32mESM[39m [1mdist/index.js.map [22m[
|
|
15
|
-
[32mESM[39m [1mdist/lambda.js.map [22m[
|
|
16
|
-
[32mESM[39m ⚡️ Build success in
|
|
12
|
+
[32mESM[39m [1mdist/index.js [22m[32m32.89 KB[39m
|
|
13
|
+
[32mESM[39m [1mdist/lambda.js [22m[32m32.57 KB[39m
|
|
14
|
+
[32mESM[39m [1mdist/index.js.map [22m[32m59.37 KB[39m
|
|
15
|
+
[32mESM[39m [1mdist/lambda.js.map [22m[32m58.51 KB[39m
|
|
16
|
+
[32mESM[39m ⚡️ Build success in 155ms
|
|
17
17
|
[34mDTS[39m Build start
|
|
18
|
-
[32mDTS[39m ⚡️ Build success in
|
|
18
|
+
[32mDTS[39m ⚡️ Build success in 14442ms
|
|
19
19
|
[32mDTS[39m [1mdist/index.d.ts [22m[32m2.55 KB[39m
|
|
20
20
|
[32mDTS[39m [1mdist/lambda.d.ts [22m[32m171.00 B[39m
|
package/dist/index.js
CHANGED
|
@@ -492,13 +492,6 @@ var EDIT_PROMPT_ID = "edit-patchwork-widget";
|
|
|
492
492
|
|
|
493
493
|
// src/gateway-session.ts
|
|
494
494
|
var sessionCache = /* @__PURE__ */ new Map();
|
|
495
|
-
var toolsCache = /* @__PURE__ */ new Map();
|
|
496
|
-
function getCachedTools(sub) {
|
|
497
|
-
return toolsCache.get(sub);
|
|
498
|
-
}
|
|
499
|
-
function setCachedTools(sub, tools) {
|
|
500
|
-
toolsCache.set(sub, tools);
|
|
501
|
-
}
|
|
502
495
|
function gatewayUrl() {
|
|
503
496
|
const url = process.env["GATEWAY_URL"];
|
|
504
497
|
if (!url) throw new Error("GATEWAY_URL is not set");
|
|
@@ -516,13 +509,7 @@ async function getGatewaySession(claims, workspaceId, cognitoToken) {
|
|
|
516
509
|
return entry;
|
|
517
510
|
}
|
|
518
511
|
async function exchangeSession(cognitoToken, workspaceId, exp) {
|
|
519
|
-
|
|
520
|
-
try {
|
|
521
|
-
res = await callAuthSessions(cognitoToken, workspaceId);
|
|
522
|
-
} catch {
|
|
523
|
-
await new Promise((r) => setTimeout(r, 200));
|
|
524
|
-
res = await callAuthSessions(cognitoToken, workspaceId);
|
|
525
|
-
}
|
|
512
|
+
const res = await callAuthSessions(cognitoToken, workspaceId);
|
|
526
513
|
if (res.status === 401) {
|
|
527
514
|
const retryRes = await callAuthSessions(cognitoToken, workspaceId);
|
|
528
515
|
if (!retryRes.ok) {
|
|
@@ -530,14 +517,6 @@ async function exchangeSession(cognitoToken, workspaceId, exp) {
|
|
|
530
517
|
}
|
|
531
518
|
return { token: cognitoToken, expires_at: exp };
|
|
532
519
|
}
|
|
533
|
-
if (res.status >= 500) {
|
|
534
|
-
await new Promise((r) => setTimeout(r, 200));
|
|
535
|
-
const retryRes = await callAuthSessions(cognitoToken, workspaceId);
|
|
536
|
-
if (!retryRes.ok) {
|
|
537
|
-
throw new GatewaySessionError(retryRes.status, await retryRes.text());
|
|
538
|
-
}
|
|
539
|
-
return { token: cognitoToken, expires_at: exp };
|
|
540
|
-
}
|
|
541
520
|
if (!res.ok) {
|
|
542
521
|
throw new GatewaySessionError(res.status, await res.text());
|
|
543
522
|
}
|
|
@@ -555,7 +534,6 @@ async function callAuthSessions(cognitoToken, workspaceId) {
|
|
|
555
534
|
}
|
|
556
535
|
function evictGatewaySession(sub) {
|
|
557
536
|
sessionCache.delete(sub);
|
|
558
|
-
toolsCache.delete(sub);
|
|
559
537
|
}
|
|
560
538
|
var GatewaySessionError = class extends Error {
|
|
561
539
|
constructor(status, message) {
|
|
@@ -782,18 +760,11 @@ chatRoute.post("/", async (c) => {
|
|
|
782
760
|
}
|
|
783
761
|
let gatewayTools = [];
|
|
784
762
|
if (sessionToken && gatewayUrl2) {
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
() => []
|
|
791
|
-
);
|
|
792
|
-
if (gatewayTools.length === 0) {
|
|
793
|
-
evictGatewaySession(claims.sub);
|
|
794
|
-
} else {
|
|
795
|
-
setCachedTools(claims.sub, gatewayTools);
|
|
796
|
-
}
|
|
763
|
+
gatewayTools = await fetchGatewayTools(gatewayUrl2, sessionToken).catch(
|
|
764
|
+
() => []
|
|
765
|
+
);
|
|
766
|
+
if (gatewayTools.length === 0) {
|
|
767
|
+
evictGatewaySession(claims.sub);
|
|
797
768
|
}
|
|
798
769
|
}
|
|
799
770
|
const tools = sessionToken && gatewayUrl2 ? buildTools(gatewayTools, gatewayUrl2, sessionToken) : {};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/app.ts","../src/middleware/auth.ts","../src/middleware/workspace.ts","../src/middleware/plan.ts","../src/routes/health.ts","../src/routes/chat.ts","../src/fallback-prompts.ts","../src/gateway-session.ts","../src/posthog.ts","../src/providers/openrouter.ts","../src/tool-docs.ts","../src/routes/edit.ts","../src/routes/services.ts","../src/routes/proxy.ts","../src/env.ts","../src/lambda.ts"],"sourcesContent":["import { serve } from \"@hono/node-server\";\nimport { createChatApp, initPostHog } from \"./app.js\";\nimport { parseEnv } from \"./env.js\";\n\nexport { createChatApp, initPostHog } from \"./app.js\";\nexport type { ChatApp } from \"./app.js\";\nexport { handler } from \"./lambda.js\";\nexport type { LambdaEvent, LambdaContext } from \"hono/aws-lambda\";\n\nconst env = parseEnv(process.env);\n\ninitPostHog(env);\n\nif (env.NODE_ENV !== \"test\") {\n const app = createChatApp();\n serve({ fetch: app.fetch, port: env.PORT }, () => {\n console.log(`Chat API listening on :${env.PORT}`);\n });\n}\n","import { Hono } from \"hono\";\nimport { authMiddleware } from \"./middleware/auth.js\";\nimport { workspaceMiddleware } from \"./middleware/workspace.js\";\nimport { planMiddleware } from \"./middleware/plan.js\";\nimport { health } from \"./routes/health.js\";\nimport { chatRoute } from \"./routes/chat.js\";\nimport { editRoute } from \"./routes/edit.js\";\nimport { services } from \"./routes/services.js\";\nimport { proxy } from \"./routes/proxy.js\";\nimport type { AppVariables } from \"./types.js\";\n\nexport { initPostHog } from \"./posthog.js\";\n\nexport function createChatApp() {\n const app = new Hono<{ Variables: AppVariables }>();\n\n // Unauthenticated routes\n app.route(\"/\", health);\n\n // Protected route group: auth → workspace → plan\n const api = app.basePath(\"/api\");\n api.use(authMiddleware, workspaceMiddleware, planMiddleware);\n api.route(\"/chat\", chatRoute);\n api.route(\"/edit\", editRoute);\n api.route(\"/services\", services);\n api.route(\"/proxy\", proxy);\n\n return app;\n}\n\nexport type ChatApp = ReturnType<typeof createChatApp>;\n","import { CognitoJwtVerifier } from \"aws-jwt-verify\";\nimport type { CognitoAccessTokenPayload } from \"aws-jwt-verify/jwt-model\";\nimport type { MiddlewareHandler } from \"hono\";\nimport type { AppVariables } from \"../types\";\n\ninterface JwtVerifier {\n verify(token: string): Promise<CognitoAccessTokenPayload>;\n}\n\nlet verifier: JwtVerifier | null = null;\n\nfunction getVerifier(): JwtVerifier {\n if (!verifier) {\n verifier = CognitoJwtVerifier.create({\n userPoolId: process.env[\"COGNITO_USER_POOL_ID\"]!,\n clientId: process.env[\"COGNITO_CLIENT_ID\"]!,\n tokenUse: \"access\",\n });\n }\n return verifier;\n}\n\nexport const authMiddleware: MiddlewareHandler<{ Variables: AppVariables }> =\n async (c, next) => {\n const authHeader = c.req.header(\"Authorization\");\n if (!authHeader?.startsWith(\"Bearer \")) {\n return c.json({ error: \"Unauthorized\" }, 401);\n }\n\n const token = authHeader.slice(7);\n try {\n const payload = await getVerifier().verify(token);\n c.set(\"claims\", payload);\n return next();\n } catch {\n return c.json({ error: \"Unauthorized\" }, 401);\n }\n };\n","import { DynamoDBClient } from \"@aws-sdk/client-dynamodb\";\nimport { DynamoDBDocumentClient, QueryCommand } from \"@aws-sdk/lib-dynamodb\";\nimport type { MiddlewareHandler } from \"hono\";\nimport type { AppVariables } from \"../types\";\n\nconst MEMBERSHIP_CACHE_TTL_MS = 300_000;\n\ninterface CacheEntry {\n workspaceId: string;\n fetchedAt: number;\n}\n\nconst membershipCache = new Map<string, CacheEntry>();\n\nlet ddbClient: DynamoDBDocumentClient | null = null;\n\nfunction getDdb() {\n if (!ddbClient) {\n ddbClient = DynamoDBDocumentClient.from(\n new DynamoDBClient({ region: process.env[\"AWS_REGION\"] }),\n );\n }\n return ddbClient;\n}\n\nexport async function resolveWorkspaceId(userSub: string): Promise<string | null> {\n const now = Date.now();\n const cached = membershipCache.get(userSub);\n if (cached && now - cached.fetchedAt < MEMBERSHIP_CACHE_TTL_MS) {\n return cached.workspaceId;\n }\n\n const result = await getDdb().send(\n new QueryCommand({\n TableName: process.env[\"MEMBERSHIPS_TABLE_NAME\"]!,\n IndexName: \"ByUserSub\",\n KeyConditionExpression: \"userSub = :sub\",\n ExpressionAttributeValues: { \":sub\": userSub },\n Limit: 1,\n }),\n );\n\n const item = result.Items?.[0] as { workspaceId: string } | undefined;\n if (!item) return null;\n\n membershipCache.set(userSub, { workspaceId: item.workspaceId, fetchedAt: now });\n return item.workspaceId;\n}\n\nexport const workspaceMiddleware: MiddlewareHandler<{ Variables: AppVariables }> =\n async (c, next) => {\n const claims = c.get(\"claims\");\n const workspaceId = await resolveWorkspaceId(claims.sub);\n if (!workspaceId) {\n return c.json({ error: \"No workspace membership\" }, 403);\n }\n c.set(\"workspaceId\", workspaceId);\n return next();\n };\n","import { DynamoDBClient } from \"@aws-sdk/client-dynamodb\";\nimport { DynamoDBDocumentClient, GetCommand } from \"@aws-sdk/lib-dynamodb\";\nimport type { MiddlewareHandler } from \"hono\";\nimport type { AppVariables, WorkspaceItem } from \"../types\";\n\nconst WORKSPACE_CACHE_TTL_MS = 60_000;\n\ninterface CacheEntry {\n workspace: WorkspaceItem;\n fetchedAt: number;\n}\n\nconst workspaceCache = new Map<string, CacheEntry>();\n\nlet ddbClient: DynamoDBDocumentClient | null = null;\n\nfunction getDdb() {\n if (!ddbClient) {\n ddbClient = DynamoDBDocumentClient.from(\n new DynamoDBClient({ region: process.env[\"AWS_REGION\"] }),\n );\n }\n return ddbClient;\n}\n\nexport async function getWorkspace(workspaceId: string): Promise<WorkspaceItem | null> {\n const now = Date.now();\n const cached = workspaceCache.get(workspaceId);\n if (cached && now - cached.fetchedAt < WORKSPACE_CACHE_TTL_MS) {\n return cached.workspace;\n }\n\n const result = await getDdb().send(\n new GetCommand({\n TableName: process.env[\"WORKSPACE_TABLE_NAME\"]!,\n Key: { workspaceId },\n }),\n );\n\n if (!result.Item) return null;\n\n const workspace = result.Item as WorkspaceItem;\n workspaceCache.set(workspaceId, { workspace, fetchedAt: now });\n return workspace;\n}\n\nexport const planMiddleware: MiddlewareHandler<{ Variables: AppVariables }> =\n async (c, next) => {\n const workspaceId = c.get(\"workspaceId\");\n const workspace = await getWorkspace(workspaceId);\n if (!workspace) {\n return c.json({ error: \"Workspace not found\" }, 404);\n }\n\n // 402 Payment Required when chat is not included in the plan.\n // Currently all plans include chat; this gate is a forward-looking hook\n // for future restricted plans where chat is a paid add-on.\n if (\"chat\" in workspace.features && !workspace.features.chat) {\n return c.json(\n { error: \"Chat is not available on your current plan\", plan: workspace.plan },\n 402,\n );\n }\n\n c.set(\"workspace\", workspace);\n return next();\n };\n","import { Hono } from \"hono\";\n\nconst health = new Hono();\n\nhealth.get(\"/health\", (c) => c.json({ status: \"ok\" }));\n\nexport { health };\n","import { withTracing } from \"@posthog/ai\";\nimport {\n streamText,\n convertToModelMessages,\n wrapLanguageModel,\n stepCountIs,\n jsonSchema,\n type LanguageModelMiddleware,\n type UIMessage,\n type Tool,\n} from \"ai\";\nimport { Hono } from \"hono\";\nimport { z } from \"zod\";\nimport { CHAT_PROMPT_ALLOWLIST } from \"../fallback-prompts.js\";\nimport {\n evictGatewaySession,\n getGatewaySession,\n getCachedTools,\n setCachedTools,\n} from \"../gateway-session.js\";\nimport { getPrompt, compilePrompt, getPostHogClient } from \"../posthog.js\";\nimport {\n getOpenRouterKey,\n createOpenRouterProvider,\n} from \"../providers/openrouter.js\";\nimport { getToolDocs, makeHttpGatewayClient } from \"../tool-docs.js\";\nimport type { AppVariables } from \"../types.js\";\n\nconst chatBodySchema = z.object({\n id: z.string(),\n messages: z.array(z.any()),\n trigger: z.string(),\n metadata: z.unknown().optional(),\n prompt: z\n .object({\n id: z.string(),\n vars: z\n .object({\n compilers: z.array(z.string()).optional(),\n })\n .optional(),\n })\n .optional(),\n});\n\n// Retry once (200 ms backoff) if the provider rejects before the first\n// streamed byte. Once doStream() resolves (headers received, 2xx), the\n// connection is committed and mid-stream errors surface as error UI parts.\nconst retryAtStartMiddleware: LanguageModelMiddleware = {\n specificationVersion: \"v3\",\n async wrapStream({ doStream }) {\n try {\n return await doStream();\n } catch {\n await new Promise<void>((r) => setTimeout(r, 200));\n return doStream();\n }\n },\n};\n\nexport const chatRoute = new Hono<{ Variables: AppVariables }>();\n\ninterface GatewayToolEntry {\n provider: string;\n name: string;\n operation: string;\n description?: string;\n inputSchema?: unknown;\n}\n\nasync function fetchGatewayTools(\n gatewayUrl: string,\n bearerToken: string,\n): Promise<GatewayToolEntry[]> {\n const res = await fetch(`${gatewayUrl}/tools`, {\n headers: { Authorization: `Bearer ${bearerToken}` },\n });\n if (!res.ok) return [];\n const data = (await res.json()) as { tools: GatewayToolEntry[] };\n return data.tools ?? [];\n}\n\nfunction buildTools(\n gatewayTools: GatewayToolEntry[],\n gatewayUrl: string,\n bearerToken: string,\n): Record<string, Tool> {\n const tools: Record<string, Tool> = {};\n\n for (const t of gatewayTools) {\n // Replace dots with underscores — some models reject dots in function names.\n const toolKey = t.name.replace(/\\./g, \"_\");\n\n const rawSchema =\n t.inputSchema && typeof t.inputSchema === \"object\"\n ? t.inputSchema\n : { type: \"object\", properties: {} };\n\n const parameters = jsonSchema<Record<string, unknown>>(\n rawSchema as Parameters<typeof jsonSchema>[0],\n );\n\n const execute = async (args: Record<string, unknown>): Promise<unknown> => {\n const res = await fetch(`${gatewayUrl}/tools/${t.provider}/${t.operation}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${bearerToken}`,\n },\n body: JSON.stringify(args),\n });\n if (!res.ok) {\n const err = await res.json().catch(() => ({ error: res.statusText }));\n return { error: (err as { error?: string }).error ?? res.statusText };\n }\n return res.json();\n };\n\n tools[toolKey] = {\n description: t.description ?? `Call ${t.name}`,\n parameters,\n execute,\n } as unknown as Tool;\n }\n\n return tools;\n}\n\nchatRoute.post(\"/\", async (c) => {\n const body = await c.req.json().catch(() => null);\n const parsed = chatBodySchema.safeParse(body);\n if (!parsed.success) {\n return c.json({ error: \"Invalid request body\" }, 400);\n }\n\n const { messages, prompt: promptBody } = parsed.data;\n const claims = c.get(\"claims\");\n const workspaceId = c.get(\"workspaceId\");\n const workspace = c.get(\"workspace\");\n\n const promptId = promptBody?.id ?? \"chat-patchwork-widget\";\n if (!CHAT_PROMPT_ALLOWLIST.has(promptId)) {\n return c.json({ error: \"Unknown prompt id\" }, 400);\n }\n\n // Gateway tools are additive — chat works without them.\n const gatewayUrl = process.env[\"GATEWAY_URL\"]?.replace(/\\/$/, \"\");\n let sessionToken: string | null = null;\n\n if (gatewayUrl) {\n const authHeader = c.req.header(\"Authorization\") ?? \"\";\n const cognitoToken = authHeader.replace(/^Bearer /, \"\");\n try {\n const session = await getGatewaySession(claims, workspaceId, cognitoToken);\n sessionToken = session.token;\n } catch {\n // Non-fatal — continue without gateway tools\n }\n }\n\n let gatewayTools: GatewayToolEntry[] = [];\n if (sessionToken && gatewayUrl) {\n const cached = getCachedTools(claims.sub) as GatewayToolEntry[] | undefined;\n if (cached) {\n gatewayTools = cached;\n } else {\n gatewayTools = await fetchGatewayTools(gatewayUrl, sessionToken).catch(\n () => [] as GatewayToolEntry[],\n );\n if (gatewayTools.length === 0) {\n evictGatewaySession(claims.sub);\n } else {\n setCachedTools(claims.sub, gatewayTools);\n }\n }\n }\n\n const tools =\n sessionToken && gatewayUrl\n ? buildTools(gatewayTools, gatewayUrl, sessionToken)\n : {};\n\n // Load system prompt from PostHog (cached, with fallback to bundled copy)\n const gatewayClient = gatewayUrl ? makeHttpGatewayClient(gatewayUrl) : null;\n const [promptResult, toolDocs] = await Promise.all([\n getPrompt(promptId),\n getToolDocs(gatewayClient).catch(() => \"\"),\n ]);\n const compilers = (promptBody?.vars?.compilers ?? []).join(\", \");\n const systemPrompt = compilePrompt(promptResult.prompt, {\n compilers,\n tool_docs: toolDocs,\n });\n\n const apiKey = await getOpenRouterKey();\n const provider = createOpenRouterProvider(apiKey);\n const modelId = workspace.limits.maxModels[0] ?? \"openrouter/auto\";\n const baseModel = provider(modelId);\n\n const phClient = getPostHogClient();\n const tracedModel =\n phClient && promptResult.source !== \"code_fallback\"\n ? withTracing(baseModel, phClient, {\n posthogDistinctId: claims.sub,\n posthogProperties: {\n $ai_prompt_name: promptResult.name,\n $ai_prompt_version: promptResult.version,\n },\n })\n : baseModel;\n\n const model = wrapLanguageModel({\n model: tracedModel,\n middleware: retryAtStartMiddleware,\n });\n\n const result = streamText({\n model,\n system: systemPrompt,\n messages: await convertToModelMessages(messages as UIMessage[]),\n stopWhen: stepCountIs(workspace.limits.maxToolSteps),\n maxOutputTokens: workspace.limits.maxTokensPerRequest,\n tools,\n });\n\n return result.toUIMessageStreamResponse();\n});\n","// Frozen copies of the canonical prompts, used when PostHog is unreachable\n// and the SDK cache is cold. Keep in sync with the seeded PostHog prompt versions.\n\nconst PATCHWORK_PROMPT = `\nYou are a friendly assistant! When responding to the user, you _must_ respond with JSX files!\n\nLook at 'patchwork.compilers' to see what specific runtime components and libraries are supported. (e.g. '['@aprovan/patchwork-image-shadcn' supports React, Tailwind, & ShadCN components). If there are no compilers, respond as you normally would. If compilers are available, ALWAYS respond with a component following [Component Generation](#component-generation).\n\nLook at 'patchwork.services' to see what services are available for widgets to call. If services are listed, you can generate widgets that make service calls using global namespace objects.\n\n**IMPORTANT: If you need to discover available services or get details about a specific service, use the \\`search_services\\` tool.**\n\n## Component Generation\n\nRespond with code blocks using tagged attributes on the fence line. The \\`note\\` attribute (optional but encouraged) provides a brief description visible in the UI. The \\`path\\` attribute specifies the virtual file path.\n\n### Code Block Format\n\n\\`\\`\\`tsx note=\"Main component\" path=\"components/weather/main.tsx\"\nexport default function WeatherWidget() {\n // component code\n}\n\\`\\`\\`\n\n### Attribute Order\nPut \\`note\\` first so it's available soonest in streaming UI.\n\n### Multi-File Generation\n\nWhen generating complex widgets, you can output multiple files. Use the \\`@/\\` prefix for virtual file system paths. ALWAYS prefer to generate visible components before metadata.\n\n**Example multi-file widget:**\n\n\\`\\`\\`json note=\"Widget configuration\" path=\"components/dashboard/package.json\"\n{\n \"description\": \"Interactive dashboard widget\",\n \"patchwork\": {\n \"inputs\": {\n \"type\": \"object\",\n \"properties\": {\n \"title\": { \"type\": \"string\" }\n }\n },\n \"services\": {\n \"analytics\": [\"getMetrics\", \"getChartData\"]\n }\n }\n}\n\\`\\`\\`\n\n\\`\\`\\`tsx note=\"Main widget component\" path=\"components/dashboard/main.tsx\"\nimport { Card } from './Card';\nimport { Chart } from './Chart';\n\nexport default function Dashboard({ title = \"Dashboard\" }) {\n return (\n <div>\n <h1>{title}</h1>\n <Card />\n <Chart />\n </div>\n );\n}\n\\`\\`\\`\n\n\\`\\`\\`tsx note=\"Card subcomponent\" path=\"components/dashboard/Card.tsx\"\nexport function Card() {\n return <div className=\"p-4 rounded border\">Card content</div>;\n}\n\\`\\`\\`\n\n### Requirements\n- DO think heavily about correctness of code and syntax\n- DO keep things simple and self-contained\n- ALWAYS include the \\`path\\` attribute specifying the file location.\n- ALWAYS use generic component/path names that is not dependent on arguments (e.g. 'dinner.tsx' not 'spaghetti.tsx')\n- ALWAYS output the COMPLETE code block with opening \\`\\`\\`tsx and closing \\`\\`\\` markers\n- Use \\`note\\` attribute to describe what each code block does (optional but encouraged)\n- NEVER truncate or cut off code - finish the entire component before stopping\n- If the component is complex, simplify it rather than leaving it incomplete\n- Do NOT include: a heading/title\n\n### Visual Design Guidelines\nCreate professional, polished interfaces that present information **spatially** rather than as vertical lists:\n- Use **cards, grids, and flexbox layouts** to organize related data into visual groups\n- Leverage **icons** (from lucide-react) alongside text to communicate meaning at a glance\n- Apply **visual hierarchy** through typography scale, weight, and color contrast\n- Use **whitespace strategically** to create breathing room and separation\n- Prefer **horizontal arrangements** where data fits naturally (e.g., stats in a row, badges inline)\n- Group related metrics into **compact visual clusters** rather than separate line items\n- Use **subtle backgrounds, borders, and shadows** to define sections without heavy dividers\n\n### Root Element Constraints\nThe component will be rendered inside a parent container that handles positioning. Your root element should:\n- ✅ Use intrinsic sizing (let content determine dimensions)\n- ✅ Handle internal padding (e.g., \\`p-4\\`, \\`p-6\\`)\n- ❌ NEVER add centering utilities (\\`items-center\\`, \\`justify-center\\`) to position itself\n- ❌ NEVER add viewport-relative sizing (\\`min-h-screen\\`, \\`h-screen\\`, \\`w-screen\\`)\n- ❌ NEVER add flex/grid on root just for self-centering\n\n### Using Services in Widgets (CRITICAL)\n\n**MANDATORY workflow - you must follow these steps IN ORDER:**\n\n1. **Use \\`search_services\\`** to discover the service schema\n2. **STOP. Make an actual call to the service tool itself** (e.g., \\`weather_get_forecast\\`, \\`github_get_repo\\`) with real arguments. This is NOT optional. Do NOT skip this step.\n3. **Observe the response** - verify it succeeded and note the exact data structure\n4. **Only then generate the widget** that fetches the same data at runtime\n\n**\\`search_services\\` is NOT a substitute for calling the actual service.** It only returns documentation. You MUST invoke the real service tool to validate your arguments work.\n\n**Tool naming:** Service tools use underscores, not dots. For example: \\`weather_get_forecast\\`, \\`github_list_repos\\`.\n\n**Example workflow for a weather widget:**\n\\`\\`\\`\nStep 1: search_services({ query: \"weather\" })\n → Learn that weather_get_current_conditions exists with params: { latitude, longitude }\n\nStep 2: weather_get_current_conditions({ latitude: 29.7604, longitude: -95.3698 }) ← REQUIRED!\n → Verify it returns { temp: 72, humidity: 65, ... }\n\nStep 3: Generate widget that calls weather.get_current_conditions at runtime\n\\`\\`\\`\n\n**If you skip Step 2, you will generate broken widgets.** Arguments that look correct in the schema may fail at runtime due to validation rules, required formats, or service-specific constraints.\n\n**NEVER embed static data directly in the component.**\n\n❌ **WRONG** - Embedding data directly:\n\\`\\`\\`tsx path=\"components/weather/bad-example.tsx\"\n// DON'T DO THIS - calling tool, then embedding the response as static data\nexport default function WeatherWidget() {\n // Static data embedded at generation time - BAD!\n const weather = { temp: 72, condition: \"sunny\", humidity: 45 };\n return <div>Temperature: {weather.temp}°F</div>;\n}\n\\`\\`\\`\n\n✅ **CORRECT** - Fetching data at runtime:\n\\`\\`\\`tsx note=\"Weather widget with runtime data\" path=\"components/weather/main.tsx\"\nexport default function WeatherWidget() {\n const [data, setData] = useState(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState(null);\n\n useEffect(() => {\n // Fetch data at runtime - GOOD!\n weather.get_forecast({ latitude: 48.8566, longitude: 2.3522 })\n .then(setData)\n .catch(setError)\n .finally(() => setLoading(false));\n }, []);\n\n if (loading) return <Skeleton className=\"h-32 w-full\" />;\n if (error) return <Alert variant=\"destructive\">{error.message}</Alert>;\n\n return <div>Temperature: {data.temp}°F</div>;\n}\n\\`\\`\\`\n\n**Why this matters:**\n- Widgets with runtime service calls show **live data** that updates when refreshed\n- Static embedded data becomes **stale immediately** after generation\n- The proxy pattern allows widgets to be **reusable** across different contexts\n- Error handling and loading states improve **user experience**\n\n**Service call pattern:**\n\\`\\`\\`tsx\n// Services are available as global namespace objects\n// Call format: namespace.procedure_name({ ...args })\n\nconst [data, setData] = useState(null);\nconst [loading, setLoading] = useState(true);\nconst [error, setError] = useState(null);\n\nuseEffect(() => {\n serviceName.procedure_name({ param1: value1 })\n .then(setData)\n .catch(setError)\n .finally(() => setLoading(false));\n}, [/* dependencies */]);\n\\`\\`\\`\n\n**Required for service-using widgets:**\n- Always show loading indicators (Skeleton, Loader2 spinner, etc.)\n- Always handle errors gracefully with user-friendly messages\n- Use appropriate React hooks (useState, useEffect) for async data\n- Services are injected as globals - NO imports needed\n\n### Validating Service Calls (CRITICAL - READ CAREFULLY)\n\n**Calling \\`search_services\\` multiple times is NOT validation.** You must call the actual service tool.\n\n❌ **WRONG workflow (will produce broken widgets):**\n\\`\\`\\`\n1. search_services({ query: \"weather\" }) ← Only gets schema\n2. search_services({ query: \"location\" }) ← Still only schema\n3. Generate widget ← BROKEN - never tested the actual service!\n\\`\\`\\`\n\n✅ **CORRECT workflow:**\n\\`\\`\\`\n1. search_services({ query: \"weather\" }) ← Get schema\n2. weather_get_forecast({ latitude: 29.76, longitude: -95.37 }) ← ACTUALLY CALL IT\n3. Observe response: { temp: 72, conditions: \"sunny\", ... }\n4. Generate widget that calls weather.get_forecast at runtime\n\\`\\`\\`\n\n**The service tool (e.g., \\`weather_get_forecast\\`, \\`github_list_repos\\`) is a DIFFERENT tool from \\`search_services\\`.** You have access to both. Use both.\n\n**Only after a successful test call to the actual service should you generate the widget.**\n\n### Component Parameterization (IMPORTANT)\n\n**Widgets should accept props for dynamic values instead of hardcoding:**\n\n❌ **WRONG** - Hardcoded values:\n\\`\\`\\`tsx path=\"components/weather/bad-example.tsx\"\nexport default function WeatherWidget() {\n // Location hardcoded - BAD!\n const [lat, lon] = [48.8566, 2.3522]; // Paris\n // ...\n}\n\\`\\`\\`\n\n✅ **CORRECT** - Parameterized with props and defaults:\n\\`\\`\\`tsx note=\"Parameterized weather widget\" path=\"components/weather/main.tsx\"\ninterface WeatherWidgetProps {\n location?: string; // e.g., \"Paris, France\"\n latitude?: number; // Direct coordinates (optional)\n longitude?: number;\n}\n\nexport default function WeatherWidget({\n location = \"Paris, France\",\n latitude,\n longitude\n}: WeatherWidgetProps) {\n // Use provided coordinates or look up from location name\n // ...\n}\n\\`\\`\\`\n\n**Why parameterize:**\n- Components become **reusable** across different contexts\n- Users can **customize behavior** without editing code\n- Enables **composition** - parent components can pass different values\n- Supports **testing** with various inputs\n\n**What to parameterize:**\n- Location names, coordinates, IDs\n- Search queries and filters\n- Display options (count, format, theme)\n- API-specific identifiers (usernames, repo names, etc.)\n\n### Anti-patterns to Avoid\n- ❌ Bulleted or numbered lists of key-value pairs\n- ❌ Vertical stacks where horizontal layouts would fit\n- ❌ Plain text labels without visual treatment\n- ❌ Uniform styling that doesn't distinguish primary from secondary information\n- ❌ Wrapping components in centering containers (parent handles this)\n- ❌ **Embedding API response data directly in components instead of fetching at runtime**\n- ❌ **Calling a tool, then putting the response as static JSX/JSON in the generated code**\n- ❌ **Hardcoding values that should be component props**\n- ❌ **Calling \\`search_services\\` multiple times instead of calling the actual service tool**\n- ❌ **Generating a widget without first making a real call to the service with your intended arguments**\n- ❌ **Treating schema documentation as proof that a service call will work**\n- ❌ **Omitting the \\`path\\` attribute on code blocks**\n`;\n\nconst EDIT_PROMPT = `\nYou are editing an existing JSX component. The user will provide the current code and describe the changes they want.\n\n## Response Format\n\nUse code fences with tagged attributes. The \\`note\\` attribute (optional but encouraged) provides a brief description visible in the UI. The \\`path\\` attribute specifies the target file.\n\n\\`\\`\\`diff note=\"Brief description of this change\" path=\"@/components/Button.tsx\"\n<<<<<<< SEARCH\nexact code to find\n=======\nreplacement code\n>>>>>>> REPLACE\n\\`\\`\\`\n\n### Attribute Order\nPut \\`note\\` first so it's available soonest in streaming UI.\n\n### Multi-File Edits\nWhen editing multiple files, use the \\`path\\` attribute with virtual paths (\\`@/\\` prefix for generated files):\n\n\\`\\`\\`diff note=\"Update button handler\" path=\"@/components/Button.tsx\"\n<<<<<<< SEARCH\nonClick={() => {}}\n=======\nonClick={() => handleClick()}\n>>>>>>> REPLACE\n\\`\\`\\`\n\n\\`\\`\\`diff note=\"Add utility function\" path=\"@/lib/utils.ts\"\n<<<<<<< SEARCH\nexport const formatDate = ...\n=======\nexport const formatDate = ...\n\nexport const handleClick = () => console.log('clicked');\n>>>>>>> REPLACE\n\\`\\`\\`\n\n## Rules\n- SEARCH block must match the existing code EXACTLY (whitespace, indentation, everything)\n- You can include multiple diff blocks for multiple changes\n- Each diff block should have its own \\`note\\` attribute annotation\n- Keep changes minimal and targeted\n- Do NOT output the full file - only the diffs\n- If clarification is needed, ask briefly before any diffs\n\n## CRITICAL: Diff Marker Safety\n- NEVER include the strings \"<<<<<<< SEARCH\", \"=======\", or \">>>>>>> REPLACE\" inside your replacement code\n- These are reserved markers for parsing the diff format\n- If you need to show diff-like content, use alternative notation (e.g., \"// old code\" / \"// new code\")\n- Malformed diff markers will cause the edit to fail\n\n## Summary\nAfter all diffs, provide a brief markdown summary of the changes made. Use formatting like:\n- **Bold** for emphasis on key changes\n- Bullet points for listing multiple changes\n- Keep it concise (2-4 lines max)\n- Do NOT include: a heading/title\n`;\n\nconst PLAIN_PROMPT = `You are a helpful assistant.\n\n{{tool_docs}}`;\n\nexport const FALLBACK_PROMPTS: Record<string, string> = {\n \"chat-patchwork-widget\": `---\\npatchwork:\\n compilers: {{compilers}}\\n---\\n\\n${PATCHWORK_PROMPT}\\n\\n{{tool_docs}}`,\n \"chat-plain\": PLAIN_PROMPT,\n \"edit-patchwork-widget\": `Current component code:\\n\\`\\`\\`tsx\\n{{code}}\\n\\`\\`\\`\\n\\n${EDIT_PROMPT}`,\n};\n\nexport const CHAT_PROMPT_ALLOWLIST = new Set([\"chat-patchwork-widget\", \"chat-plain\"]);\nexport const EDIT_PROMPT_ID = \"edit-patchwork-widget\";\n","/**\n * Gateway session client.\n *\n * The registry gateway authenticates every request with the caller's Cognito\n * access token and requires the active workspace to be persisted in DDB via\n * `POST /auth/sessions` before other endpoints will accept requests.\n *\n * GatewaySessionClient handles that one-time setup per user and returns the\n * Cognito token itself as the bearer to use on subsequent gateway calls. The\n * result is cached in-memory per user sub for the remaining lifetime of the\n * Cognito token.\n */\n\nimport type { CognitoAccessTokenPayload } from \"aws-jwt-verify/jwt-model\";\n\ninterface SessionEntry {\n /** The Cognito access token to use as bearer for gateway calls. */\n token: string;\n /** Unix seconds — mirrors the Cognito token's `exp` claim. */\n expires_at: number;\n}\n\nconst sessionCache = new Map<string, SessionEntry>();\n\n/**\n * Per-session tool-list cache. Keyed by userSub, parallel to sessionCache.\n * Cleared on session eviction so a fresh session always re-fetches tools.\n */\nconst toolsCache = new Map<string, unknown[]>();\n\nexport function getCachedTools(sub: string): unknown[] | undefined {\n return toolsCache.get(sub);\n}\n\nexport function setCachedTools(sub: string, tools: unknown[]): void {\n toolsCache.set(sub, tools);\n}\n\nexport function evictCachedTools(sub: string): void {\n toolsCache.delete(sub);\n}\n\nfunction gatewayUrl(): string {\n const url = process.env[\"GATEWAY_URL\"];\n if (!url) throw new Error(\"GATEWAY_URL is not set\");\n return url.replace(/\\/$/, \"\");\n}\n\n/**\n * Establish (or reuse) a gateway session for the caller.\n *\n * 1. Returns the cached entry if still valid.\n * 2. Calls `POST /auth/sessions` on the gateway to register the active\n * workspace for this user.\n * 3. Returns the Cognito token as the bearer — the gateway uses it directly.\n * 4. On 401, evicts the cache and retries once.\n */\nexport async function getGatewaySession(\n claims: CognitoAccessTokenPayload,\n workspaceId: string,\n cognitoToken: string,\n): Promise<SessionEntry> {\n const sub = claims.sub;\n const now = Math.floor(Date.now() / 1000);\n\n const cached = sessionCache.get(sub);\n if (cached && cached.expires_at > now + 60) {\n return cached;\n }\n\n const entry = await exchangeSession(cognitoToken, workspaceId, claims.exp as number);\n sessionCache.set(sub, entry);\n return entry;\n}\n\nasync function exchangeSession(\n cognitoToken: string,\n workspaceId: string,\n exp: number,\n): Promise<SessionEntry> {\n let res: Response;\n try {\n res = await callAuthSessions(cognitoToken, workspaceId);\n } catch {\n // Network error — retry once after 200ms.\n await new Promise<void>((r) => setTimeout(r, 200));\n res = await callAuthSessions(cognitoToken, workspaceId);\n }\n\n if (res.status === 401) {\n // Retry once — token may have just been refreshed by the caller.\n const retryRes = await callAuthSessions(cognitoToken, workspaceId);\n if (!retryRes.ok) {\n throw new GatewaySessionError(retryRes.status, await retryRes.text());\n }\n return { token: cognitoToken, expires_at: exp };\n }\n\n if (res.status >= 500) {\n // 5xx — retry once after 200ms.\n await new Promise<void>((r) => setTimeout(r, 200));\n const retryRes = await callAuthSessions(cognitoToken, workspaceId);\n if (!retryRes.ok) {\n throw new GatewaySessionError(retryRes.status, await retryRes.text());\n }\n return { token: cognitoToken, expires_at: exp };\n }\n\n if (!res.ok) {\n throw new GatewaySessionError(res.status, await res.text());\n }\n\n return { token: cognitoToken, expires_at: exp };\n}\n\nasync function callAuthSessions(\n cognitoToken: string,\n workspaceId: string,\n): Promise<Response> {\n return fetch(`${gatewayUrl()}/auth/sessions`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${cognitoToken}`,\n },\n body: JSON.stringify({ workspace_id: workspaceId }),\n });\n}\n\nexport function evictGatewaySession(sub: string): void {\n sessionCache.delete(sub);\n toolsCache.delete(sub);\n}\n\nexport function resetGatewaySessionCache(): void {\n sessionCache.clear();\n toolsCache.clear();\n}\n\nexport class GatewaySessionError extends Error {\n constructor(\n public readonly status: number,\n message: string,\n ) {\n super(`Gateway session exchange failed (${status}): ${message}`);\n this.name = \"GatewaySessionError\";\n }\n}\n","import { Prompts, type PromptResult } from \"@posthog/ai\";\nimport { PostHog } from \"posthog-node\";\nimport { FALLBACK_PROMPTS } from \"./fallback-prompts.js\";\nimport type { Env } from \"./env.js\";\n\n// Module-scope singletons — survive Lambda warm invocations\nlet _client: PostHog | null = null;\nlet _prompts: Prompts | null = null;\n\nexport function initPostHog(env: Env): void {\n if (!env.POSTHOG_PROJECT_API_KEY || !env.POSTHOG_PERSONAL_API_KEY) return;\n\n _client = new PostHog(env.POSTHOG_PROJECT_API_KEY, {\n host: env.POSTHOG_HOST,\n personalApiKey: env.POSTHOG_PERSONAL_API_KEY,\n });\n\n _prompts = new Prompts({ posthog: _client });\n}\n\nexport async function getPrompt(\n name: string,\n cacheTtlSeconds = 300,\n): Promise<PromptResult> {\n const fallback = FALLBACK_PROMPTS[name] ?? \"\";\n\n if (!_prompts) {\n return { source: \"code_fallback\", prompt: fallback, name: undefined, version: undefined };\n }\n\n return _prompts.get(name, { cacheTtlSeconds, fallback });\n}\n\nexport function compilePrompt(\n template: string,\n vars: Record<string, string>,\n): string {\n if (_prompts) {\n return _prompts.compile(template, vars);\n }\n return template.replace(/\\{\\{(\\w+)\\}\\}/g, (_, key) => vars[key] ?? \"\");\n}\n\nexport function getPostHogClient(): PostHog | null {\n return _client;\n}\n\nexport type { PostHog };\n","import {\n SecretsManagerClient,\n GetSecretValueCommand,\n} from \"@aws-sdk/client-secrets-manager\";\nimport { createOpenAICompatible } from \"@ai-sdk/openai-compatible\";\n\nlet secretsClient: SecretsManagerClient | null = null;\nlet cachedKey: string | null = null;\n\nfunction getSecretsClient(): SecretsManagerClient {\n if (!secretsClient) {\n secretsClient = new SecretsManagerClient({\n region: process.env[\"AWS_REGION\"],\n });\n }\n return secretsClient;\n}\n\nexport async function getOpenRouterKey(): Promise<string> {\n if (cachedKey) return cachedKey;\n\n const result = await getSecretsClient().send(\n new GetSecretValueCommand({\n SecretId: process.env[\"OPENROUTER_SECRET_ARN\"]!,\n }),\n );\n cachedKey = result.SecretString!;\n return cachedKey!;\n}\n\nexport function createOpenRouterProvider(apiKey: string) {\n return createOpenAICompatible({\n name: \"openrouter\",\n apiKey,\n baseURL: \"https://openrouter.ai/api/v1\",\n });\n}\n","export interface ToolInfo {\n namespace: string;\n name: string;\n description?: string;\n}\n\nexport interface GatewayClient {\n listTools(): Promise<ToolInfo[]>;\n}\n\n// Module-scope cache — 60 s TTL, survives Lambda warm invocations\nlet _cachedToolDocs = \"\";\nlet _toolDocsCachedAt = 0;\nconst TOOL_DOCS_TTL_MS = 60_000;\n\nexport async function getToolDocs(\n gateway: GatewayClient | null,\n): Promise<string> {\n if (!gateway) return \"\";\n\n const now = Date.now();\n if (_cachedToolDocs && now - _toolDocsCachedAt < TOOL_DOCS_TTL_MS) {\n return _cachedToolDocs;\n }\n\n const tools = await gateway.listTools();\n _cachedToolDocs = renderToolDocs(tools);\n _toolDocsCachedAt = now;\n return _cachedToolDocs;\n}\n\nfunction renderToolDocs(tools: ToolInfo[]): string {\n if (tools.length === 0) return \"\";\n\n const byNamespace = new Map<string, ToolInfo[]>();\n for (const tool of tools) {\n const existing = byNamespace.get(tool.namespace) ?? [];\n existing.push(tool);\n byNamespace.set(tool.namespace, existing);\n }\n\n const namespaces = [...byNamespace.keys()];\n let doc = `## Services\\n\\nThe following services are available for generated widgets to call:\\n\\n`;\n\n for (const [ns, nsTools] of byNamespace) {\n doc += `### \\`${ns}\\`\\n`;\n for (const tool of nsTools) {\n doc += `- \\`${ns}.${tool.name}()\\``;\n if (tool.description) doc += `: ${tool.description}`;\n doc += \"\\n\";\n }\n doc += \"\\n\";\n }\n\n const firstNs = namespaces[0] ?? \"service\";\n const firstTool = byNamespace.get(firstNs)?.[0]?.name ?? \"example\";\n doc += `**Usage in widgets:**\n\\`\\`\\`tsx\n// Services are available as global namespaces\nconst result = await ${firstNs}.${firstTool}({ /* args */ });\n\\`\\`\\`\n\nMake sure to handle loading states and errors when calling services.\n`;\n\n return doc;\n}\n\nexport function makeHttpGatewayClient(gatewayUrl: string): GatewayClient {\n return {\n async listTools(): Promise<ToolInfo[]> {\n const res = await fetch(`${gatewayUrl}/tools`);\n if (!res.ok) throw new Error(`Gateway returned ${res.status}`);\n return res.json() as Promise<ToolInfo[]>;\n },\n };\n}\n","import { withTracing } from \"@posthog/ai\";\nimport { streamText } from \"ai\";\nimport { Hono } from \"hono\";\nimport { z } from \"zod\";\nimport { EDIT_PROMPT_ID } from \"../fallback-prompts.js\";\nimport { getPrompt, compilePrompt, getPostHogClient } from \"../posthog.js\";\nimport {\n getOpenRouterKey,\n createOpenRouterProvider,\n} from \"../providers/openrouter.js\";\nimport type { AppVariables } from \"../types.js\";\n\nconst editBodySchema = z.object({\n code: z.string(),\n prompt: z.string(),\n});\n\nconst MODEL_ID = \"openrouter/auto\";\n\nexport const editRoute = new Hono<{ Variables: AppVariables }>();\n\neditRoute.post(\"/\", async (c) => {\n const body = await c.req.json().catch(() => null);\n const parsed = editBodySchema.safeParse(body);\n if (!parsed.success) {\n return c.json({ error: \"Invalid request body\" }, 400);\n }\n\n const promptResult = await getPrompt(EDIT_PROMPT_ID);\n const systemPrompt = compilePrompt(promptResult.prompt, {\n code: parsed.data.code,\n });\n\n const apiKey = await getOpenRouterKey();\n const provider = createOpenRouterProvider(apiKey);\n const baseModel = provider(MODEL_ID);\n\n const phClient = getPostHogClient();\n const model =\n phClient && promptResult.source !== \"code_fallback\"\n ? withTracing(baseModel, phClient, {\n posthogDistinctId: \"chat-api\",\n posthogProperties: {\n $ai_prompt_name: promptResult.name,\n $ai_prompt_version: promptResult.version,\n },\n })\n : baseModel;\n\n const result = streamText({\n model,\n system: systemPrompt,\n messages: [{ role: \"user\", content: parsed.data.prompt }],\n });\n\n return result.toTextStreamResponse();\n});\n","/**\n * GET /api/services\n *\n * Fetches the gateway's tool list and returns a service summary the frontend\n * uses to populate namespace suggestions and the ServicesInspector panel.\n *\n * Response shape:\n * { namespaces: string[], services: ServiceInfo[] }\n *\n * where ServiceInfo matches the frontend's expected `ServiceInfo` type:\n * { namespace: string, name: string, description?: string }\n */\n\nimport { Hono } from \"hono\";\nimport { evictGatewaySession, GatewaySessionError, getGatewaySession } from \"../gateway-session.js\";\nimport type { AppVariables } from \"../types.js\";\n\nexport interface ServiceInfo {\n namespace: string;\n name: string;\n procedure: string;\n description: string;\n parameters?: Record<string, unknown>;\n}\n\nexport const services = new Hono<{ Variables: AppVariables }>();\n\nservices.get(\"/\", async (c) => {\n const claims = c.get(\"claims\");\n const workspaceId = c.get(\"workspaceId\");\n\n const authHeader = c.req.header(\"Authorization\")!;\n const cognitoToken = authHeader.slice(\"Bearer \".length);\n\n let sessionToken: string;\n try {\n const session = await getGatewaySession(claims, workspaceId, cognitoToken);\n sessionToken = session.token;\n } catch (err) {\n if (err instanceof GatewaySessionError && err.status === 401) {\n return c.json({ error: \"Gateway session setup failed\" }, 401);\n }\n return c.json({ error: \"Failed to connect to gateway\" }, 502);\n }\n\n const gatewayUrl = process.env[\"GATEWAY_URL\"]!.replace(/\\/$/, \"\");\n const res = await fetch(`${gatewayUrl}/tools`, {\n headers: { Authorization: `Bearer ${sessionToken}` },\n });\n\n if (res.status === 401) {\n evictGatewaySession(claims.sub);\n return c.json({ error: \"Gateway authentication failed\" }, 502);\n }\n\n if (!res.ok) {\n return c.json({ error: \"Gateway tools fetch failed\" }, 502);\n }\n\n const data = await res.json() as {\n tools: Array<{\n provider: string;\n name: string;\n operation: string;\n description?: string;\n inputSchema?: unknown;\n }>;\n workspace_id: string;\n };\n\n const serviceList: ServiceInfo[] = data.tools.map((t) => ({\n namespace: t.provider,\n name: t.name,\n procedure: t.operation,\n description: t.description ?? \"\",\n parameters: t.inputSchema as Record<string, unknown> | undefined,\n }));\n\n const namespaces = Array.from(new Set(data.tools.map((t) => t.provider)));\n\n return c.json({ namespaces, services: serviceList });\n});\n","/**\n * POST /api/proxy/:ns/:proc\n *\n * Forwards the request body to `POST <gateway>/tools/:ns/:proc` using the\n * caller's gateway session bearer. This keeps tool invocations server-side\n * (credentials never reach the browser).\n */\n\nimport { Hono } from \"hono\";\nimport { evictGatewaySession, GatewaySessionError, getGatewaySession } from \"../gateway-session.js\";\nimport type { AppVariables } from \"../types.js\";\n\nexport const proxy = new Hono<{ Variables: AppVariables }>();\n\nproxy.post(\"/:ns/:proc{.*}\", async (c) => {\n const claims = c.get(\"claims\");\n const workspaceId = c.get(\"workspaceId\");\n\n const ns = c.req.param(\"ns\");\n const proc = c.req.param(\"proc\");\n\n const authHeader = c.req.header(\"Authorization\")!;\n const cognitoToken = authHeader.slice(\"Bearer \".length);\n\n let sessionToken: string;\n try {\n const session = await getGatewaySession(claims, workspaceId, cognitoToken);\n sessionToken = session.token;\n } catch (err) {\n if (err instanceof GatewaySessionError && err.status === 401) {\n return c.json({ error: \"Gateway session setup failed\" }, 401);\n }\n return c.json({ error: \"Failed to connect to gateway\" }, 502);\n }\n\n let body: unknown = {};\n try {\n body = await c.req.json();\n } catch {\n // empty body is fine\n }\n\n const gatewayUrl = process.env[\"GATEWAY_URL\"]!.replace(/\\/$/, \"\");\n const res = await fetch(`${gatewayUrl}/tools/${ns}/${proc}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${sessionToken}`,\n },\n body: JSON.stringify(body),\n });\n\n if (res.status === 401) {\n evictGatewaySession(claims.sub);\n return c.json({ error: \"Gateway authentication failed\" }, 502);\n }\n\n const responseData = await res.json();\n return c.json(responseData, res.status as 200);\n});\n","import { z } from \"zod\";\n\nconst envSchema = z.object({\n NODE_ENV: z\n .enum([\"development\", \"production\", \"test\"])\n .default(\"development\"),\n PORT: z.coerce.number().default(3001),\n COGNITO_USER_POOL_ID: z.string().min(1),\n COGNITO_CLIENT_ID: z.string().min(1),\n AWS_REGION: z.string().default(\"us-east-1\"),\n WORKSPACE_TABLE_NAME: z.string().min(1),\n MEMBERSHIPS_TABLE_NAME: z.string().min(1),\n OPENROUTER_SECRET_ARN: z.string().min(1),\n GATEWAY_URL: z.string().url(),\n\n // PostHog prompt management (optional — falls back to code prompts when absent)\n POSTHOG_PROJECT_API_KEY: z.string().optional(),\n POSTHOG_PERSONAL_API_KEY: z.string().optional(),\n POSTHOG_HOST: z.string().default(\"https://us.posthog.com\"),\n});\n\nexport type Env = z.infer<typeof envSchema>;\n\nexport function parseEnv(raw: Record<string, string | undefined>): Env {\n return envSchema.parse(raw);\n}\n","import { streamHandle } from \"hono/aws-lambda\";\nimport { createChatApp, initPostHog } from \"./app.js\";\nimport { parseEnv } from \"./env.js\";\nimport type { LambdaEvent } from \"hono/aws-lambda\";\n\nconst env = parseEnv(process.env);\n\n// Initialize module-scope PostHog singletons once per cold start\ninitPostHog(env);\n\nconst app = createChatApp();\n\nexport const handler = streamHandle(app) as (\n event: LambdaEvent,\n context: unknown,\n callback: unknown,\n) => Promise<unknown>;\n"],"mappings":"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","names":["Hono","DynamoDBClient","DynamoDBDocumentClient","ddbClient","getDdb","Hono","env","gatewayUrl","Hono","gatewayUrl","withTracing","streamText","Hono","z","z","Hono","withTracing","streamText","Hono","Hono","gatewayUrl","Hono","Hono","gatewayUrl","app","Hono","z","env","app"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/app.ts","../src/middleware/auth.ts","../src/middleware/workspace.ts","../src/middleware/plan.ts","../src/routes/health.ts","../src/routes/chat.ts","../src/fallback-prompts.ts","../src/gateway-session.ts","../src/posthog.ts","../src/providers/openrouter.ts","../src/tool-docs.ts","../src/routes/edit.ts","../src/routes/services.ts","../src/routes/proxy.ts","../src/env.ts","../src/lambda.ts"],"sourcesContent":["import { serve } from \"@hono/node-server\";\nimport { createChatApp, initPostHog } from \"./app.js\";\nimport { parseEnv } from \"./env.js\";\n\nexport { createChatApp, initPostHog } from \"./app.js\";\nexport type { ChatApp } from \"./app.js\";\nexport { handler } from \"./lambda.js\";\nexport type { LambdaEvent, LambdaContext } from \"hono/aws-lambda\";\n\nconst env = parseEnv(process.env);\n\ninitPostHog(env);\n\nif (env.NODE_ENV !== \"test\") {\n const app = createChatApp();\n serve({ fetch: app.fetch, port: env.PORT }, () => {\n console.log(`Chat API listening on :${env.PORT}`);\n });\n}\n","import { Hono } from \"hono\";\nimport { authMiddleware } from \"./middleware/auth.js\";\nimport { workspaceMiddleware } from \"./middleware/workspace.js\";\nimport { planMiddleware } from \"./middleware/plan.js\";\nimport { health } from \"./routes/health.js\";\nimport { chatRoute } from \"./routes/chat.js\";\nimport { editRoute } from \"./routes/edit.js\";\nimport { services } from \"./routes/services.js\";\nimport { proxy } from \"./routes/proxy.js\";\nimport type { AppVariables } from \"./types.js\";\n\nexport { initPostHog } from \"./posthog.js\";\n\nexport function createChatApp() {\n const app = new Hono<{ Variables: AppVariables }>();\n\n // Unauthenticated routes\n app.route(\"/\", health);\n\n // Protected route group: auth → workspace → plan\n const api = app.basePath(\"/api\");\n api.use(authMiddleware, workspaceMiddleware, planMiddleware);\n api.route(\"/chat\", chatRoute);\n api.route(\"/edit\", editRoute);\n api.route(\"/services\", services);\n api.route(\"/proxy\", proxy);\n\n return app;\n}\n\nexport type ChatApp = ReturnType<typeof createChatApp>;\n","import { CognitoJwtVerifier } from \"aws-jwt-verify\";\nimport type { CognitoAccessTokenPayload } from \"aws-jwt-verify/jwt-model\";\nimport type { MiddlewareHandler } from \"hono\";\nimport type { AppVariables } from \"../types\";\n\ninterface JwtVerifier {\n verify(token: string): Promise<CognitoAccessTokenPayload>;\n}\n\nlet verifier: JwtVerifier | null = null;\n\nfunction getVerifier(): JwtVerifier {\n if (!verifier) {\n verifier = CognitoJwtVerifier.create({\n userPoolId: process.env[\"COGNITO_USER_POOL_ID\"]!,\n clientId: process.env[\"COGNITO_CLIENT_ID\"]!,\n tokenUse: \"access\",\n });\n }\n return verifier;\n}\n\nexport const authMiddleware: MiddlewareHandler<{ Variables: AppVariables }> =\n async (c, next) => {\n const authHeader = c.req.header(\"Authorization\");\n if (!authHeader?.startsWith(\"Bearer \")) {\n return c.json({ error: \"Unauthorized\" }, 401);\n }\n\n const token = authHeader.slice(7);\n try {\n const payload = await getVerifier().verify(token);\n c.set(\"claims\", payload);\n return next();\n } catch {\n return c.json({ error: \"Unauthorized\" }, 401);\n }\n };\n","import { DynamoDBClient } from \"@aws-sdk/client-dynamodb\";\nimport { DynamoDBDocumentClient, QueryCommand } from \"@aws-sdk/lib-dynamodb\";\nimport type { MiddlewareHandler } from \"hono\";\nimport type { AppVariables } from \"../types\";\n\nconst MEMBERSHIP_CACHE_TTL_MS = 300_000;\n\ninterface CacheEntry {\n workspaceId: string;\n fetchedAt: number;\n}\n\nconst membershipCache = new Map<string, CacheEntry>();\n\nlet ddbClient: DynamoDBDocumentClient | null = null;\n\nfunction getDdb() {\n if (!ddbClient) {\n ddbClient = DynamoDBDocumentClient.from(\n new DynamoDBClient({ region: process.env[\"AWS_REGION\"] }),\n );\n }\n return ddbClient;\n}\n\nexport async function resolveWorkspaceId(userSub: string): Promise<string | null> {\n const now = Date.now();\n const cached = membershipCache.get(userSub);\n if (cached && now - cached.fetchedAt < MEMBERSHIP_CACHE_TTL_MS) {\n return cached.workspaceId;\n }\n\n const result = await getDdb().send(\n new QueryCommand({\n TableName: process.env[\"MEMBERSHIPS_TABLE_NAME\"]!,\n IndexName: \"ByUserSub\",\n KeyConditionExpression: \"userSub = :sub\",\n ExpressionAttributeValues: { \":sub\": userSub },\n Limit: 1,\n }),\n );\n\n const item = result.Items?.[0] as { workspaceId: string } | undefined;\n if (!item) return null;\n\n membershipCache.set(userSub, { workspaceId: item.workspaceId, fetchedAt: now });\n return item.workspaceId;\n}\n\nexport const workspaceMiddleware: MiddlewareHandler<{ Variables: AppVariables }> =\n async (c, next) => {\n const claims = c.get(\"claims\");\n const workspaceId = await resolveWorkspaceId(claims.sub);\n if (!workspaceId) {\n return c.json({ error: \"No workspace membership\" }, 403);\n }\n c.set(\"workspaceId\", workspaceId);\n return next();\n };\n","import { DynamoDBClient } from \"@aws-sdk/client-dynamodb\";\nimport { DynamoDBDocumentClient, GetCommand } from \"@aws-sdk/lib-dynamodb\";\nimport type { MiddlewareHandler } from \"hono\";\nimport type { AppVariables, WorkspaceItem } from \"../types\";\n\nconst WORKSPACE_CACHE_TTL_MS = 60_000;\n\ninterface CacheEntry {\n workspace: WorkspaceItem;\n fetchedAt: number;\n}\n\nconst workspaceCache = new Map<string, CacheEntry>();\n\nlet ddbClient: DynamoDBDocumentClient | null = null;\n\nfunction getDdb() {\n if (!ddbClient) {\n ddbClient = DynamoDBDocumentClient.from(\n new DynamoDBClient({ region: process.env[\"AWS_REGION\"] }),\n );\n }\n return ddbClient;\n}\n\nexport async function getWorkspace(workspaceId: string): Promise<WorkspaceItem | null> {\n const now = Date.now();\n const cached = workspaceCache.get(workspaceId);\n if (cached && now - cached.fetchedAt < WORKSPACE_CACHE_TTL_MS) {\n return cached.workspace;\n }\n\n const result = await getDdb().send(\n new GetCommand({\n TableName: process.env[\"WORKSPACE_TABLE_NAME\"]!,\n Key: { workspaceId },\n }),\n );\n\n if (!result.Item) return null;\n\n const workspace = result.Item as WorkspaceItem;\n workspaceCache.set(workspaceId, { workspace, fetchedAt: now });\n return workspace;\n}\n\nexport const planMiddleware: MiddlewareHandler<{ Variables: AppVariables }> =\n async (c, next) => {\n const workspaceId = c.get(\"workspaceId\");\n const workspace = await getWorkspace(workspaceId);\n if (!workspace) {\n return c.json({ error: \"Workspace not found\" }, 404);\n }\n\n // 402 Payment Required when chat is not included in the plan.\n // Currently all plans include chat; this gate is a forward-looking hook\n // for future restricted plans where chat is a paid add-on.\n if (\"chat\" in workspace.features && !workspace.features.chat) {\n return c.json(\n { error: \"Chat is not available on your current plan\", plan: workspace.plan },\n 402,\n );\n }\n\n c.set(\"workspace\", workspace);\n return next();\n };\n","import { Hono } from \"hono\";\n\nconst health = new Hono();\n\nhealth.get(\"/health\", (c) => c.json({ status: \"ok\" }));\n\nexport { health };\n","import { withTracing } from \"@posthog/ai\";\nimport {\n streamText,\n convertToModelMessages,\n wrapLanguageModel,\n stepCountIs,\n jsonSchema,\n type LanguageModelMiddleware,\n type UIMessage,\n type Tool,\n} from \"ai\";\nimport { Hono } from \"hono\";\nimport { z } from \"zod\";\nimport { CHAT_PROMPT_ALLOWLIST } from \"../fallback-prompts.js\";\nimport { evictGatewaySession, getGatewaySession } from \"../gateway-session.js\";\nimport { getPrompt, compilePrompt, getPostHogClient } from \"../posthog.js\";\nimport {\n getOpenRouterKey,\n createOpenRouterProvider,\n} from \"../providers/openrouter.js\";\nimport { getToolDocs, makeHttpGatewayClient } from \"../tool-docs.js\";\nimport type { AppVariables } from \"../types.js\";\n\nconst chatBodySchema = z.object({\n id: z.string(),\n messages: z.array(z.any()),\n trigger: z.string(),\n metadata: z.unknown().optional(),\n prompt: z\n .object({\n id: z.string(),\n vars: z\n .object({\n compilers: z.array(z.string()).optional(),\n })\n .optional(),\n })\n .optional(),\n});\n\n// Retry once (200 ms backoff) if the provider rejects before the first\n// streamed byte. Once doStream() resolves (headers received, 2xx), the\n// connection is committed and mid-stream errors surface as error UI parts.\nconst retryAtStartMiddleware: LanguageModelMiddleware = {\n specificationVersion: \"v3\",\n async wrapStream({ doStream }) {\n try {\n return await doStream();\n } catch {\n await new Promise<void>((r) => setTimeout(r, 200));\n return doStream();\n }\n },\n};\n\nexport const chatRoute = new Hono<{ Variables: AppVariables }>();\n\ninterface GatewayToolEntry {\n provider: string;\n name: string;\n operation: string;\n description?: string;\n inputSchema?: unknown;\n}\n\nasync function fetchGatewayTools(\n gatewayUrl: string,\n bearerToken: string,\n): Promise<GatewayToolEntry[]> {\n const res = await fetch(`${gatewayUrl}/tools`, {\n headers: { Authorization: `Bearer ${bearerToken}` },\n });\n if (!res.ok) return [];\n const data = (await res.json()) as { tools: GatewayToolEntry[] };\n return data.tools ?? [];\n}\n\nfunction buildTools(\n gatewayTools: GatewayToolEntry[],\n gatewayUrl: string,\n bearerToken: string,\n): Record<string, Tool> {\n const tools: Record<string, Tool> = {};\n\n for (const t of gatewayTools) {\n // Replace dots with underscores — some models reject dots in function names.\n const toolKey = t.name.replace(/\\./g, \"_\");\n\n const rawSchema =\n t.inputSchema && typeof t.inputSchema === \"object\"\n ? t.inputSchema\n : { type: \"object\", properties: {} };\n\n const parameters = jsonSchema<Record<string, unknown>>(\n rawSchema as Parameters<typeof jsonSchema>[0],\n );\n\n const execute = async (args: Record<string, unknown>): Promise<unknown> => {\n const res = await fetch(`${gatewayUrl}/tools/${t.provider}/${t.operation}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${bearerToken}`,\n },\n body: JSON.stringify(args),\n });\n if (!res.ok) {\n const err = await res.json().catch(() => ({ error: res.statusText }));\n return { error: (err as { error?: string }).error ?? res.statusText };\n }\n return res.json();\n };\n\n tools[toolKey] = {\n description: t.description ?? `Call ${t.name}`,\n parameters,\n execute,\n } as unknown as Tool;\n }\n\n return tools;\n}\n\nchatRoute.post(\"/\", async (c) => {\n const body = await c.req.json().catch(() => null);\n const parsed = chatBodySchema.safeParse(body);\n if (!parsed.success) {\n return c.json({ error: \"Invalid request body\" }, 400);\n }\n\n const { messages, prompt: promptBody } = parsed.data;\n const claims = c.get(\"claims\");\n const workspaceId = c.get(\"workspaceId\");\n const workspace = c.get(\"workspace\");\n\n const promptId = promptBody?.id ?? \"chat-patchwork-widget\";\n if (!CHAT_PROMPT_ALLOWLIST.has(promptId)) {\n return c.json({ error: \"Unknown prompt id\" }, 400);\n }\n\n // Gateway tools are additive — chat works without them.\n const gatewayUrl = process.env[\"GATEWAY_URL\"]?.replace(/\\/$/, \"\");\n let sessionToken: string | null = null;\n\n if (gatewayUrl) {\n const authHeader = c.req.header(\"Authorization\") ?? \"\";\n const cognitoToken = authHeader.replace(/^Bearer /, \"\");\n try {\n const session = await getGatewaySession(claims, workspaceId, cognitoToken);\n sessionToken = session.token;\n } catch {\n // Non-fatal — continue without gateway tools\n }\n }\n\n let gatewayTools: GatewayToolEntry[] = [];\n if (sessionToken && gatewayUrl) {\n gatewayTools = await fetchGatewayTools(gatewayUrl, sessionToken).catch(\n () => [] as GatewayToolEntry[],\n );\n if (gatewayTools.length === 0) {\n evictGatewaySession(claims.sub);\n }\n }\n\n const tools =\n sessionToken && gatewayUrl\n ? buildTools(gatewayTools, gatewayUrl, sessionToken)\n : {};\n\n // Load system prompt from PostHog (cached, with fallback to bundled copy)\n const gatewayClient = gatewayUrl ? makeHttpGatewayClient(gatewayUrl) : null;\n const [promptResult, toolDocs] = await Promise.all([\n getPrompt(promptId),\n getToolDocs(gatewayClient).catch(() => \"\"),\n ]);\n const compilers = (promptBody?.vars?.compilers ?? []).join(\", \");\n const systemPrompt = compilePrompt(promptResult.prompt, {\n compilers,\n tool_docs: toolDocs,\n });\n\n const apiKey = await getOpenRouterKey();\n const provider = createOpenRouterProvider(apiKey);\n const modelId = workspace.limits.maxModels[0] ?? \"openrouter/auto\";\n const baseModel = provider(modelId);\n\n const phClient = getPostHogClient();\n const tracedModel =\n phClient && promptResult.source !== \"code_fallback\"\n ? withTracing(baseModel, phClient, {\n posthogDistinctId: claims.sub,\n posthogProperties: {\n $ai_prompt_name: promptResult.name,\n $ai_prompt_version: promptResult.version,\n },\n })\n : baseModel;\n\n const model = wrapLanguageModel({\n model: tracedModel,\n middleware: retryAtStartMiddleware,\n });\n\n const result = streamText({\n model,\n system: systemPrompt,\n messages: await convertToModelMessages(messages as UIMessage[]),\n stopWhen: stepCountIs(workspace.limits.maxToolSteps),\n maxOutputTokens: workspace.limits.maxTokensPerRequest,\n tools,\n });\n\n return result.toUIMessageStreamResponse();\n});\n","// Frozen copies of the canonical prompts, used when PostHog is unreachable\n// and the SDK cache is cold. Keep in sync with the seeded PostHog prompt versions.\n\nconst PATCHWORK_PROMPT = `\nYou are a friendly assistant! When responding to the user, you _must_ respond with JSX files!\n\nLook at 'patchwork.compilers' to see what specific runtime components and libraries are supported. (e.g. '['@aprovan/patchwork-image-shadcn' supports React, Tailwind, & ShadCN components). If there are no compilers, respond as you normally would. If compilers are available, ALWAYS respond with a component following [Component Generation](#component-generation).\n\nLook at 'patchwork.services' to see what services are available for widgets to call. If services are listed, you can generate widgets that make service calls using global namespace objects.\n\n**IMPORTANT: If you need to discover available services or get details about a specific service, use the \\`search_services\\` tool.**\n\n## Component Generation\n\nRespond with code blocks using tagged attributes on the fence line. The \\`note\\` attribute (optional but encouraged) provides a brief description visible in the UI. The \\`path\\` attribute specifies the virtual file path.\n\n### Code Block Format\n\n\\`\\`\\`tsx note=\"Main component\" path=\"components/weather/main.tsx\"\nexport default function WeatherWidget() {\n // component code\n}\n\\`\\`\\`\n\n### Attribute Order\nPut \\`note\\` first so it's available soonest in streaming UI.\n\n### Multi-File Generation\n\nWhen generating complex widgets, you can output multiple files. Use the \\`@/\\` prefix for virtual file system paths. ALWAYS prefer to generate visible components before metadata.\n\n**Example multi-file widget:**\n\n\\`\\`\\`json note=\"Widget configuration\" path=\"components/dashboard/package.json\"\n{\n \"description\": \"Interactive dashboard widget\",\n \"patchwork\": {\n \"inputs\": {\n \"type\": \"object\",\n \"properties\": {\n \"title\": { \"type\": \"string\" }\n }\n },\n \"services\": {\n \"analytics\": [\"getMetrics\", \"getChartData\"]\n }\n }\n}\n\\`\\`\\`\n\n\\`\\`\\`tsx note=\"Main widget component\" path=\"components/dashboard/main.tsx\"\nimport { Card } from './Card';\nimport { Chart } from './Chart';\n\nexport default function Dashboard({ title = \"Dashboard\" }) {\n return (\n <div>\n <h1>{title}</h1>\n <Card />\n <Chart />\n </div>\n );\n}\n\\`\\`\\`\n\n\\`\\`\\`tsx note=\"Card subcomponent\" path=\"components/dashboard/Card.tsx\"\nexport function Card() {\n return <div className=\"p-4 rounded border\">Card content</div>;\n}\n\\`\\`\\`\n\n### Requirements\n- DO think heavily about correctness of code and syntax\n- DO keep things simple and self-contained\n- ALWAYS include the \\`path\\` attribute specifying the file location.\n- ALWAYS use generic component/path names that is not dependent on arguments (e.g. 'dinner.tsx' not 'spaghetti.tsx')\n- ALWAYS output the COMPLETE code block with opening \\`\\`\\`tsx and closing \\`\\`\\` markers\n- Use \\`note\\` attribute to describe what each code block does (optional but encouraged)\n- NEVER truncate or cut off code - finish the entire component before stopping\n- If the component is complex, simplify it rather than leaving it incomplete\n- Do NOT include: a heading/title\n\n### Visual Design Guidelines\nCreate professional, polished interfaces that present information **spatially** rather than as vertical lists:\n- Use **cards, grids, and flexbox layouts** to organize related data into visual groups\n- Leverage **icons** (from lucide-react) alongside text to communicate meaning at a glance\n- Apply **visual hierarchy** through typography scale, weight, and color contrast\n- Use **whitespace strategically** to create breathing room and separation\n- Prefer **horizontal arrangements** where data fits naturally (e.g., stats in a row, badges inline)\n- Group related metrics into **compact visual clusters** rather than separate line items\n- Use **subtle backgrounds, borders, and shadows** to define sections without heavy dividers\n\n### Root Element Constraints\nThe component will be rendered inside a parent container that handles positioning. Your root element should:\n- ✅ Use intrinsic sizing (let content determine dimensions)\n- ✅ Handle internal padding (e.g., \\`p-4\\`, \\`p-6\\`)\n- ❌ NEVER add centering utilities (\\`items-center\\`, \\`justify-center\\`) to position itself\n- ❌ NEVER add viewport-relative sizing (\\`min-h-screen\\`, \\`h-screen\\`, \\`w-screen\\`)\n- ❌ NEVER add flex/grid on root just for self-centering\n\n### Using Services in Widgets (CRITICAL)\n\n**MANDATORY workflow - you must follow these steps IN ORDER:**\n\n1. **Use \\`search_services\\`** to discover the service schema\n2. **STOP. Make an actual call to the service tool itself** (e.g., \\`weather_get_forecast\\`, \\`github_get_repo\\`) with real arguments. This is NOT optional. Do NOT skip this step.\n3. **Observe the response** - verify it succeeded and note the exact data structure\n4. **Only then generate the widget** that fetches the same data at runtime\n\n**\\`search_services\\` is NOT a substitute for calling the actual service.** It only returns documentation. You MUST invoke the real service tool to validate your arguments work.\n\n**Tool naming:** Service tools use underscores, not dots. For example: \\`weather_get_forecast\\`, \\`github_list_repos\\`.\n\n**Example workflow for a weather widget:**\n\\`\\`\\`\nStep 1: search_services({ query: \"weather\" })\n → Learn that weather_get_current_conditions exists with params: { latitude, longitude }\n\nStep 2: weather_get_current_conditions({ latitude: 29.7604, longitude: -95.3698 }) ← REQUIRED!\n → Verify it returns { temp: 72, humidity: 65, ... }\n\nStep 3: Generate widget that calls weather.get_current_conditions at runtime\n\\`\\`\\`\n\n**If you skip Step 2, you will generate broken widgets.** Arguments that look correct in the schema may fail at runtime due to validation rules, required formats, or service-specific constraints.\n\n**NEVER embed static data directly in the component.**\n\n❌ **WRONG** - Embedding data directly:\n\\`\\`\\`tsx path=\"components/weather/bad-example.tsx\"\n// DON'T DO THIS - calling tool, then embedding the response as static data\nexport default function WeatherWidget() {\n // Static data embedded at generation time - BAD!\n const weather = { temp: 72, condition: \"sunny\", humidity: 45 };\n return <div>Temperature: {weather.temp}°F</div>;\n}\n\\`\\`\\`\n\n✅ **CORRECT** - Fetching data at runtime:\n\\`\\`\\`tsx note=\"Weather widget with runtime data\" path=\"components/weather/main.tsx\"\nexport default function WeatherWidget() {\n const [data, setData] = useState(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState(null);\n\n useEffect(() => {\n // Fetch data at runtime - GOOD!\n weather.get_forecast({ latitude: 48.8566, longitude: 2.3522 })\n .then(setData)\n .catch(setError)\n .finally(() => setLoading(false));\n }, []);\n\n if (loading) return <Skeleton className=\"h-32 w-full\" />;\n if (error) return <Alert variant=\"destructive\">{error.message}</Alert>;\n\n return <div>Temperature: {data.temp}°F</div>;\n}\n\\`\\`\\`\n\n**Why this matters:**\n- Widgets with runtime service calls show **live data** that updates when refreshed\n- Static embedded data becomes **stale immediately** after generation\n- The proxy pattern allows widgets to be **reusable** across different contexts\n- Error handling and loading states improve **user experience**\n\n**Service call pattern:**\n\\`\\`\\`tsx\n// Services are available as global namespace objects\n// Call format: namespace.procedure_name({ ...args })\n\nconst [data, setData] = useState(null);\nconst [loading, setLoading] = useState(true);\nconst [error, setError] = useState(null);\n\nuseEffect(() => {\n serviceName.procedure_name({ param1: value1 })\n .then(setData)\n .catch(setError)\n .finally(() => setLoading(false));\n}, [/* dependencies */]);\n\\`\\`\\`\n\n**Required for service-using widgets:**\n- Always show loading indicators (Skeleton, Loader2 spinner, etc.)\n- Always handle errors gracefully with user-friendly messages\n- Use appropriate React hooks (useState, useEffect) for async data\n- Services are injected as globals - NO imports needed\n\n### Validating Service Calls (CRITICAL - READ CAREFULLY)\n\n**Calling \\`search_services\\` multiple times is NOT validation.** You must call the actual service tool.\n\n❌ **WRONG workflow (will produce broken widgets):**\n\\`\\`\\`\n1. search_services({ query: \"weather\" }) ← Only gets schema\n2. search_services({ query: \"location\" }) ← Still only schema\n3. Generate widget ← BROKEN - never tested the actual service!\n\\`\\`\\`\n\n✅ **CORRECT workflow:**\n\\`\\`\\`\n1. search_services({ query: \"weather\" }) ← Get schema\n2. weather_get_forecast({ latitude: 29.76, longitude: -95.37 }) ← ACTUALLY CALL IT\n3. Observe response: { temp: 72, conditions: \"sunny\", ... }\n4. Generate widget that calls weather.get_forecast at runtime\n\\`\\`\\`\n\n**The service tool (e.g., \\`weather_get_forecast\\`, \\`github_list_repos\\`) is a DIFFERENT tool from \\`search_services\\`.** You have access to both. Use both.\n\n**Only after a successful test call to the actual service should you generate the widget.**\n\n### Component Parameterization (IMPORTANT)\n\n**Widgets should accept props for dynamic values instead of hardcoding:**\n\n❌ **WRONG** - Hardcoded values:\n\\`\\`\\`tsx path=\"components/weather/bad-example.tsx\"\nexport default function WeatherWidget() {\n // Location hardcoded - BAD!\n const [lat, lon] = [48.8566, 2.3522]; // Paris\n // ...\n}\n\\`\\`\\`\n\n✅ **CORRECT** - Parameterized with props and defaults:\n\\`\\`\\`tsx note=\"Parameterized weather widget\" path=\"components/weather/main.tsx\"\ninterface WeatherWidgetProps {\n location?: string; // e.g., \"Paris, France\"\n latitude?: number; // Direct coordinates (optional)\n longitude?: number;\n}\n\nexport default function WeatherWidget({\n location = \"Paris, France\",\n latitude,\n longitude\n}: WeatherWidgetProps) {\n // Use provided coordinates or look up from location name\n // ...\n}\n\\`\\`\\`\n\n**Why parameterize:**\n- Components become **reusable** across different contexts\n- Users can **customize behavior** without editing code\n- Enables **composition** - parent components can pass different values\n- Supports **testing** with various inputs\n\n**What to parameterize:**\n- Location names, coordinates, IDs\n- Search queries and filters\n- Display options (count, format, theme)\n- API-specific identifiers (usernames, repo names, etc.)\n\n### Anti-patterns to Avoid\n- ❌ Bulleted or numbered lists of key-value pairs\n- ❌ Vertical stacks where horizontal layouts would fit\n- ❌ Plain text labels without visual treatment\n- ❌ Uniform styling that doesn't distinguish primary from secondary information\n- ❌ Wrapping components in centering containers (parent handles this)\n- ❌ **Embedding API response data directly in components instead of fetching at runtime**\n- ❌ **Calling a tool, then putting the response as static JSX/JSON in the generated code**\n- ❌ **Hardcoding values that should be component props**\n- ❌ **Calling \\`search_services\\` multiple times instead of calling the actual service tool**\n- ❌ **Generating a widget without first making a real call to the service with your intended arguments**\n- ❌ **Treating schema documentation as proof that a service call will work**\n- ❌ **Omitting the \\`path\\` attribute on code blocks**\n`;\n\nconst EDIT_PROMPT = `\nYou are editing an existing JSX component. The user will provide the current code and describe the changes they want.\n\n## Response Format\n\nUse code fences with tagged attributes. The \\`note\\` attribute (optional but encouraged) provides a brief description visible in the UI. The \\`path\\` attribute specifies the target file.\n\n\\`\\`\\`diff note=\"Brief description of this change\" path=\"@/components/Button.tsx\"\n<<<<<<< SEARCH\nexact code to find\n=======\nreplacement code\n>>>>>>> REPLACE\n\\`\\`\\`\n\n### Attribute Order\nPut \\`note\\` first so it's available soonest in streaming UI.\n\n### Multi-File Edits\nWhen editing multiple files, use the \\`path\\` attribute with virtual paths (\\`@/\\` prefix for generated files):\n\n\\`\\`\\`diff note=\"Update button handler\" path=\"@/components/Button.tsx\"\n<<<<<<< SEARCH\nonClick={() => {}}\n=======\nonClick={() => handleClick()}\n>>>>>>> REPLACE\n\\`\\`\\`\n\n\\`\\`\\`diff note=\"Add utility function\" path=\"@/lib/utils.ts\"\n<<<<<<< SEARCH\nexport const formatDate = ...\n=======\nexport const formatDate = ...\n\nexport const handleClick = () => console.log('clicked');\n>>>>>>> REPLACE\n\\`\\`\\`\n\n## Rules\n- SEARCH block must match the existing code EXACTLY (whitespace, indentation, everything)\n- You can include multiple diff blocks for multiple changes\n- Each diff block should have its own \\`note\\` attribute annotation\n- Keep changes minimal and targeted\n- Do NOT output the full file - only the diffs\n- If clarification is needed, ask briefly before any diffs\n\n## CRITICAL: Diff Marker Safety\n- NEVER include the strings \"<<<<<<< SEARCH\", \"=======\", or \">>>>>>> REPLACE\" inside your replacement code\n- These are reserved markers for parsing the diff format\n- If you need to show diff-like content, use alternative notation (e.g., \"// old code\" / \"// new code\")\n- Malformed diff markers will cause the edit to fail\n\n## Summary\nAfter all diffs, provide a brief markdown summary of the changes made. Use formatting like:\n- **Bold** for emphasis on key changes\n- Bullet points for listing multiple changes\n- Keep it concise (2-4 lines max)\n- Do NOT include: a heading/title\n`;\n\nconst PLAIN_PROMPT = `You are a helpful assistant.\n\n{{tool_docs}}`;\n\nexport const FALLBACK_PROMPTS: Record<string, string> = {\n \"chat-patchwork-widget\": `---\\npatchwork:\\n compilers: {{compilers}}\\n---\\n\\n${PATCHWORK_PROMPT}\\n\\n{{tool_docs}}`,\n \"chat-plain\": PLAIN_PROMPT,\n \"edit-patchwork-widget\": `Current component code:\\n\\`\\`\\`tsx\\n{{code}}\\n\\`\\`\\`\\n\\n${EDIT_PROMPT}`,\n};\n\nexport const CHAT_PROMPT_ALLOWLIST = new Set([\"chat-patchwork-widget\", \"chat-plain\"]);\nexport const EDIT_PROMPT_ID = \"edit-patchwork-widget\";\n","/**\n * Gateway session client.\n *\n * The registry gateway authenticates every request with the caller's Cognito\n * access token and requires the active workspace to be persisted in DDB via\n * `POST /auth/sessions` before other endpoints will accept requests.\n *\n * GatewaySessionClient handles that one-time setup per user and returns the\n * Cognito token itself as the bearer to use on subsequent gateway calls. The\n * result is cached in-memory per user sub for the remaining lifetime of the\n * Cognito token.\n */\n\nimport type { CognitoAccessTokenPayload } from \"aws-jwt-verify/jwt-model\";\n\ninterface SessionEntry {\n /** The Cognito access token to use as bearer for gateway calls. */\n token: string;\n /** Unix seconds — mirrors the Cognito token's `exp` claim. */\n expires_at: number;\n}\n\nconst sessionCache = new Map<string, SessionEntry>();\n\nfunction gatewayUrl(): string {\n const url = process.env[\"GATEWAY_URL\"];\n if (!url) throw new Error(\"GATEWAY_URL is not set\");\n return url.replace(/\\/$/, \"\");\n}\n\n/**\n * Establish (or reuse) a gateway session for the caller.\n *\n * 1. Returns the cached entry if still valid.\n * 2. Calls `POST /auth/sessions` on the gateway to register the active\n * workspace for this user.\n * 3. Returns the Cognito token as the bearer — the gateway uses it directly.\n * 4. On 401, evicts the cache and retries once.\n */\nexport async function getGatewaySession(\n claims: CognitoAccessTokenPayload,\n workspaceId: string,\n cognitoToken: string,\n): Promise<SessionEntry> {\n const sub = claims.sub;\n const now = Math.floor(Date.now() / 1000);\n\n const cached = sessionCache.get(sub);\n if (cached && cached.expires_at > now + 60) {\n return cached;\n }\n\n const entry = await exchangeSession(cognitoToken, workspaceId, claims.exp as number);\n sessionCache.set(sub, entry);\n return entry;\n}\n\nasync function exchangeSession(\n cognitoToken: string,\n workspaceId: string,\n exp: number,\n): Promise<SessionEntry> {\n const res = await callAuthSessions(cognitoToken, workspaceId);\n\n if (res.status === 401) {\n // Retry once — token may have just been refreshed by the caller.\n const retryRes = await callAuthSessions(cognitoToken, workspaceId);\n if (!retryRes.ok) {\n throw new GatewaySessionError(retryRes.status, await retryRes.text());\n }\n return { token: cognitoToken, expires_at: exp };\n }\n\n if (!res.ok) {\n throw new GatewaySessionError(res.status, await res.text());\n }\n\n return { token: cognitoToken, expires_at: exp };\n}\n\nasync function callAuthSessions(\n cognitoToken: string,\n workspaceId: string,\n): Promise<Response> {\n return fetch(`${gatewayUrl()}/auth/sessions`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${cognitoToken}`,\n },\n body: JSON.stringify({ workspace_id: workspaceId }),\n });\n}\n\nexport function evictGatewaySession(sub: string): void {\n sessionCache.delete(sub);\n}\n\nexport function resetGatewaySessionCache(): void {\n sessionCache.clear();\n}\n\nexport class GatewaySessionError extends Error {\n constructor(\n public readonly status: number,\n message: string,\n ) {\n super(`Gateway session exchange failed (${status}): ${message}`);\n this.name = \"GatewaySessionError\";\n }\n}\n","import { Prompts, type PromptResult } from \"@posthog/ai\";\nimport { PostHog } from \"posthog-node\";\nimport { FALLBACK_PROMPTS } from \"./fallback-prompts.js\";\nimport type { Env } from \"./env.js\";\n\n// Module-scope singletons — survive Lambda warm invocations\nlet _client: PostHog | null = null;\nlet _prompts: Prompts | null = null;\n\nexport function initPostHog(env: Env): void {\n if (!env.POSTHOG_PROJECT_API_KEY || !env.POSTHOG_PERSONAL_API_KEY) return;\n\n _client = new PostHog(env.POSTHOG_PROJECT_API_KEY, {\n host: env.POSTHOG_HOST,\n personalApiKey: env.POSTHOG_PERSONAL_API_KEY,\n });\n\n _prompts = new Prompts({ posthog: _client });\n}\n\nexport async function getPrompt(\n name: string,\n cacheTtlSeconds = 300,\n): Promise<PromptResult> {\n const fallback = FALLBACK_PROMPTS[name] ?? \"\";\n\n if (!_prompts) {\n return { source: \"code_fallback\", prompt: fallback, name: undefined, version: undefined };\n }\n\n return _prompts.get(name, { cacheTtlSeconds, fallback });\n}\n\nexport function compilePrompt(\n template: string,\n vars: Record<string, string>,\n): string {\n if (_prompts) {\n return _prompts.compile(template, vars);\n }\n return template.replace(/\\{\\{(\\w+)\\}\\}/g, (_, key) => vars[key] ?? \"\");\n}\n\nexport function getPostHogClient(): PostHog | null {\n return _client;\n}\n\nexport type { PostHog };\n","import {\n SecretsManagerClient,\n GetSecretValueCommand,\n} from \"@aws-sdk/client-secrets-manager\";\nimport { createOpenAICompatible } from \"@ai-sdk/openai-compatible\";\n\nlet secretsClient: SecretsManagerClient | null = null;\nlet cachedKey: string | null = null;\n\nfunction getSecretsClient(): SecretsManagerClient {\n if (!secretsClient) {\n secretsClient = new SecretsManagerClient({\n region: process.env[\"AWS_REGION\"],\n });\n }\n return secretsClient;\n}\n\nexport async function getOpenRouterKey(): Promise<string> {\n if (cachedKey) return cachedKey;\n\n const result = await getSecretsClient().send(\n new GetSecretValueCommand({\n SecretId: process.env[\"OPENROUTER_SECRET_ARN\"]!,\n }),\n );\n cachedKey = result.SecretString!;\n return cachedKey!;\n}\n\nexport function createOpenRouterProvider(apiKey: string) {\n return createOpenAICompatible({\n name: \"openrouter\",\n apiKey,\n baseURL: \"https://openrouter.ai/api/v1\",\n });\n}\n","export interface ToolInfo {\n namespace: string;\n name: string;\n description?: string;\n}\n\nexport interface GatewayClient {\n listTools(): Promise<ToolInfo[]>;\n}\n\n// Module-scope cache — 60 s TTL, survives Lambda warm invocations\nlet _cachedToolDocs = \"\";\nlet _toolDocsCachedAt = 0;\nconst TOOL_DOCS_TTL_MS = 60_000;\n\nexport async function getToolDocs(\n gateway: GatewayClient | null,\n): Promise<string> {\n if (!gateway) return \"\";\n\n const now = Date.now();\n if (_cachedToolDocs && now - _toolDocsCachedAt < TOOL_DOCS_TTL_MS) {\n return _cachedToolDocs;\n }\n\n const tools = await gateway.listTools();\n _cachedToolDocs = renderToolDocs(tools);\n _toolDocsCachedAt = now;\n return _cachedToolDocs;\n}\n\nfunction renderToolDocs(tools: ToolInfo[]): string {\n if (tools.length === 0) return \"\";\n\n const byNamespace = new Map<string, ToolInfo[]>();\n for (const tool of tools) {\n const existing = byNamespace.get(tool.namespace) ?? [];\n existing.push(tool);\n byNamespace.set(tool.namespace, existing);\n }\n\n const namespaces = [...byNamespace.keys()];\n let doc = `## Services\\n\\nThe following services are available for generated widgets to call:\\n\\n`;\n\n for (const [ns, nsTools] of byNamespace) {\n doc += `### \\`${ns}\\`\\n`;\n for (const tool of nsTools) {\n doc += `- \\`${ns}.${tool.name}()\\``;\n if (tool.description) doc += `: ${tool.description}`;\n doc += \"\\n\";\n }\n doc += \"\\n\";\n }\n\n const firstNs = namespaces[0] ?? \"service\";\n const firstTool = byNamespace.get(firstNs)?.[0]?.name ?? \"example\";\n doc += `**Usage in widgets:**\n\\`\\`\\`tsx\n// Services are available as global namespaces\nconst result = await ${firstNs}.${firstTool}({ /* args */ });\n\\`\\`\\`\n\nMake sure to handle loading states and errors when calling services.\n`;\n\n return doc;\n}\n\nexport function makeHttpGatewayClient(gatewayUrl: string): GatewayClient {\n return {\n async listTools(): Promise<ToolInfo[]> {\n const res = await fetch(`${gatewayUrl}/tools`);\n if (!res.ok) throw new Error(`Gateway returned ${res.status}`);\n return res.json() as Promise<ToolInfo[]>;\n },\n };\n}\n","import { withTracing } from \"@posthog/ai\";\nimport { streamText } from \"ai\";\nimport { Hono } from \"hono\";\nimport { z } from \"zod\";\nimport { EDIT_PROMPT_ID } from \"../fallback-prompts.js\";\nimport { getPrompt, compilePrompt, getPostHogClient } from \"../posthog.js\";\nimport {\n getOpenRouterKey,\n createOpenRouterProvider,\n} from \"../providers/openrouter.js\";\nimport type { AppVariables } from \"../types.js\";\n\nconst editBodySchema = z.object({\n code: z.string(),\n prompt: z.string(),\n});\n\nconst MODEL_ID = \"openrouter/auto\";\n\nexport const editRoute = new Hono<{ Variables: AppVariables }>();\n\neditRoute.post(\"/\", async (c) => {\n const body = await c.req.json().catch(() => null);\n const parsed = editBodySchema.safeParse(body);\n if (!parsed.success) {\n return c.json({ error: \"Invalid request body\" }, 400);\n }\n\n const promptResult = await getPrompt(EDIT_PROMPT_ID);\n const systemPrompt = compilePrompt(promptResult.prompt, {\n code: parsed.data.code,\n });\n\n const apiKey = await getOpenRouterKey();\n const provider = createOpenRouterProvider(apiKey);\n const baseModel = provider(MODEL_ID);\n\n const phClient = getPostHogClient();\n const model =\n phClient && promptResult.source !== \"code_fallback\"\n ? withTracing(baseModel, phClient, {\n posthogDistinctId: \"chat-api\",\n posthogProperties: {\n $ai_prompt_name: promptResult.name,\n $ai_prompt_version: promptResult.version,\n },\n })\n : baseModel;\n\n const result = streamText({\n model,\n system: systemPrompt,\n messages: [{ role: \"user\", content: parsed.data.prompt }],\n });\n\n return result.toTextStreamResponse();\n});\n","/**\n * GET /api/services\n *\n * Fetches the gateway's tool list and returns a service summary the frontend\n * uses to populate namespace suggestions and the ServicesInspector panel.\n *\n * Response shape:\n * { namespaces: string[], services: ServiceInfo[] }\n *\n * where ServiceInfo matches the frontend's expected `ServiceInfo` type:\n * { namespace: string, name: string, description?: string }\n */\n\nimport { Hono } from \"hono\";\nimport { evictGatewaySession, GatewaySessionError, getGatewaySession } from \"../gateway-session.js\";\nimport type { AppVariables } from \"../types.js\";\n\nexport interface ServiceInfo {\n namespace: string;\n name: string;\n procedure: string;\n description: string;\n parameters?: Record<string, unknown>;\n}\n\nexport const services = new Hono<{ Variables: AppVariables }>();\n\nservices.get(\"/\", async (c) => {\n const claims = c.get(\"claims\");\n const workspaceId = c.get(\"workspaceId\");\n\n const authHeader = c.req.header(\"Authorization\")!;\n const cognitoToken = authHeader.slice(\"Bearer \".length);\n\n let sessionToken: string;\n try {\n const session = await getGatewaySession(claims, workspaceId, cognitoToken);\n sessionToken = session.token;\n } catch (err) {\n if (err instanceof GatewaySessionError && err.status === 401) {\n return c.json({ error: \"Gateway session setup failed\" }, 401);\n }\n return c.json({ error: \"Failed to connect to gateway\" }, 502);\n }\n\n const gatewayUrl = process.env[\"GATEWAY_URL\"]!.replace(/\\/$/, \"\");\n const res = await fetch(`${gatewayUrl}/tools`, {\n headers: { Authorization: `Bearer ${sessionToken}` },\n });\n\n if (res.status === 401) {\n evictGatewaySession(claims.sub);\n return c.json({ error: \"Gateway authentication failed\" }, 502);\n }\n\n if (!res.ok) {\n return c.json({ error: \"Gateway tools fetch failed\" }, 502);\n }\n\n const data = await res.json() as {\n tools: Array<{\n provider: string;\n name: string;\n operation: string;\n description?: string;\n inputSchema?: unknown;\n }>;\n workspace_id: string;\n };\n\n const serviceList: ServiceInfo[] = data.tools.map((t) => ({\n namespace: t.provider,\n name: t.name,\n procedure: t.operation,\n description: t.description ?? \"\",\n parameters: t.inputSchema as Record<string, unknown> | undefined,\n }));\n\n const namespaces = Array.from(new Set(data.tools.map((t) => t.provider)));\n\n return c.json({ namespaces, services: serviceList });\n});\n","/**\n * POST /api/proxy/:ns/:proc\n *\n * Forwards the request body to `POST <gateway>/tools/:ns/:proc` using the\n * caller's gateway session bearer. This keeps tool invocations server-side\n * (credentials never reach the browser).\n */\n\nimport { Hono } from \"hono\";\nimport { evictGatewaySession, GatewaySessionError, getGatewaySession } from \"../gateway-session.js\";\nimport type { AppVariables } from \"../types.js\";\n\nexport const proxy = new Hono<{ Variables: AppVariables }>();\n\nproxy.post(\"/:ns/:proc{.*}\", async (c) => {\n const claims = c.get(\"claims\");\n const workspaceId = c.get(\"workspaceId\");\n\n const ns = c.req.param(\"ns\");\n const proc = c.req.param(\"proc\");\n\n const authHeader = c.req.header(\"Authorization\")!;\n const cognitoToken = authHeader.slice(\"Bearer \".length);\n\n let sessionToken: string;\n try {\n const session = await getGatewaySession(claims, workspaceId, cognitoToken);\n sessionToken = session.token;\n } catch (err) {\n if (err instanceof GatewaySessionError && err.status === 401) {\n return c.json({ error: \"Gateway session setup failed\" }, 401);\n }\n return c.json({ error: \"Failed to connect to gateway\" }, 502);\n }\n\n let body: unknown = {};\n try {\n body = await c.req.json();\n } catch {\n // empty body is fine\n }\n\n const gatewayUrl = process.env[\"GATEWAY_URL\"]!.replace(/\\/$/, \"\");\n const res = await fetch(`${gatewayUrl}/tools/${ns}/${proc}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${sessionToken}`,\n },\n body: JSON.stringify(body),\n });\n\n if (res.status === 401) {\n evictGatewaySession(claims.sub);\n return c.json({ error: \"Gateway authentication failed\" }, 502);\n }\n\n const responseData = await res.json();\n return c.json(responseData, res.status as 200);\n});\n","import { z } from \"zod\";\n\nconst envSchema = z.object({\n NODE_ENV: z\n .enum([\"development\", \"production\", \"test\"])\n .default(\"development\"),\n PORT: z.coerce.number().default(3001),\n COGNITO_USER_POOL_ID: z.string().min(1),\n COGNITO_CLIENT_ID: z.string().min(1),\n AWS_REGION: z.string().default(\"us-east-1\"),\n WORKSPACE_TABLE_NAME: z.string().min(1),\n MEMBERSHIPS_TABLE_NAME: z.string().min(1),\n OPENROUTER_SECRET_ARN: z.string().min(1),\n GATEWAY_URL: z.string().url(),\n\n // PostHog prompt management (optional — falls back to code prompts when absent)\n POSTHOG_PROJECT_API_KEY: z.string().optional(),\n POSTHOG_PERSONAL_API_KEY: z.string().optional(),\n POSTHOG_HOST: z.string().default(\"https://us.posthog.com\"),\n});\n\nexport type Env = z.infer<typeof envSchema>;\n\nexport function parseEnv(raw: Record<string, string | undefined>): Env {\n return envSchema.parse(raw);\n}\n","import { streamHandle } from \"hono/aws-lambda\";\nimport { createChatApp, initPostHog } from \"./app.js\";\nimport { parseEnv } from \"./env.js\";\nimport type { LambdaEvent } from \"hono/aws-lambda\";\n\nconst env = parseEnv(process.env);\n\n// Initialize module-scope PostHog singletons once per cold start\ninitPostHog(env);\n\nconst app = createChatApp();\n\nexport const handler = streamHandle(app) as (\n event: LambdaEvent,\n context: unknown,\n callback: unknown,\n) => Promise<unknown>;\n"],"mappings":"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","names":["Hono","DynamoDBClient","DynamoDBDocumentClient","ddbClient","getDdb","Hono","env","gatewayUrl","Hono","gatewayUrl","withTracing","streamText","Hono","z","z","Hono","withTracing","streamText","Hono","Hono","gatewayUrl","Hono","Hono","gatewayUrl","app","Hono","z","env","app"]}
|
package/dist/lambda.js
CHANGED
|
@@ -492,13 +492,6 @@ var EDIT_PROMPT_ID = "edit-patchwork-widget";
|
|
|
492
492
|
|
|
493
493
|
// src/gateway-session.ts
|
|
494
494
|
var sessionCache = /* @__PURE__ */ new Map();
|
|
495
|
-
var toolsCache = /* @__PURE__ */ new Map();
|
|
496
|
-
function getCachedTools(sub) {
|
|
497
|
-
return toolsCache.get(sub);
|
|
498
|
-
}
|
|
499
|
-
function setCachedTools(sub, tools) {
|
|
500
|
-
toolsCache.set(sub, tools);
|
|
501
|
-
}
|
|
502
495
|
function gatewayUrl() {
|
|
503
496
|
const url = process.env["GATEWAY_URL"];
|
|
504
497
|
if (!url) throw new Error("GATEWAY_URL is not set");
|
|
@@ -516,13 +509,7 @@ async function getGatewaySession(claims, workspaceId, cognitoToken) {
|
|
|
516
509
|
return entry;
|
|
517
510
|
}
|
|
518
511
|
async function exchangeSession(cognitoToken, workspaceId, exp) {
|
|
519
|
-
|
|
520
|
-
try {
|
|
521
|
-
res = await callAuthSessions(cognitoToken, workspaceId);
|
|
522
|
-
} catch {
|
|
523
|
-
await new Promise((r) => setTimeout(r, 200));
|
|
524
|
-
res = await callAuthSessions(cognitoToken, workspaceId);
|
|
525
|
-
}
|
|
512
|
+
const res = await callAuthSessions(cognitoToken, workspaceId);
|
|
526
513
|
if (res.status === 401) {
|
|
527
514
|
const retryRes = await callAuthSessions(cognitoToken, workspaceId);
|
|
528
515
|
if (!retryRes.ok) {
|
|
@@ -530,14 +517,6 @@ async function exchangeSession(cognitoToken, workspaceId, exp) {
|
|
|
530
517
|
}
|
|
531
518
|
return { token: cognitoToken, expires_at: exp };
|
|
532
519
|
}
|
|
533
|
-
if (res.status >= 500) {
|
|
534
|
-
await new Promise((r) => setTimeout(r, 200));
|
|
535
|
-
const retryRes = await callAuthSessions(cognitoToken, workspaceId);
|
|
536
|
-
if (!retryRes.ok) {
|
|
537
|
-
throw new GatewaySessionError(retryRes.status, await retryRes.text());
|
|
538
|
-
}
|
|
539
|
-
return { token: cognitoToken, expires_at: exp };
|
|
540
|
-
}
|
|
541
520
|
if (!res.ok) {
|
|
542
521
|
throw new GatewaySessionError(res.status, await res.text());
|
|
543
522
|
}
|
|
@@ -555,7 +534,6 @@ async function callAuthSessions(cognitoToken, workspaceId) {
|
|
|
555
534
|
}
|
|
556
535
|
function evictGatewaySession(sub) {
|
|
557
536
|
sessionCache.delete(sub);
|
|
558
|
-
toolsCache.delete(sub);
|
|
559
537
|
}
|
|
560
538
|
var GatewaySessionError = class extends Error {
|
|
561
539
|
constructor(status, message) {
|
|
@@ -782,18 +760,11 @@ chatRoute.post("/", async (c) => {
|
|
|
782
760
|
}
|
|
783
761
|
let gatewayTools = [];
|
|
784
762
|
if (sessionToken && gatewayUrl2) {
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
() => []
|
|
791
|
-
);
|
|
792
|
-
if (gatewayTools.length === 0) {
|
|
793
|
-
evictGatewaySession(claims.sub);
|
|
794
|
-
} else {
|
|
795
|
-
setCachedTools(claims.sub, gatewayTools);
|
|
796
|
-
}
|
|
763
|
+
gatewayTools = await fetchGatewayTools(gatewayUrl2, sessionToken).catch(
|
|
764
|
+
() => []
|
|
765
|
+
);
|
|
766
|
+
if (gatewayTools.length === 0) {
|
|
767
|
+
evictGatewaySession(claims.sub);
|
|
797
768
|
}
|
|
798
769
|
}
|
|
799
770
|
const tools = sessionToken && gatewayUrl2 ? buildTools(gatewayTools, gatewayUrl2, sessionToken) : {};
|
package/dist/lambda.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lambda.ts","../src/app.ts","../src/middleware/auth.ts","../src/middleware/workspace.ts","../src/middleware/plan.ts","../src/routes/health.ts","../src/routes/chat.ts","../src/fallback-prompts.ts","../src/gateway-session.ts","../src/posthog.ts","../src/providers/openrouter.ts","../src/tool-docs.ts","../src/routes/edit.ts","../src/routes/services.ts","../src/routes/proxy.ts","../src/env.ts"],"sourcesContent":["import { streamHandle } from \"hono/aws-lambda\";\nimport { createChatApp, initPostHog } from \"./app.js\";\nimport { parseEnv } from \"./env.js\";\nimport type { LambdaEvent } from \"hono/aws-lambda\";\n\nconst env = parseEnv(process.env);\n\n// Initialize module-scope PostHog singletons once per cold start\ninitPostHog(env);\n\nconst app = createChatApp();\n\nexport const handler = streamHandle(app) as (\n event: LambdaEvent,\n context: unknown,\n callback: unknown,\n) => Promise<unknown>;\n","import { Hono } from \"hono\";\nimport { authMiddleware } from \"./middleware/auth.js\";\nimport { workspaceMiddleware } from \"./middleware/workspace.js\";\nimport { planMiddleware } from \"./middleware/plan.js\";\nimport { health } from \"./routes/health.js\";\nimport { chatRoute } from \"./routes/chat.js\";\nimport { editRoute } from \"./routes/edit.js\";\nimport { services } from \"./routes/services.js\";\nimport { proxy } from \"./routes/proxy.js\";\nimport type { AppVariables } from \"./types.js\";\n\nexport { initPostHog } from \"./posthog.js\";\n\nexport function createChatApp() {\n const app = new Hono<{ Variables: AppVariables }>();\n\n // Unauthenticated routes\n app.route(\"/\", health);\n\n // Protected route group: auth → workspace → plan\n const api = app.basePath(\"/api\");\n api.use(authMiddleware, workspaceMiddleware, planMiddleware);\n api.route(\"/chat\", chatRoute);\n api.route(\"/edit\", editRoute);\n api.route(\"/services\", services);\n api.route(\"/proxy\", proxy);\n\n return app;\n}\n\nexport type ChatApp = ReturnType<typeof createChatApp>;\n","import { CognitoJwtVerifier } from \"aws-jwt-verify\";\nimport type { CognitoAccessTokenPayload } from \"aws-jwt-verify/jwt-model\";\nimport type { MiddlewareHandler } from \"hono\";\nimport type { AppVariables } from \"../types\";\n\ninterface JwtVerifier {\n verify(token: string): Promise<CognitoAccessTokenPayload>;\n}\n\nlet verifier: JwtVerifier | null = null;\n\nfunction getVerifier(): JwtVerifier {\n if (!verifier) {\n verifier = CognitoJwtVerifier.create({\n userPoolId: process.env[\"COGNITO_USER_POOL_ID\"]!,\n clientId: process.env[\"COGNITO_CLIENT_ID\"]!,\n tokenUse: \"access\",\n });\n }\n return verifier;\n}\n\nexport const authMiddleware: MiddlewareHandler<{ Variables: AppVariables }> =\n async (c, next) => {\n const authHeader = c.req.header(\"Authorization\");\n if (!authHeader?.startsWith(\"Bearer \")) {\n return c.json({ error: \"Unauthorized\" }, 401);\n }\n\n const token = authHeader.slice(7);\n try {\n const payload = await getVerifier().verify(token);\n c.set(\"claims\", payload);\n return next();\n } catch {\n return c.json({ error: \"Unauthorized\" }, 401);\n }\n };\n","import { DynamoDBClient } from \"@aws-sdk/client-dynamodb\";\nimport { DynamoDBDocumentClient, QueryCommand } from \"@aws-sdk/lib-dynamodb\";\nimport type { MiddlewareHandler } from \"hono\";\nimport type { AppVariables } from \"../types\";\n\nconst MEMBERSHIP_CACHE_TTL_MS = 300_000;\n\ninterface CacheEntry {\n workspaceId: string;\n fetchedAt: number;\n}\n\nconst membershipCache = new Map<string, CacheEntry>();\n\nlet ddbClient: DynamoDBDocumentClient | null = null;\n\nfunction getDdb() {\n if (!ddbClient) {\n ddbClient = DynamoDBDocumentClient.from(\n new DynamoDBClient({ region: process.env[\"AWS_REGION\"] }),\n );\n }\n return ddbClient;\n}\n\nexport async function resolveWorkspaceId(userSub: string): Promise<string | null> {\n const now = Date.now();\n const cached = membershipCache.get(userSub);\n if (cached && now - cached.fetchedAt < MEMBERSHIP_CACHE_TTL_MS) {\n return cached.workspaceId;\n }\n\n const result = await getDdb().send(\n new QueryCommand({\n TableName: process.env[\"MEMBERSHIPS_TABLE_NAME\"]!,\n IndexName: \"ByUserSub\",\n KeyConditionExpression: \"userSub = :sub\",\n ExpressionAttributeValues: { \":sub\": userSub },\n Limit: 1,\n }),\n );\n\n const item = result.Items?.[0] as { workspaceId: string } | undefined;\n if (!item) return null;\n\n membershipCache.set(userSub, { workspaceId: item.workspaceId, fetchedAt: now });\n return item.workspaceId;\n}\n\nexport const workspaceMiddleware: MiddlewareHandler<{ Variables: AppVariables }> =\n async (c, next) => {\n const claims = c.get(\"claims\");\n const workspaceId = await resolveWorkspaceId(claims.sub);\n if (!workspaceId) {\n return c.json({ error: \"No workspace membership\" }, 403);\n }\n c.set(\"workspaceId\", workspaceId);\n return next();\n };\n","import { DynamoDBClient } from \"@aws-sdk/client-dynamodb\";\nimport { DynamoDBDocumentClient, GetCommand } from \"@aws-sdk/lib-dynamodb\";\nimport type { MiddlewareHandler } from \"hono\";\nimport type { AppVariables, WorkspaceItem } from \"../types\";\n\nconst WORKSPACE_CACHE_TTL_MS = 60_000;\n\ninterface CacheEntry {\n workspace: WorkspaceItem;\n fetchedAt: number;\n}\n\nconst workspaceCache = new Map<string, CacheEntry>();\n\nlet ddbClient: DynamoDBDocumentClient | null = null;\n\nfunction getDdb() {\n if (!ddbClient) {\n ddbClient = DynamoDBDocumentClient.from(\n new DynamoDBClient({ region: process.env[\"AWS_REGION\"] }),\n );\n }\n return ddbClient;\n}\n\nexport async function getWorkspace(workspaceId: string): Promise<WorkspaceItem | null> {\n const now = Date.now();\n const cached = workspaceCache.get(workspaceId);\n if (cached && now - cached.fetchedAt < WORKSPACE_CACHE_TTL_MS) {\n return cached.workspace;\n }\n\n const result = await getDdb().send(\n new GetCommand({\n TableName: process.env[\"WORKSPACE_TABLE_NAME\"]!,\n Key: { workspaceId },\n }),\n );\n\n if (!result.Item) return null;\n\n const workspace = result.Item as WorkspaceItem;\n workspaceCache.set(workspaceId, { workspace, fetchedAt: now });\n return workspace;\n}\n\nexport const planMiddleware: MiddlewareHandler<{ Variables: AppVariables }> =\n async (c, next) => {\n const workspaceId = c.get(\"workspaceId\");\n const workspace = await getWorkspace(workspaceId);\n if (!workspace) {\n return c.json({ error: \"Workspace not found\" }, 404);\n }\n\n // 402 Payment Required when chat is not included in the plan.\n // Currently all plans include chat; this gate is a forward-looking hook\n // for future restricted plans where chat is a paid add-on.\n if (\"chat\" in workspace.features && !workspace.features.chat) {\n return c.json(\n { error: \"Chat is not available on your current plan\", plan: workspace.plan },\n 402,\n );\n }\n\n c.set(\"workspace\", workspace);\n return next();\n };\n","import { Hono } from \"hono\";\n\nconst health = new Hono();\n\nhealth.get(\"/health\", (c) => c.json({ status: \"ok\" }));\n\nexport { health };\n","import { withTracing } from \"@posthog/ai\";\nimport {\n streamText,\n convertToModelMessages,\n wrapLanguageModel,\n stepCountIs,\n jsonSchema,\n type LanguageModelMiddleware,\n type UIMessage,\n type Tool,\n} from \"ai\";\nimport { Hono } from \"hono\";\nimport { z } from \"zod\";\nimport { CHAT_PROMPT_ALLOWLIST } from \"../fallback-prompts.js\";\nimport {\n evictGatewaySession,\n getGatewaySession,\n getCachedTools,\n setCachedTools,\n} from \"../gateway-session.js\";\nimport { getPrompt, compilePrompt, getPostHogClient } from \"../posthog.js\";\nimport {\n getOpenRouterKey,\n createOpenRouterProvider,\n} from \"../providers/openrouter.js\";\nimport { getToolDocs, makeHttpGatewayClient } from \"../tool-docs.js\";\nimport type { AppVariables } from \"../types.js\";\n\nconst chatBodySchema = z.object({\n id: z.string(),\n messages: z.array(z.any()),\n trigger: z.string(),\n metadata: z.unknown().optional(),\n prompt: z\n .object({\n id: z.string(),\n vars: z\n .object({\n compilers: z.array(z.string()).optional(),\n })\n .optional(),\n })\n .optional(),\n});\n\n// Retry once (200 ms backoff) if the provider rejects before the first\n// streamed byte. Once doStream() resolves (headers received, 2xx), the\n// connection is committed and mid-stream errors surface as error UI parts.\nconst retryAtStartMiddleware: LanguageModelMiddleware = {\n specificationVersion: \"v3\",\n async wrapStream({ doStream }) {\n try {\n return await doStream();\n } catch {\n await new Promise<void>((r) => setTimeout(r, 200));\n return doStream();\n }\n },\n};\n\nexport const chatRoute = new Hono<{ Variables: AppVariables }>();\n\ninterface GatewayToolEntry {\n provider: string;\n name: string;\n operation: string;\n description?: string;\n inputSchema?: unknown;\n}\n\nasync function fetchGatewayTools(\n gatewayUrl: string,\n bearerToken: string,\n): Promise<GatewayToolEntry[]> {\n const res = await fetch(`${gatewayUrl}/tools`, {\n headers: { Authorization: `Bearer ${bearerToken}` },\n });\n if (!res.ok) return [];\n const data = (await res.json()) as { tools: GatewayToolEntry[] };\n return data.tools ?? [];\n}\n\nfunction buildTools(\n gatewayTools: GatewayToolEntry[],\n gatewayUrl: string,\n bearerToken: string,\n): Record<string, Tool> {\n const tools: Record<string, Tool> = {};\n\n for (const t of gatewayTools) {\n // Replace dots with underscores — some models reject dots in function names.\n const toolKey = t.name.replace(/\\./g, \"_\");\n\n const rawSchema =\n t.inputSchema && typeof t.inputSchema === \"object\"\n ? t.inputSchema\n : { type: \"object\", properties: {} };\n\n const parameters = jsonSchema<Record<string, unknown>>(\n rawSchema as Parameters<typeof jsonSchema>[0],\n );\n\n const execute = async (args: Record<string, unknown>): Promise<unknown> => {\n const res = await fetch(`${gatewayUrl}/tools/${t.provider}/${t.operation}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${bearerToken}`,\n },\n body: JSON.stringify(args),\n });\n if (!res.ok) {\n const err = await res.json().catch(() => ({ error: res.statusText }));\n return { error: (err as { error?: string }).error ?? res.statusText };\n }\n return res.json();\n };\n\n tools[toolKey] = {\n description: t.description ?? `Call ${t.name}`,\n parameters,\n execute,\n } as unknown as Tool;\n }\n\n return tools;\n}\n\nchatRoute.post(\"/\", async (c) => {\n const body = await c.req.json().catch(() => null);\n const parsed = chatBodySchema.safeParse(body);\n if (!parsed.success) {\n return c.json({ error: \"Invalid request body\" }, 400);\n }\n\n const { messages, prompt: promptBody } = parsed.data;\n const claims = c.get(\"claims\");\n const workspaceId = c.get(\"workspaceId\");\n const workspace = c.get(\"workspace\");\n\n const promptId = promptBody?.id ?? \"chat-patchwork-widget\";\n if (!CHAT_PROMPT_ALLOWLIST.has(promptId)) {\n return c.json({ error: \"Unknown prompt id\" }, 400);\n }\n\n // Gateway tools are additive — chat works without them.\n const gatewayUrl = process.env[\"GATEWAY_URL\"]?.replace(/\\/$/, \"\");\n let sessionToken: string | null = null;\n\n if (gatewayUrl) {\n const authHeader = c.req.header(\"Authorization\") ?? \"\";\n const cognitoToken = authHeader.replace(/^Bearer /, \"\");\n try {\n const session = await getGatewaySession(claims, workspaceId, cognitoToken);\n sessionToken = session.token;\n } catch {\n // Non-fatal — continue without gateway tools\n }\n }\n\n let gatewayTools: GatewayToolEntry[] = [];\n if (sessionToken && gatewayUrl) {\n const cached = getCachedTools(claims.sub) as GatewayToolEntry[] | undefined;\n if (cached) {\n gatewayTools = cached;\n } else {\n gatewayTools = await fetchGatewayTools(gatewayUrl, sessionToken).catch(\n () => [] as GatewayToolEntry[],\n );\n if (gatewayTools.length === 0) {\n evictGatewaySession(claims.sub);\n } else {\n setCachedTools(claims.sub, gatewayTools);\n }\n }\n }\n\n const tools =\n sessionToken && gatewayUrl\n ? buildTools(gatewayTools, gatewayUrl, sessionToken)\n : {};\n\n // Load system prompt from PostHog (cached, with fallback to bundled copy)\n const gatewayClient = gatewayUrl ? makeHttpGatewayClient(gatewayUrl) : null;\n const [promptResult, toolDocs] = await Promise.all([\n getPrompt(promptId),\n getToolDocs(gatewayClient).catch(() => \"\"),\n ]);\n const compilers = (promptBody?.vars?.compilers ?? []).join(\", \");\n const systemPrompt = compilePrompt(promptResult.prompt, {\n compilers,\n tool_docs: toolDocs,\n });\n\n const apiKey = await getOpenRouterKey();\n const provider = createOpenRouterProvider(apiKey);\n const modelId = workspace.limits.maxModels[0] ?? \"openrouter/auto\";\n const baseModel = provider(modelId);\n\n const phClient = getPostHogClient();\n const tracedModel =\n phClient && promptResult.source !== \"code_fallback\"\n ? withTracing(baseModel, phClient, {\n posthogDistinctId: claims.sub,\n posthogProperties: {\n $ai_prompt_name: promptResult.name,\n $ai_prompt_version: promptResult.version,\n },\n })\n : baseModel;\n\n const model = wrapLanguageModel({\n model: tracedModel,\n middleware: retryAtStartMiddleware,\n });\n\n const result = streamText({\n model,\n system: systemPrompt,\n messages: await convertToModelMessages(messages as UIMessage[]),\n stopWhen: stepCountIs(workspace.limits.maxToolSteps),\n maxOutputTokens: workspace.limits.maxTokensPerRequest,\n tools,\n });\n\n return result.toUIMessageStreamResponse();\n});\n","// Frozen copies of the canonical prompts, used when PostHog is unreachable\n// and the SDK cache is cold. Keep in sync with the seeded PostHog prompt versions.\n\nconst PATCHWORK_PROMPT = `\nYou are a friendly assistant! When responding to the user, you _must_ respond with JSX files!\n\nLook at 'patchwork.compilers' to see what specific runtime components and libraries are supported. (e.g. '['@aprovan/patchwork-image-shadcn' supports React, Tailwind, & ShadCN components). If there are no compilers, respond as you normally would. If compilers are available, ALWAYS respond with a component following [Component Generation](#component-generation).\n\nLook at 'patchwork.services' to see what services are available for widgets to call. If services are listed, you can generate widgets that make service calls using global namespace objects.\n\n**IMPORTANT: If you need to discover available services or get details about a specific service, use the \\`search_services\\` tool.**\n\n## Component Generation\n\nRespond with code blocks using tagged attributes on the fence line. The \\`note\\` attribute (optional but encouraged) provides a brief description visible in the UI. The \\`path\\` attribute specifies the virtual file path.\n\n### Code Block Format\n\n\\`\\`\\`tsx note=\"Main component\" path=\"components/weather/main.tsx\"\nexport default function WeatherWidget() {\n // component code\n}\n\\`\\`\\`\n\n### Attribute Order\nPut \\`note\\` first so it's available soonest in streaming UI.\n\n### Multi-File Generation\n\nWhen generating complex widgets, you can output multiple files. Use the \\`@/\\` prefix for virtual file system paths. ALWAYS prefer to generate visible components before metadata.\n\n**Example multi-file widget:**\n\n\\`\\`\\`json note=\"Widget configuration\" path=\"components/dashboard/package.json\"\n{\n \"description\": \"Interactive dashboard widget\",\n \"patchwork\": {\n \"inputs\": {\n \"type\": \"object\",\n \"properties\": {\n \"title\": { \"type\": \"string\" }\n }\n },\n \"services\": {\n \"analytics\": [\"getMetrics\", \"getChartData\"]\n }\n }\n}\n\\`\\`\\`\n\n\\`\\`\\`tsx note=\"Main widget component\" path=\"components/dashboard/main.tsx\"\nimport { Card } from './Card';\nimport { Chart } from './Chart';\n\nexport default function Dashboard({ title = \"Dashboard\" }) {\n return (\n <div>\n <h1>{title}</h1>\n <Card />\n <Chart />\n </div>\n );\n}\n\\`\\`\\`\n\n\\`\\`\\`tsx note=\"Card subcomponent\" path=\"components/dashboard/Card.tsx\"\nexport function Card() {\n return <div className=\"p-4 rounded border\">Card content</div>;\n}\n\\`\\`\\`\n\n### Requirements\n- DO think heavily about correctness of code and syntax\n- DO keep things simple and self-contained\n- ALWAYS include the \\`path\\` attribute specifying the file location.\n- ALWAYS use generic component/path names that is not dependent on arguments (e.g. 'dinner.tsx' not 'spaghetti.tsx')\n- ALWAYS output the COMPLETE code block with opening \\`\\`\\`tsx and closing \\`\\`\\` markers\n- Use \\`note\\` attribute to describe what each code block does (optional but encouraged)\n- NEVER truncate or cut off code - finish the entire component before stopping\n- If the component is complex, simplify it rather than leaving it incomplete\n- Do NOT include: a heading/title\n\n### Visual Design Guidelines\nCreate professional, polished interfaces that present information **spatially** rather than as vertical lists:\n- Use **cards, grids, and flexbox layouts** to organize related data into visual groups\n- Leverage **icons** (from lucide-react) alongside text to communicate meaning at a glance\n- Apply **visual hierarchy** through typography scale, weight, and color contrast\n- Use **whitespace strategically** to create breathing room and separation\n- Prefer **horizontal arrangements** where data fits naturally (e.g., stats in a row, badges inline)\n- Group related metrics into **compact visual clusters** rather than separate line items\n- Use **subtle backgrounds, borders, and shadows** to define sections without heavy dividers\n\n### Root Element Constraints\nThe component will be rendered inside a parent container that handles positioning. Your root element should:\n- ✅ Use intrinsic sizing (let content determine dimensions)\n- ✅ Handle internal padding (e.g., \\`p-4\\`, \\`p-6\\`)\n- ❌ NEVER add centering utilities (\\`items-center\\`, \\`justify-center\\`) to position itself\n- ❌ NEVER add viewport-relative sizing (\\`min-h-screen\\`, \\`h-screen\\`, \\`w-screen\\`)\n- ❌ NEVER add flex/grid on root just for self-centering\n\n### Using Services in Widgets (CRITICAL)\n\n**MANDATORY workflow - you must follow these steps IN ORDER:**\n\n1. **Use \\`search_services\\`** to discover the service schema\n2. **STOP. Make an actual call to the service tool itself** (e.g., \\`weather_get_forecast\\`, \\`github_get_repo\\`) with real arguments. This is NOT optional. Do NOT skip this step.\n3. **Observe the response** - verify it succeeded and note the exact data structure\n4. **Only then generate the widget** that fetches the same data at runtime\n\n**\\`search_services\\` is NOT a substitute for calling the actual service.** It only returns documentation. You MUST invoke the real service tool to validate your arguments work.\n\n**Tool naming:** Service tools use underscores, not dots. For example: \\`weather_get_forecast\\`, \\`github_list_repos\\`.\n\n**Example workflow for a weather widget:**\n\\`\\`\\`\nStep 1: search_services({ query: \"weather\" })\n → Learn that weather_get_current_conditions exists with params: { latitude, longitude }\n\nStep 2: weather_get_current_conditions({ latitude: 29.7604, longitude: -95.3698 }) ← REQUIRED!\n → Verify it returns { temp: 72, humidity: 65, ... }\n\nStep 3: Generate widget that calls weather.get_current_conditions at runtime\n\\`\\`\\`\n\n**If you skip Step 2, you will generate broken widgets.** Arguments that look correct in the schema may fail at runtime due to validation rules, required formats, or service-specific constraints.\n\n**NEVER embed static data directly in the component.**\n\n❌ **WRONG** - Embedding data directly:\n\\`\\`\\`tsx path=\"components/weather/bad-example.tsx\"\n// DON'T DO THIS - calling tool, then embedding the response as static data\nexport default function WeatherWidget() {\n // Static data embedded at generation time - BAD!\n const weather = { temp: 72, condition: \"sunny\", humidity: 45 };\n return <div>Temperature: {weather.temp}°F</div>;\n}\n\\`\\`\\`\n\n✅ **CORRECT** - Fetching data at runtime:\n\\`\\`\\`tsx note=\"Weather widget with runtime data\" path=\"components/weather/main.tsx\"\nexport default function WeatherWidget() {\n const [data, setData] = useState(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState(null);\n\n useEffect(() => {\n // Fetch data at runtime - GOOD!\n weather.get_forecast({ latitude: 48.8566, longitude: 2.3522 })\n .then(setData)\n .catch(setError)\n .finally(() => setLoading(false));\n }, []);\n\n if (loading) return <Skeleton className=\"h-32 w-full\" />;\n if (error) return <Alert variant=\"destructive\">{error.message}</Alert>;\n\n return <div>Temperature: {data.temp}°F</div>;\n}\n\\`\\`\\`\n\n**Why this matters:**\n- Widgets with runtime service calls show **live data** that updates when refreshed\n- Static embedded data becomes **stale immediately** after generation\n- The proxy pattern allows widgets to be **reusable** across different contexts\n- Error handling and loading states improve **user experience**\n\n**Service call pattern:**\n\\`\\`\\`tsx\n// Services are available as global namespace objects\n// Call format: namespace.procedure_name({ ...args })\n\nconst [data, setData] = useState(null);\nconst [loading, setLoading] = useState(true);\nconst [error, setError] = useState(null);\n\nuseEffect(() => {\n serviceName.procedure_name({ param1: value1 })\n .then(setData)\n .catch(setError)\n .finally(() => setLoading(false));\n}, [/* dependencies */]);\n\\`\\`\\`\n\n**Required for service-using widgets:**\n- Always show loading indicators (Skeleton, Loader2 spinner, etc.)\n- Always handle errors gracefully with user-friendly messages\n- Use appropriate React hooks (useState, useEffect) for async data\n- Services are injected as globals - NO imports needed\n\n### Validating Service Calls (CRITICAL - READ CAREFULLY)\n\n**Calling \\`search_services\\` multiple times is NOT validation.** You must call the actual service tool.\n\n❌ **WRONG workflow (will produce broken widgets):**\n\\`\\`\\`\n1. search_services({ query: \"weather\" }) ← Only gets schema\n2. search_services({ query: \"location\" }) ← Still only schema\n3. Generate widget ← BROKEN - never tested the actual service!\n\\`\\`\\`\n\n✅ **CORRECT workflow:**\n\\`\\`\\`\n1. search_services({ query: \"weather\" }) ← Get schema\n2. weather_get_forecast({ latitude: 29.76, longitude: -95.37 }) ← ACTUALLY CALL IT\n3. Observe response: { temp: 72, conditions: \"sunny\", ... }\n4. Generate widget that calls weather.get_forecast at runtime\n\\`\\`\\`\n\n**The service tool (e.g., \\`weather_get_forecast\\`, \\`github_list_repos\\`) is a DIFFERENT tool from \\`search_services\\`.** You have access to both. Use both.\n\n**Only after a successful test call to the actual service should you generate the widget.**\n\n### Component Parameterization (IMPORTANT)\n\n**Widgets should accept props for dynamic values instead of hardcoding:**\n\n❌ **WRONG** - Hardcoded values:\n\\`\\`\\`tsx path=\"components/weather/bad-example.tsx\"\nexport default function WeatherWidget() {\n // Location hardcoded - BAD!\n const [lat, lon] = [48.8566, 2.3522]; // Paris\n // ...\n}\n\\`\\`\\`\n\n✅ **CORRECT** - Parameterized with props and defaults:\n\\`\\`\\`tsx note=\"Parameterized weather widget\" path=\"components/weather/main.tsx\"\ninterface WeatherWidgetProps {\n location?: string; // e.g., \"Paris, France\"\n latitude?: number; // Direct coordinates (optional)\n longitude?: number;\n}\n\nexport default function WeatherWidget({\n location = \"Paris, France\",\n latitude,\n longitude\n}: WeatherWidgetProps) {\n // Use provided coordinates or look up from location name\n // ...\n}\n\\`\\`\\`\n\n**Why parameterize:**\n- Components become **reusable** across different contexts\n- Users can **customize behavior** without editing code\n- Enables **composition** - parent components can pass different values\n- Supports **testing** with various inputs\n\n**What to parameterize:**\n- Location names, coordinates, IDs\n- Search queries and filters\n- Display options (count, format, theme)\n- API-specific identifiers (usernames, repo names, etc.)\n\n### Anti-patterns to Avoid\n- ❌ Bulleted or numbered lists of key-value pairs\n- ❌ Vertical stacks where horizontal layouts would fit\n- ❌ Plain text labels without visual treatment\n- ❌ Uniform styling that doesn't distinguish primary from secondary information\n- ❌ Wrapping components in centering containers (parent handles this)\n- ❌ **Embedding API response data directly in components instead of fetching at runtime**\n- ❌ **Calling a tool, then putting the response as static JSX/JSON in the generated code**\n- ❌ **Hardcoding values that should be component props**\n- ❌ **Calling \\`search_services\\` multiple times instead of calling the actual service tool**\n- ❌ **Generating a widget without first making a real call to the service with your intended arguments**\n- ❌ **Treating schema documentation as proof that a service call will work**\n- ❌ **Omitting the \\`path\\` attribute on code blocks**\n`;\n\nconst EDIT_PROMPT = `\nYou are editing an existing JSX component. The user will provide the current code and describe the changes they want.\n\n## Response Format\n\nUse code fences with tagged attributes. The \\`note\\` attribute (optional but encouraged) provides a brief description visible in the UI. The \\`path\\` attribute specifies the target file.\n\n\\`\\`\\`diff note=\"Brief description of this change\" path=\"@/components/Button.tsx\"\n<<<<<<< SEARCH\nexact code to find\n=======\nreplacement code\n>>>>>>> REPLACE\n\\`\\`\\`\n\n### Attribute Order\nPut \\`note\\` first so it's available soonest in streaming UI.\n\n### Multi-File Edits\nWhen editing multiple files, use the \\`path\\` attribute with virtual paths (\\`@/\\` prefix for generated files):\n\n\\`\\`\\`diff note=\"Update button handler\" path=\"@/components/Button.tsx\"\n<<<<<<< SEARCH\nonClick={() => {}}\n=======\nonClick={() => handleClick()}\n>>>>>>> REPLACE\n\\`\\`\\`\n\n\\`\\`\\`diff note=\"Add utility function\" path=\"@/lib/utils.ts\"\n<<<<<<< SEARCH\nexport const formatDate = ...\n=======\nexport const formatDate = ...\n\nexport const handleClick = () => console.log('clicked');\n>>>>>>> REPLACE\n\\`\\`\\`\n\n## Rules\n- SEARCH block must match the existing code EXACTLY (whitespace, indentation, everything)\n- You can include multiple diff blocks for multiple changes\n- Each diff block should have its own \\`note\\` attribute annotation\n- Keep changes minimal and targeted\n- Do NOT output the full file - only the diffs\n- If clarification is needed, ask briefly before any diffs\n\n## CRITICAL: Diff Marker Safety\n- NEVER include the strings \"<<<<<<< SEARCH\", \"=======\", or \">>>>>>> REPLACE\" inside your replacement code\n- These are reserved markers for parsing the diff format\n- If you need to show diff-like content, use alternative notation (e.g., \"// old code\" / \"// new code\")\n- Malformed diff markers will cause the edit to fail\n\n## Summary\nAfter all diffs, provide a brief markdown summary of the changes made. Use formatting like:\n- **Bold** for emphasis on key changes\n- Bullet points for listing multiple changes\n- Keep it concise (2-4 lines max)\n- Do NOT include: a heading/title\n`;\n\nconst PLAIN_PROMPT = `You are a helpful assistant.\n\n{{tool_docs}}`;\n\nexport const FALLBACK_PROMPTS: Record<string, string> = {\n \"chat-patchwork-widget\": `---\\npatchwork:\\n compilers: {{compilers}}\\n---\\n\\n${PATCHWORK_PROMPT}\\n\\n{{tool_docs}}`,\n \"chat-plain\": PLAIN_PROMPT,\n \"edit-patchwork-widget\": `Current component code:\\n\\`\\`\\`tsx\\n{{code}}\\n\\`\\`\\`\\n\\n${EDIT_PROMPT}`,\n};\n\nexport const CHAT_PROMPT_ALLOWLIST = new Set([\"chat-patchwork-widget\", \"chat-plain\"]);\nexport const EDIT_PROMPT_ID = \"edit-patchwork-widget\";\n","/**\n * Gateway session client.\n *\n * The registry gateway authenticates every request with the caller's Cognito\n * access token and requires the active workspace to be persisted in DDB via\n * `POST /auth/sessions` before other endpoints will accept requests.\n *\n * GatewaySessionClient handles that one-time setup per user and returns the\n * Cognito token itself as the bearer to use on subsequent gateway calls. The\n * result is cached in-memory per user sub for the remaining lifetime of the\n * Cognito token.\n */\n\nimport type { CognitoAccessTokenPayload } from \"aws-jwt-verify/jwt-model\";\n\ninterface SessionEntry {\n /** The Cognito access token to use as bearer for gateway calls. */\n token: string;\n /** Unix seconds — mirrors the Cognito token's `exp` claim. */\n expires_at: number;\n}\n\nconst sessionCache = new Map<string, SessionEntry>();\n\n/**\n * Per-session tool-list cache. Keyed by userSub, parallel to sessionCache.\n * Cleared on session eviction so a fresh session always re-fetches tools.\n */\nconst toolsCache = new Map<string, unknown[]>();\n\nexport function getCachedTools(sub: string): unknown[] | undefined {\n return toolsCache.get(sub);\n}\n\nexport function setCachedTools(sub: string, tools: unknown[]): void {\n toolsCache.set(sub, tools);\n}\n\nexport function evictCachedTools(sub: string): void {\n toolsCache.delete(sub);\n}\n\nfunction gatewayUrl(): string {\n const url = process.env[\"GATEWAY_URL\"];\n if (!url) throw new Error(\"GATEWAY_URL is not set\");\n return url.replace(/\\/$/, \"\");\n}\n\n/**\n * Establish (or reuse) a gateway session for the caller.\n *\n * 1. Returns the cached entry if still valid.\n * 2. Calls `POST /auth/sessions` on the gateway to register the active\n * workspace for this user.\n * 3. Returns the Cognito token as the bearer — the gateway uses it directly.\n * 4. On 401, evicts the cache and retries once.\n */\nexport async function getGatewaySession(\n claims: CognitoAccessTokenPayload,\n workspaceId: string,\n cognitoToken: string,\n): Promise<SessionEntry> {\n const sub = claims.sub;\n const now = Math.floor(Date.now() / 1000);\n\n const cached = sessionCache.get(sub);\n if (cached && cached.expires_at > now + 60) {\n return cached;\n }\n\n const entry = await exchangeSession(cognitoToken, workspaceId, claims.exp as number);\n sessionCache.set(sub, entry);\n return entry;\n}\n\nasync function exchangeSession(\n cognitoToken: string,\n workspaceId: string,\n exp: number,\n): Promise<SessionEntry> {\n let res: Response;\n try {\n res = await callAuthSessions(cognitoToken, workspaceId);\n } catch {\n // Network error — retry once after 200ms.\n await new Promise<void>((r) => setTimeout(r, 200));\n res = await callAuthSessions(cognitoToken, workspaceId);\n }\n\n if (res.status === 401) {\n // Retry once — token may have just been refreshed by the caller.\n const retryRes = await callAuthSessions(cognitoToken, workspaceId);\n if (!retryRes.ok) {\n throw new GatewaySessionError(retryRes.status, await retryRes.text());\n }\n return { token: cognitoToken, expires_at: exp };\n }\n\n if (res.status >= 500) {\n // 5xx — retry once after 200ms.\n await new Promise<void>((r) => setTimeout(r, 200));\n const retryRes = await callAuthSessions(cognitoToken, workspaceId);\n if (!retryRes.ok) {\n throw new GatewaySessionError(retryRes.status, await retryRes.text());\n }\n return { token: cognitoToken, expires_at: exp };\n }\n\n if (!res.ok) {\n throw new GatewaySessionError(res.status, await res.text());\n }\n\n return { token: cognitoToken, expires_at: exp };\n}\n\nasync function callAuthSessions(\n cognitoToken: string,\n workspaceId: string,\n): Promise<Response> {\n return fetch(`${gatewayUrl()}/auth/sessions`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${cognitoToken}`,\n },\n body: JSON.stringify({ workspace_id: workspaceId }),\n });\n}\n\nexport function evictGatewaySession(sub: string): void {\n sessionCache.delete(sub);\n toolsCache.delete(sub);\n}\n\nexport function resetGatewaySessionCache(): void {\n sessionCache.clear();\n toolsCache.clear();\n}\n\nexport class GatewaySessionError extends Error {\n constructor(\n public readonly status: number,\n message: string,\n ) {\n super(`Gateway session exchange failed (${status}): ${message}`);\n this.name = \"GatewaySessionError\";\n }\n}\n","import { Prompts, type PromptResult } from \"@posthog/ai\";\nimport { PostHog } from \"posthog-node\";\nimport { FALLBACK_PROMPTS } from \"./fallback-prompts.js\";\nimport type { Env } from \"./env.js\";\n\n// Module-scope singletons — survive Lambda warm invocations\nlet _client: PostHog | null = null;\nlet _prompts: Prompts | null = null;\n\nexport function initPostHog(env: Env): void {\n if (!env.POSTHOG_PROJECT_API_KEY || !env.POSTHOG_PERSONAL_API_KEY) return;\n\n _client = new PostHog(env.POSTHOG_PROJECT_API_KEY, {\n host: env.POSTHOG_HOST,\n personalApiKey: env.POSTHOG_PERSONAL_API_KEY,\n });\n\n _prompts = new Prompts({ posthog: _client });\n}\n\nexport async function getPrompt(\n name: string,\n cacheTtlSeconds = 300,\n): Promise<PromptResult> {\n const fallback = FALLBACK_PROMPTS[name] ?? \"\";\n\n if (!_prompts) {\n return { source: \"code_fallback\", prompt: fallback, name: undefined, version: undefined };\n }\n\n return _prompts.get(name, { cacheTtlSeconds, fallback });\n}\n\nexport function compilePrompt(\n template: string,\n vars: Record<string, string>,\n): string {\n if (_prompts) {\n return _prompts.compile(template, vars);\n }\n return template.replace(/\\{\\{(\\w+)\\}\\}/g, (_, key) => vars[key] ?? \"\");\n}\n\nexport function getPostHogClient(): PostHog | null {\n return _client;\n}\n\nexport type { PostHog };\n","import {\n SecretsManagerClient,\n GetSecretValueCommand,\n} from \"@aws-sdk/client-secrets-manager\";\nimport { createOpenAICompatible } from \"@ai-sdk/openai-compatible\";\n\nlet secretsClient: SecretsManagerClient | null = null;\nlet cachedKey: string | null = null;\n\nfunction getSecretsClient(): SecretsManagerClient {\n if (!secretsClient) {\n secretsClient = new SecretsManagerClient({\n region: process.env[\"AWS_REGION\"],\n });\n }\n return secretsClient;\n}\n\nexport async function getOpenRouterKey(): Promise<string> {\n if (cachedKey) return cachedKey;\n\n const result = await getSecretsClient().send(\n new GetSecretValueCommand({\n SecretId: process.env[\"OPENROUTER_SECRET_ARN\"]!,\n }),\n );\n cachedKey = result.SecretString!;\n return cachedKey!;\n}\n\nexport function createOpenRouterProvider(apiKey: string) {\n return createOpenAICompatible({\n name: \"openrouter\",\n apiKey,\n baseURL: \"https://openrouter.ai/api/v1\",\n });\n}\n","export interface ToolInfo {\n namespace: string;\n name: string;\n description?: string;\n}\n\nexport interface GatewayClient {\n listTools(): Promise<ToolInfo[]>;\n}\n\n// Module-scope cache — 60 s TTL, survives Lambda warm invocations\nlet _cachedToolDocs = \"\";\nlet _toolDocsCachedAt = 0;\nconst TOOL_DOCS_TTL_MS = 60_000;\n\nexport async function getToolDocs(\n gateway: GatewayClient | null,\n): Promise<string> {\n if (!gateway) return \"\";\n\n const now = Date.now();\n if (_cachedToolDocs && now - _toolDocsCachedAt < TOOL_DOCS_TTL_MS) {\n return _cachedToolDocs;\n }\n\n const tools = await gateway.listTools();\n _cachedToolDocs = renderToolDocs(tools);\n _toolDocsCachedAt = now;\n return _cachedToolDocs;\n}\n\nfunction renderToolDocs(tools: ToolInfo[]): string {\n if (tools.length === 0) return \"\";\n\n const byNamespace = new Map<string, ToolInfo[]>();\n for (const tool of tools) {\n const existing = byNamespace.get(tool.namespace) ?? [];\n existing.push(tool);\n byNamespace.set(tool.namespace, existing);\n }\n\n const namespaces = [...byNamespace.keys()];\n let doc = `## Services\\n\\nThe following services are available for generated widgets to call:\\n\\n`;\n\n for (const [ns, nsTools] of byNamespace) {\n doc += `### \\`${ns}\\`\\n`;\n for (const tool of nsTools) {\n doc += `- \\`${ns}.${tool.name}()\\``;\n if (tool.description) doc += `: ${tool.description}`;\n doc += \"\\n\";\n }\n doc += \"\\n\";\n }\n\n const firstNs = namespaces[0] ?? \"service\";\n const firstTool = byNamespace.get(firstNs)?.[0]?.name ?? \"example\";\n doc += `**Usage in widgets:**\n\\`\\`\\`tsx\n// Services are available as global namespaces\nconst result = await ${firstNs}.${firstTool}({ /* args */ });\n\\`\\`\\`\n\nMake sure to handle loading states and errors when calling services.\n`;\n\n return doc;\n}\n\nexport function makeHttpGatewayClient(gatewayUrl: string): GatewayClient {\n return {\n async listTools(): Promise<ToolInfo[]> {\n const res = await fetch(`${gatewayUrl}/tools`);\n if (!res.ok) throw new Error(`Gateway returned ${res.status}`);\n return res.json() as Promise<ToolInfo[]>;\n },\n };\n}\n","import { withTracing } from \"@posthog/ai\";\nimport { streamText } from \"ai\";\nimport { Hono } from \"hono\";\nimport { z } from \"zod\";\nimport { EDIT_PROMPT_ID } from \"../fallback-prompts.js\";\nimport { getPrompt, compilePrompt, getPostHogClient } from \"../posthog.js\";\nimport {\n getOpenRouterKey,\n createOpenRouterProvider,\n} from \"../providers/openrouter.js\";\nimport type { AppVariables } from \"../types.js\";\n\nconst editBodySchema = z.object({\n code: z.string(),\n prompt: z.string(),\n});\n\nconst MODEL_ID = \"openrouter/auto\";\n\nexport const editRoute = new Hono<{ Variables: AppVariables }>();\n\neditRoute.post(\"/\", async (c) => {\n const body = await c.req.json().catch(() => null);\n const parsed = editBodySchema.safeParse(body);\n if (!parsed.success) {\n return c.json({ error: \"Invalid request body\" }, 400);\n }\n\n const promptResult = await getPrompt(EDIT_PROMPT_ID);\n const systemPrompt = compilePrompt(promptResult.prompt, {\n code: parsed.data.code,\n });\n\n const apiKey = await getOpenRouterKey();\n const provider = createOpenRouterProvider(apiKey);\n const baseModel = provider(MODEL_ID);\n\n const phClient = getPostHogClient();\n const model =\n phClient && promptResult.source !== \"code_fallback\"\n ? withTracing(baseModel, phClient, {\n posthogDistinctId: \"chat-api\",\n posthogProperties: {\n $ai_prompt_name: promptResult.name,\n $ai_prompt_version: promptResult.version,\n },\n })\n : baseModel;\n\n const result = streamText({\n model,\n system: systemPrompt,\n messages: [{ role: \"user\", content: parsed.data.prompt }],\n });\n\n return result.toTextStreamResponse();\n});\n","/**\n * GET /api/services\n *\n * Fetches the gateway's tool list and returns a service summary the frontend\n * uses to populate namespace suggestions and the ServicesInspector panel.\n *\n * Response shape:\n * { namespaces: string[], services: ServiceInfo[] }\n *\n * where ServiceInfo matches the frontend's expected `ServiceInfo` type:\n * { namespace: string, name: string, description?: string }\n */\n\nimport { Hono } from \"hono\";\nimport { evictGatewaySession, GatewaySessionError, getGatewaySession } from \"../gateway-session.js\";\nimport type { AppVariables } from \"../types.js\";\n\nexport interface ServiceInfo {\n namespace: string;\n name: string;\n procedure: string;\n description: string;\n parameters?: Record<string, unknown>;\n}\n\nexport const services = new Hono<{ Variables: AppVariables }>();\n\nservices.get(\"/\", async (c) => {\n const claims = c.get(\"claims\");\n const workspaceId = c.get(\"workspaceId\");\n\n const authHeader = c.req.header(\"Authorization\")!;\n const cognitoToken = authHeader.slice(\"Bearer \".length);\n\n let sessionToken: string;\n try {\n const session = await getGatewaySession(claims, workspaceId, cognitoToken);\n sessionToken = session.token;\n } catch (err) {\n if (err instanceof GatewaySessionError && err.status === 401) {\n return c.json({ error: \"Gateway session setup failed\" }, 401);\n }\n return c.json({ error: \"Failed to connect to gateway\" }, 502);\n }\n\n const gatewayUrl = process.env[\"GATEWAY_URL\"]!.replace(/\\/$/, \"\");\n const res = await fetch(`${gatewayUrl}/tools`, {\n headers: { Authorization: `Bearer ${sessionToken}` },\n });\n\n if (res.status === 401) {\n evictGatewaySession(claims.sub);\n return c.json({ error: \"Gateway authentication failed\" }, 502);\n }\n\n if (!res.ok) {\n return c.json({ error: \"Gateway tools fetch failed\" }, 502);\n }\n\n const data = await res.json() as {\n tools: Array<{\n provider: string;\n name: string;\n operation: string;\n description?: string;\n inputSchema?: unknown;\n }>;\n workspace_id: string;\n };\n\n const serviceList: ServiceInfo[] = data.tools.map((t) => ({\n namespace: t.provider,\n name: t.name,\n procedure: t.operation,\n description: t.description ?? \"\",\n parameters: t.inputSchema as Record<string, unknown> | undefined,\n }));\n\n const namespaces = Array.from(new Set(data.tools.map((t) => t.provider)));\n\n return c.json({ namespaces, services: serviceList });\n});\n","/**\n * POST /api/proxy/:ns/:proc\n *\n * Forwards the request body to `POST <gateway>/tools/:ns/:proc` using the\n * caller's gateway session bearer. This keeps tool invocations server-side\n * (credentials never reach the browser).\n */\n\nimport { Hono } from \"hono\";\nimport { evictGatewaySession, GatewaySessionError, getGatewaySession } from \"../gateway-session.js\";\nimport type { AppVariables } from \"../types.js\";\n\nexport const proxy = new Hono<{ Variables: AppVariables }>();\n\nproxy.post(\"/:ns/:proc{.*}\", async (c) => {\n const claims = c.get(\"claims\");\n const workspaceId = c.get(\"workspaceId\");\n\n const ns = c.req.param(\"ns\");\n const proc = c.req.param(\"proc\");\n\n const authHeader = c.req.header(\"Authorization\")!;\n const cognitoToken = authHeader.slice(\"Bearer \".length);\n\n let sessionToken: string;\n try {\n const session = await getGatewaySession(claims, workspaceId, cognitoToken);\n sessionToken = session.token;\n } catch (err) {\n if (err instanceof GatewaySessionError && err.status === 401) {\n return c.json({ error: \"Gateway session setup failed\" }, 401);\n }\n return c.json({ error: \"Failed to connect to gateway\" }, 502);\n }\n\n let body: unknown = {};\n try {\n body = await c.req.json();\n } catch {\n // empty body is fine\n }\n\n const gatewayUrl = process.env[\"GATEWAY_URL\"]!.replace(/\\/$/, \"\");\n const res = await fetch(`${gatewayUrl}/tools/${ns}/${proc}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${sessionToken}`,\n },\n body: JSON.stringify(body),\n });\n\n if (res.status === 401) {\n evictGatewaySession(claims.sub);\n return c.json({ error: \"Gateway authentication failed\" }, 502);\n }\n\n const responseData = await res.json();\n return c.json(responseData, res.status as 200);\n});\n","import { z } from \"zod\";\n\nconst envSchema = z.object({\n NODE_ENV: z\n .enum([\"development\", \"production\", \"test\"])\n .default(\"development\"),\n PORT: z.coerce.number().default(3001),\n COGNITO_USER_POOL_ID: z.string().min(1),\n COGNITO_CLIENT_ID: z.string().min(1),\n AWS_REGION: z.string().default(\"us-east-1\"),\n WORKSPACE_TABLE_NAME: z.string().min(1),\n MEMBERSHIPS_TABLE_NAME: z.string().min(1),\n OPENROUTER_SECRET_ARN: z.string().min(1),\n GATEWAY_URL: z.string().url(),\n\n // PostHog prompt management (optional — falls back to code prompts when absent)\n POSTHOG_PROJECT_API_KEY: z.string().optional(),\n POSTHOG_PERSONAL_API_KEY: z.string().optional(),\n POSTHOG_HOST: z.string().default(\"https://us.posthog.com\"),\n});\n\nexport type Env = z.infer<typeof envSchema>;\n\nexport function parseEnv(raw: Record<string, string | undefined>): Env {\n return envSchema.parse(raw);\n}\n"],"mappings":"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","names":["Hono","DynamoDBClient","DynamoDBDocumentClient","ddbClient","getDdb","Hono","env","gatewayUrl","Hono","gatewayUrl","withTracing","streamText","Hono","z","z","Hono","withTracing","streamText","Hono","Hono","gatewayUrl","Hono","Hono","gatewayUrl","app","Hono","z"]}
|
|
1
|
+
{"version":3,"sources":["../src/lambda.ts","../src/app.ts","../src/middleware/auth.ts","../src/middleware/workspace.ts","../src/middleware/plan.ts","../src/routes/health.ts","../src/routes/chat.ts","../src/fallback-prompts.ts","../src/gateway-session.ts","../src/posthog.ts","../src/providers/openrouter.ts","../src/tool-docs.ts","../src/routes/edit.ts","../src/routes/services.ts","../src/routes/proxy.ts","../src/env.ts"],"sourcesContent":["import { streamHandle } from \"hono/aws-lambda\";\nimport { createChatApp, initPostHog } from \"./app.js\";\nimport { parseEnv } from \"./env.js\";\nimport type { LambdaEvent } from \"hono/aws-lambda\";\n\nconst env = parseEnv(process.env);\n\n// Initialize module-scope PostHog singletons once per cold start\ninitPostHog(env);\n\nconst app = createChatApp();\n\nexport const handler = streamHandle(app) as (\n event: LambdaEvent,\n context: unknown,\n callback: unknown,\n) => Promise<unknown>;\n","import { Hono } from \"hono\";\nimport { authMiddleware } from \"./middleware/auth.js\";\nimport { workspaceMiddleware } from \"./middleware/workspace.js\";\nimport { planMiddleware } from \"./middleware/plan.js\";\nimport { health } from \"./routes/health.js\";\nimport { chatRoute } from \"./routes/chat.js\";\nimport { editRoute } from \"./routes/edit.js\";\nimport { services } from \"./routes/services.js\";\nimport { proxy } from \"./routes/proxy.js\";\nimport type { AppVariables } from \"./types.js\";\n\nexport { initPostHog } from \"./posthog.js\";\n\nexport function createChatApp() {\n const app = new Hono<{ Variables: AppVariables }>();\n\n // Unauthenticated routes\n app.route(\"/\", health);\n\n // Protected route group: auth → workspace → plan\n const api = app.basePath(\"/api\");\n api.use(authMiddleware, workspaceMiddleware, planMiddleware);\n api.route(\"/chat\", chatRoute);\n api.route(\"/edit\", editRoute);\n api.route(\"/services\", services);\n api.route(\"/proxy\", proxy);\n\n return app;\n}\n\nexport type ChatApp = ReturnType<typeof createChatApp>;\n","import { CognitoJwtVerifier } from \"aws-jwt-verify\";\nimport type { CognitoAccessTokenPayload } from \"aws-jwt-verify/jwt-model\";\nimport type { MiddlewareHandler } from \"hono\";\nimport type { AppVariables } from \"../types\";\n\ninterface JwtVerifier {\n verify(token: string): Promise<CognitoAccessTokenPayload>;\n}\n\nlet verifier: JwtVerifier | null = null;\n\nfunction getVerifier(): JwtVerifier {\n if (!verifier) {\n verifier = CognitoJwtVerifier.create({\n userPoolId: process.env[\"COGNITO_USER_POOL_ID\"]!,\n clientId: process.env[\"COGNITO_CLIENT_ID\"]!,\n tokenUse: \"access\",\n });\n }\n return verifier;\n}\n\nexport const authMiddleware: MiddlewareHandler<{ Variables: AppVariables }> =\n async (c, next) => {\n const authHeader = c.req.header(\"Authorization\");\n if (!authHeader?.startsWith(\"Bearer \")) {\n return c.json({ error: \"Unauthorized\" }, 401);\n }\n\n const token = authHeader.slice(7);\n try {\n const payload = await getVerifier().verify(token);\n c.set(\"claims\", payload);\n return next();\n } catch {\n return c.json({ error: \"Unauthorized\" }, 401);\n }\n };\n","import { DynamoDBClient } from \"@aws-sdk/client-dynamodb\";\nimport { DynamoDBDocumentClient, QueryCommand } from \"@aws-sdk/lib-dynamodb\";\nimport type { MiddlewareHandler } from \"hono\";\nimport type { AppVariables } from \"../types\";\n\nconst MEMBERSHIP_CACHE_TTL_MS = 300_000;\n\ninterface CacheEntry {\n workspaceId: string;\n fetchedAt: number;\n}\n\nconst membershipCache = new Map<string, CacheEntry>();\n\nlet ddbClient: DynamoDBDocumentClient | null = null;\n\nfunction getDdb() {\n if (!ddbClient) {\n ddbClient = DynamoDBDocumentClient.from(\n new DynamoDBClient({ region: process.env[\"AWS_REGION\"] }),\n );\n }\n return ddbClient;\n}\n\nexport async function resolveWorkspaceId(userSub: string): Promise<string | null> {\n const now = Date.now();\n const cached = membershipCache.get(userSub);\n if (cached && now - cached.fetchedAt < MEMBERSHIP_CACHE_TTL_MS) {\n return cached.workspaceId;\n }\n\n const result = await getDdb().send(\n new QueryCommand({\n TableName: process.env[\"MEMBERSHIPS_TABLE_NAME\"]!,\n IndexName: \"ByUserSub\",\n KeyConditionExpression: \"userSub = :sub\",\n ExpressionAttributeValues: { \":sub\": userSub },\n Limit: 1,\n }),\n );\n\n const item = result.Items?.[0] as { workspaceId: string } | undefined;\n if (!item) return null;\n\n membershipCache.set(userSub, { workspaceId: item.workspaceId, fetchedAt: now });\n return item.workspaceId;\n}\n\nexport const workspaceMiddleware: MiddlewareHandler<{ Variables: AppVariables }> =\n async (c, next) => {\n const claims = c.get(\"claims\");\n const workspaceId = await resolveWorkspaceId(claims.sub);\n if (!workspaceId) {\n return c.json({ error: \"No workspace membership\" }, 403);\n }\n c.set(\"workspaceId\", workspaceId);\n return next();\n };\n","import { DynamoDBClient } from \"@aws-sdk/client-dynamodb\";\nimport { DynamoDBDocumentClient, GetCommand } from \"@aws-sdk/lib-dynamodb\";\nimport type { MiddlewareHandler } from \"hono\";\nimport type { AppVariables, WorkspaceItem } from \"../types\";\n\nconst WORKSPACE_CACHE_TTL_MS = 60_000;\n\ninterface CacheEntry {\n workspace: WorkspaceItem;\n fetchedAt: number;\n}\n\nconst workspaceCache = new Map<string, CacheEntry>();\n\nlet ddbClient: DynamoDBDocumentClient | null = null;\n\nfunction getDdb() {\n if (!ddbClient) {\n ddbClient = DynamoDBDocumentClient.from(\n new DynamoDBClient({ region: process.env[\"AWS_REGION\"] }),\n );\n }\n return ddbClient;\n}\n\nexport async function getWorkspace(workspaceId: string): Promise<WorkspaceItem | null> {\n const now = Date.now();\n const cached = workspaceCache.get(workspaceId);\n if (cached && now - cached.fetchedAt < WORKSPACE_CACHE_TTL_MS) {\n return cached.workspace;\n }\n\n const result = await getDdb().send(\n new GetCommand({\n TableName: process.env[\"WORKSPACE_TABLE_NAME\"]!,\n Key: { workspaceId },\n }),\n );\n\n if (!result.Item) return null;\n\n const workspace = result.Item as WorkspaceItem;\n workspaceCache.set(workspaceId, { workspace, fetchedAt: now });\n return workspace;\n}\n\nexport const planMiddleware: MiddlewareHandler<{ Variables: AppVariables }> =\n async (c, next) => {\n const workspaceId = c.get(\"workspaceId\");\n const workspace = await getWorkspace(workspaceId);\n if (!workspace) {\n return c.json({ error: \"Workspace not found\" }, 404);\n }\n\n // 402 Payment Required when chat is not included in the plan.\n // Currently all plans include chat; this gate is a forward-looking hook\n // for future restricted plans where chat is a paid add-on.\n if (\"chat\" in workspace.features && !workspace.features.chat) {\n return c.json(\n { error: \"Chat is not available on your current plan\", plan: workspace.plan },\n 402,\n );\n }\n\n c.set(\"workspace\", workspace);\n return next();\n };\n","import { Hono } from \"hono\";\n\nconst health = new Hono();\n\nhealth.get(\"/health\", (c) => c.json({ status: \"ok\" }));\n\nexport { health };\n","import { withTracing } from \"@posthog/ai\";\nimport {\n streamText,\n convertToModelMessages,\n wrapLanguageModel,\n stepCountIs,\n jsonSchema,\n type LanguageModelMiddleware,\n type UIMessage,\n type Tool,\n} from \"ai\";\nimport { Hono } from \"hono\";\nimport { z } from \"zod\";\nimport { CHAT_PROMPT_ALLOWLIST } from \"../fallback-prompts.js\";\nimport { evictGatewaySession, getGatewaySession } from \"../gateway-session.js\";\nimport { getPrompt, compilePrompt, getPostHogClient } from \"../posthog.js\";\nimport {\n getOpenRouterKey,\n createOpenRouterProvider,\n} from \"../providers/openrouter.js\";\nimport { getToolDocs, makeHttpGatewayClient } from \"../tool-docs.js\";\nimport type { AppVariables } from \"../types.js\";\n\nconst chatBodySchema = z.object({\n id: z.string(),\n messages: z.array(z.any()),\n trigger: z.string(),\n metadata: z.unknown().optional(),\n prompt: z\n .object({\n id: z.string(),\n vars: z\n .object({\n compilers: z.array(z.string()).optional(),\n })\n .optional(),\n })\n .optional(),\n});\n\n// Retry once (200 ms backoff) if the provider rejects before the first\n// streamed byte. Once doStream() resolves (headers received, 2xx), the\n// connection is committed and mid-stream errors surface as error UI parts.\nconst retryAtStartMiddleware: LanguageModelMiddleware = {\n specificationVersion: \"v3\",\n async wrapStream({ doStream }) {\n try {\n return await doStream();\n } catch {\n await new Promise<void>((r) => setTimeout(r, 200));\n return doStream();\n }\n },\n};\n\nexport const chatRoute = new Hono<{ Variables: AppVariables }>();\n\ninterface GatewayToolEntry {\n provider: string;\n name: string;\n operation: string;\n description?: string;\n inputSchema?: unknown;\n}\n\nasync function fetchGatewayTools(\n gatewayUrl: string,\n bearerToken: string,\n): Promise<GatewayToolEntry[]> {\n const res = await fetch(`${gatewayUrl}/tools`, {\n headers: { Authorization: `Bearer ${bearerToken}` },\n });\n if (!res.ok) return [];\n const data = (await res.json()) as { tools: GatewayToolEntry[] };\n return data.tools ?? [];\n}\n\nfunction buildTools(\n gatewayTools: GatewayToolEntry[],\n gatewayUrl: string,\n bearerToken: string,\n): Record<string, Tool> {\n const tools: Record<string, Tool> = {};\n\n for (const t of gatewayTools) {\n // Replace dots with underscores — some models reject dots in function names.\n const toolKey = t.name.replace(/\\./g, \"_\");\n\n const rawSchema =\n t.inputSchema && typeof t.inputSchema === \"object\"\n ? t.inputSchema\n : { type: \"object\", properties: {} };\n\n const parameters = jsonSchema<Record<string, unknown>>(\n rawSchema as Parameters<typeof jsonSchema>[0],\n );\n\n const execute = async (args: Record<string, unknown>): Promise<unknown> => {\n const res = await fetch(`${gatewayUrl}/tools/${t.provider}/${t.operation}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${bearerToken}`,\n },\n body: JSON.stringify(args),\n });\n if (!res.ok) {\n const err = await res.json().catch(() => ({ error: res.statusText }));\n return { error: (err as { error?: string }).error ?? res.statusText };\n }\n return res.json();\n };\n\n tools[toolKey] = {\n description: t.description ?? `Call ${t.name}`,\n parameters,\n execute,\n } as unknown as Tool;\n }\n\n return tools;\n}\n\nchatRoute.post(\"/\", async (c) => {\n const body = await c.req.json().catch(() => null);\n const parsed = chatBodySchema.safeParse(body);\n if (!parsed.success) {\n return c.json({ error: \"Invalid request body\" }, 400);\n }\n\n const { messages, prompt: promptBody } = parsed.data;\n const claims = c.get(\"claims\");\n const workspaceId = c.get(\"workspaceId\");\n const workspace = c.get(\"workspace\");\n\n const promptId = promptBody?.id ?? \"chat-patchwork-widget\";\n if (!CHAT_PROMPT_ALLOWLIST.has(promptId)) {\n return c.json({ error: \"Unknown prompt id\" }, 400);\n }\n\n // Gateway tools are additive — chat works without them.\n const gatewayUrl = process.env[\"GATEWAY_URL\"]?.replace(/\\/$/, \"\");\n let sessionToken: string | null = null;\n\n if (gatewayUrl) {\n const authHeader = c.req.header(\"Authorization\") ?? \"\";\n const cognitoToken = authHeader.replace(/^Bearer /, \"\");\n try {\n const session = await getGatewaySession(claims, workspaceId, cognitoToken);\n sessionToken = session.token;\n } catch {\n // Non-fatal — continue without gateway tools\n }\n }\n\n let gatewayTools: GatewayToolEntry[] = [];\n if (sessionToken && gatewayUrl) {\n gatewayTools = await fetchGatewayTools(gatewayUrl, sessionToken).catch(\n () => [] as GatewayToolEntry[],\n );\n if (gatewayTools.length === 0) {\n evictGatewaySession(claims.sub);\n }\n }\n\n const tools =\n sessionToken && gatewayUrl\n ? buildTools(gatewayTools, gatewayUrl, sessionToken)\n : {};\n\n // Load system prompt from PostHog (cached, with fallback to bundled copy)\n const gatewayClient = gatewayUrl ? makeHttpGatewayClient(gatewayUrl) : null;\n const [promptResult, toolDocs] = await Promise.all([\n getPrompt(promptId),\n getToolDocs(gatewayClient).catch(() => \"\"),\n ]);\n const compilers = (promptBody?.vars?.compilers ?? []).join(\", \");\n const systemPrompt = compilePrompt(promptResult.prompt, {\n compilers,\n tool_docs: toolDocs,\n });\n\n const apiKey = await getOpenRouterKey();\n const provider = createOpenRouterProvider(apiKey);\n const modelId = workspace.limits.maxModels[0] ?? \"openrouter/auto\";\n const baseModel = provider(modelId);\n\n const phClient = getPostHogClient();\n const tracedModel =\n phClient && promptResult.source !== \"code_fallback\"\n ? withTracing(baseModel, phClient, {\n posthogDistinctId: claims.sub,\n posthogProperties: {\n $ai_prompt_name: promptResult.name,\n $ai_prompt_version: promptResult.version,\n },\n })\n : baseModel;\n\n const model = wrapLanguageModel({\n model: tracedModel,\n middleware: retryAtStartMiddleware,\n });\n\n const result = streamText({\n model,\n system: systemPrompt,\n messages: await convertToModelMessages(messages as UIMessage[]),\n stopWhen: stepCountIs(workspace.limits.maxToolSteps),\n maxOutputTokens: workspace.limits.maxTokensPerRequest,\n tools,\n });\n\n return result.toUIMessageStreamResponse();\n});\n","// Frozen copies of the canonical prompts, used when PostHog is unreachable\n// and the SDK cache is cold. Keep in sync with the seeded PostHog prompt versions.\n\nconst PATCHWORK_PROMPT = `\nYou are a friendly assistant! When responding to the user, you _must_ respond with JSX files!\n\nLook at 'patchwork.compilers' to see what specific runtime components and libraries are supported. (e.g. '['@aprovan/patchwork-image-shadcn' supports React, Tailwind, & ShadCN components). If there are no compilers, respond as you normally would. If compilers are available, ALWAYS respond with a component following [Component Generation](#component-generation).\n\nLook at 'patchwork.services' to see what services are available for widgets to call. If services are listed, you can generate widgets that make service calls using global namespace objects.\n\n**IMPORTANT: If you need to discover available services or get details about a specific service, use the \\`search_services\\` tool.**\n\n## Component Generation\n\nRespond with code blocks using tagged attributes on the fence line. The \\`note\\` attribute (optional but encouraged) provides a brief description visible in the UI. The \\`path\\` attribute specifies the virtual file path.\n\n### Code Block Format\n\n\\`\\`\\`tsx note=\"Main component\" path=\"components/weather/main.tsx\"\nexport default function WeatherWidget() {\n // component code\n}\n\\`\\`\\`\n\n### Attribute Order\nPut \\`note\\` first so it's available soonest in streaming UI.\n\n### Multi-File Generation\n\nWhen generating complex widgets, you can output multiple files. Use the \\`@/\\` prefix for virtual file system paths. ALWAYS prefer to generate visible components before metadata.\n\n**Example multi-file widget:**\n\n\\`\\`\\`json note=\"Widget configuration\" path=\"components/dashboard/package.json\"\n{\n \"description\": \"Interactive dashboard widget\",\n \"patchwork\": {\n \"inputs\": {\n \"type\": \"object\",\n \"properties\": {\n \"title\": { \"type\": \"string\" }\n }\n },\n \"services\": {\n \"analytics\": [\"getMetrics\", \"getChartData\"]\n }\n }\n}\n\\`\\`\\`\n\n\\`\\`\\`tsx note=\"Main widget component\" path=\"components/dashboard/main.tsx\"\nimport { Card } from './Card';\nimport { Chart } from './Chart';\n\nexport default function Dashboard({ title = \"Dashboard\" }) {\n return (\n <div>\n <h1>{title}</h1>\n <Card />\n <Chart />\n </div>\n );\n}\n\\`\\`\\`\n\n\\`\\`\\`tsx note=\"Card subcomponent\" path=\"components/dashboard/Card.tsx\"\nexport function Card() {\n return <div className=\"p-4 rounded border\">Card content</div>;\n}\n\\`\\`\\`\n\n### Requirements\n- DO think heavily about correctness of code and syntax\n- DO keep things simple and self-contained\n- ALWAYS include the \\`path\\` attribute specifying the file location.\n- ALWAYS use generic component/path names that is not dependent on arguments (e.g. 'dinner.tsx' not 'spaghetti.tsx')\n- ALWAYS output the COMPLETE code block with opening \\`\\`\\`tsx and closing \\`\\`\\` markers\n- Use \\`note\\` attribute to describe what each code block does (optional but encouraged)\n- NEVER truncate or cut off code - finish the entire component before stopping\n- If the component is complex, simplify it rather than leaving it incomplete\n- Do NOT include: a heading/title\n\n### Visual Design Guidelines\nCreate professional, polished interfaces that present information **spatially** rather than as vertical lists:\n- Use **cards, grids, and flexbox layouts** to organize related data into visual groups\n- Leverage **icons** (from lucide-react) alongside text to communicate meaning at a glance\n- Apply **visual hierarchy** through typography scale, weight, and color contrast\n- Use **whitespace strategically** to create breathing room and separation\n- Prefer **horizontal arrangements** where data fits naturally (e.g., stats in a row, badges inline)\n- Group related metrics into **compact visual clusters** rather than separate line items\n- Use **subtle backgrounds, borders, and shadows** to define sections without heavy dividers\n\n### Root Element Constraints\nThe component will be rendered inside a parent container that handles positioning. Your root element should:\n- ✅ Use intrinsic sizing (let content determine dimensions)\n- ✅ Handle internal padding (e.g., \\`p-4\\`, \\`p-6\\`)\n- ❌ NEVER add centering utilities (\\`items-center\\`, \\`justify-center\\`) to position itself\n- ❌ NEVER add viewport-relative sizing (\\`min-h-screen\\`, \\`h-screen\\`, \\`w-screen\\`)\n- ❌ NEVER add flex/grid on root just for self-centering\n\n### Using Services in Widgets (CRITICAL)\n\n**MANDATORY workflow - you must follow these steps IN ORDER:**\n\n1. **Use \\`search_services\\`** to discover the service schema\n2. **STOP. Make an actual call to the service tool itself** (e.g., \\`weather_get_forecast\\`, \\`github_get_repo\\`) with real arguments. This is NOT optional. Do NOT skip this step.\n3. **Observe the response** - verify it succeeded and note the exact data structure\n4. **Only then generate the widget** that fetches the same data at runtime\n\n**\\`search_services\\` is NOT a substitute for calling the actual service.** It only returns documentation. You MUST invoke the real service tool to validate your arguments work.\n\n**Tool naming:** Service tools use underscores, not dots. For example: \\`weather_get_forecast\\`, \\`github_list_repos\\`.\n\n**Example workflow for a weather widget:**\n\\`\\`\\`\nStep 1: search_services({ query: \"weather\" })\n → Learn that weather_get_current_conditions exists with params: { latitude, longitude }\n\nStep 2: weather_get_current_conditions({ latitude: 29.7604, longitude: -95.3698 }) ← REQUIRED!\n → Verify it returns { temp: 72, humidity: 65, ... }\n\nStep 3: Generate widget that calls weather.get_current_conditions at runtime\n\\`\\`\\`\n\n**If you skip Step 2, you will generate broken widgets.** Arguments that look correct in the schema may fail at runtime due to validation rules, required formats, or service-specific constraints.\n\n**NEVER embed static data directly in the component.**\n\n❌ **WRONG** - Embedding data directly:\n\\`\\`\\`tsx path=\"components/weather/bad-example.tsx\"\n// DON'T DO THIS - calling tool, then embedding the response as static data\nexport default function WeatherWidget() {\n // Static data embedded at generation time - BAD!\n const weather = { temp: 72, condition: \"sunny\", humidity: 45 };\n return <div>Temperature: {weather.temp}°F</div>;\n}\n\\`\\`\\`\n\n✅ **CORRECT** - Fetching data at runtime:\n\\`\\`\\`tsx note=\"Weather widget with runtime data\" path=\"components/weather/main.tsx\"\nexport default function WeatherWidget() {\n const [data, setData] = useState(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState(null);\n\n useEffect(() => {\n // Fetch data at runtime - GOOD!\n weather.get_forecast({ latitude: 48.8566, longitude: 2.3522 })\n .then(setData)\n .catch(setError)\n .finally(() => setLoading(false));\n }, []);\n\n if (loading) return <Skeleton className=\"h-32 w-full\" />;\n if (error) return <Alert variant=\"destructive\">{error.message}</Alert>;\n\n return <div>Temperature: {data.temp}°F</div>;\n}\n\\`\\`\\`\n\n**Why this matters:**\n- Widgets with runtime service calls show **live data** that updates when refreshed\n- Static embedded data becomes **stale immediately** after generation\n- The proxy pattern allows widgets to be **reusable** across different contexts\n- Error handling and loading states improve **user experience**\n\n**Service call pattern:**\n\\`\\`\\`tsx\n// Services are available as global namespace objects\n// Call format: namespace.procedure_name({ ...args })\n\nconst [data, setData] = useState(null);\nconst [loading, setLoading] = useState(true);\nconst [error, setError] = useState(null);\n\nuseEffect(() => {\n serviceName.procedure_name({ param1: value1 })\n .then(setData)\n .catch(setError)\n .finally(() => setLoading(false));\n}, [/* dependencies */]);\n\\`\\`\\`\n\n**Required for service-using widgets:**\n- Always show loading indicators (Skeleton, Loader2 spinner, etc.)\n- Always handle errors gracefully with user-friendly messages\n- Use appropriate React hooks (useState, useEffect) for async data\n- Services are injected as globals - NO imports needed\n\n### Validating Service Calls (CRITICAL - READ CAREFULLY)\n\n**Calling \\`search_services\\` multiple times is NOT validation.** You must call the actual service tool.\n\n❌ **WRONG workflow (will produce broken widgets):**\n\\`\\`\\`\n1. search_services({ query: \"weather\" }) ← Only gets schema\n2. search_services({ query: \"location\" }) ← Still only schema\n3. Generate widget ← BROKEN - never tested the actual service!\n\\`\\`\\`\n\n✅ **CORRECT workflow:**\n\\`\\`\\`\n1. search_services({ query: \"weather\" }) ← Get schema\n2. weather_get_forecast({ latitude: 29.76, longitude: -95.37 }) ← ACTUALLY CALL IT\n3. Observe response: { temp: 72, conditions: \"sunny\", ... }\n4. Generate widget that calls weather.get_forecast at runtime\n\\`\\`\\`\n\n**The service tool (e.g., \\`weather_get_forecast\\`, \\`github_list_repos\\`) is a DIFFERENT tool from \\`search_services\\`.** You have access to both. Use both.\n\n**Only after a successful test call to the actual service should you generate the widget.**\n\n### Component Parameterization (IMPORTANT)\n\n**Widgets should accept props for dynamic values instead of hardcoding:**\n\n❌ **WRONG** - Hardcoded values:\n\\`\\`\\`tsx path=\"components/weather/bad-example.tsx\"\nexport default function WeatherWidget() {\n // Location hardcoded - BAD!\n const [lat, lon] = [48.8566, 2.3522]; // Paris\n // ...\n}\n\\`\\`\\`\n\n✅ **CORRECT** - Parameterized with props and defaults:\n\\`\\`\\`tsx note=\"Parameterized weather widget\" path=\"components/weather/main.tsx\"\ninterface WeatherWidgetProps {\n location?: string; // e.g., \"Paris, France\"\n latitude?: number; // Direct coordinates (optional)\n longitude?: number;\n}\n\nexport default function WeatherWidget({\n location = \"Paris, France\",\n latitude,\n longitude\n}: WeatherWidgetProps) {\n // Use provided coordinates or look up from location name\n // ...\n}\n\\`\\`\\`\n\n**Why parameterize:**\n- Components become **reusable** across different contexts\n- Users can **customize behavior** without editing code\n- Enables **composition** - parent components can pass different values\n- Supports **testing** with various inputs\n\n**What to parameterize:**\n- Location names, coordinates, IDs\n- Search queries and filters\n- Display options (count, format, theme)\n- API-specific identifiers (usernames, repo names, etc.)\n\n### Anti-patterns to Avoid\n- ❌ Bulleted or numbered lists of key-value pairs\n- ❌ Vertical stacks where horizontal layouts would fit\n- ❌ Plain text labels without visual treatment\n- ❌ Uniform styling that doesn't distinguish primary from secondary information\n- ❌ Wrapping components in centering containers (parent handles this)\n- ❌ **Embedding API response data directly in components instead of fetching at runtime**\n- ❌ **Calling a tool, then putting the response as static JSX/JSON in the generated code**\n- ❌ **Hardcoding values that should be component props**\n- ❌ **Calling \\`search_services\\` multiple times instead of calling the actual service tool**\n- ❌ **Generating a widget without first making a real call to the service with your intended arguments**\n- ❌ **Treating schema documentation as proof that a service call will work**\n- ❌ **Omitting the \\`path\\` attribute on code blocks**\n`;\n\nconst EDIT_PROMPT = `\nYou are editing an existing JSX component. The user will provide the current code and describe the changes they want.\n\n## Response Format\n\nUse code fences with tagged attributes. The \\`note\\` attribute (optional but encouraged) provides a brief description visible in the UI. The \\`path\\` attribute specifies the target file.\n\n\\`\\`\\`diff note=\"Brief description of this change\" path=\"@/components/Button.tsx\"\n<<<<<<< SEARCH\nexact code to find\n=======\nreplacement code\n>>>>>>> REPLACE\n\\`\\`\\`\n\n### Attribute Order\nPut \\`note\\` first so it's available soonest in streaming UI.\n\n### Multi-File Edits\nWhen editing multiple files, use the \\`path\\` attribute with virtual paths (\\`@/\\` prefix for generated files):\n\n\\`\\`\\`diff note=\"Update button handler\" path=\"@/components/Button.tsx\"\n<<<<<<< SEARCH\nonClick={() => {}}\n=======\nonClick={() => handleClick()}\n>>>>>>> REPLACE\n\\`\\`\\`\n\n\\`\\`\\`diff note=\"Add utility function\" path=\"@/lib/utils.ts\"\n<<<<<<< SEARCH\nexport const formatDate = ...\n=======\nexport const formatDate = ...\n\nexport const handleClick = () => console.log('clicked');\n>>>>>>> REPLACE\n\\`\\`\\`\n\n## Rules\n- SEARCH block must match the existing code EXACTLY (whitespace, indentation, everything)\n- You can include multiple diff blocks for multiple changes\n- Each diff block should have its own \\`note\\` attribute annotation\n- Keep changes minimal and targeted\n- Do NOT output the full file - only the diffs\n- If clarification is needed, ask briefly before any diffs\n\n## CRITICAL: Diff Marker Safety\n- NEVER include the strings \"<<<<<<< SEARCH\", \"=======\", or \">>>>>>> REPLACE\" inside your replacement code\n- These are reserved markers for parsing the diff format\n- If you need to show diff-like content, use alternative notation (e.g., \"// old code\" / \"// new code\")\n- Malformed diff markers will cause the edit to fail\n\n## Summary\nAfter all diffs, provide a brief markdown summary of the changes made. Use formatting like:\n- **Bold** for emphasis on key changes\n- Bullet points for listing multiple changes\n- Keep it concise (2-4 lines max)\n- Do NOT include: a heading/title\n`;\n\nconst PLAIN_PROMPT = `You are a helpful assistant.\n\n{{tool_docs}}`;\n\nexport const FALLBACK_PROMPTS: Record<string, string> = {\n \"chat-patchwork-widget\": `---\\npatchwork:\\n compilers: {{compilers}}\\n---\\n\\n${PATCHWORK_PROMPT}\\n\\n{{tool_docs}}`,\n \"chat-plain\": PLAIN_PROMPT,\n \"edit-patchwork-widget\": `Current component code:\\n\\`\\`\\`tsx\\n{{code}}\\n\\`\\`\\`\\n\\n${EDIT_PROMPT}`,\n};\n\nexport const CHAT_PROMPT_ALLOWLIST = new Set([\"chat-patchwork-widget\", \"chat-plain\"]);\nexport const EDIT_PROMPT_ID = \"edit-patchwork-widget\";\n","/**\n * Gateway session client.\n *\n * The registry gateway authenticates every request with the caller's Cognito\n * access token and requires the active workspace to be persisted in DDB via\n * `POST /auth/sessions` before other endpoints will accept requests.\n *\n * GatewaySessionClient handles that one-time setup per user and returns the\n * Cognito token itself as the bearer to use on subsequent gateway calls. The\n * result is cached in-memory per user sub for the remaining lifetime of the\n * Cognito token.\n */\n\nimport type { CognitoAccessTokenPayload } from \"aws-jwt-verify/jwt-model\";\n\ninterface SessionEntry {\n /** The Cognito access token to use as bearer for gateway calls. */\n token: string;\n /** Unix seconds — mirrors the Cognito token's `exp` claim. */\n expires_at: number;\n}\n\nconst sessionCache = new Map<string, SessionEntry>();\n\nfunction gatewayUrl(): string {\n const url = process.env[\"GATEWAY_URL\"];\n if (!url) throw new Error(\"GATEWAY_URL is not set\");\n return url.replace(/\\/$/, \"\");\n}\n\n/**\n * Establish (or reuse) a gateway session for the caller.\n *\n * 1. Returns the cached entry if still valid.\n * 2. Calls `POST /auth/sessions` on the gateway to register the active\n * workspace for this user.\n * 3. Returns the Cognito token as the bearer — the gateway uses it directly.\n * 4. On 401, evicts the cache and retries once.\n */\nexport async function getGatewaySession(\n claims: CognitoAccessTokenPayload,\n workspaceId: string,\n cognitoToken: string,\n): Promise<SessionEntry> {\n const sub = claims.sub;\n const now = Math.floor(Date.now() / 1000);\n\n const cached = sessionCache.get(sub);\n if (cached && cached.expires_at > now + 60) {\n return cached;\n }\n\n const entry = await exchangeSession(cognitoToken, workspaceId, claims.exp as number);\n sessionCache.set(sub, entry);\n return entry;\n}\n\nasync function exchangeSession(\n cognitoToken: string,\n workspaceId: string,\n exp: number,\n): Promise<SessionEntry> {\n const res = await callAuthSessions(cognitoToken, workspaceId);\n\n if (res.status === 401) {\n // Retry once — token may have just been refreshed by the caller.\n const retryRes = await callAuthSessions(cognitoToken, workspaceId);\n if (!retryRes.ok) {\n throw new GatewaySessionError(retryRes.status, await retryRes.text());\n }\n return { token: cognitoToken, expires_at: exp };\n }\n\n if (!res.ok) {\n throw new GatewaySessionError(res.status, await res.text());\n }\n\n return { token: cognitoToken, expires_at: exp };\n}\n\nasync function callAuthSessions(\n cognitoToken: string,\n workspaceId: string,\n): Promise<Response> {\n return fetch(`${gatewayUrl()}/auth/sessions`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${cognitoToken}`,\n },\n body: JSON.stringify({ workspace_id: workspaceId }),\n });\n}\n\nexport function evictGatewaySession(sub: string): void {\n sessionCache.delete(sub);\n}\n\nexport function resetGatewaySessionCache(): void {\n sessionCache.clear();\n}\n\nexport class GatewaySessionError extends Error {\n constructor(\n public readonly status: number,\n message: string,\n ) {\n super(`Gateway session exchange failed (${status}): ${message}`);\n this.name = \"GatewaySessionError\";\n }\n}\n","import { Prompts, type PromptResult } from \"@posthog/ai\";\nimport { PostHog } from \"posthog-node\";\nimport { FALLBACK_PROMPTS } from \"./fallback-prompts.js\";\nimport type { Env } from \"./env.js\";\n\n// Module-scope singletons — survive Lambda warm invocations\nlet _client: PostHog | null = null;\nlet _prompts: Prompts | null = null;\n\nexport function initPostHog(env: Env): void {\n if (!env.POSTHOG_PROJECT_API_KEY || !env.POSTHOG_PERSONAL_API_KEY) return;\n\n _client = new PostHog(env.POSTHOG_PROJECT_API_KEY, {\n host: env.POSTHOG_HOST,\n personalApiKey: env.POSTHOG_PERSONAL_API_KEY,\n });\n\n _prompts = new Prompts({ posthog: _client });\n}\n\nexport async function getPrompt(\n name: string,\n cacheTtlSeconds = 300,\n): Promise<PromptResult> {\n const fallback = FALLBACK_PROMPTS[name] ?? \"\";\n\n if (!_prompts) {\n return { source: \"code_fallback\", prompt: fallback, name: undefined, version: undefined };\n }\n\n return _prompts.get(name, { cacheTtlSeconds, fallback });\n}\n\nexport function compilePrompt(\n template: string,\n vars: Record<string, string>,\n): string {\n if (_prompts) {\n return _prompts.compile(template, vars);\n }\n return template.replace(/\\{\\{(\\w+)\\}\\}/g, (_, key) => vars[key] ?? \"\");\n}\n\nexport function getPostHogClient(): PostHog | null {\n return _client;\n}\n\nexport type { PostHog };\n","import {\n SecretsManagerClient,\n GetSecretValueCommand,\n} from \"@aws-sdk/client-secrets-manager\";\nimport { createOpenAICompatible } from \"@ai-sdk/openai-compatible\";\n\nlet secretsClient: SecretsManagerClient | null = null;\nlet cachedKey: string | null = null;\n\nfunction getSecretsClient(): SecretsManagerClient {\n if (!secretsClient) {\n secretsClient = new SecretsManagerClient({\n region: process.env[\"AWS_REGION\"],\n });\n }\n return secretsClient;\n}\n\nexport async function getOpenRouterKey(): Promise<string> {\n if (cachedKey) return cachedKey;\n\n const result = await getSecretsClient().send(\n new GetSecretValueCommand({\n SecretId: process.env[\"OPENROUTER_SECRET_ARN\"]!,\n }),\n );\n cachedKey = result.SecretString!;\n return cachedKey!;\n}\n\nexport function createOpenRouterProvider(apiKey: string) {\n return createOpenAICompatible({\n name: \"openrouter\",\n apiKey,\n baseURL: \"https://openrouter.ai/api/v1\",\n });\n}\n","export interface ToolInfo {\n namespace: string;\n name: string;\n description?: string;\n}\n\nexport interface GatewayClient {\n listTools(): Promise<ToolInfo[]>;\n}\n\n// Module-scope cache — 60 s TTL, survives Lambda warm invocations\nlet _cachedToolDocs = \"\";\nlet _toolDocsCachedAt = 0;\nconst TOOL_DOCS_TTL_MS = 60_000;\n\nexport async function getToolDocs(\n gateway: GatewayClient | null,\n): Promise<string> {\n if (!gateway) return \"\";\n\n const now = Date.now();\n if (_cachedToolDocs && now - _toolDocsCachedAt < TOOL_DOCS_TTL_MS) {\n return _cachedToolDocs;\n }\n\n const tools = await gateway.listTools();\n _cachedToolDocs = renderToolDocs(tools);\n _toolDocsCachedAt = now;\n return _cachedToolDocs;\n}\n\nfunction renderToolDocs(tools: ToolInfo[]): string {\n if (tools.length === 0) return \"\";\n\n const byNamespace = new Map<string, ToolInfo[]>();\n for (const tool of tools) {\n const existing = byNamespace.get(tool.namespace) ?? [];\n existing.push(tool);\n byNamespace.set(tool.namespace, existing);\n }\n\n const namespaces = [...byNamespace.keys()];\n let doc = `## Services\\n\\nThe following services are available for generated widgets to call:\\n\\n`;\n\n for (const [ns, nsTools] of byNamespace) {\n doc += `### \\`${ns}\\`\\n`;\n for (const tool of nsTools) {\n doc += `- \\`${ns}.${tool.name}()\\``;\n if (tool.description) doc += `: ${tool.description}`;\n doc += \"\\n\";\n }\n doc += \"\\n\";\n }\n\n const firstNs = namespaces[0] ?? \"service\";\n const firstTool = byNamespace.get(firstNs)?.[0]?.name ?? \"example\";\n doc += `**Usage in widgets:**\n\\`\\`\\`tsx\n// Services are available as global namespaces\nconst result = await ${firstNs}.${firstTool}({ /* args */ });\n\\`\\`\\`\n\nMake sure to handle loading states and errors when calling services.\n`;\n\n return doc;\n}\n\nexport function makeHttpGatewayClient(gatewayUrl: string): GatewayClient {\n return {\n async listTools(): Promise<ToolInfo[]> {\n const res = await fetch(`${gatewayUrl}/tools`);\n if (!res.ok) throw new Error(`Gateway returned ${res.status}`);\n return res.json() as Promise<ToolInfo[]>;\n },\n };\n}\n","import { withTracing } from \"@posthog/ai\";\nimport { streamText } from \"ai\";\nimport { Hono } from \"hono\";\nimport { z } from \"zod\";\nimport { EDIT_PROMPT_ID } from \"../fallback-prompts.js\";\nimport { getPrompt, compilePrompt, getPostHogClient } from \"../posthog.js\";\nimport {\n getOpenRouterKey,\n createOpenRouterProvider,\n} from \"../providers/openrouter.js\";\nimport type { AppVariables } from \"../types.js\";\n\nconst editBodySchema = z.object({\n code: z.string(),\n prompt: z.string(),\n});\n\nconst MODEL_ID = \"openrouter/auto\";\n\nexport const editRoute = new Hono<{ Variables: AppVariables }>();\n\neditRoute.post(\"/\", async (c) => {\n const body = await c.req.json().catch(() => null);\n const parsed = editBodySchema.safeParse(body);\n if (!parsed.success) {\n return c.json({ error: \"Invalid request body\" }, 400);\n }\n\n const promptResult = await getPrompt(EDIT_PROMPT_ID);\n const systemPrompt = compilePrompt(promptResult.prompt, {\n code: parsed.data.code,\n });\n\n const apiKey = await getOpenRouterKey();\n const provider = createOpenRouterProvider(apiKey);\n const baseModel = provider(MODEL_ID);\n\n const phClient = getPostHogClient();\n const model =\n phClient && promptResult.source !== \"code_fallback\"\n ? withTracing(baseModel, phClient, {\n posthogDistinctId: \"chat-api\",\n posthogProperties: {\n $ai_prompt_name: promptResult.name,\n $ai_prompt_version: promptResult.version,\n },\n })\n : baseModel;\n\n const result = streamText({\n model,\n system: systemPrompt,\n messages: [{ role: \"user\", content: parsed.data.prompt }],\n });\n\n return result.toTextStreamResponse();\n});\n","/**\n * GET /api/services\n *\n * Fetches the gateway's tool list and returns a service summary the frontend\n * uses to populate namespace suggestions and the ServicesInspector panel.\n *\n * Response shape:\n * { namespaces: string[], services: ServiceInfo[] }\n *\n * where ServiceInfo matches the frontend's expected `ServiceInfo` type:\n * { namespace: string, name: string, description?: string }\n */\n\nimport { Hono } from \"hono\";\nimport { evictGatewaySession, GatewaySessionError, getGatewaySession } from \"../gateway-session.js\";\nimport type { AppVariables } from \"../types.js\";\n\nexport interface ServiceInfo {\n namespace: string;\n name: string;\n procedure: string;\n description: string;\n parameters?: Record<string, unknown>;\n}\n\nexport const services = new Hono<{ Variables: AppVariables }>();\n\nservices.get(\"/\", async (c) => {\n const claims = c.get(\"claims\");\n const workspaceId = c.get(\"workspaceId\");\n\n const authHeader = c.req.header(\"Authorization\")!;\n const cognitoToken = authHeader.slice(\"Bearer \".length);\n\n let sessionToken: string;\n try {\n const session = await getGatewaySession(claims, workspaceId, cognitoToken);\n sessionToken = session.token;\n } catch (err) {\n if (err instanceof GatewaySessionError && err.status === 401) {\n return c.json({ error: \"Gateway session setup failed\" }, 401);\n }\n return c.json({ error: \"Failed to connect to gateway\" }, 502);\n }\n\n const gatewayUrl = process.env[\"GATEWAY_URL\"]!.replace(/\\/$/, \"\");\n const res = await fetch(`${gatewayUrl}/tools`, {\n headers: { Authorization: `Bearer ${sessionToken}` },\n });\n\n if (res.status === 401) {\n evictGatewaySession(claims.sub);\n return c.json({ error: \"Gateway authentication failed\" }, 502);\n }\n\n if (!res.ok) {\n return c.json({ error: \"Gateway tools fetch failed\" }, 502);\n }\n\n const data = await res.json() as {\n tools: Array<{\n provider: string;\n name: string;\n operation: string;\n description?: string;\n inputSchema?: unknown;\n }>;\n workspace_id: string;\n };\n\n const serviceList: ServiceInfo[] = data.tools.map((t) => ({\n namespace: t.provider,\n name: t.name,\n procedure: t.operation,\n description: t.description ?? \"\",\n parameters: t.inputSchema as Record<string, unknown> | undefined,\n }));\n\n const namespaces = Array.from(new Set(data.tools.map((t) => t.provider)));\n\n return c.json({ namespaces, services: serviceList });\n});\n","/**\n * POST /api/proxy/:ns/:proc\n *\n * Forwards the request body to `POST <gateway>/tools/:ns/:proc` using the\n * caller's gateway session bearer. This keeps tool invocations server-side\n * (credentials never reach the browser).\n */\n\nimport { Hono } from \"hono\";\nimport { evictGatewaySession, GatewaySessionError, getGatewaySession } from \"../gateway-session.js\";\nimport type { AppVariables } from \"../types.js\";\n\nexport const proxy = new Hono<{ Variables: AppVariables }>();\n\nproxy.post(\"/:ns/:proc{.*}\", async (c) => {\n const claims = c.get(\"claims\");\n const workspaceId = c.get(\"workspaceId\");\n\n const ns = c.req.param(\"ns\");\n const proc = c.req.param(\"proc\");\n\n const authHeader = c.req.header(\"Authorization\")!;\n const cognitoToken = authHeader.slice(\"Bearer \".length);\n\n let sessionToken: string;\n try {\n const session = await getGatewaySession(claims, workspaceId, cognitoToken);\n sessionToken = session.token;\n } catch (err) {\n if (err instanceof GatewaySessionError && err.status === 401) {\n return c.json({ error: \"Gateway session setup failed\" }, 401);\n }\n return c.json({ error: \"Failed to connect to gateway\" }, 502);\n }\n\n let body: unknown = {};\n try {\n body = await c.req.json();\n } catch {\n // empty body is fine\n }\n\n const gatewayUrl = process.env[\"GATEWAY_URL\"]!.replace(/\\/$/, \"\");\n const res = await fetch(`${gatewayUrl}/tools/${ns}/${proc}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${sessionToken}`,\n },\n body: JSON.stringify(body),\n });\n\n if (res.status === 401) {\n evictGatewaySession(claims.sub);\n return c.json({ error: \"Gateway authentication failed\" }, 502);\n }\n\n const responseData = await res.json();\n return c.json(responseData, res.status as 200);\n});\n","import { z } from \"zod\";\n\nconst envSchema = z.object({\n NODE_ENV: z\n .enum([\"development\", \"production\", \"test\"])\n .default(\"development\"),\n PORT: z.coerce.number().default(3001),\n COGNITO_USER_POOL_ID: z.string().min(1),\n COGNITO_CLIENT_ID: z.string().min(1),\n AWS_REGION: z.string().default(\"us-east-1\"),\n WORKSPACE_TABLE_NAME: z.string().min(1),\n MEMBERSHIPS_TABLE_NAME: z.string().min(1),\n OPENROUTER_SECRET_ARN: z.string().min(1),\n GATEWAY_URL: z.string().url(),\n\n // PostHog prompt management (optional — falls back to code prompts when absent)\n POSTHOG_PROJECT_API_KEY: z.string().optional(),\n POSTHOG_PERSONAL_API_KEY: z.string().optional(),\n POSTHOG_HOST: z.string().default(\"https://us.posthog.com\"),\n});\n\nexport type Env = z.infer<typeof envSchema>;\n\nexport function parseEnv(raw: Record<string, string | undefined>): Env {\n return envSchema.parse(raw);\n}\n"],"mappings":"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","names":["Hono","DynamoDBClient","DynamoDBDocumentClient","ddbClient","getDdb","Hono","env","gatewayUrl","Hono","gatewayUrl","withTracing","streamText","Hono","z","z","Hono","withTracing","streamText","Hono","Hono","gatewayUrl","Hono","Hono","gatewayUrl","app","Hono","z"]}
|
package/package.json
CHANGED
package/src/gateway-session.ts
CHANGED
|
@@ -22,24 +22,6 @@ interface SessionEntry {
|
|
|
22
22
|
|
|
23
23
|
const sessionCache = new Map<string, SessionEntry>();
|
|
24
24
|
|
|
25
|
-
/**
|
|
26
|
-
* Per-session tool-list cache. Keyed by userSub, parallel to sessionCache.
|
|
27
|
-
* Cleared on session eviction so a fresh session always re-fetches tools.
|
|
28
|
-
*/
|
|
29
|
-
const toolsCache = new Map<string, unknown[]>();
|
|
30
|
-
|
|
31
|
-
export function getCachedTools(sub: string): unknown[] | undefined {
|
|
32
|
-
return toolsCache.get(sub);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function setCachedTools(sub: string, tools: unknown[]): void {
|
|
36
|
-
toolsCache.set(sub, tools);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function evictCachedTools(sub: string): void {
|
|
40
|
-
toolsCache.delete(sub);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
25
|
function gatewayUrl(): string {
|
|
44
26
|
const url = process.env["GATEWAY_URL"];
|
|
45
27
|
if (!url) throw new Error("GATEWAY_URL is not set");
|
|
@@ -78,14 +60,7 @@ async function exchangeSession(
|
|
|
78
60
|
workspaceId: string,
|
|
79
61
|
exp: number,
|
|
80
62
|
): Promise<SessionEntry> {
|
|
81
|
-
|
|
82
|
-
try {
|
|
83
|
-
res = await callAuthSessions(cognitoToken, workspaceId);
|
|
84
|
-
} catch {
|
|
85
|
-
// Network error — retry once after 200ms.
|
|
86
|
-
await new Promise<void>((r) => setTimeout(r, 200));
|
|
87
|
-
res = await callAuthSessions(cognitoToken, workspaceId);
|
|
88
|
-
}
|
|
63
|
+
const res = await callAuthSessions(cognitoToken, workspaceId);
|
|
89
64
|
|
|
90
65
|
if (res.status === 401) {
|
|
91
66
|
// Retry once — token may have just been refreshed by the caller.
|
|
@@ -96,16 +71,6 @@ async function exchangeSession(
|
|
|
96
71
|
return { token: cognitoToken, expires_at: exp };
|
|
97
72
|
}
|
|
98
73
|
|
|
99
|
-
if (res.status >= 500) {
|
|
100
|
-
// 5xx — retry once after 200ms.
|
|
101
|
-
await new Promise<void>((r) => setTimeout(r, 200));
|
|
102
|
-
const retryRes = await callAuthSessions(cognitoToken, workspaceId);
|
|
103
|
-
if (!retryRes.ok) {
|
|
104
|
-
throw new GatewaySessionError(retryRes.status, await retryRes.text());
|
|
105
|
-
}
|
|
106
|
-
return { token: cognitoToken, expires_at: exp };
|
|
107
|
-
}
|
|
108
|
-
|
|
109
74
|
if (!res.ok) {
|
|
110
75
|
throw new GatewaySessionError(res.status, await res.text());
|
|
111
76
|
}
|
|
@@ -129,12 +94,10 @@ async function callAuthSessions(
|
|
|
129
94
|
|
|
130
95
|
export function evictGatewaySession(sub: string): void {
|
|
131
96
|
sessionCache.delete(sub);
|
|
132
|
-
toolsCache.delete(sub);
|
|
133
97
|
}
|
|
134
98
|
|
|
135
99
|
export function resetGatewaySessionCache(): void {
|
|
136
100
|
sessionCache.clear();
|
|
137
|
-
toolsCache.clear();
|
|
138
101
|
}
|
|
139
102
|
|
|
140
103
|
export class GatewaySessionError extends Error {
|
package/src/routes/chat.ts
CHANGED
|
@@ -12,12 +12,7 @@ import {
|
|
|
12
12
|
import { Hono } from "hono";
|
|
13
13
|
import { z } from "zod";
|
|
14
14
|
import { CHAT_PROMPT_ALLOWLIST } from "../fallback-prompts.js";
|
|
15
|
-
import {
|
|
16
|
-
evictGatewaySession,
|
|
17
|
-
getGatewaySession,
|
|
18
|
-
getCachedTools,
|
|
19
|
-
setCachedTools,
|
|
20
|
-
} from "../gateway-session.js";
|
|
15
|
+
import { evictGatewaySession, getGatewaySession } from "../gateway-session.js";
|
|
21
16
|
import { getPrompt, compilePrompt, getPostHogClient } from "../posthog.js";
|
|
22
17
|
import {
|
|
23
18
|
getOpenRouterKey,
|
|
@@ -160,18 +155,11 @@ chatRoute.post("/", async (c) => {
|
|
|
160
155
|
|
|
161
156
|
let gatewayTools: GatewayToolEntry[] = [];
|
|
162
157
|
if (sessionToken && gatewayUrl) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
() => [] as GatewayToolEntry[],
|
|
169
|
-
);
|
|
170
|
-
if (gatewayTools.length === 0) {
|
|
171
|
-
evictGatewaySession(claims.sub);
|
|
172
|
-
} else {
|
|
173
|
-
setCachedTools(claims.sub, gatewayTools);
|
|
174
|
-
}
|
|
158
|
+
gatewayTools = await fetchGatewayTools(gatewayUrl, sessionToken).catch(
|
|
159
|
+
() => [] as GatewayToolEntry[],
|
|
160
|
+
);
|
|
161
|
+
if (gatewayTools.length === 0) {
|
|
162
|
+
evictGatewaySession(claims.sub);
|
|
175
163
|
}
|
|
176
164
|
}
|
|
177
165
|
|
|
@@ -3,9 +3,6 @@ import {
|
|
|
3
3
|
getGatewaySession,
|
|
4
4
|
evictGatewaySession,
|
|
5
5
|
resetGatewaySessionCache,
|
|
6
|
-
getCachedTools,
|
|
7
|
-
setCachedTools,
|
|
8
|
-
evictCachedTools,
|
|
9
6
|
GatewaySessionError,
|
|
10
7
|
} from "../src/gateway-session";
|
|
11
8
|
import type { CognitoAccessTokenPayload } from "aws-jwt-verify/jwt-model";
|
|
@@ -132,80 +129,4 @@ describe("getGatewaySession", () => {
|
|
|
132
129
|
|
|
133
130
|
expect(fetchSpy).toHaveBeenCalledTimes(2);
|
|
134
131
|
});
|
|
135
|
-
|
|
136
|
-
it("retries once after 200ms on 5xx and returns session if retry succeeds", async () => {
|
|
137
|
-
const fetchSpy = vi.spyOn(globalThis, "fetch")
|
|
138
|
-
.mockResolvedValueOnce(new Response("Service Unavailable", { status: 503 }))
|
|
139
|
-
.mockResolvedValueOnce(
|
|
140
|
-
new Response(JSON.stringify({ workspace_id: WORKSPACE_ID }), { status: 200 }),
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
const session = await getGatewaySession(makeClaims(), WORKSPACE_ID, COGNITO_TOKEN);
|
|
144
|
-
expect(session.token).toBe(COGNITO_TOKEN);
|
|
145
|
-
expect(fetchSpy).toHaveBeenCalledTimes(2);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it("throws GatewaySessionError on persistent 5xx after retry", async () => {
|
|
149
|
-
vi.spyOn(globalThis, "fetch").mockResolvedValue(
|
|
150
|
-
new Response("Service Unavailable", { status: 503 }),
|
|
151
|
-
);
|
|
152
|
-
|
|
153
|
-
await expect(
|
|
154
|
-
getGatewaySession(makeClaims(), WORKSPACE_ID, COGNITO_TOKEN),
|
|
155
|
-
).rejects.toThrow(GatewaySessionError);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it("retries once after 200ms on network error and returns session if retry succeeds", async () => {
|
|
159
|
-
const fetchSpy = vi.spyOn(globalThis, "fetch")
|
|
160
|
-
.mockRejectedValueOnce(new TypeError("fetch failed"))
|
|
161
|
-
.mockResolvedValueOnce(
|
|
162
|
-
new Response(JSON.stringify({ workspace_id: WORKSPACE_ID }), { status: 200 }),
|
|
163
|
-
);
|
|
164
|
-
|
|
165
|
-
const session = await getGatewaySession(makeClaims(), WORKSPACE_ID, COGNITO_TOKEN);
|
|
166
|
-
expect(session.token).toBe(COGNITO_TOKEN);
|
|
167
|
-
expect(fetchSpy).toHaveBeenCalledTimes(2);
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
describe("tool cache", () => {
|
|
172
|
-
beforeEach(() => {
|
|
173
|
-
resetGatewaySessionCache();
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
afterEach(() => {
|
|
177
|
-
resetGatewaySessionCache();
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
it("getCachedTools returns undefined when nothing cached", () => {
|
|
181
|
-
expect(getCachedTools("sub-123")).toBeUndefined();
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
it("setCachedTools stores tools and getCachedTools retrieves them", () => {
|
|
185
|
-
const tools = [{ name: "github_repos_list" }];
|
|
186
|
-
setCachedTools("sub-123", tools);
|
|
187
|
-
expect(getCachedTools("sub-123")).toEqual(tools);
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
it("evictCachedTools removes tools for a sub", () => {
|
|
191
|
-
setCachedTools("sub-123", [{ name: "github_repos_list" }]);
|
|
192
|
-
evictCachedTools("sub-123");
|
|
193
|
-
expect(getCachedTools("sub-123")).toBeUndefined();
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
it("evictGatewaySession also evicts the tool cache for that sub", async () => {
|
|
197
|
-
vi.spyOn(globalThis, "fetch").mockResolvedValue(
|
|
198
|
-
new Response(JSON.stringify({ workspace_id: WORKSPACE_ID }), { status: 200 }),
|
|
199
|
-
);
|
|
200
|
-
await getGatewaySession(makeClaims(), WORKSPACE_ID, COGNITO_TOKEN);
|
|
201
|
-
setCachedTools(USER_SUB, [{ name: "tool" }]);
|
|
202
|
-
evictGatewaySession(USER_SUB);
|
|
203
|
-
expect(getCachedTools(USER_SUB)).toBeUndefined();
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
it("resetGatewaySessionCache also clears the tool cache", () => {
|
|
207
|
-
setCachedTools("sub-a", [{ name: "tool" }]);
|
|
208
|
-
resetGatewaySessionCache();
|
|
209
|
-
expect(getCachedTools("sub-a")).toBeUndefined();
|
|
210
|
-
});
|
|
211
132
|
});
|
package/test/routes/chat.test.ts
CHANGED
|
@@ -12,8 +12,6 @@ const {
|
|
|
12
12
|
mockProviderFactory,
|
|
13
13
|
mockGetGatewaySession,
|
|
14
14
|
mockEvictGatewaySession,
|
|
15
|
-
mockGetCachedTools,
|
|
16
|
-
mockSetCachedTools,
|
|
17
15
|
mockGetPrompt,
|
|
18
16
|
mockCompilePrompt,
|
|
19
17
|
mockGetPostHogClient,
|
|
@@ -27,8 +25,6 @@ const {
|
|
|
27
25
|
mockProviderFactory,
|
|
28
26
|
mockGetGatewaySession: vi.fn(),
|
|
29
27
|
mockEvictGatewaySession: vi.fn(),
|
|
30
|
-
mockGetCachedTools: vi.fn().mockReturnValue(undefined),
|
|
31
|
-
mockSetCachedTools: vi.fn(),
|
|
32
28
|
mockGetPrompt: vi.fn().mockResolvedValue({
|
|
33
29
|
source: "code_fallback",
|
|
34
30
|
prompt: "System: {{compilers}} {{tool_docs}}",
|
|
@@ -55,8 +51,6 @@ vi.mock("../../src/providers/openrouter.js", () => ({
|
|
|
55
51
|
vi.mock("../../src/gateway-session.js", () => ({
|
|
56
52
|
getGatewaySession: mockGetGatewaySession,
|
|
57
53
|
evictGatewaySession: mockEvictGatewaySession,
|
|
58
|
-
getCachedTools: mockGetCachedTools,
|
|
59
|
-
setCachedTools: mockSetCachedTools,
|
|
60
54
|
GatewaySessionError: class GatewaySessionError extends Error {
|
|
61
55
|
status: number;
|
|
62
56
|
constructor(status: number, message: string) {
|
|
@@ -188,8 +182,6 @@ describe("POST /chat", () => {
|
|
|
188
182
|
mockProviderFactory.mockReturnValue(mockModel);
|
|
189
183
|
mockCreateOpenRouterProvider.mockReturnValue(mockProviderFactory);
|
|
190
184
|
mockGetOpenRouterKey.mockResolvedValue("test-key");
|
|
191
|
-
mockGetCachedTools.mockReturnValue(undefined);
|
|
192
|
-
mockSetCachedTools.mockReset();
|
|
193
185
|
|
|
194
186
|
// Reset prompt + tool-docs mocks to defaults
|
|
195
187
|
mockGetPrompt.mockResolvedValue({
|
|
@@ -492,47 +484,5 @@ describe("POST /chat", () => {
|
|
|
492
484
|
expect.any(String),
|
|
493
485
|
);
|
|
494
486
|
});
|
|
495
|
-
|
|
496
|
-
it("uses cached tools when available — skips gateway fetch", async () => {
|
|
497
|
-
mockGetCachedTools.mockReturnValue(MOCK_TOOLS_RESPONSE.tools);
|
|
498
|
-
mockDoStream.mockResolvedValueOnce({
|
|
499
|
-
stream: makeSuccessStream(),
|
|
500
|
-
rawResponse: { headers: {} },
|
|
501
|
-
});
|
|
502
|
-
|
|
503
|
-
const app = buildApp();
|
|
504
|
-
const res = await app.request("/chat", {
|
|
505
|
-
method: "POST",
|
|
506
|
-
headers: validHeaders,
|
|
507
|
-
body: validBody,
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
expect(res.status).toBe(200);
|
|
511
|
-
// No gateway tools fetch should have occurred
|
|
512
|
-
expect(mockFetch).not.toHaveBeenCalled();
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
it("caches fetched tools via setCachedTools", async () => {
|
|
516
|
-
mockFetch.mockResolvedValueOnce(
|
|
517
|
-
new Response(JSON.stringify(MOCK_TOOLS_RESPONSE), { status: 200 }),
|
|
518
|
-
);
|
|
519
|
-
mockDoStream.mockResolvedValueOnce({
|
|
520
|
-
stream: makeSuccessStream(),
|
|
521
|
-
rawResponse: { headers: {} },
|
|
522
|
-
});
|
|
523
|
-
|
|
524
|
-
const app = buildApp();
|
|
525
|
-
await app.request("/chat", {
|
|
526
|
-
method: "POST",
|
|
527
|
-
headers: validHeaders,
|
|
528
|
-
body: validBody,
|
|
529
|
-
});
|
|
530
|
-
|
|
531
|
-
expect(mockSetCachedTools).toHaveBeenCalledOnce();
|
|
532
|
-
expect(mockSetCachedTools).toHaveBeenCalledWith(
|
|
533
|
-
fakeClaims.sub,
|
|
534
|
-
MOCK_TOOLS_RESPONSE.tools,
|
|
535
|
-
);
|
|
536
|
-
});
|
|
537
487
|
});
|
|
538
488
|
});
|