Preserve frozen AI admission requests and expose receipt lookup
This commit is contained in:
@@ -4,7 +4,7 @@ import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
const allowed =
|
||||
/^(profile|auth\/(login|register|verify-email|exchange|me|logout)|workspaces(?:\/[a-zA-Z0-9_-]+(?:\/(?:materials(?:\/[a-zA-Z0-9_-]+(?:\/canvas)?)?|search))?)?)$/;
|
||||
const canvasRoute = /^workspaces\/[a-zA-Z0-9_-]+\/materials\/[a-zA-Z0-9_-]+\/canvas(?:\/(?:model(?:\/(?:migrate|operations(?:\/[a-zA-Z0-9_-]+)?))?|operations(?:\/[a-zA-Z0-9_-]+)?|connections|capabilities|subgraphs(?:\/(?:migrate|[a-zA-Z0-9_-]+\/(?:members|dissolve)))?|transactions\/[a-zA-Z0-9_-]+\/undo|ai\/(?:models|runs(?:\/[a-zA-Z0-9_-]+(?:\/(?:cancel|apply))?)?)))?$/;
|
||||
const canvasRoute = /^workspaces\/[a-zA-Z0-9_-]+\/materials\/[a-zA-Z0-9_-]+\/canvas(?:\/(?:model(?:\/(?:migrate|operations(?:\/[a-zA-Z0-9_-]+)?))?|operations(?:\/[a-zA-Z0-9_-]+)?|connections|capabilities|subgraphs(?:\/(?:migrate|[a-zA-Z0-9_-]+\/(?:members|dissolve)))?|transactions\/[a-zA-Z0-9_-]+\/undo|ai\/(?:models|operations\/[a-zA-Z0-9_-]+|runs(?:\/[a-zA-Z0-9_-]+(?:\/(?:cancel|apply))?)?)))?$/;
|
||||
const assetRoute = /^workspaces\/[a-zA-Z0-9_-]+\/assets(?:\/(?:operations\/[a-zA-Z0-9_-]+|[a-zA-Z0-9_-]+))?$/;
|
||||
const bodyLimit = 1024 * 1024;
|
||||
|
||||
@@ -57,10 +57,11 @@ async function handler(
|
||||
const asset = assetRoute.test(path) && !assetReceipt;
|
||||
const canvas = canvasRoute.test(path);
|
||||
const canonical = canvas && /\/canvas\/model(?:\/|$)/.test(path);
|
||||
const frozenAI = canvas && /\/canvas\/ai\/runs(?:\/|$)/.test(path);
|
||||
let requestBody: unknown;
|
||||
if (req.method !== "GET") {
|
||||
try {
|
||||
requestBody = await readBody(req, asset ? 21 * 1024 * 1024 : canonical || path.endsWith("/canvas") || path.endsWith("/canvas/operations") ? 5 * 1024 * 1024 : bodyLimit, asset || canonical);
|
||||
requestBody = await readBody(req, asset ? 21 * 1024 * 1024 : canonical || path.endsWith("/canvas") || path.endsWith("/canvas/operations") ? 5 * 1024 * 1024 : bodyLimit, asset || canonical || frozenAI);
|
||||
} catch (error) {
|
||||
const tooLarge = error instanceof Error && error.message === "body_too_large";
|
||||
return json({ error: tooLarge ? "body_too_large" : "invalid_json" }, tooLarge ? 413 : 400);
|
||||
@@ -83,8 +84,8 @@ async function handler(
|
||||
if (values.length === 1 && values[0].length < 300) query.set(key, values[0]);
|
||||
}
|
||||
const encodingStarted = performance.now();
|
||||
// Frozen canonical requests cross the proxy byte-for-byte; do not rebuild their JSON.
|
||||
const outboundBody = req.method === "GET" ? undefined : asset || canonical ? requestBody as BodyInit : JSON.stringify(requestBody || {});
|
||||
// Frozen document and AI requests cross the proxy byte-for-byte.
|
||||
const outboundBody = req.method === "GET" ? undefined : asset || canonical || frozenAI ? requestBody as BodyInit : JSON.stringify(requestBody || {});
|
||||
const encodeMs = performance.now() - encodingStarted;
|
||||
const started = performance.now();
|
||||
try {
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { EngineSnapshot, PersistencePort } from '../canvas-runtime/contract
|
||||
import type { FrozenRequest } from '../canvas-runtime/ledger.ts';
|
||||
import type { OperationStatus } from '../canvas-runtime/scheduler.ts';
|
||||
|
||||
export type ServiceRequest = { readonly path: string; readonly method: 'GET' | 'POST'; readonly requestId?: Id; readonly body?: string | FormData; readonly resourceId?: Id; readonly signal?: AbortSignal };
|
||||
export type ServiceRequest = { readonly path: string; readonly method: 'GET' | 'POST'; readonly requestId?: Id; readonly expectedVersion?: number; readonly body?: string | FormData; readonly resourceId?: Id; readonly signal?: AbortSignal };
|
||||
export type ServiceResponse = { readonly status: number; readonly body: unknown; readonly retryAfterMs?: number };
|
||||
export type ServiceTransport = (request: ServiceRequest) => Promise<ServiceResponse>;
|
||||
const uuid = /^[a-f\d]{8}-[a-f\d]{4}-[a-f\d]{4}-[a-f\d]{4}-[a-f\d]{12}$/i;
|
||||
@@ -25,8 +25,9 @@ export function createFoundryTransport(options: { accountId: Id; isCurrentIdenti
|
||||
const requestFetch = options.fetch ?? globalThis.fetch;
|
||||
return async input => {
|
||||
if (!options.isCurrentIdentity()) throw fault('identity_changed');
|
||||
if (input.expectedVersion !== undefined && (!Number.isSafeInteger(input.expectedVersion) || input.expectedVersion < 1)) throw fault('invalid_server_version');
|
||||
const response = await requestFetch(`${options.basePath ?? '/api/foundry/'}${input.path}`, { method: input.method, body: input.body, signal: input.signal, credentials: 'same-origin', cache: 'no-store', redirect: 'error', headers: {
|
||||
Accept: 'application/json', ...(input.body instanceof FormData ? {} : { 'Content-Type': 'application/json' }), ...(input.resourceId ? { 'X-Resource-Id': input.resourceId } : {}), 'X-Expected-Account': options.accountId, ...(input.requestId ? { 'Idempotency-Key': input.requestId } : {}),
|
||||
Accept: 'application/json', ...(input.body instanceof FormData ? {} : { 'Content-Type': 'application/json' }), ...(input.resourceId ? { 'X-Resource-Id': input.resourceId } : {}), 'X-Expected-Account': options.accountId, ...(input.requestId ? { 'Idempotency-Key': input.requestId } : {}), ...(input.expectedVersion !== undefined ? { 'If-Match': `"${input.expectedVersion}"` } : {}),
|
||||
} });
|
||||
if (!options.isCurrentIdentity()) { await response.body?.cancel(); throw fault('identity_changed'); }
|
||||
let body: unknown;
|
||||
|
||||
Reference in New Issue
Block a user