-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstructured.mjs
More file actions
75 lines (70 loc) · 2.58 KB
/
Copy pathstructured.mjs
File metadata and controls
75 lines (70 loc) · 2.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/**
* src/tools/structured.mjs — Gemini Flash with strict structured output.
*
* Used by the `arena` template (strategy schema), the `rhythm` template
* (level designer), and any game that wants JSON back instead of prose.
*/
import { getClient } from "./client.mjs";
const FLASH_MODEL = process.env.GEMINI_MODEL || "gemini-flash-latest";
/**
* @param {object} options
* @param {string} options.prompt required
* @param {object} [options.schema] JSON schema or example object;
* included verbatim in the prompt as guidance
* @param {string} [options.model]
* @param {{data: string, mimeType?: string}} [options.imageContext]
* @returns {Promise<{ parsed: object|null, raw: string, model: string }>}
*/
export async function requestStructured(options) {
if (!options?.prompt) throw new Error("requestStructured: options.prompt is required");
const client = getClient();
const model = options.model || FLASH_MODEL;
const contents = [];
if (options.imageContext?.data) {
contents.push({
inlineData: {
mimeType: options.imageContext.mimeType || "image/jpeg",
data: options.imageContext.data,
},
});
}
const schemaHint = options.schema
? `\n\nReturn JSON only, matching this shape exactly:\n${JSON.stringify(options.schema, null, 2)}`
: "\n\nReturn JSON only. No prose, no code fences, no explanation.";
contents.push({ text: options.prompt + schemaHint });
const response = await client.models.generateContent({ model, contents });
const raw = response.text || "";
const jsonText = firstJsonObject(raw);
let parsed = null;
if (jsonText) {
try { parsed = JSON.parse(jsonText); } catch { parsed = null; }
}
return { parsed, raw, model };
}
/** Scan a string for the first complete JSON object. Handles fenced ```json``` blocks. */
export function firstJsonObject(text = "") {
const source = String(text);
const fenced = source.match(/```(?:json)?\s*(\{[\s\S]*?\})\s*```/);
if (fenced) return fenced[1];
const start = source.indexOf("{");
if (start < 0) return null;
let depth = 0;
let inString = false;
let escaped = false;
for (let i = start; i < source.length; i += 1) {
const ch = source[i];
if (inString) {
if (escaped) escaped = false;
else if (ch === "\\") escaped = true;
else if (ch === "\"") inString = false;
continue;
}
if (ch === "\"") inString = true;
else if (ch === "{") depth += 1;
else if (ch === "}") {
depth -= 1;
if (depth === 0) return source.slice(start, i + 1);
}
}
return null;
}