decision1-studio / static /contract.js
Xunzhuo's picture
Launch Decision 1.0 studio with independent model queues and Mars banner
935d4b2 verified
Raw History Blame Contribute Delete
7.74 kB
// Strict browser-side mirror of the bounded local System One request contract.
// The server remains authoritative; this module never edits unsupported fields.
const object = v => v !== null && typeof v === 'object' && !Array.isArray(v);
const keys = (value, allowed, label) => { if (!object(value) || Object.keys(value).some(k => !allowed.includes(k))) throw Error(`${label}: unsupported fields or invalid object.`); };
const content = (v,label) => { if (typeof v === 'string') { if(!v.trim()) throw Error(`${label} must not be empty.`); } else if(!object(v) && !Array.isArray(v)) throw Error(`${label} must be text, an object, or an array.`); };
const id = (v,label,maxLength) => {
if(typeof v!=='string'||!v.trim())throw Error(`${label} needs a nonempty string.`);
if(maxLength!==undefined&&v.length>maxLength)throw Error(`${label} needs at most ${maxLength} characters.`);
};
function finiteJSON(value) {
if(typeof value==='number'&&!Number.isFinite(value))throw Error('JSON numbers must be finite.');
if(value && typeof value==='object')Object.values(value).forEach(finiteJSON);
}
export function validateRequest(body, expectedModel) {
finiteJSON(body);
keys(body,['state','states','model','questions'],'Request');
if(typeof expectedModel!=='string'||!expectedModel||body.model!==expectedModel)throw Error('Request model does not match the selected model.');
if(Object.hasOwn(body,'state')===Object.hasOwn(body,'states'))throw Error('Use one context or batch contexts, not both.');
if(Object.hasOwn(body,'states')){
if(!Array.isArray(body.states)||body.states.length<1)throw Error('Provide at least one context.');
const seen=new Set();
for(const row of body.states){keys(row,['id','state'],'Context');id(row.id,'Context ID',128);if(seen.has(row.id))throw Error('Context IDs must be unique.');seen.add(row.id);content(row.state,`Context ${row.id}`);}
}else content(body.state,'State');
if(!object(body.questions)||Object.keys(body.questions).length<1)throw Error('Provide at least one named question.');
for(const[qid,q]of Object.entries(body.questions)) {
id(qid,'Question ID');keys(q,['type','instructions','criteria'],qid);
if(!['choice','noul','score'].includes(q.type))throw Error(`${qid}: unsupported question type.`);
content(q.instructions,`${qid} instructions`);
if(q.type==='choice') {
if(!object(q.criteria)||Object.keys(q.criteria).length<2||Object.keys(q.criteria).length>255)throw Error(`${qid}: Choice needs 2–255 named options.`);
for(const[name,v]of Object.entries(q.criteria)){id(name,'Option name');if(v!==null)content(v,`${qid} option description`);}
} else if(q.type==='score') {
if(!Array.isArray(q.criteria)||q.criteria.length<2||q.criteria.length>10)throw Error(`${qid}: Score needs 2–10 ordered levels.`);
q.criteria.forEach(v=>content(v,`${qid} level`));
} else if(q.criteria!==undefined&&q.criteria!==null) {
keys(q.criteria,['false','true'],`${qid} criteria`);
Object.values(q.criteria).forEach(v=>{if(v!==null)content(v,`${qid} criterion`);});
}
}
const requestLimit=Object.hasOwn(body,'states')?2*1024*1024:256*1024;
if(new TextEncoder().encode(JSON.stringify(body)).length>requestLimit)throw Error(`The complete request must fit within ${requestLimit===256*1024?'256 KiB':'2 MiB'}.`);
const utf8=new TextEncoder(), states=body.states?body.states.map(x=>x.state):[body.state];
const stateBytes=states.reduce((n,v)=>n+utf8.encode(typeof v==='string'?v:JSON.stringify(v)).length,0);
const expanded=stateBytes*Object.keys(body.questions).length+utf8.encode(JSON.stringify(body.questions)).length*states.length;
if(expanded>16*1024*1024)throw Error('Expanded context/question input exceeds 16 MiB; split this workload into requests.');
return body;
}
export function validateResult(result,request,canonicalModel=request.model){
if(!object(result))throw Error('The server returned an invalid response.');
const legacy=Object.hasOwn(result,'source');
if(legacy){
if(result.model!==request.model||result.source!=='live_native')throw Error('The server returned a different model or prediction source.');
}else{
if(result.model!==canonicalModel)throw Error('The server returned a different model.');
const fields=request.states?['model','results','usage']:['model','answers','usage'];
if(Object.keys(result).length!==fields.length||fields.some(key=>!Object.hasOwn(result,key)))throw Error('The server returned a non-standard response.');
}
const usage=value=>object(value)&&Object.keys(value).length===2&&Number.isInteger(value.input_tokens)&&value.input_tokens>=0&&Number.isInteger(value.output_tokens)&&value.output_tokens>=0;
if(!usage(result.usage))throw Error('Invalid response usage.');
const expectedIds=Object.keys(request.questions);
const answerSet=answers=>{
if(!object(answers)||Object.keys(answers).length!==expectedIds.length||expectedIds.some(id=>!Object.hasOwn(answers,id))||legacy&&JSON.stringify(Object.keys(answers))!==JSON.stringify(expectedIds))throw Error('The response does not match the shared questions.');
for(const id of expectedIds){const a=answers[id],q=request.questions[id];
if(!object(a)||a.type!==q.type)throw Error('Invalid decision identity.');
if(legacy){if(!Number.isInteger(a.input_tokens)||a.input_tokens<1)throw Error('Invalid decision token usage.');}
else{
const fields=q.type==='noul'?['type','noul']:q.type==='choice'?['type','choice','confidence','probabilities']:['type','score','confidence','legend','probabilities'];
if(Object.keys(a).length!==fields.length||fields.some(key=>!Object.hasOwn(a,key)))throw Error('The server returned non-standard answer fields.');
}
if(q.type==='noul'){if(!Number.isFinite(a.noul)||a.noul<0||a.noul>1)throw Error('Invalid yes probability.');}
else {const ids=q.type==='choice'?Object.keys(q.criteria):q.criteria.map((_,i)=>String(i));
if(!object(a.probabilities)||Object.keys(a.probabilities).length!==ids.length||ids.some(k=>!Object.hasOwn(a.probabilities,k)))throw Error('Invalid candidate identity.');
const p=ids.map(k=>a.probabilities[k]);if(p.some(v=>!Number.isFinite(v)||v<0||v>1)||Math.abs(p.reduce((a,b)=>a+b,0)-1)>2e-5)throw Error('Invalid candidate probabilities.');
if(!legacy&&(!Number.isFinite(a.confidence)||a.confidence<0||a.confidence>1))throw Error('Invalid decision confidence.');
if(q.type==='choice'&&!ids.includes(a.choice)||q.type==='score'&&(!Number.isFinite(a.score)||a.score<0||a.score>ids.length-1))throw Error('Invalid decision value.');
if(q.type==='score'&&(!object(a.legend)||JSON.stringify(Object.keys(a.legend))!==JSON.stringify(ids)))throw Error('Invalid score legend.');
}
}
};
if(request.states){
if(!Array.isArray(result.results)||result.results.length!==request.states.length)throw Error('Incomplete context results.');
result.results.forEach((r,i)=>{if(!object(r)||r.id!==request.states[i].id||!usage(r.usage))throw Error('Context order or identity mismatch.');if(!legacy&&(Object.keys(r).length!==3||['id','answers','usage'].some(key=>!Object.hasOwn(r,key))))throw Error('The server returned non-standard context fields.');answerSet(r.answers);});
}else answerSet(result.answers);
return result;
}
export function validateServingResult(result,request,canonicalModel,backend){
const native=result!==null&&typeof result==='object'&&Object.hasOwn(result,'source');
if(backend==='native_rocm'&&!native||backend==='direct'&&native||!['native_rocm','direct','pull_queue'].includes(backend))throw Error('The server returned an unexpected prediction format.');
return validateResult(result,request,canonicalModel);
}