-
Notifications
You must be signed in to change notification settings - Fork 9
Re-point POST /api/chat/generate onto runAgentWorkflow + ephemeral key (chat#1813) #704
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
72b79ab
28edafc
e152356
572f2f1
f407058
897cee2
d139489
10a727c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| import type { NextRequest } from "next/server"; | ||
| import { NextResponse } from "next/server"; | ||
| import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; | ||
| import { handleChatRunStatus } from "@/lib/chat/runs/handleChatRunStatus"; | ||
|
|
||
| /** | ||
| * OPTIONS handler for CORS preflight requests. | ||
| * | ||
| * @returns A NextResponse with CORS headers. | ||
| */ | ||
| export async function OPTIONS() { | ||
| return new NextResponse(null, { | ||
| status: 200, | ||
| headers: getCorsHeaders(), | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * GET /api/chat/runs/{runId} | ||
| * | ||
| * Point-in-time status of an asynchronous run started via `POST /api/chat/runs` | ||
| * (recoupable/chat#1813). Returns `{ runId, status }` — a snapshot ("is it | ||
| * done?"), not the generated content. Read the content via the chat (`chatId` | ||
| * from the start response): `GET /api/chat/{chatId}/stream`, or the persisted | ||
| * messages. | ||
| * | ||
| * Authentication: x-api-key header required. | ||
| * | ||
| * @param request - The request object. | ||
| * @param ctx - Route context with the `runId` path param. | ||
| * @param ctx.params - Promise resolving to the `{ runId }` path params. | ||
| * @returns 200 `{ runId, status }`, 401/403 on auth, or 404 if the run is unknown. | ||
| */ | ||
| export async function GET( | ||
| request: NextRequest, | ||
| ctx: { params: Promise<{ runId: string }> }, | ||
| ): Promise<Response> { | ||
| const { runId } = await ctx.params; | ||
| return handleChatRunStatus(request, runId); | ||
| } | ||
|
|
||
| export const dynamic = "force-dynamic"; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| import type { NextRequest } from "next/server"; | ||
| import { NextResponse } from "next/server"; | ||
| import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; | ||
| import { handleStartChatRun } from "@/lib/chat/runs/handleStartChatRun"; | ||
|
|
||
| /** | ||
| * OPTIONS handler for CORS preflight requests. | ||
| * | ||
| * @returns A NextResponse with CORS headers. | ||
| */ | ||
| export async function OPTIONS() { | ||
| return new NextResponse(null, { | ||
| status: 200, | ||
| headers: getCorsHeaders(), | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * POST /api/chat/runs | ||
| * | ||
| * Start an asynchronous, headless chat-generation run on the durable | ||
| * `runAgentWorkflow` (recoupable/chat#1813). Provisions a session + sandbox, | ||
| * starts a workflow run, and returns `{ runId, chatId, sessionId }` with **202** | ||
| * immediately (plus a `Location` header at the run-status resource) — generation, | ||
| * assistant-message persistence, and side effects happen server-side after the | ||
| * response. | ||
| * | ||
| * Authentication: x-api-key header required (account inferred from the key; | ||
| * org keys may override via body `accountId`). | ||
| * | ||
| * Request body: | ||
| * - prompt: String prompt (mutually exclusive with messages) | ||
| * - messages: Array of UIMessages (mutually exclusive with prompt) | ||
| * - artistId: Optional UUID of the artist account | ||
| * - model: Optional model ID override (default anthropic/claude-haiku-4.5) | ||
| * - topic: Optional session title | ||
| * - accountId: Optional accountId override (requires org API key) | ||
|
Comment on lines
+28
to
+37
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Keep the route JSDoc aligned with the actual contract. This block still says 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| * | ||
| * Response body (202): `{ runId, chatId, sessionId }`. Read the result later via | ||
| * `GET /api/chat/{chatId}/stream` (watch the stream) or the chat's persisted | ||
| * messages; poll `GET /api/chat/runs/{runId}` for status (status route lands in | ||
| * a follow-up). | ||
| * | ||
| * @param request - The request object | ||
| * @returns 202 `{ runId, chatId, sessionId }`, or a 4xx/5xx error | ||
| */ | ||
| export async function POST(request: NextRequest): Promise<Response> { | ||
| return handleStartChatRun(request); | ||
| } | ||
|
|
||
| // Provisioning (repo + session + sandbox) runs before the 202 returns, so give | ||
| // the function headroom beyond the default. The workflow itself runs durably | ||
| // outside this request. | ||
| export const maxDuration = 120; | ||
| export const dynamic = "force-dynamic"; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import { deleteApiKey } from "@/lib/supabase/account_api_keys/deleteApiKey"; | ||
|
|
||
| /** | ||
| * Vercel Workflow `"use step"` that deletes the ephemeral, account-scoped | ||
| * `recoup_sk_…` key minted for a headless `/api/chat/runs` run | ||
| * (recoupable/chat#1813). Called from `runAgentWorkflow`'s `finally` so the | ||
| * credential is revoked the moment the run ends — the key's ~15m `expires_at` | ||
| * TTL (enforced in `getApiKeyAccountId`) is only the backstop if this is missed. | ||
| * | ||
| * Defensively swallows its own errors: a cleanup hiccup must not fail the run, | ||
| * and the TTL still guarantees the key can't outlive its window. | ||
| * | ||
| * @param keyId - `account_api_keys.id` of the ephemeral key to delete. | ||
| */ | ||
| export async function deleteEphemeralKeyStep(keyId: string): Promise<void> { | ||
| "use step"; | ||
| try { | ||
| const { error } = await deleteApiKey(keyId); | ||
|
Comment on lines
+15
to
+18
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify deleteApiKey enforces account + ephemeral-only predicates.
# Expected: delete query filters by id, account, and an ephemeral marker.
fd -a 'deleteApiKey.ts' . --exec sh -c 'echo "### $1"; sed -n "1,200p" "$1"' sh {}
rg -n -C3 '\bdeleteApiKey\s*\(' .Repository: recoupable/api Length of output: 2791 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Inspect the delete workflow and related key lifecycle code for scoping and ephemeral markers.
sed -n '1,220p' app/lib/workflows/deleteEphemeralKeyStep.ts
printf '\n---\n'
sed -n '1,220p' lib/supabase/account_api_keys/deleteApiKey.ts
printf '\n---\n'
fd -a 'mint*Key*.ts' lib app . | head -50
printf '\n---\n'
fd -a '*account_api_keys*' lib app . | head -50
printf '\n---\n'
rg -n -C4 'expires_at|ephemeral|account_api_keys|deleteApiKey\(' lib appRepository: recoupable/api Length of output: 1909 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Inspect the minting and cleanup flow for the ephemeral key.
rg -n -C4 'deleteEphemeralKeyStep|runAgentWorkflow|mint.*Key|expires_at|getApiKeyAccountId|account_api_keys' app lib
printf '\n---\n'
sed -n '1,260p' lib/chat/handleStartChatRun.ts
printf '\n---\n'
fd -a '*getApiKeyAccountId*.ts' lib app . | xargs -r -I{} sh -c 'echo "### {}"; sed -n "1,220p" "{}"'Repository: recoupable/api Length of output: 50371 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Find every call site for the workflow step and the raw delete helper.
rg -n -C3 'deleteEphemeralKeyStep\(|deleteApiKey\(' app lib --glob '!**/__tests__/**'Repository: recoupable/api Length of output: 3035 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Find every call site for the workflow step and the raw delete helper.
rg -n -C3 'deleteEphemeralKeyStep\(|deleteApiKey\(' app lib --glob '!**/__tests__/**'Repository: recoupable/api Length of output: 3035 Scope the delete to the minted ephemeral key. 🤖 Prompt for AI Agents |
||
| if (error) { | ||
| console.error(`[deleteEphemeralKeyStep] failed to delete key ${keyId}:`, error); | ||
| } | ||
| } catch (error) { | ||
| console.error(`[deleteEphemeralKeyStep] unhandled error deleting key ${keyId}:`, error); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Tighten the JSDoc auth contract to match
validateAuthContext().The docblock states
Authentication: x-api-key header required(Line 27), but the downstreamhandleChatRunStatususesvalidateAuthContext(), which accepts both thex-api-keyheader andAuthorization: Bearertokens. Self-documenting code should not understate the supported auth surface—callers may wrongly assume Bearer tokens are rejected here.📝 Suggested doc tweak
As per coding guidelines: "Always use
validateAuthContext()for authentication in API routes; it supports bothx-api-keyheader andAuthorization: Bearertoken authentication".📝 Committable suggestion
🤖 Prompt for AI Agents
Source: Coding guidelines