diff --git a/README.md b/README.md index 8a95de1..8cfc7e4 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A React + Vite frontend (Tailwind, Noto Serif) that collects public media station details and streams an Anthropic Managed Agent response back to the -browser through a Netlify Functions v2 proxy. +browser through a Netlify Edge Function proxy. ## Setup @@ -31,17 +31,52 @@ Open http://localhost:8888. ## How streaming works -1. The browser POSTs the form to `/.netlify/functions/agent-proxy`. -2. The Netlify v2 function calls `anthropic.messages.stream(...)` and wraps - the upstream iterator in a `ReadableStream`. Each - `content_block_delta` text chunk is enqueued as plain UTF‑8 bytes. -3. The React app reads `response.body.getReader()` and decodes chunks with - `TextDecoder`, appending them to the result state. -4. A `Thinking` flag stays `true` until the first chunk arrives, then flips - to a streaming state with a pulsing cursor. +1. The browser POSTs the form to `/api/agent-proxy`. +2. A Netlify Edge Function (Deno runtime) creates an Anthropic Managed + Agent session, opens the upstream SSE event stream, and sends the + user message. It tails the stream and re-opens it (with an + `events.list` backfill, deduped by event id) whenever it drops mid + session, until the session reports a terminal `session.status_*` + event or a 20-minute wall-clock budget is hit. +3. Downstream to the browser the function emits newline-delimited JSON + (`application/x-ndjson`) — `text`, `status`, `heartbeat`, + `segment_end`, `done`, `error` — one object per line. +4. The React app reads `response.body.getReader()`, splits on `\n`, and + parses each line. `text` lines append to the brief; `status` lines + drive a separate “what the agent is doing now” banner. +5. A `Thinking` flag stays `true` until the first chunk arrives, then + flips to a streaming state with a pulsing cursor. + +### Why segmented streaming + +Netlify Edge Functions empirically cap a single streaming response at +~60 s wall-clock (Netlify's own canonical SSE example cuts at the same +mark, even though the docs imply "indefinite" streaming). The Anthropic +Managed Agent session itself is fine for several minutes; the limit is +per-HTTP-response. So the proxy segments the stream: + +- Just before ~54 s the function writes one final NDJSON line — + `{"type":"segment_end","sessionId":"sess_…","lastEventId":"evt_…","startedAt":…}` — + and closes the response. +- The React side sees `segment_end`, immediately POSTs to + `/api/agent-proxy` again with `{sessionId, lastEventId, startedAt}` + in the body. +- The function recognises this as a resume payload: it does **not** + create a new session or re-send the user message. It just reopens + the live SSE stream, backfills via + `GET /v1/sessions/{id}/events?after_id=lastEventId` (deduped by + `event.id`), and keeps tailing. + +The 20-minute overall budget is enforced via `Date.now() - startedAt` +so it spans across all segments. The previous v2 Node Function (on AWS +Lambda) was killed at ~27 s, well before any reconnect could fire — +moving to Edge Functions extended that to ~60 s per response, and the +segment loop extends it the rest of the way. ## Files -- [src/App.jsx](src/App.jsx) — form, streaming reader, UI states. -- [netlify/functions/agent-proxy.js](netlify/functions/agent-proxy.js) — - Managed Agent proxy with `ReadableStream`. +- [src/App.jsx](src/App.jsx) — form, NDJSON streaming reader, + segment-end resume loop, UI states. +- [netlify/edge-functions/agent-proxy.ts](netlify/edge-functions/agent-proxy.ts) + — Managed Agent proxy with reconnect, backfill, segmentation, and + NDJSON wire protocol. diff --git a/netlify.toml b/netlify.toml index 97e8591..f8cab62 100644 --- a/netlify.toml +++ b/netlify.toml @@ -1,10 +1,6 @@ [build] command = "npm run build" publish = "dist" - functions = "netlify/functions" - -[functions] - node_bundler = "esbuild" [dev] framework = "vite" diff --git a/netlify/edge-functions/agent-proxy.ts b/netlify/edge-functions/agent-proxy.ts new file mode 100644 index 0000000..df256ee --- /dev/null +++ b/netlify/edge-functions/agent-proxy.ts @@ -0,0 +1,766 @@ +import type { Config, Context } from '@netlify/edge-functions' + +/** + * Netlify Edge Function (Deno runtime). + * + * Proxies a request from the React frontend to a Claude Console-defined + * Managed Agent. The agent's model, system prompt, tools, MCP servers + * and skills are configured in the Console and referenced here by ID. + * + * ## Why this exists + * + * Two layered problems are in scope: + * + * 1. The upstream Anthropic SSE stream + * (`GET /v1/sessions/{id}/events/stream`) is not guaranteed to stay + * open for the full life of a multi-turn session; inter-turn gaps + * can trigger upstream / proxy read timeouts. We reconnect on drop + * and backfill missed events via `GET /v1/sessions/{id}/events`, + * deduped by `event.id`. + * + * 2. Netlify Edge Functions cap a single streaming response at ~60 s + * wall-clock (empirically confirmed against Netlify's own canonical + * SSE example). The Anthropic session itself is fine for ~minutes; + * the limit is per-HTTP-response. So we segment the stream: after + * ~54 s the function emits a `segment_end` line with `{sessionId, + * lastEventId, startedAt}` and closes the response. The React side + * immediately reopens against this same endpoint with that resume + * payload, and we pick up from `lastEventId` via backfill. To the + * user this looks like one continuous stream. + * + * ## Wire protocol (downstream to the browser): NDJSON + * + * {"type":"text","text":"...markdown..."} // append to brief + * {"type":"status","kind":"thinking"} + * {"type":"status","kind":"tool_use","name":"...","label":"..."} + * {"type":"status","kind":"tool_result","ok":true} + * {"type":"status","kind":"tool_result","ok":false,"message":"..."} + * {"type":"status","kind":"session_error","message":"..."} + * {"type":"heartbeat"} // keep-alive + * {"type":"segment_end","sessionId":"sess_...", + * "lastEventId":"evt_...","startedAt":1234567890} // reopen me + * {"type":"done"} // terminal + * {"type":"error","message":"..."} // unrecoverable + * + * ## Request payloads + * + * - New session (first call): + * {stationName, stationLocation, stationWebsite} + * - Resume (subsequent calls after a segment_end): + * {sessionId, lastEventId?, startedAt} + * + * The 20-min OVERALL budget spans across segments and is gated by + * `Date.now() - startedAt`. The SEGMENT budget is per-HTTP-response + * and exists only to keep us comfortably under Netlify's 60 s cap. + */ + +const MANAGED_AGENTS_BETA = 'managed-agents-2026-04-01' +const ANTHROPIC_VERSION = '2023-06-01' +const ANTHROPIC_API_BASE = 'https://api.anthropic.com' + +// Downstream keep-alive cadence. The browser's fetch reader appears +// stuck if no bytes flow for too long. We keep this tighter than the +// segment budget so the connection stays warm even during long +// model-thinking gaps with no agent events. +const HEARTBEAT_MS = 5_000 + +// Close the current response well before Netlify's empirical streaming +// cap. We've observed responses cut anywhere between ~49 s and ~60 s, +// so a 54 s budget is too aggressive — sometimes the platform pulls +// the rug out before our final `segment_end` write + `controller.close()` +// can flush. 40 s gives ~10 s of headroom on the low end of the +// observed range while still amortising session setup (~1 segment per +// ~40 s of brief) across a typical 3-5 min brief. +const SEGMENT_BUDGET_MS = 40_000 + +// Total wall-clock allowance across ALL segments for a single user +// brief. Past this we emit a final `error` line and stop reopening. +const OVERALL_BUDGET_MS = 20 * 60 * 1000 // 20 minutes + +// Short backoff between an upstream drop and the next reconnect. +const RECONNECT_BACKOFF_MS = 500 + +// --------------------------------------------------------------------------- +// Types +// --------------------------------------------------------------------------- + +type ContentBlock = { type?: string; text?: string } + +type AgentEvent = { + id?: string + type?: string + content?: ContentBlock[] + name?: string + input?: Record + is_error?: boolean + stop_reason?: { type?: string } + error?: { message?: string } +} + +type EventsListPage = { + data?: AgentEvent[] + next_page?: string +} + +type NewSessionPayload = { + stationName?: string + stationLocation?: string + stationWebsite?: string +} + +type ResumePayload = { + sessionId: string + lastEventId?: string + startedAt?: number +} + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +function authHeaders(apiKey: string): Record { + return { + 'x-api-key': apiKey, + 'anthropic-version': ANTHROPIC_VERSION, + 'anthropic-beta': MANAGED_AGENTS_BETA, + } +} + +// A `session.status_idle` with `stop_reason: requires_action` means +// the agent is waiting on a client-side response (custom tool result, +// tool confirmation). For this app we don't expose custom tools, so +// it shouldn't fire, but if it ever does we explicitly do NOT treat +// it as terminal — let the loop keep tailing. +function isTerminal(event: AgentEvent | undefined): boolean { + if (!event) return false + if (event.type === 'session.status_terminated') return true + if (event.type === 'session.status_idle') { + const t = event.stop_reason?.type + return t === 'end_turn' || t === 'retries_exhausted' + } + return false +} + +function describeToolUse(event: AgentEvent): string { + const name = event.name || 'tool' + const input = (event.input || {}) as Record + if (name === 'web_search' && typeof input.query === 'string') { + return `Searching the web: ${input.query}` + } + if (name === 'web_fetch' && typeof input.url === 'string') { + return `Fetching: ${input.url}` + } + if (event.type === 'agent.mcp_tool_use') { + const args = Object.entries(input) + .filter(([k]) => k !== 'limit') + .slice(0, 3) + .map(([k, v]) => + typeof v === 'object' && v !== null + ? `${k}=…` + : `${k}=${String(v).slice(0, 40)}`, + ) + .join(', ') + return `${name}${args ? ` (${args})` : ''}` + } + return name +} + +async function anthropicFetch( + path: string, + apiKey: string, + init: RequestInit = {}, +): Promise { + const headers = { + ...authHeaders(apiKey), + ...(init.headers as Record | undefined), + } + return await fetch(`${ANTHROPIC_API_BASE}${path}`, { ...init, headers }) +} + +// Parse the SSE stream into an async iterable of decoded events. +// Each SSE message is `event: \ndata: \n\n`. +// +// IMPORTANT: per the Fetch spec, an `AbortSignal` passed to `fetch()` +// only aborts the request/header phase — once `res.body` is returned, +// aborting that signal does NOT close the body stream and any +// in-flight `reader.read()` will hang forever. So we attach our own +// listener and `reader.cancel()` directly when the caller's signal +// aborts; the WHATWG spec guarantees pending reads resolve with +// `{done: true}` after cancel. +async function* parseSse( + stream: ReadableStream, + signal?: AbortSignal, +): AsyncGenerator { + const reader = stream.getReader() + const decoder = new TextDecoder('utf-8') + let buffer = '' + + const onAbort = () => { + reader.cancel().catch(() => {}) + } + if (signal) { + if (signal.aborted) onAbort() + else signal.addEventListener('abort', onAbort, { once: true }) + } + + try { + while (true) { + const { value, done } = await reader.read() + if (done) break + buffer += decoder.decode(value, { stream: true }) + + let sep + while ((sep = buffer.indexOf('\n\n')) !== -1) { + const raw = buffer.slice(0, sep) + buffer = buffer.slice(sep + 2) + + let dataPayload = '' + let evType: string | undefined + for (const line of raw.split('\n')) { + if (line.startsWith('event:')) { + evType = line.slice(6).trim() + } else if (line.startsWith('data:')) { + dataPayload += line.slice(5).trim() + } + } + if (!dataPayload) continue + try { + const parsed = JSON.parse(dataPayload) as AgentEvent + if (evType && !parsed.type) parsed.type = evType + yield parsed + } catch { + // Malformed event — skip. + } + } + } + } finally { + if (signal) { + signal.removeEventListener('abort', onAbort) + } + try { + reader.releaseLock() + } catch { + /* ignore */ + } + } +} + +// Page through `GET /v1/sessions/{id}/events` to backfill anything +// we missed. The Managed Agents endpoint uses an opaque `page` cursor +// returned as `next_page` on each response; there is no `after_id` +// filter, so we always pull the full history and the caller is +// responsible for skipping events it has already handled (via +// `seenEventIds` and the resume-payload `lastEventId`). +async function* listAllEvents( + sessionId: string, + apiKey: string, + signal?: AbortSignal, +): AsyncGenerator { + let pageCursor: string | undefined + for (;;) { + const params = new URLSearchParams({ order: 'asc', limit: '1000' }) + if (pageCursor) params.set('page', pageCursor) + const res = await anthropicFetch( + `/v1/sessions/${sessionId}/events?${params.toString()}`, + apiKey, + { signal }, + ) + if (!res.ok) { + throw new Error( + `events.list returned ${res.status} ${res.statusText}`, + ) + } + const page = (await res.json()) as EventsListPage + const items = page.data || [] + for (const ev of items) yield ev + if (!page.next_page) break + pageCursor = page.next_page + } +} + +async function openStream( + sessionId: string, + apiKey: string, + signal: AbortSignal, +): Promise> { + const res = await anthropicFetch( + `/v1/sessions/${sessionId}/events/stream`, + apiKey, + { + method: 'GET', + headers: { accept: 'text/event-stream' }, + signal, + }, + ) + if (!res.ok || !res.body) { + throw new Error( + `events.stream returned ${res.status} ${res.statusText}`, + ) + } + return res.body +} + +// `setTimeout` that resolves early on abort. +function abortableSleep(ms: number, signal: AbortSignal): Promise { + return new Promise((resolve) => { + const t = setTimeout(resolve, ms) + const onAbort = () => { + clearTimeout(t) + resolve() + } + if (signal.aborted) { + onAbort() + return + } + signal.addEventListener('abort', onAbort, { once: true }) + }) +} + +// --------------------------------------------------------------------------- +// Handler +// --------------------------------------------------------------------------- + +const URL_REGEX = + /^(https?:\/\/)?([\w-]+\.)+[\w-]{2,}(\/[\w\-._~:/?#[\]@!$&'()*+,;=%]*)?$/i + +export default async (req: Request, _context: Context) => { + if (req.method !== 'POST') { + return new Response('Method Not Allowed', { status: 405 }) + } + + let payload: NewSessionPayload & Partial + try { + payload = await req.json() + } catch { + return new Response('Invalid JSON body', { status: 400 }) + } + + const apiKey = Netlify.env.get('ANTHROPIC_API_KEY') + if (!apiKey) { + return new Response('Server is missing ANTHROPIC_API_KEY', { + status: 500, + }) + } + + const isResume = + typeof payload.sessionId === 'string' && payload.sessionId.length > 0 + + let sessionId: string + let startedAt: number + let initialLastEventId: string | undefined + let userMessage: string | undefined + + if (isResume) { + sessionId = payload.sessionId as string + startedAt = + typeof payload.startedAt === 'number' ? payload.startedAt : Date.now() + initialLastEventId = + typeof payload.lastEventId === 'string' && payload.lastEventId + ? payload.lastEventId + : undefined + + if (Date.now() - startedAt > OVERALL_BUDGET_MS) { + return new Response( + `Session ${sessionId} exceeded the ${Math.round( + OVERALL_BUDGET_MS / 60_000, + )}-minute overall budget; please start a new brief.`, + { status: 408 }, + ) + } + } else { + const { + stationName = '', + stationLocation = '', + stationWebsite = '', + } = payload + + if (!URL_REGEX.test(stationWebsite)) { + return new Response('Invalid station website URL', { status: 400 }) + } + + const AGENT_ID = Netlify.env.get('ANTHROPIC_AGENT_ID') + const ENVIRONMENT_ID = Netlify.env.get('ANTHROPIC_ENVIRONMENT_ID') + const VAULT_IDS = (Netlify.env.get('ANTHROPIC_VAULT_IDS') || '') + .split(',') + .map((s) => s.trim()) + .filter(Boolean) + + const missing: string[] = [] + if (!AGENT_ID) missing.push('ANTHROPIC_AGENT_ID') + if (!ENVIRONMENT_ID) missing.push('ANTHROPIC_ENVIRONMENT_ID') + if (missing.length) { + return new Response( + `Server is missing env var(s): ${missing.join(', ')}`, + { status: 500 }, + ) + } + + userMessage = `Here is a new public media station intake: + +- Station Name: ${stationName} +- Station Location: ${stationLocation} +- Station Website: ${stationWebsite} + +Please follow your instructions to produce the funding outlook brief.` + + const createBody: Record = { + agent: AGENT_ID, + environment_id: ENVIRONMENT_ID, + } + if (VAULT_IDS.length) createBody.vault_ids = VAULT_IDS + + try { + const createRes = await anthropicFetch('/v1/sessions', apiKey, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify(createBody), + }) + if (!createRes.ok) { + const text = await createRes.text() + return new Response( + `Failed to create session: ${createRes.status} ${text}`, + { status: 500 }, + ) + } + const created = (await createRes.json()) as { id?: string } + if (!created.id) { + return new Response('Failed to create session: no id returned', { + status: 500, + }) + } + sessionId = created.id + startedAt = Date.now() + } catch (err) { + return new Response( + `Failed to create session: ${(err as Error)?.message || err}`, + { status: 500 }, + ) + } + } + + const encoder = new TextEncoder() + const seenEventIds = new Set() + + const body = new ReadableStream({ + async start(controller) { + let lastSendAt = Date.now() + let lastEventIdSeen = initialLastEventId + // For resumes we replay the full session history via + // `listAllEvents` but mute every event up to and including + // `initialLastEventId` (the user has already seen those). Once + // we've cleared that cursor (or on a brand-new session where it + // is undefined), we start actually delivering events. + let pastInitialId = !initialLastEventId + + const writeJson = (obj: unknown) => { + try { + controller.enqueue(encoder.encode(JSON.stringify(obj) + '\n')) + lastSendAt = Date.now() + } catch { + /* controller closed */ + } + } + + const heartbeat = setInterval(() => { + if (Date.now() - lastSendAt >= HEARTBEAT_MS) { + writeJson({ type: 'heartbeat' }) + } + }, HEARTBEAT_MS / 2) + + // Single AbortController for this segment. The segment timer + // calls .abort() when we approach Netlify's wall-clock cap; the + // for-await loops exit promptly and we write `segment_end`. + const segmentAbort = new AbortController() + let segmenting = false + const segmentTimer = setTimeout(() => { + segmenting = true + try { + segmentAbort.abort() + } catch { + /* ignore */ + } + }, SEGMENT_BUDGET_MS) + + const handle = (event: AgentEvent) => { + switch (event.type) { + case 'agent.message': { + if (Array.isArray(event.content)) { + for (const block of event.content) { + if (block?.type === 'text' && block.text) { + writeJson({ type: 'text', text: block.text + '\n\n' }) + } + } + } + break + } + case 'agent.thinking': { + writeJson({ type: 'status', kind: 'thinking' }) + break + } + case 'agent.tool_use': + case 'agent.mcp_tool_use': { + writeJson({ + type: 'status', + kind: 'tool_use', + name: event.name || 'tool', + label: describeToolUse(event), + }) + break + } + case 'agent.tool_result': + case 'agent.mcp_tool_result': { + if (event.is_error) { + const msg = + (Array.isArray(event.content) && event.content[0]?.text) || + 'tool error' + writeJson({ + type: 'status', + kind: 'tool_result', + ok: false, + message: String(msg).slice(0, 300), + }) + } else { + writeJson({ type: 'status', kind: 'tool_result', ok: true }) + } + break + } + case 'session.error': { + const msg = event.error?.message || 'unknown session error' + writeJson({ + type: 'status', + kind: 'session_error', + message: msg, + }) + break + } + } + } + + // Brand new session: send the kickoff user.message BEFORE we + // start tailing. (For resume, the user.message was sent on the + // first segment, so we never re-send it.) + if (!isResume && userMessage) { + try { + const sendRes = await anthropicFetch( + `/v1/sessions/${sessionId}/events`, + apiKey, + { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ + events: [ + { + type: 'user.message', + content: [{ type: 'text', text: userMessage }], + }, + ], + }), + }, + ) + if (!sendRes.ok) { + const text = await sendRes.text() + writeJson({ + type: 'error', + message: `Failed to send user message: ${sendRes.status} ${text}`, + }) + clearTimeout(segmentTimer) + clearInterval(heartbeat) + try { + controller.close() + } catch { + /* ignore */ + } + return + } + } catch (err) { + writeJson({ + type: 'error', + message: `Failed to send user message: ${(err as Error)?.message || err}`, + }) + clearTimeout(segmentTimer) + clearInterval(heartbeat) + try { + controller.close() + } catch { + /* ignore */ + } + return + } + } + + let done = false + let iteration = 0 + + try { + while (!done && !segmenting) { + iteration++ + + if (iteration > 1) { + await abortableSleep(RECONNECT_BACKOFF_MS, segmentAbort.signal) + if (segmenting) break + } + + if (Date.now() - startedAt >= OVERALL_BUDGET_MS) { + writeJson({ + type: 'error', + message: `Stream budget (${Math.round( + OVERALL_BUDGET_MS / 60_000, + )} min) exhausted. The session may still be running — try again or check the Console.`, + }) + break + } + + // Open the live stream FIRST so any events emitted while + // we're backfilling are still captured on the wire. + let upstream: ReadableStream + try { + upstream = await openStream( + sessionId, + apiKey, + segmentAbort.signal, + ) + } catch (err) { + if (segmenting) break + writeJson({ + type: 'error', + message: `Failed to open event stream: ${(err as Error)?.message || err}`, + }) + break + } + + // Backfill anything we missed. The events endpoint does + // NOT support `after_id`, so we always pull the full session + // history and skip everything up to and including + // `initialLastEventId` (the cursor handed to us by the + // previous segment via the resume payload). Within this + // segment, `seenEventIds` dedupes against the live stream. + try { + for await (const event of listAllEvents( + sessionId, + apiKey, + segmentAbort.signal, + )) { + if (segmenting) break + if (event.id) { + if (!pastInitialId) { + // Already delivered to the user in a previous + // segment — mark seen so the live stream doesn't + // re-emit it, but do NOT call handle(). + seenEventIds.add(event.id) + lastEventIdSeen = event.id + if (event.id === initialLastEventId) { + pastInitialId = true + } + } else if (!seenEventIds.has(event.id)) { + seenEventIds.add(event.id) + lastEventIdSeen = event.id + handle(event) + } else { + // Already handled earlier in this segment — keep + // the cursor moving forward. + lastEventIdSeen = event.id + } + } + if (isTerminal(event) && pastInitialId) { + done = true + break + } + } + } catch (err) { + if (!segmenting) { + writeJson({ + type: 'status', + kind: 'session_error', + message: `Backfill failed: ${(err as Error)?.message || err}`, + }) + // Fall through and try the live stream. + } + } + + // If the cursor handed to us by the previous segment didn't + // appear in the session's history (stale / corrupted / + // history truncated), don't get stuck muting events forever + // — start delivering whatever shows up next. + if (!pastInitialId) { + pastInitialId = true + } + + if (done || segmenting) { + // The upstream fetch is already cleaned up by + // `segmentAbort.abort()` (or will be by the runtime once + // we drop our reference). Don't `await upstream.cancel()` + // here — on an already-aborted body that call can hang + // on Deno Edge, which would prevent us from writing the + // final `segment_end` line. + break + } + + // Tail the live stream until upstream EOFs (drop), the + // segment timer fires, or a terminal event arrives. + try { + for await (const event of parseSse( + upstream, + segmentAbort.signal, + )) { + if (segmenting) break + if (event.id) { + if (!seenEventIds.has(event.id)) { + seenEventIds.add(event.id) + lastEventIdSeen = event.id + handle(event) + } else { + lastEventIdSeen = event.id + } + } + if (isTerminal(event)) { + done = true + break + } + } + } catch { + // Most commonly: AbortError from segmentAbort. Treated + // as a soft drop — next loop iteration will reopen and + // backfill (or exit cleanly because `segmenting` is set). + } + } + + if (done) { + writeJson({ type: 'done' }) + } else if (segmenting) { + writeJson({ + type: 'segment_end', + sessionId, + lastEventId: lastEventIdSeen, + startedAt, + }) + } + } catch (err) { + writeJson({ + type: 'error', + message: (err as Error)?.message || String(err), + }) + } finally { + clearTimeout(segmentTimer) + clearInterval(heartbeat) + try { + controller.close() + } catch { + /* ignore */ + } + } + }, + }) + + return new Response(body, { + status: 200, + headers: { + 'Content-Type': 'application/x-ndjson; charset=utf-8', + 'Cache-Control': 'no-cache, no-transform', + 'X-Accel-Buffering': 'no', + }, + }) +} + +export const config: Config = { + path: '/api/agent-proxy', +} diff --git a/netlify/functions/agent-proxy.js b/netlify/functions/agent-proxy.js deleted file mode 100644 index f864b7e..0000000 --- a/netlify/functions/agent-proxy.js +++ /dev/null @@ -1,298 +0,0 @@ -import Anthropic from '@anthropic-ai/sdk' - -/** - * Netlify Functions v2 handler. - * - * Proxies a request from the React frontend to a Claude Console-defined - * Managed Agent via the /v1/sessions endpoints. The agent's model, system - * prompt, tools, MCP servers, and skills are configured in the Console and - * referenced here by ID. We open the SSE event stream first, then send a - * `user.message` event, and pipe each `agent.message` text block to the - * client through a ReadableStream so we never hit Netlify's 26-second - * synchronous execution limit. - */ - -// All Managed Agents endpoints require this beta header. -const MANAGED_AGENTS_BETA = 'managed-agents-2026-04-01' - -// --------------------------------------------------------------------------- -// Anthropic client (reused across warm Lambda invocations) -// --------------------------------------------------------------------------- -let _client -function getClient() { - if (_client) return _client - if (!process.env.ANTHROPIC_API_KEY) { - throw new Error('Server is missing ANTHROPIC_API_KEY') - } - _client = new Anthropic({ - apiKey: process.env.ANTHROPIC_API_KEY, - defaultHeaders: { 'anthropic-beta': MANAGED_AGENTS_BETA }, - }) - return _client -} - -// --------------------------------------------------------------------------- -// Handler -// --------------------------------------------------------------------------- -export default async (req /*, context */) => { - if (req.method !== 'POST') { - return new Response('Method Not Allowed', { status: 405 }) - } - - let payload - try { - payload = await req.json() - } catch { - return new Response('Invalid JSON body', { status: 400 }) - } - - const { - stationName = '', - stationLocation = '', - stationWebsite = '', - } = payload || {} - - // Server-side URL validation (mirrors the client-side regex). - const URL_REGEX = - /^(https?:\/\/)?([\w-]+\.)+[\w-]{2,}(\/[\w\-._~:/?#[\]@!$&'()*+,;=%]*)?$/i - if (!URL_REGEX.test(stationWebsite)) { - return new Response('Invalid station website URL', { status: 400 }) - } - - // Read env vars at request time (Netlify makes them available per-invocation). - const AGENT_ID = process.env.ANTHROPIC_AGENT_ID - const ENVIRONMENT_ID = process.env.ANTHROPIC_ENVIRONMENT_ID - const VAULT_IDS = (process.env.ANTHROPIC_VAULT_IDS || '') - .split(',') - .map((s) => s.trim()) - .filter(Boolean) - - const missing = [] - if (!AGENT_ID) missing.push('ANTHROPIC_AGENT_ID') - if (!ENVIRONMENT_ID) missing.push('ANTHROPIC_ENVIRONMENT_ID') - if (missing.length) { - return new Response(`Server is missing env var(s): ${missing.join(', ')}`, { - status: 500, - }) - } - - let client - try { - client = getClient() - } catch (err) { - return new Response( - `Failed to initialize agent: ${err.message || err}`, - { status: 500 }, - ) - } - - const userMessage = `Here is a new public media station intake: - -- Station Name: ${stationName} -- Station Location: ${stationLocation} -- Station Website: ${stationWebsite} - -Please follow your instructions to produce the funding outlook brief.` - - // ----- Create the session ----- - let session - try { - const sessionParams = { - agent: AGENT_ID, - environment_id: ENVIRONMENT_ID, - } - if (VAULT_IDS.length) sessionParams.vault_ids = VAULT_IDS - session = await client.beta.sessions.create(sessionParams) - } catch (err) { - return new Response( - `Failed to create session: ${err.message || err}`, - { status: 500 }, - ) - } - - // ----- Open the event stream BEFORE sending the user message ----- - // (Stream-first ensures we don't miss any events the agent emits.) - let upstream - try { - upstream = await client.beta.sessions.events.stream(session.id) - } catch (err) { - return new Response( - `Failed to open session event stream: ${err.message || err}`, - { status: 500 }, - ) - } - - // ----- Send the kickoff user.message event ----- - try { - await client.beta.sessions.events.send(session.id, { - events: [ - { - type: 'user.message', - content: [{ type: 'text', text: userMessage }], - }, - ], - }) - } catch (err) { - return new Response( - `Failed to send user message: ${err.message || err}`, - { status: 500 }, - ) - } - - // ----- Pipe agent activity to the client ----- - // Long stretches between agent.message events (model "thinking", - // multi-tool batches, etc.) can silence the response stream long enough - // that Netlify's edge proxy drops the connection. To keep bytes flowing - // we (a) forward extra event types as concise status lines and (b) emit - // a periodic heartbeat space when the upstream goes quiet. - const encoder = new TextEncoder() - const seenEventIds = new Set() - const writtenFiles = new Set() // dedupe writes by path - const HEARTBEAT_MS = 10_000 - - const stream = new ReadableStream({ - async start(controller) { - let lastSendAt = Date.now() - const send = (s) => { - controller.enqueue(encoder.encode(s)) - lastSendAt = Date.now() - } - // Zero-width space — invisible in rendered Markdown but counts as a - // byte on the wire, which is enough to defeat proxy idle-timeouts. - const heartbeat = setInterval(() => { - if (Date.now() - lastSendAt >= HEARTBEAT_MS) { - try { - controller.enqueue(encoder.encode('\u200B')) - lastSendAt = Date.now() - } catch { - /* controller may have closed */ - } - } - }, HEARTBEAT_MS / 2) - - try { - for await (const event of upstream) { - if (event.id && !seenEventIds.has(event.id)) { - seenEventIds.add(event.id) - - switch (event.type) { - case 'agent.message': { - if (Array.isArray(event.content)) { - for (const block of event.content) { - if (block?.type === 'text' && block.text) { - send(block.text + '\n\n') - } - } - } - break - } - - case 'agent.thinking': { - send(`\n\n_💭 thinking…_\n\n`) - break - } - - case 'agent.tool_use': { - // The write tool carries the actual brief in input.content. - if ( - event.name === 'write' && - typeof event.input?.content === 'string' - ) { - const path = event.input.file_path || event.input.path || '' - if (!writtenFiles.has(path)) { - writtenFiles.add(path) - send('\n\n---\n\n' + event.input.content + '\n\n') - } - } else { - const label = describeToolUse(event) - if (label) send(`\n\n_${label}_\n\n`) - } - break - } - - case 'agent.mcp_tool_use': { - const label = describeToolUse(event) - if (label) send(`\n\n_${label}_\n\n`) - break - } - - case 'agent.tool_result': - case 'agent.mcp_tool_result': { - if (event.is_error) { - const msg = - (Array.isArray(event.content) && - event.content[0]?.text) || - 'tool error' - send(`\n\n_⚠️ tool error: ${String(msg).slice(0, 200)}_\n\n`) - } else { - send(`\n\n_✓ result_\n\n`) - } - break - } - - case 'session.error': { - const msg = event.error?.message || 'unknown session error' - send(`\n\n[session error: ${msg}]`) - break - } - } - } - - // Terminal-state gate. - if (event.type === 'session.status_terminated') break - if ( - event.type === 'session.status_idle' && - event.stop_reason?.type !== 'requires_action' - ) { - break - } - } - clearInterval(heartbeat) - controller.close() - } catch (err) { - clearInterval(heartbeat) - send(`\n\n[stream error: ${err.message || err}]`) - controller.close() - } - }, - cancel() { - // If the client disconnects, abort the upstream stream. - try { - upstream.controller?.abort?.() - } catch { - /* ignore */ - } - }, - }) - - function describeToolUse(event) { - const name = event.name || 'tool' - const input = event.input || {} - if (name === 'web_search' && input.query) return `🔍 Searching: ${input.query}` - if (name === 'web_fetch' && input.url) return `🌐 Fetching: ${input.url}` - if (event.type === 'agent.mcp_tool_use') { - const args = Object.entries(input) - .filter(([k]) => k !== 'limit') - .slice(0, 3) - .map(([k, v]) => - typeof v === 'object' ? `${k}=…` : `${k}=${String(v).slice(0, 40)}`, - ) - .join(', ') - return `🛠️ ${name}${args ? ` (${args})` : ''}` - } - return `🛠️ ${name}` - } - - return new Response(stream, { - status: 200, - headers: { - 'Content-Type': 'text/plain; charset=utf-8', - 'Cache-Control': 'no-cache, no-transform', - 'X-Accel-Buffering': 'no', - }, - }) -} - -export const config = { - path: '/.netlify/functions/agent-proxy', -} diff --git a/package-lock.json b/package-lock.json index 1cd33cc..a4c4ff4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,13 +8,13 @@ "name": "pmc-funder-tool", "version": "0.1.0", "dependencies": { - "@anthropic-ai/sdk": "^0.95.2", "react": "^18.3.1", "react-dom": "^18.3.1", "react-markdown": "^9.0.1", "remark-gfm": "^4.0.0" }, "devDependencies": { + "@netlify/edge-functions": "^2.11.1", "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.20", "postcss": "^8.4.49", @@ -35,27 +35,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@anthropic-ai/sdk": { - "version": "0.95.2", - "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.95.2.tgz", - "integrity": "sha512-Egddwo3sheo1PzUrMkZnH6VkQYwS0h/b/i8vSK8Ta9M45UQipAMeDFH57dYuDAfXMEUUGeKw6CMlremgMZgrSQ==", - "license": "MIT", - "dependencies": { - "json-schema-to-ts": "^3.1.1", - "standardwebhooks": "^1.0.0" - }, - "bin": { - "anthropic-ai-sdk": "bin/cli" - }, - "peerDependencies": { - "zod": "^3.25.0 || ^4.0.0" - }, - "peerDependenciesMeta": { - "zod": { - "optional": true - } - } - }, "node_modules/@babel/code-frame": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", @@ -290,15 +269,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/runtime": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", - "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/template": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", @@ -347,6 +317,20 @@ "node": ">=6.9.0" } }, + "node_modules/@envelop/instrumentation": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@envelop/instrumentation/-/instrumentation-1.0.0.tgz", + "integrity": "sha512-cxgkB66RQB95H3X27jlnxCRNTmPuSTgmBAq6/4n2Dtv4hsk4yz8FadA1ggmd0uZzvKqWD6CR+WFgTjhDqg7eyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@whatwg-node/promise-helpers": "^1.2.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -636,6 +620,23 @@ "node": ">=12" } }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/netbsd-x64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", @@ -653,6 +654,23 @@ "node": ">=12" } }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/openbsd-x64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", @@ -670,6 +688,23 @@ "node": ">=12" } }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/sunos-x64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", @@ -738,6 +773,43 @@ "node": ">=12" } }, + "node_modules/@fastify/busboy": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-3.2.0.tgz", + "integrity": "sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@humanwhocodes/momoa": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@humanwhocodes/momoa/-/momoa-2.0.4.tgz", + "integrity": "sha512-RE815I4arJFtt+FVeU1Tgp9/Xvecacji8w/V6XtXsWWH/wz/eNkNbhb+ny/+PlVZjV0rxQpRSQKNKE3lcktHEA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@import-maps/resolve": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@import-maps/resolve/-/resolve-2.0.0.tgz", + "integrity": "sha512-RwzRTpmrrS6Q1ZhQExwuxJGK1Wqhv4stt+OF2JzS+uawewpwNyU7EJL1WpBex7aDiiGLs4FsXGkfUBdYuX7xiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -788,6 +860,600 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@netlify/dev-utils": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@netlify/dev-utils/-/dev-utils-4.3.0.tgz", + "integrity": "sha512-vZAL8pMuj3yPQlmHSgyaA/UQFxc6pZgU0LucFJ1+IPWGJtIzBXHRvuR4acpoP72HtyQPUHJ42s7U9GaaSGVNHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@whatwg-node/server": "^0.10.0", + "ansis": "^4.1.0", + "chokidar": "^4.0.1", + "decache": "^4.6.2", + "dettle": "^1.0.5", + "dot-prop": "9.0.0", + "empathic": "^2.0.0", + "env-paths": "^3.0.0", + "image-size": "^2.0.2", + "js-image-generator": "^1.0.4", + "parse-gitignore": "^2.0.0", + "semver": "^7.7.2", + "tmp-promise": "^3.0.3", + "uuid": "^11.1.0", + "write-file-atomic": "^5.0.1" + }, + "engines": { + "node": "^18.14.0 || >=20" + } + }, + "node_modules/@netlify/dev-utils/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@netlify/dev-utils/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@netlify/dev-utils/node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@netlify/edge-bundler": { + "version": "14.10.2", + "resolved": "https://registry.npmjs.org/@netlify/edge-bundler/-/edge-bundler-14.10.2.tgz", + "integrity": "sha512-ZSPFap1mBegChh8KMQ1QOcoGNSisccX6aMC3eEzACQaV9l1YnNwS88FDqphnA5dLEH6AgI87AE2bIJ/1QB98hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@import-maps/resolve": "^2.0.0", + "@sveltejs/acorn-typescript": "^1.0.9", + "acorn": "^8.15.0", + "ajv": "^8.11.2", + "ajv-errors": "^3.0.0", + "better-ajv-errors": "^1.2.0", + "common-path-prefix": "^3.0.0", + "env-paths": "^3.0.0", + "esbuild": "0.28.0", + "execa": "^8.0.0", + "find-up": "^7.0.0", + "get-port": "^7.0.0", + "node-stream-zip": "^1.15.0", + "p-retry": "^6.0.0", + "p-wait-for": "^5.0.0", + "parse-imports": "^2.2.1", + "path-key": "^4.0.0", + "semver": "^7.3.8", + "tar": "^7.5.12", + "tmp-promise": "^3.0.3", + "urlpattern-polyfill": "8.0.2" + }, + "engines": { + "node": ">=18.14.0" + } + }, + "node_modules/@netlify/edge-bundler/node_modules/@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@netlify/edge-bundler/node_modules/@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@netlify/edge-bundler/node_modules/@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@netlify/edge-bundler/node_modules/@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@netlify/edge-bundler/node_modules/@esbuild/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@netlify/edge-bundler/node_modules/@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@netlify/edge-bundler/node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@netlify/edge-bundler/node_modules/@esbuild/freebsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@netlify/edge-bundler/node_modules/@esbuild/linux-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@netlify/edge-bundler/node_modules/@esbuild/linux-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@netlify/edge-bundler/node_modules/@esbuild/linux-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@netlify/edge-bundler/node_modules/@esbuild/linux-loong64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@netlify/edge-bundler/node_modules/@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@netlify/edge-bundler/node_modules/@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@netlify/edge-bundler/node_modules/@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@netlify/edge-bundler/node_modules/@esbuild/linux-s390x": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@netlify/edge-bundler/node_modules/@esbuild/linux-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@netlify/edge-bundler/node_modules/@esbuild/netbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@netlify/edge-bundler/node_modules/@esbuild/openbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@netlify/edge-bundler/node_modules/@esbuild/sunos-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@netlify/edge-bundler/node_modules/@esbuild/win32-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@netlify/edge-bundler/node_modules/@esbuild/win32-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@netlify/edge-bundler/node_modules/@esbuild/win32-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@netlify/edge-bundler/node_modules/esbuild": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" + } + }, + "node_modules/@netlify/edge-bundler/node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@netlify/edge-functions": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/@netlify/edge-functions/-/edge-functions-2.19.0.tgz", + "integrity": "sha512-OsTi1Ch59MRmr0/8QUqPADbtpcoGapBU7NLScfax1tKi43tTIleZRynIKlY4fx2X7orJc4tzU+zErf1JXOQZ8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@netlify/dev-utils": "4.3.0", + "@netlify/edge-bundler": "^14.5.2", + "@netlify/edge-functions-bootstrap": "2.16.0", + "@netlify/runtime-utils": "2.2.0", + "@netlify/types": "2.1.0", + "get-port": "^7.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@netlify/edge-functions-bootstrap": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/@netlify/edge-functions-bootstrap/-/edge-functions-bootstrap-2.16.0.tgz", + "integrity": "sha512-v8QQihSbBHj3JxtJsHoepXALpNumD9M7egHoc8z62FYl5it34dWczkaJoFFopEyhiBVKi4K/n0ZYpdzwfujd6g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@netlify/runtime-utils": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@netlify/runtime-utils/-/runtime-utils-2.2.0.tgz", + "integrity": "sha512-K3kWIxIMucibzQsATU2xw2JI+OpS9PZfPW/a+81gmeLC8tLv5YAxTVT0NFY/3imk1kcOJb9g7658jPLqDJaiAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || >=20" + } + }, + "node_modules/@netlify/types": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@netlify/types/-/types-2.1.0.tgz", + "integrity": "sha512-ktUb5d58pt1lQGXO5E9S0F1ljM0g+CoQuGTVII0IxBc0apmPq5RI0o3OWLY7U3ZERRiYTg5UfjiMihBEzuZsuw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || >=20" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1183,11 +1849,15 @@ "win32" ] }, - "node_modules/@stablelib/base64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", - "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", - "license": "MIT" + "node_modules/@sveltejs/acorn-typescript": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.9.tgz", + "integrity": "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^8.9.0" + } }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -1292,6 +1962,13 @@ "csstype": "^3.2.2" } }, + "node_modules/@types/retry": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", @@ -1325,6 +2002,153 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/@whatwg-node/disposablestack": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@whatwg-node/disposablestack/-/disposablestack-0.0.6.tgz", + "integrity": "sha512-LOtTn+JgJvX8WfBVJtF08TGrdjuFzGJc4mkP8EdDI8ADbvO7kiexYep1o8dwnt0okb0jYclCDXF13xU7Ge4zSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@whatwg-node/promise-helpers": "^1.0.0", + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@whatwg-node/fetch": { + "version": "0.10.13", + "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.10.13.tgz", + "integrity": "sha512-b4PhJ+zYj4357zwk4TTuF2nEe0vVtOrwdsrNo5hL+u1ojXNhh1FgJ6pg1jzDlwlT4oBdzfSwaBwMCtFCsIWg8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@whatwg-node/node-fetch": "^0.8.3", + "urlpattern-polyfill": "^10.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@whatwg-node/fetch/node_modules/urlpattern-polyfill": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.1.0.tgz", + "integrity": "sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@whatwg-node/node-fetch": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.8.5.tgz", + "integrity": "sha512-4xzCl/zphPqlp9tASLVeUhB5+WJHbuWGYpfoC2q1qh5dw0AqZBW7L27V5roxYWijPxj4sspRAAoOH3d2ztaHUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^3.1.1", + "@whatwg-node/disposablestack": "^0.0.6", + "@whatwg-node/promise-helpers": "^1.3.2", + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@whatwg-node/promise-helpers": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@whatwg-node/promise-helpers/-/promise-helpers-1.3.2.tgz", + "integrity": "sha512-Nst5JdK47VIl9UcGwtv2Rcgyn5lWtZ0/mhRQ4G8NN2isxpq2TO30iqHzmwoJycjWuyUfg3GFXqP/gFHXeV57IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@whatwg-node/server": { + "version": "0.10.18", + "resolved": "https://registry.npmjs.org/@whatwg-node/server/-/server-0.10.18.tgz", + "integrity": "sha512-kMwLlxUbduttIgaPdSkmEarFpP+mSY8FEm+QWMBRJwxOHWkri+cxd8KZHO9EMrB9vgUuz+5WEaCawaL5wGVoXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@envelop/instrumentation": "^1.0.0", + "@whatwg-node/disposablestack": "^0.0.6", + "@whatwg-node/fetch": "^0.10.13", + "@whatwg-node/promise-helpers": "^1.3.2", + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-errors": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-3.0.0.tgz", + "integrity": "sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^8.0.1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansis": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.3.0.tgz", + "integrity": "sha512-44mvgtPvohuU/70DdY5Oz2AIrLJ9k6/5x4KmoSvPwO+5Moijo0+N9D0fKbbYZQWP1hNm5CpOf+E01jhxG/r8xg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + } + }, "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", @@ -1413,6 +2237,26 @@ "node": ">=6.0.0" } }, + "node_modules/better-ajv-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/better-ajv-errors/-/better-ajv-errors-1.2.0.tgz", + "integrity": "sha512-UW+IsFycygIo7bclP9h5ugkNH8EjCSgqyFB/yQ4Hqqa1OEYDtb0uFIkYE0b6+CjkgJYVM5UKI/pJPxjYe9EZlA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@babel/code-frame": "^7.16.0", + "@humanwhocodes/momoa": "^2.0.2", + "chalk": "^4.1.2", + "jsonpointer": "^5.0.0", + "leven": "^3.1.0 < 4" + }, + "engines": { + "node": ">= 12.13.0" + }, + "peerDependencies": { + "ajv": "4.11.8 - 8" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -1473,6 +2317,15 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha512-0vdNRFXn5q+dtOqjfFtmtlI9N2eVZ7LMyEV2iKC5mEEFvSg/69Ml6b/WU2qF8W1nLRa0wiSrDT3Y5jOHZCwKPQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/camelcase-css": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", @@ -1514,6 +2367,23 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/character-entities": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", @@ -1592,6 +2462,36 @@ "node": ">= 6" } }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, "node_modules/comma-separated-tokens": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", @@ -1612,6 +2512,13 @@ "node": ">= 6" } }, + "node_modules/common-path-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", + "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", + "dev": true, + "license": "ISC" + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -1619,6 +2526,31 @@ "dev": true, "license": "MIT" }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -1656,6 +2588,16 @@ } } }, + "node_modules/decache": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/decache/-/decache-4.6.2.tgz", + "integrity": "sha512-2LPqkLeu8XWHU8qNCS3kcF6sCcb5zIzvWaAHYSvPfwhdd7mHuah29NssMzrTYyHN4F5oFy2ko9OBYxegtU0FEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsite": "^1.0.0" + } + }, "node_modules/decode-named-character-reference": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", @@ -1678,6 +2620,13 @@ "node": ">=6" } }, + "node_modules/dettle": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/dettle/-/dettle-1.0.5.tgz", + "integrity": "sha512-ZVyjhAJ7sCe1PNXEGveObOH9AC8QvMga3HJIghHawtG7mE4K5pW9nz/vDGAr/U7a3LWgdOzEE7ac9MURnyfaTA==", + "dev": true, + "license": "MIT" + }, "node_modules/devlop": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", @@ -1705,6 +2654,22 @@ "dev": true, "license": "MIT" }, + "node_modules/dot-prop": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-9.0.0.tgz", + "integrity": "sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^4.18.2" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.353", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.353.tgz", @@ -1712,6 +2677,29 @@ "dev": true, "license": "ISC" }, + "node_modules/empathic": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.1.tgz", + "integrity": "sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/env-paths": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/es-errors": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", @@ -1722,6 +2710,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, "node_modules/esbuild": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", @@ -1793,12 +2788,43 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -1829,11 +2855,22 @@ "node": ">= 6" } }, - "node_modules/fast-sha256": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", - "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==", - "license": "Unlicense" + "node_modules/fast-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" }, "node_modules/fastq": { "version": "1.20.1", @@ -1858,6 +2895,24 @@ "node": ">=8" } }, + "node_modules/find-up": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-7.0.0.tgz", + "integrity": "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^7.2.0", + "path-exists": "^5.0.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/fraction.js": { "version": "5.3.4", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", @@ -1907,6 +2962,32 @@ "node": ">=6.9.0" } }, + "node_modules/get-port": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.2.0.tgz", + "integrity": "sha512-afP4W205ONCuMoPBqcR6PSXnzX35KTcJygfJfcp+QY+uwm3p20p1YczWXhlICIzGMCxYBQcySEcOgsJcrkyobg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -1920,6 +3001,16 @@ "node": ">=10.13.0" } }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/hasown": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", @@ -1983,6 +3074,39 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/image-size": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-2.0.2.tgz", + "integrity": "sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==", + "dev": true, + "license": "MIT", + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=16.x" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/inline-style-parser": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", @@ -2085,6 +3209,19 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-network-error": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.2.tgz", + "integrity": "sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -2107,6 +3244,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, "node_modules/jiti": { "version": "1.21.7", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", @@ -2117,6 +3274,23 @@ "jiti": "bin/jiti.js" } }, + "node_modules/jpeg-js": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", + "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/js-image-generator": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/js-image-generator/-/js-image-generator-1.0.4.tgz", + "integrity": "sha512-ckb7kyVojGAnArouVR+5lBIuwU1fcrn7E/YYSd0FK7oIngAkMmRvHASLro9Zt5SQdWToaI66NybG+OGxPw/HlQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "jpeg-js": "^0.4.2" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -2136,18 +3310,12 @@ "node": ">=6" } }, - "node_modules/json-schema-to-ts": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", - "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.18.3", - "ts-algebra": "^2.0.0" - }, - "engines": { - "node": ">=16" - } + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" }, "node_modules/json5": { "version": "2.2.3", @@ -2162,6 +3330,26 @@ "node": ">=6" } }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -2182,6 +3370,22 @@ "dev": true, "license": "MIT" }, + "node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", @@ -2494,6 +3698,13 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3081,6 +4292,42 @@ "node": ">=8.6" } }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -3125,6 +4372,20 @@ "dev": true, "license": "MIT" }, + "node_modules/node-stream-zip": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", + "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/antelle" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -3135,6 +4396,22 @@ "node": ">=0.10.0" } }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -3155,6 +4432,101 @@ "node": ">= 6" } }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", + "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", + "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-wait-for": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/p-wait-for/-/p-wait-for-5.0.2.tgz", + "integrity": "sha512-lwx6u1CotQYPVju77R+D0vFomni/AqRfqLmqQ8hekklqZ6gAY9rONh7lBQ0uxWMkC2AuX9b2DVAl8To0NyP1JA==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-timeout": "^6.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parse-entities": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", @@ -3180,6 +4552,53 @@ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", "license": "MIT" }, + "node_modules/parse-gitignore": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-gitignore/-/parse-gitignore-2.0.0.tgz", + "integrity": "sha512-RmVuCHWsfu0QPNW+mraxh/xjQVw/lhUCUru8Zni3Ctq3AoMhpDTq0OVdKS6iesd6Kqb7viCV3isAL43dciOSog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/parse-imports": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/parse-imports/-/parse-imports-2.2.1.tgz", + "integrity": "sha512-OL/zLggRp8mFhKL0rNORUTR4yBYujK/uU+xZL+/0Rgm2QE4nLO9v8PzEweSJEbMGKmDRjJE4R3IMJlL2di4JeQ==", + "dev": true, + "license": "Apache-2.0 AND MIT", + "dependencies": { + "es-module-lexer": "^1.5.3", + "slashes": "^3.0.12" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -3572,6 +4991,16 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.12", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", @@ -3594,6 +5023,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -3693,6 +5132,49 @@ "semver": "bin/semver.js" } }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slashes": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/slashes/-/slashes-3.0.12.tgz", + "integrity": "sha512-Q9VME8WyGkc7pJf6QEkj3wE+2CnvZMI+XJhwdTPR8Z/kWQRXi7boAWLDibRPyHRTUTPx5FaU7MsyrjI3yLB4HA==", + "dev": true, + "license": "ISC" + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -3713,16 +5195,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/standardwebhooks": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.0.0.tgz", - "integrity": "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==", - "license": "MIT", - "dependencies": { - "@stablelib/base64": "^1.0.0", - "fast-sha256": "^1.3.0" - } - }, "node_modules/stringify-entities": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", @@ -3737,6 +5209,19 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/style-to-js": { "version": "1.1.21", "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", @@ -3778,6 +5263,19 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -3829,6 +5327,33 @@ "node": ">=14.0.0" } }, + "node_modules/tar": { + "version": "7.5.15", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.15.tgz", + "integrity": "sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -3900,6 +5425,26 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tmp": "^0.2.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -3933,12 +5478,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/ts-algebra": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", - "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", - "license": "MIT" - }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -3946,6 +5485,39 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", @@ -4064,6 +5636,13 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/urlpattern-polyfill": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-8.0.2.tgz", + "integrity": "sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==", + "dev": true, + "license": "MIT" + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -4071,6 +5650,20 @@ "dev": true, "license": "MIT" }, + "node_modules/uuid": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, "node_modules/vfile": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", @@ -4159,6 +5752,36 @@ } } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -4166,6 +5789,19 @@ "dev": true, "license": "ISC" }, + "node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/package.json b/package.json index aed25de..cbce231 100644 --- a/package.json +++ b/package.json @@ -10,13 +10,13 @@ "preview": "vite preview" }, "dependencies": { - "@anthropic-ai/sdk": "^0.95.2", "react": "^18.3.1", "react-dom": "^18.3.1", "react-markdown": "^9.0.1", "remark-gfm": "^4.0.0" }, "devDependencies": { + "@netlify/edge-functions": "^2.11.1", "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.20", "postcss": "^8.4.49", diff --git a/src/App.jsx b/src/App.jsx index f94a020..86e38cc 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -20,6 +20,7 @@ export default function App() { const [isThinking, setIsThinking] = useState(false) const [isStreaming, setIsStreaming] = useState(false) const [result, setResult] = useState('') + const [status, setStatus] = useState(null) const [streamError, setStreamError] = useState(null) const resultRef = useRef(null) @@ -47,27 +48,91 @@ export default function App() { return Object.keys(next).length === 0 } + // Parse the NDJSON stream from /api/agent-proxy: one JSON object per + // line. Returns a resume payload `{sessionId, lastEventId, startedAt}` + // if the server emitted a `segment_end` (Netlify Edge caps a single + // streaming response at ~60 s, so longer briefs span multiple + // segments). Returns null when the stream ends cleanly (`done`, + // `error`, or upstream EOF without a segment_end). const readStream = async (response) => { const reader = response.body.getReader() const decoder = new TextDecoder('utf-8') - let firstChunk = true + let buffer = '' + let firstByte = true + let segmentEnd = null + let fatalError = null + + const handleEvent = (msg) => { + switch (msg?.type) { + case 'text': + if (typeof msg.text === 'string' && msg.text) { + setResult((prev) => prev + msg.text) + } + break + case 'status': + setStatus(msg) + break + case 'segment_end': + if (typeof msg.sessionId === 'string' && msg.sessionId) { + segmentEnd = { + sessionId: msg.sessionId, + lastEventId: msg.lastEventId, + startedAt: msg.startedAt, + } + } + break + case 'error': + case 'session_error': + fatalError = msg.message || 'streaming error' + setStreamError(fatalError) + break + case 'done': + case 'heartbeat': + default: + break + } + } + + const flushLines = () => { + let nl + while ((nl = buffer.indexOf('\n')) !== -1) { + const line = buffer.slice(0, nl).trim() + buffer = buffer.slice(nl + 1) + if (!line) continue + try { + handleEvent(JSON.parse(line)) + } catch { + // Ignore malformed lines — the heartbeat / done sentinel + // will still tell us when the stream is over. + } + } + } while (true) { const { done, value } = await reader.read() if (done) break - const chunk = decoder.decode(value, { stream: true }) - if (firstChunk) { - // Once data starts flowing, flip 'thinking' off and 'streaming' on. + buffer += decoder.decode(value, { stream: true }) + if (firstByte) { + // Once any bytes arrive, flip 'thinking' off and 'streaming' on. setIsThinking(false) setIsStreaming(true) - firstChunk = false + firstByte = false } - setResult((prev) => prev + chunk) + flushLines() } - // Flush any remaining buffered bytes - const tail = decoder.decode() - if (tail) setResult((prev) => prev + tail) - setIsStreaming(false) + // Flush any trailing buffered bytes (line without final \n). + buffer += decoder.decode() + if (buffer.trim()) { + try { + handleEvent(JSON.parse(buffer.trim())) + } catch { + /* ignore */ + } + } + + // Only return a resume payload if we got a clean segment_end AND + // no fatal error fired in the same segment. + return fatalError ? null : segmentEnd } const handleSubmit = async (e) => { @@ -75,25 +140,44 @@ export default function App() { if (!validate()) return setResult('') + setStatus(null) setStreamError(null) setIsSubmitting(true) setIsThinking(true) setIsStreaming(false) try { - const response = await fetch('/.netlify/functions/agent-proxy', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(form), - }) + let resume = null + // Loop across segments. Each iteration is one HTTP request; + // the server closes it at ~54 s and the next iteration picks + // up from `lastEventId` so the user sees one continuous brief. + // Hard cap on iterations as a belt-and-braces guard against a + // runaway segment loop. + for (let i = 0; i < 40; i++) { + const body = resume + ? { + sessionId: resume.sessionId, + lastEventId: resume.lastEventId, + startedAt: resume.startedAt, + } + : form - if (!response.ok || !response.body) { - throw new Error( - `Request failed: ${response.status} ${response.statusText}`, - ) + const response = await fetch('/api/agent-proxy', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + }) + + if (!response.ok || !response.body) { + throw new Error( + `Request failed: ${response.status} ${response.statusText}`, + ) + } + + const next = await readStream(response) + if (!next) break + resume = next } - - await readStream(response) } catch (err) { console.error(err) setStreamError(err.message || 'Something went wrong while streaming.') @@ -101,6 +185,7 @@ export default function App() { setIsSubmitting(false) setIsThinking(false) setIsStreaming(false) + setStatus(null) } } @@ -184,6 +269,12 @@ export default function App() { )} + {isStreaming && status && ( + + {renderStatus(status)} + + )} + {streamError && (
{streamError} @@ -228,6 +319,23 @@ function Field({ label, name, type = 'text', value, onChange, error, placeholder ) } +function renderStatus(status) { + if (!status) return null + switch (status.kind) { + case 'thinking': + return 'Thinking…' + case 'tool_use': + return status.label || `Using tool: ${status.name || 'tool'}` + case 'tool_result': + if (status.ok) return 'Tool finished — continuing…' + return `Tool error: ${status.message || 'unknown error'}` + case 'session_error': + return `Session error: ${status.message || 'unknown error'}` + default: + return 'Working…' + } +} + function StatusBanner({ children }) { return (