openvibers/openvibe.ai Commits
AI: the space builder answers in its schema without length caps and not strict (strict decoding of a whole app spent the budget reasoning and answered nothing) (#109)
Changes
@@@ -137,10 +137,14 @@ function cleanBuild(out, { synthetic = false } = {}) {};}++// The schema the model answers in: the output's shape without its length caps, and not strict. In strict mode the++// provider's constrained decoding of a whole app (one string of ~15,000 characters) spent the budget reasoning and++// answered nothing (finish_reason "length", empty content, 2026-10-16); the caps are enforced by cleanBuild instead.++const uncapped = (schema) => JSON.parse(JSON.stringify(schema, (k, v) => (k === 'maxLength' || k === 'maxItems' ? undefined : v)));const PREPARE = {'space.app.build'(input) {const current = (input.current || []).map((f) => `--- ${f.path} ---\n${f.content}`).join('\n\n');-- return { vars: { prompt: input.prompt, name: input.name || '', current } };++ return { vars: { prompt: input.prompt, name: input.name || '', current }, jsonSchema: uncapped(workflows.find((w) => w.key === 'space.app.build').output_schema) };},};// A JSON answer that came back as text (a fenced block, or prose around the object).@@@ -18,7 +18,9 @@ t.test('the builder\'s prompt: the SDK reference, and the current files when chaassert.match(sys, /<script src="\/_openvibe\/sdk\.js"><\/script>/);assert.match(sys, /OV\.items\.set\(collection, value\)/);assert.match(sys, /never ask for passwords, payment details/);-- const fresh = render(tpl('space.app.build').user_prompt, PREPARE['space.app.build']({ prompt: 'a poll about pizza' }).vars);++ const prep = PREPARE['space.app.build']({ prompt: 'a poll about pizza' });++ assert.ok(prep.jsonSchema && prep.jsonSchema.properties.files && !/maxLength|maxItems/.test(JSON.stringify(prep.jsonSchema)), 'the model answers in the uncapped, non-strict schema');++ const fresh = render(tpl('space.app.build').user_prompt, prep.vars);assert.strictEqual(fresh, 'What to build: a poll about pizza');const change = render(tpl('space.app.build').user_prompt, PREPARE['space.app.build']({ prompt: 'make it blue', name: 'Pizza poll', current: [{ path: 'index.html', content: INDEX }] }).vars);assert.match(change, /^The space's current files:\n--- index\.html ---\n<!doctype html>/);
@@ -137,10 +137,14 @@ function cleanBuild(out, { synthetic = false } = {}) {
@@ -137,10 +137,14 @@ function cleanBuild(out, { synthetic = false } = {}) {
};
};
}
}
+// The schema the model answers in: the output's shape without its length caps, and not strict. In strict mode the
+// provider's constrained decoding of a whole app (one string of ~15,000 characters) spent the budget reasoning and
+// answered nothing (finish_reason "length", empty content, 2026-10-16); the caps are enforced by cleanBuild instead.
+const uncapped = (schema) => JSON.parse(JSON.stringify(schema, (k, v) => (k === 'maxLength' || k === 'maxItems' ? undefined : v)));
const PREPARE = {
const PREPARE = {
'space.app.build'(input) {
'space.app.build'(input) {
const current = (input.current || []).map((f) => `--- ${f.path} ---\n${f.content}`).join('\n\n');
const current = (input.current || []).map((f) => `--- ${f.path} ---\n${f.content}`).join('\n\n');
- return { vars: { prompt: input.prompt, name: input.name || '', current } };
+ return { vars: { prompt: input.prompt, name: input.name || '', current }, jsonSchema: uncapped(workflows.find((w) => w.key === 'space.app.build').output_schema) };
},
},
};
};
// A JSON answer that came back as text (a fenced block, or prose around the object).
// A JSON answer that came back as text (a fenced block, or prose around the object).
@@ -18,7 +18,9 @@ t.test('the builder\'s prompt: the SDK reference, and the current files when cha
@@ -18,7 +18,9 @@ t.test('the builder\'s prompt: the SDK reference, and the current files when cha
assert.match(sys, /<script src="\/_openvibe\/sdk\.js"><\/script>/);
assert.match(sys, /<script src="\/_openvibe\/sdk\.js"><\/script>/);
assert.match(sys, /OV\.items\.set\(collection, value\)/);
assert.match(sys, /OV\.items\.set\(collection, value\)/);
assert.match(sys, /never ask for passwords, payment details/);
assert.match(sys, /never ask for passwords, payment details/);
- const fresh = render(tpl('space.app.build').user_prompt, PREPARE['space.app.build']({ prompt: 'a poll about pizza' }).vars);
+ const prep = PREPARE['space.app.build']({ prompt: 'a poll about pizza' });
+ assert.ok(prep.jsonSchema && prep.jsonSchema.properties.files && !/maxLength|maxItems/.test(JSON.stringify(prep.jsonSchema)), 'the model answers in the uncapped, non-strict schema');
+ const fresh = render(tpl('space.app.build').user_prompt, prep.vars);
assert.strictEqual(fresh, 'What to build: a poll about pizza');
assert.strictEqual(fresh, 'What to build: a poll about pizza');
const change = render(tpl('space.app.build').user_prompt, PREPARE['space.app.build']({ prompt: 'make it blue', name: 'Pizza poll', current: [{ path: 'index.html', content: INDEX }] }).vars);
const change = render(tpl('space.app.build').user_prompt, PREPARE['space.app.build']({ prompt: 'make it blue', name: 'Pizza poll', current: [{ path: 'index.html', content: INDEX }] }).vars);
assert.match(change, /^The space's current files:\n--- index\.html ---\n<!doctype html>/);
assert.match(change, /^The space's current files:\n--- index\.html ---\n<!doctype html>/);