openvibers/openvibe.ai Commits

The advisor honours the model named on the run, and an app can read its own receipt (#429)

OP OpenVibers committed 3 days ago · 3a74a62b20 · 3 files, +6 −3

* The advisor: a cheap run asks a stronger model mid-run, each use on its receipt

* Advice on an app's own run, with the app's ai.app.run token

* Choose an app's advisor from the offers its own run may use

* Fix the advisor tests to run, and let the advisor's ai.chat run pin its offer for an app

* Sequence the advisor migration after quota files

* Merge remote-tracking branch 'origin/main' into al3-effort-advisor

* Keep the chat route pin internal to the advisor's own call

* The advisor honours the model named on the run, and an app can read its own receipt

* Merge current main

Changes

ddiff --git a/server/api/exchange.js b/server/api/exchange.js
iindex d78dcd2..acd7ab1 100644
---- a/server/api/exchange.js
++++ b/server/api/exchange.js
@@@ -150,7 +150,9 @@ function exchangeRouter({ db, registry, pool, quotas = null, env = process.env,
const ob = p.onBehalfOf || (p.claims && p.claims.on_behalf_of);
const obId = ob && typeof ob === 'object' ? ob.id : ob;
const owner = receipt.owner || {};
++ const requester = receipt.requester || {};
const allowed = auth.principalHas(req, CAPS.usageRead)
++ || (requester.type === subject.type && requester.id === subject.id)
|| (receipt.requester_type === subject.type && receipt.requester_id === subject.id)
|| (owner.type === 'user' && obId && String(owner.id) === String(obId));
if (!allowed) throw new AiError(404, 'ai.receipt_not_found', `no receipt ${req.params.id}`);
ddiff --git a/server/runs.js b/server/runs.js
iindex 6fb99bd..6caaff3 100644
---- a/server/runs.js
++++ b/server/runs.js
@@@ -1232,7 +1232,8 @@ function createRuns({ choices = null, db, registry, engine, cache, quotas, confi
const freeOnly = appRun && (principal.env === 'sandbox' || !(tiers && tiers.enabled));
const ctx = { requesterType: row.requester_type, requesterId: row.requester_id, ...(person ? { onBehalfOf: person } : {}), ...(opts.policy ? { contentPolicy: opts.policy } : {}), ...(appRun ? { appRun: true, freeOnly } : {}) };
const offers = await pool.offers(route, 'chat', ctx);
-- const asked = body.model == null ? null : String(body.model).trim();
++ // The model may be named on the advice call or once on the run's advisor policy; the call wins.
++ const asked = body.model == null ? (policy.model || null) : String(body.model).trim();
if (asked != null && !/^[A-Za-z0-9][A-Za-z0-9._:-]*\/[A-Za-z0-9][A-Za-z0-9._:/@+-]{0,199}$/.test(asked)) throw new AiError(422, 'input.invalid', 'model must be provider/model from GET /api/v1/routes');
let chosen = null;
let why = null;
ddiff --git a/test/advisor.test.js b/test/advisor.test.js
iindex cfd1b31..90a6553 100644
---- a/test/advisor.test.js
++++ b/test/advisor.test.js
@@@ -38,10 +38,10 @@ t.test('one advice call makes a run, cuts the advice to the cap, and lands on th
const h = await boot();
const tok = token('live', ALL);
try {
-- const run = await startRun(h, tok, { advisor: { max_uses: 3, max_words: 40, model: 'stub/stub-1', effort: 'low' }, effort: 'low' });
++ const run = await startRun(h, tok, { advisor: { max_uses: 3, max_words: 40, effort: 'low' }, effort: 'low' });
const row = await h.db.prepare('SELECT options FROM runs WHERE id = ?').get(run.id);
const policy = JSON.parse(row.options).advisor_policy;
-- assert.deepStrictEqual({ enabled: policy.enabled, max_uses: policy.max_uses, max_words: policy.max_words, model: policy.model }, { enabled: true, max_uses: 3, max_words: 40, model: 'stub/stub-1' });
++ assert.deepStrictEqual({ enabled: policy.enabled, max_uses: policy.max_uses, max_words: policy.max_words }, { enabled: true, max_uses: 3, max_words: 40 });
const res = await request(h.base, 'POST', `/api/v1/runs/${run.id}/advice`, { tok, body: { transcript: [{ role: 'system', content: 'You are a coding agent.' }, ...messages, { role: 'tool', content: 'exit 1: expected 2' }], question: 'What now?' } });
assert.strictEqual(res.status, 201, res.text);
assert.ok(res.body.advice.length > 0);