@gafj/gafj 0.1.15 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bridge/byo_key.js +1 -1
- package/bridge/providers/anthropic.js +22 -3
- package/package.json +1 -1
- package/store/onboarding.js +3 -1
package/bridge/byo_key.js
CHANGED
|
@@ -70,7 +70,7 @@ async function runPacket(homeDir, id, { packet, fetchImpl }) {
|
|
|
70
70
|
const started = Date.now();
|
|
71
71
|
try {
|
|
72
72
|
const op = packet.operation || packet.op;
|
|
73
|
-
const r = await adapter.complete({ provider: p, system: "You are a careful writing engine. Reply with exactly one fenced json block matching OUTPUT SCHEMA and nothing else. Write the JSON compact, on one line, with no indentation and no spaces after commas or colons.", user: renderPasteText(packet), max_tokens: MAX_OUT[op] || 16000, effort: EFFORT[op], fetch: fetchImpl });
|
|
73
|
+
const r = await adapter.complete({ provider: p, system: "You are a careful writing engine. Reply with exactly one fenced json block matching OUTPUT SCHEMA and nothing else. Write the JSON compact, on one line, with no indentation and no spaces after commas or colons.", user: renderPasteText(packet), max_tokens: MAX_OUT[op] || 16000, effort: EFFORT[op], schema: packet.output_schema, fetch: fetchImpl });
|
|
74
74
|
return { content: r.text, provider: p.kind, model: r.model || p.model, tokens_in: r.tokens_in, tokens_out: r.tokens_out, latency_ms: Date.now() - started, stop_reason: r.stop_reason || null };
|
|
75
75
|
} catch (e) {
|
|
76
76
|
throw Object.assign(new Error(scrub(e.message, p.api_key)), { status: 502 });
|
|
@@ -9,12 +9,31 @@ const DEFAULT_MODEL = "claude-sonnet-5";
|
|
|
9
9
|
* passes effort ("low" for extraction) and it goes out as output_config.effort; the thinking
|
|
10
10
|
* parameter itself is never sent, which is the setting every current model accepts.
|
|
11
11
|
*/
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* The API's structured-output mode accepts a subset of JSON Schema: no length, range, or count
|
|
14
|
+
* limits, no vendor keys. This strips those so the schema the store validates against can be
|
|
15
|
+
* handed to the model as the shape it must produce. Enums, required, and additionalProperties
|
|
16
|
+
* stay, which is what makes the reply valid by construction.
|
|
17
|
+
*/
|
|
18
|
+
const DROP = new Set(["maxLength", "minLength", "minimum", "maximum", "minItems", "maxItems", "multipleOf", "pattern", "format"]);
|
|
19
|
+
function strictSchema(node) {
|
|
20
|
+
if (Array.isArray(node)) return node.map(strictSchema);
|
|
21
|
+
if (!node || typeof node !== "object") return node;
|
|
22
|
+
const out = {};
|
|
23
|
+
for (const [k, v] of Object.entries(node)) {
|
|
24
|
+
if (DROP.has(k) || k.startsWith("x-")) continue;
|
|
25
|
+
out[k] = k === "properties" ? Object.fromEntries(Object.entries(v).map(([pk, pv]) => [pk, strictSchema(pv)])) : strictSchema(v);
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function complete({ provider, system, user, max_tokens = 4000, effort, schema, fetch }) {
|
|
13
31
|
const { post } = require("./index");
|
|
14
32
|
const base = (provider.base_url || DEFAULT_URL).replace(/\/$/, "");
|
|
15
33
|
const model = provider.model || DEFAULT_MODEL;
|
|
16
34
|
const body = { model, max_tokens, system, messages: [{ role: "user", content: user }] };
|
|
17
|
-
|
|
35
|
+
const usable = schema && typeof schema === "object" && schema.type === "object" && schema.properties && Object.keys(schema.properties).length;
|
|
36
|
+
if (effort || usable) body.output_config = { ...(effort ? { effort } : {}), ...(usable ? { format: { type: "json_schema", schema: strictSchema(schema) } } : {}) };
|
|
18
37
|
const data = await post(fetch, `${base}/v1/messages`, { "x-api-key": provider.api_key || "", "anthropic-version": "2023-06-01" }, body);
|
|
19
38
|
if (data.stop_reason === "refusal") throw new Error(`the model declined this request${data.stop_details && data.stop_details.category ? ` (${data.stop_details.category})` : ""}`);
|
|
20
39
|
const text = (data.content || []).filter((c) => c.type === "text").map((c) => c.text).join("\n");
|
|
@@ -29,4 +48,4 @@ async function listModels({ provider, fetch }) {
|
|
|
29
48
|
return (data.data || []).map((m) => ({ id: m.id, name: m.display_name || m.id, created: m.created_at || null }));
|
|
30
49
|
}
|
|
31
50
|
|
|
32
|
-
module.exports = { complete, listModels, DEFAULT_URL, DEFAULT_MODEL };
|
|
51
|
+
module.exports = { complete, listModels, strictSchema, DEFAULT_URL, DEFAULT_MODEL };
|
package/package.json
CHANGED
package/store/onboarding.js
CHANGED
|
@@ -111,7 +111,9 @@ function checkAtom(atom, text, label, errors) {
|
|
|
111
111
|
let hits = [];
|
|
112
112
|
if (v.length) for (let i = text.indexOf(v); i !== -1; i = text.indexOf(v, i + 1)) hits.push([i, i + v.length]);
|
|
113
113
|
if (!hits.length && v.trim()) {
|
|
114
|
-
|
|
114
|
+
// the model tends to straighten quotes and dashes; match either form in the source
|
|
115
|
+
const cls = (ch) => (/['\u2018\u2019]/.test(ch) ? "['\u2018\u2019]" : /["\u201c\u201d]/.test(ch) ? "[\"\u201c\u201d]" : /[-\u2013\u2014]/.test(ch) ? "[-\u2013\u2014]" : ch.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
116
|
+
const re = new RegExp(v.trim().split(/\s+/).map((w) => Array.from(w).map(cls).join("")).join("\\s+"), "g");
|
|
115
117
|
for (let m = re.exec(text); m; m = re.exec(text)) { hits.push([m.index, m.index + m[0].length]); if (!m[0].length) break; }
|
|
116
118
|
}
|
|
117
119
|
if (!hits.length) { errors.push(`${label}: "${v.slice(0, 40)}" is not the source text at [${start}, ${end})`); return; }
|