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)

OP OpenVibers committed 28 days ago · f4c883e969 · 2 files, +8 −2

Changes

ddiff --git a/server/workflows/space.js b/server/workflows/space.js
iindex 0b44d93..4dcb955 100644
---- a/server/workflows/space.js
++++ b/server/workflows/space.js
@@@ -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).
ddiff --git a/test/space-workflows.test.js b/test/space-workflows.test.js
iindex 2b49170..801fdcc 100644
---- a/test/space-workflows.test.js
++++ b/test/space-workflows.test.js
@@@ -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, /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>/);