From 930556d8629e4aa6ce64cb2fa5d238c267a85901 Mon Sep 17 00:00:00 2001 From: Alejandro Rosales Date: Wed, 14 Jan 2026 09:28:38 -0600 Subject: [PATCH] Add configuration files and update dependencies for Supabase functions --- .vscode/launch.json | 36 ++++ .vscode/settings.json | 2 + deno.lock | 55 ++++++ files/carta.pdf | Bin 0 -> 38600 bytes package.json | 1 + supabase/functions/ai-structured/index.ts | 229 ++++++++++++++++------ testers/ai-structured-files.ts | 25 +++ 7 files changed, 293 insertions(+), 55 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 files/carta.pdf create mode 100644 testers/ai-structured-files.ts diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..919df1a --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,36 @@ +{ + "configurations": [ + { + "name": "Attach V8 Inspector", + "type": "node", + "address": "127.0.0.1", + "request": "attach", + + // This is important: You must enter the path where your edge functions + // folders are located. + "localRoot": "${workspaceFolder}/supabase/functions/", + + // CLI mounts all functions under /home/deno/functions + "remoteRoot": "/home/deno/functions/", + + // As you can see from the two options above, VsCode tries to locate the script + // paths being used by the inspector session locally by matching local and remote + // paths. + // + // If a valid path cannot be found on either side, or the script contents of + // those paths do not match each other, the breakpoint setting may not proceed + // correctly. + // + // We set the two options below for this. + "sourceMaps": true, + "enableContentValidation": false, + + // Properties below are optional. + "restart": true, + "timeout": 1000000, + + // Inspector is accepting sessions on 8083 by default. + "port": 8083 + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7a73a41 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/deno.lock b/deno.lock index 0967f08..c50e4d8 100644 --- a/deno.lock +++ b/deno.lock @@ -1,12 +1,53 @@ { "version": "5", "specifiers": { + "jsr:@openai/openai@*": "6.16.0", + "jsr:@supabase/functions-js@*": "2.90.1", "npm:@supabase/supabase-js@^2.90.1": "2.90.1", "npm:@types/bun@^1.3.5": "1.3.5", + "npm:deno@^2.6.4": "2.6.4", "npm:supabase-js@^1.0.4": "1.0.4", "npm:supabase@^2.72.6": "2.72.6" }, + "jsr": { + "@openai/openai@6.16.0": { + "integrity": "ccee548f61c382d715091fff0c2c3390a4487823644430b8235df543d6d6a78b" + }, + "@supabase/functions-js@2.90.1": { + "integrity": "9a077ecf42aa84594ef3aac621d643e15ffaa9506a4c051e35c93f127ffdb035" + } + }, "npm": { + "@deno/darwin-arm64@2.6.4": { + "integrity": "sha512-5pocuV2IVWYUzomXze+bDSO86uX1WPxUn82BS7BrLI3ix8Eq7l8DhvYgz064w7rwCM4CiLLSc6MAQpAI+0gUag==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@deno/darwin-x64@2.6.4": { + "integrity": "sha512-tX7Fz47HcoXTYR4wN2WzXjqJtEPTftzjEknCYkZa8D4BzvpN7Zo1ra3Jd6bWPN9+T6mgTL6YMPMMSigXddIUcA==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@deno/linux-arm64-glibc@2.6.4": { + "integrity": "sha512-wY4hjjVLq5idClWj6e1de9IUZ0QZ1rxo16o0dwCJpMi+w1lolxtYxGF4y7pkcxAdZOpMGsXZuYMPq1Z2n51esA==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@deno/linux-x64-glibc@2.6.4": { + "integrity": "sha512-H9b0WJw+eWyC6GcMGCXxC/3lz2kVveyWyUrzGyQ8hJmtDJpWiDnCU2iPqEpEcwXHgAUFFyjnRaFwedwwv5ApdA==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@deno/win32-arm64@2.6.4": { + "integrity": "sha512-fMH1dx54ORbCTpMorGjDshwonIu6KfgfPC+Pq+14jeq/8604EIFXiA484p3Ov+ViZycndNHEPe8ZKPNd8xLjJA==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@deno/win32-x64@2.6.4": { + "integrity": "sha512-hAxqNrBLHCINtqDgKcRiu8QtMbtdnHOVW3OFQjzA3gZEcpnGUK3JiF5ql++83SjuQH8ViZKGcdqkioLR7Bf14A==", + "os": ["win32"], + "cpu": ["x64"] + }, "@isaacs/fs-minipass@4.0.1": { "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", "dependencies": [ @@ -112,6 +153,19 @@ "ms" ] }, + "deno@2.6.4": { + "integrity": "sha512-/nHUtrwoqUsjVTBWGqz3SjAbtBSfqCLb38YdkwstrO5qpQd3RC20dxsGYPPANy9RqMFJMfDyARL7RtOcUfatpw==", + "optionalDependencies": [ + "@deno/darwin-arm64", + "@deno/darwin-x64", + "@deno/linux-arm64-glibc", + "@deno/linux-x64-glibc", + "@deno/win32-arm64", + "@deno/win32-x64" + ], + "scripts": true, + "bin": true + }, "fetch-blob@3.2.0": { "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", "dependencies": [ @@ -227,6 +281,7 @@ "dependencies": [ "npm:@supabase/supabase-js@^2.90.1", "npm:@types/bun@^1.3.5", + "npm:deno@^2.6.4", "npm:supabase-js@^1.0.4", "npm:supabase@^2.72.6" ] diff --git a/files/carta.pdf b/files/carta.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0f0af37b8df071a9af56374b38b3c48b88a87499 GIT binary patch literal 38600 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

7MhGbhC% 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 EzmRoPhX4Qo literal 0 HcmV?d00001 diff --git a/package.json b/package.json index 033340f..ded9479 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "dependencies": { "@supabase/supabase-js": "^2.90.1", + "deno": "^2.6.4", "supabase": "^2.72.6", "supabase-js": "^1.0.4" }, diff --git a/supabase/functions/ai-structured/index.ts b/supabase/functions/ai-structured/index.ts index c72dea8..3754010 100644 --- a/supabase/functions/ai-structured/index.ts +++ b/supabase/functions/ai-structured/index.ts @@ -1,7 +1,7 @@ // supabase/functions/ai-structured/index.ts -import OpenAI from'jsr:@openai/openai'; -import 'jsr:@supabase/functions-js/edge-runtime.d.ts' -import { createClient } from 'npm:@supabase/supabase-js@2' +import OpenAI from "npm:openai"; +import "jsr:@supabase/functions-js/edge-runtime.d.ts"; +import { createClient } from "npm:@supabase/supabase-js@2"; const corsHeaders = { "Access-Control-Allow-Origin": "*", @@ -9,6 +9,8 @@ const corsHeaders = { "authorization, x-client-info, apikey, content-type", }; +const MB = 1024 * 1024; + function json(body: unknown, status = 200) { return new Response(JSON.stringify(body, null, 2), { status, @@ -45,9 +47,12 @@ function normalizeInputWithOpenAIFileIds(input: any, fileIds: string[]) { if (!fileIds?.length) return input; // Responses API acepta input como string o array. - const items = Array.isArray(input) - ? [...input] - : [{ role: "user", content: input ?? "" }]; + let items = Array.isArray(input) ? [...input] : [{ + role: "user", + content: [ + input ?? "", + ], + }]; // Busca último mensaje "user" let idx = -1; @@ -58,39 +63,72 @@ function normalizeInputWithOpenAIFileIds(input: any, fileIds: string[]) { } } if (idx === -1) { - items.push({ role: "user", content: "" }); + items.push({ role: "user", content: [] }); idx = items.length - 1; } const msg = items[idx]; const fileParts = fileIds.map((id) => ({ type: "input_file", file_id: id })); + // Normalize content to array format if (typeof msg.content === "string") { msg.content = [{ type: "input_text", text: msg.content }, ...fileParts]; } else if (Array.isArray(msg.content)) { msg.content = [...msg.content, ...fileParts]; } else { // fallback raro - msg.content = [{ type: "input_text", text: String(msg.content) }, ...fileParts]; + msg.content = [ + { type: "input_text", text: String(msg.content) }, + ...fileParts, + ]; } items[idx] = msg; return items; } +function normalizeInputUser(input: any) { + if (typeof input === "string") { + return [{ + role: "user", + content: [{ + type: "input_text", + text: input, + }], + }]; + } else if (Array.isArray(input)) { + return input; + } else { + return [{ + role: "user", + content: [{ + type: "input_text", + text: String(input), + }], + }]; + } +} + Deno.serve(async (req) => { - if (req.method === "OPTIONS") return new Response(null, { status: 204, headers: corsHeaders }); + if (req.method === "OPTIONS") { + return new Response(null, { status: 204, headers: corsHeaders }); + } try { // 1) Auth requerido ✅ - const authHeader = - req.headers.get("Authorization") ?? req.headers.get("authorization"); - if (!authHeader) return json({ ok: false, error: "Missing Authorization header" }, 401); + const authHeader = req.headers.get("Authorization") ?? + req.headers.get("authorization"); + if (!authHeader) { + return json({ ok: false, error: "Missing Authorization header" }, 401); + } const SUPABASE_URL = Deno.env.get("SUPABASE_URL"); const SUPABASE_ANON_KEY = Deno.env.get("SUPABASE_ANON_KEY"); if (!SUPABASE_URL || !SUPABASE_ANON_KEY) { - return json({ ok: false, error: "Missing SUPABASE_URL / SUPABASE_ANON_KEY" }, 500); + return json({ + ok: false, + error: "Missing SUPABASE_URL / SUPABASE_ANON_KEY", + }, 500); } // Cliente con el JWT del usuario (para validar quién llama) @@ -98,7 +136,8 @@ Deno.serve(async (req) => { global: { headers: { Authorization: authHeader } }, }); - const { data: userData, error: userErr } = await supabaseUser.auth.getUser(); + const { data: userData, error: userErr } = await supabaseUser.auth + .getUser(); if (userErr || !userData?.user) { return json({ ok: false, error: "Invalid token" }, 401); } @@ -116,17 +155,32 @@ Deno.serve(async (req) => { let tempFiles: File[] = []; if (contentType.includes("multipart/form-data")) { - const form = await req.formData(); - const raw = form.get("payload")?.toString() ?? "{}"; + let form; + try { + form = await req.formData(); + } catch (error) { + return json({ + ok: false, + error: "Invalid multipart/form-data body", + details: error, + }, 400); + } + const raw = form.get("payload") ?? "{}"; payload = JSON.parse(raw); // Soporta nombres comunes - for (const key of ["files", "files[]", "archivos", "archivosTemporales"]) { - for (const v of form.getAll(key)) if (v instanceof File) tempFiles.push(v); + for ( + const key of ["files", "files[]", "archivos", "archivosTemporales"] + ) { + for (const v of form.getAll(key)) { + if (v instanceof File) tempFiles.push(v); + } } // Fallback: toma cualquier File en el form if (tempFiles.length === 0) { - for (const [, v] of form.entries()) if (v instanceof File) tempFiles.push(v); + for (const [, v] of form.entries()) { + if (v instanceof File) tempFiles.push(v); + } } } else { payload = await req.json(); @@ -140,11 +194,24 @@ Deno.serve(async (req) => { }; // Conveniencia top-level - if (payload.input !== undefined && responseCfg.input === undefined) responseCfg.input = payload.input; + if (payload.input !== undefined && responseCfg.input === undefined) { + responseCfg.input = payload.input; + } + // Normaliza input a array de mensajes + if (responseCfg.input !== undefined) { + responseCfg.input = normalizeInputUser(responseCfg.input); + } if (payload.model && !responseCfg.model) responseCfg.model = payload.model; - if (payload.conversationId && responseCfg.conversation === undefined) responseCfg.conversation = payload.conversationId; - if (payload.previousResponseId && responseCfg.previous_response_id === undefined) responseCfg.previous_response_id = payload.previousResponseId; - if (payload.reasoning && responseCfg.reasoning === undefined) responseCfg.reasoning = payload.reasoning; + if (payload.conversationId && responseCfg.conversation === undefined) { + responseCfg.conversation = payload.conversationId; + } + if ( + payload.previousResponseId && + responseCfg.previous_response_id === undefined + ) responseCfg.previous_response_id = payload.previousResponseId; + if (payload.reasoning && responseCfg.reasoning === undefined) { + responseCfg.reasoning = payload.reasoning; + } // Defaults responseCfg.model = responseCfg.model ?? "gpt-5"; @@ -152,21 +219,32 @@ Deno.serve(async (req) => { // Regla API: no mezclar conversation + previous_response_id if (responseCfg.conversation && responseCfg.previous_response_id) { return json( - { ok: false, error: "Do not send both `conversation` and `previous_response_id`." }, - 400 + { + ok: false, + error: "Do not send both `conversation` and `previous_response_id`.", + }, + 400, ); } // 4) Structured Outputs (JSON Schema) ✅ // Espera payload.structured (o payload.outputSchema) como objeto o string JSON let structured = payload.structured ?? payload.outputSchema; - if (!structured) return json({ ok: false, error: "Missing `structured` (JSON schema format)." }, 400); + if (!structured) { + return json({ + ok: false, + error: "Missing `structured` (JSON schema format).", + }, 400); + } if (typeof structured === "string") { try { structured = JSON.parse(structured); } catch { - return json({ ok: false, error: "`structured` is a string but not valid JSON." }, 400); + return json({ + ok: false, + error: "`structured` is a string but not valid JSON.", + }, 400); } } @@ -177,43 +255,60 @@ Deno.serve(async (req) => { if (format.type === "json_schema") { format.name = format.name ?? "structured_output"; format.strict = format.strict ?? true; - if (!format.schema) return json({ ok: false, error: "structured.schema is required." }, 400); + if (!format.schema) { + return json( + { ok: false, error: "structured.schema is required." }, + 400, + ); + } } responseCfg.text = { ...(responseCfg.text ?? {}), format }; // 5) Referencias (3 tipos) 📎 const references = payload.references ?? {}; - const openaiFileIds: string[] = - references.openaiFileIds ?? payload.openaiFileIds ?? []; - const vectorStoreIds: string[] = - references.vectorStoreIds ?? payload.vectorStoreIds ?? []; + const openaiFileIds: string[] = references.openaiFileIds ?? + payload.openaiFileIds ?? []; + const vectorStoreIds: string[] = references.vectorStoreIds ?? + payload.vectorStoreIds ?? []; // 5a) Vector stores => file_search tool - const tools: any[] = Array.isArray(responseCfg.tools) ? [...responseCfg.tools] : []; + const tools: any[] = Array.isArray(responseCfg.tools) + ? [...responseCfg.tools] + : []; if (vectorStoreIds.length) { tools.push({ type: "file_search", vector_store_ids: vectorStoreIds }); // Si quieres ver resultados de búsqueda en la respuesta - const include = Array.isArray(responseCfg.include) ? responseCfg.include : []; - if (!include.includes("file_search_call.results")) include.push("file_search_call.results"); + const include = Array.isArray(responseCfg.include) + ? responseCfg.include + : []; + if (!include.includes("file_search_call.results")) { + include.push("file_search_call.results"); + } responseCfg.include = include; } // 5b) MCP opcional - const usarMCP: boolean = Boolean(payload.usarMCP ?? references.usarMCP ?? payload.mcp?.enabled); + const usarMCP: boolean = Boolean( + payload.usarMCP ?? references.usarMCP ?? payload.mcp?.enabled, + ); if (usarMCP) { const mcp = payload.mcp ?? {}; const server_url = mcp.server_url ?? Deno.env.get("MCP_SERVER_URL"); if (!server_url) { return json( - { ok: false, error: "usarMCP=true pero falta MCP_SERVER_URL o mcp.server_url" }, - 400 + { + ok: false, + error: "usarMCP=true pero falta MCP_SERVER_URL o mcp.server_url", + }, + 400, ); } tools.push({ type: "mcp", - server_label: mcp.server_label ?? Deno.env.get("MCP_SERVER_LABEL") ?? "supabase", + server_label: mcp.server_label ?? Deno.env.get("MCP_SERVER_LABEL") ?? + "supabase", server_description: mcp.server_description ?? "Supabase MCP server", server_url, require_approval: mcp.require_approval ?? "never", @@ -226,7 +321,10 @@ Deno.serve(async (req) => { // 5c) OpenAI file IDs => se insertan al input como input_file if (openaiFileIds.length) { - responseCfg.input = normalizeInputWithOpenAIFileIds(responseCfg.input, openaiFileIds); + responseCfg.input = normalizeInputWithOpenAIFileIds( + responseCfg.input, + openaiFileIds, + ); } // 6) Archivos temporales => subir a Supabase Storage (NO a OpenAI) ☁️ @@ -239,19 +337,38 @@ Deno.serve(async (req) => { }> = []; if (tempFiles.length) { - const bucket = payload.storage?.bucket ?? Deno.env.get("AI_FILES_BUCKET") ?? "ai-temp"; - const prefix = payload.storage?.prefix ?? Deno.env.get("AI_FILES_PREFIX") ?? "tmp"; + const bucket = payload.storage?.bucket ?? + Deno.env.get("AI_FILES_BUCKET") ?? "ai-temp"; + const prefix = payload.storage?.prefix ?? + Deno.env.get("AI_FILES_PREFIX") ?? "tmp"; for (const f of tempFiles) { const safeName = (f.name ?? "file").replaceAll(/[^a-zA-Z0-9._-]/g, "_"); const path = `${prefix}/${user.id}/${crypto.randomUUID()}-${safeName}`; + const bytes = new Uint8Array(await f.arrayBuffer()); const { data, error } = await supabaseAdmin.storage .from(bucket) - .upload(path, f, { contentType: f.type, upsert: false }); + .upload(path, bytes, { contentType: f.type, upsert: false }); if (error) { - return json({ ok: false, error: `Storage upload failed: ${error.message}`, file: f.name }, 500); + return json({ + ok: false, + error: `Storage upload failed: ${error.message}`, + file: f.name, + }, 500); + } + // Add as an input_file base64 reference to the last user message content + const fileBase64 = btoa(String.fromCharCode(...bytes)); + if (Array.isArray(responseCfg.input) && responseCfg.input.length > 0) { + const lastMsg = responseCfg.input[responseCfg.input.length - 1]; + if (lastMsg?.role === "user" && Array.isArray(lastMsg.content)) { + lastMsg.content.push({ + type: "input_file", + filename: f.name, + file_data: `data:${f.type};base64,${fileBase64}`, + }); + } } uploadedToStorage.push({ @@ -266,19 +383,22 @@ Deno.serve(async (req) => { // 7) Llamada a OpenAI Responses API 🚀 const OPENAI_API_KEY = Deno.env.get("OPENAI_API_KEY"); - if (!OPENAI_API_KEY) return json({ ok: false, error: "Missing OPENAI_API_KEY" }, 500); + if (!OPENAI_API_KEY) { + return json({ ok: false, error: "Missing OPENAI_API_KEY" }, 500); + } const openai = new OpenAI({ apiKey: OPENAI_API_KEY }); let resp = null; try { - resp = await openai.responses.create(responseCfg); - } catch (error) { - return json({ ok: false, error: "OpenAI API error", details: (error as Error)?.message ?? String(error) }, 500); + return json({ + ok: false, + error: "OpenAI API error", + details: (error as Error)?.message ?? String(error), + }, 500); } - - + const outputText = extractOutputText(resp); let output: unknown = null; let outputParseError: string | null = null; @@ -288,12 +408,10 @@ Deno.serve(async (req) => { outputParseError = (e as Error)?.message ?? String(e); } - const conv = - resp?.conversation?.id ?? + const conv = resp?.conversation?.id ?? (typeof resp?.conversation === "string" ? resp.conversation - : responseCfg.conversation ?? null) - + : responseCfg.conversation ?? null); return json({ ok: outputParseError ? false : true, @@ -311,7 +429,8 @@ Deno.serve(async (req) => { openaiFileIds, vectorStoreIds, // Si quieres mandar tus UUID internos de archivos, aquí van (solo eco) - archivosReferenciaIds: references.archivosReferenciaIds ?? payload.archivosReferenciaIds ?? payload.archivosReferencia ?? [], + archivosReferenciaIds: references.archivosReferenciaIds ?? + payload.archivosReferenciaIds ?? payload.archivosReferencia ?? [], }, tools: sanitizeTools(responseCfg.tools), diff --git a/testers/ai-structured-files.ts b/testers/ai-structured-files.ts new file mode 100644 index 0000000..5a10360 --- /dev/null +++ b/testers/ai-structured-files.ts @@ -0,0 +1,25 @@ +import { supabase } from "./new-user"; + +const file = Bun.file("files/carta.pdf"); + +await supabase.auth.signInWithPassword({ + email: "guillermo.arrieta@lasalle.mx", + password: "admin", +}); + +const fd = new FormData(); +fd.append("payload", JSON.stringify({ + response: { input: "Resume estos documentos en JSON.", model: "gpt-5" }, + structured: { type: "json_schema", name: "resumen", strict: true, schema: { + type: "object", properties: { resumen: { type: "string" } }, required: ["resumen"], additionalProperties: false + } }, + storage: { prefix: "tmp" }, +})); +fd.append("files", file); + +const response = await supabase.functions.invoke("ai-structured", { + method: "POST", + body: fd, +}); + +console.log({ d: response.data, e: response.error }); \ No newline at end of file