@thebackstoryis/engineering-with-ai 0.3.2 → 0.3.4-beta.6
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/Docs/README.md +6 -0
- package/Docs/context-management-and-token-efficiency.md +8 -0
- package/Docs/operations/jev-beta-experiment.md +80 -0
- package/Docs/operations/openai-decisions-beta.md +62 -0
- package/Docs/releases/0.3.4-beta.6.md +22 -0
- package/README.md +24 -1
- package/package.json +6 -4
- package/public/index.html +62 -0
- package/public/jev-credentials.js +43 -0
- package/public/jev.js +11 -0
- package/public/openai-credentials.js +43 -0
- package/public/styles.css +28 -4
- package/scripts/jev-experiment.mjs +15 -0
- package/src/cli.mjs +59 -14
- package/src/jev-credentials.mjs +104 -0
- package/src/jev.mjs +220 -0
- package/src/openai-credentials.mjs +104 -0
- package/src/openai-decisions.mjs +62 -0
- package/src/runtime/context-assembly.mjs +21 -3
- package/src/runtime/dashboard-server.mjs +56 -7
- package/src/runtime/jev-operations.mjs +160 -0
- package/src/runtime/mcp-server.mjs +26 -6
- package/tests/fixtures/jev-synthetic.json +8 -0
package/src/cli.mjs
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {openaiCredentialStatus,configureOpenaiCredentials,checkOpenaiConnection,removeOpenaiCredentials,safeOpenaiCredentialError} from './openai-credentials.mjs';
|
|
2
|
+
import {jevCredentialStatus,configureJevCredentials,checkJevConnection,removeJevCredentials} from './jev-credentials.mjs';
|
|
3
|
+
import {readJevSettings,saveJevSettings,readJevMeasurements,readDecisionCredentials,safeJevError} from './jev.mjs';
|
|
4
|
+
import {decideWithJev,prepareProjectContextWithJev,recommendPersonasWithJev,readCompanionWithJev} from './runtime/jev-operations.mjs';
|
|
5
|
+
import {runJevExperiment} from '../scripts/jev-experiment.mjs';
|
|
1
6
|
import { dirname, isAbsolute, relative, resolve } from 'node:path';
|
|
2
7
|
import { fileURLToPath } from 'node:url';
|
|
3
8
|
import { readFileSync, realpathSync } from 'node:fs';
|
|
@@ -530,6 +535,15 @@ Commands:
|
|
|
530
535
|
ewai rollout assurance PROJECT_ID [--project PATH]
|
|
531
536
|
ewai discover [--project PATH] [--answers FILE] [--stack PACK] [--force]
|
|
532
537
|
ewai context register FOLDER [--project PATH] --yes [--label NAME] [--classification public|internal|confidential|restricted] [--cloud-processing allowed|denied|unknown]
|
|
538
|
+
ewai jev status|measurements [--project PATH] [--json]
|
|
539
|
+
ewai decisions status|measurements|decide|personas [--project PATH] [--json]
|
|
540
|
+
ewai decisions configure --mode off|shadow|active --provider auto|openai|jev --yes [--acknowledge-openai-processing] [--acknowledge-cloud-processing] [--revoke-openai-processing] [--revoke-cloud-processing]
|
|
541
|
+
ewai providers credentials openai status|configure|check|remove [--project PATH]
|
|
542
|
+
ewai jev credentials status|configure|check|remove [--project PATH]
|
|
543
|
+
ewai jev configure --mode off|shadow|active --yes [--acknowledge-cloud-processing] [--use-cases context,personas,skill,claim,failure,impact,tests]
|
|
544
|
+
ewai jev decide --input PROJECT_JSON [--project PATH]
|
|
545
|
+
ewai jev experiment [--limit 6] [--project PATH]
|
|
546
|
+
ewai jev personas --focus TEXT [--limit 4] [--project PATH]
|
|
533
547
|
ewai context prepare PROFILE --slug SLUG [--task TASK] [--phase PHASE] [--focus TEXT] [--budget TOKENS] [--previous DIGEST] [--project PATH]
|
|
534
548
|
ewai context benchmark [--samples COUNT]
|
|
535
549
|
ewai design-system status|list|inspect|resolve|select [ID] [--project PATH]
|
|
@@ -909,32 +923,61 @@ export async function run(args) {
|
|
|
909
923
|
print(json ? result : JSON.stringify(result, null, 2), json); return;
|
|
910
924
|
}
|
|
911
925
|
|
|
912
|
-
if(command==='providers'&&subcommand==='credentials'){
|
|
913
|
-
const action=args[3];
|
|
914
|
-
|
|
926
|
+
if(command==='providers'&&subcommand==='credentials'||command==='jev'&&subcommand==='credentials'){
|
|
927
|
+
const isJev=command==='jev',isOpenai=!isJev&&args[2]==='openai',action=args[isJev?2:3];
|
|
928
|
+
const statusCredential=isJev?jevCredentialStatus:isOpenai?openaiCredentialStatus:grokCredentialStatus,configureCredential=isJev?configureJevCredentials:isOpenai?configureOpenaiCredentials:configureGrokCredentials,checkConnection=isJev?checkJevConnection:isOpenai?checkOpenaiConnection:checkGrokConnection,removeCredential=isJev?removeJevCredentials:isOpenai?removeOpenaiCredentials:removeGrokCredentials;
|
|
929
|
+
if(!isJev&&!['grok','openai'].includes(args[2])||!['status','configure','check','remove'].includes(action))throw new Error('Choose providers credentials grok or openai, then status, configure, check or remove.');
|
|
915
930
|
const allowedValues=['--project',...(action==='remove'?['--expected-revision']:[])],allowedFlags=['--json',...(action==='remove'?['--yes']:[])];
|
|
916
|
-
for(let i=4;i<args.length;i++){
|
|
931
|
+
for(let i=isJev?3:4;i<args.length;i++){
|
|
917
932
|
if(allowedValues.includes(args[i])){if(!args[i+1]||args[i+1].startsWith('--'))throw new Error('A credential option requires a value.');i++;}
|
|
918
933
|
else if(!allowedFlags.includes(args[i]))throw new Error('Unsupported credential option. Never put API keys in command arguments.');
|
|
919
934
|
}
|
|
920
935
|
const credentialOptions={projectRoot:selectedProject(args)};
|
|
921
936
|
let result;
|
|
922
|
-
if(action==='status')result=
|
|
923
|
-
if(action==='check')result=await
|
|
924
|
-
if(action==='remove')result=
|
|
937
|
+
if(action==='status')result=statusCredential(credentialOptions);
|
|
938
|
+
if(action==='check')result=await checkConnection(credentialOptions);
|
|
939
|
+
if(action==='remove')result=removeCredential({confirmed:has(args,'--yes'),expectedRevision:option(args,'--expected-revision',statusCredential(credentialOptions).revision)},credentialOptions);
|
|
925
940
|
if(action==='configure'){
|
|
926
|
-
if(!process.stdin.isTTY||!process.stdout.isTTY)throw new Error('
|
|
927
|
-
const previous=
|
|
941
|
+
if(!process.stdin.isTTY||!process.stdout.isTTY)throw new Error('Credential setup requires an interactive terminal for hidden input. Use the local dashboard password form instead.');
|
|
942
|
+
const previous=statusCredential(credentialOptions),muted=new Writable({write(_chunk,_encoding,callback){callback();}});muted.isTTY=true;muted.columns=process.stdout.columns;
|
|
928
943
|
const prompt=createInterface({input:process.stdin,output:muted,terminal:true,historySize:0});let apiKey;
|
|
929
944
|
try{
|
|
930
|
-
process.stdout.write('This checks xAI authentication without generated output and saves the key for your account on this computer, outside the project, in an owner-only local file (not encrypted). XAI_API_KEY overrides the saved key. Cancel with Ctrl+C.\nxAI API key (hidden): ');
|
|
945
|
+
process.stdout.write(isOpenai?'This checks OpenAI metadata authentication without inference and saves an owner-only local key outside the project (not encrypted). OPENAI_API_KEY overrides it. This does not prove Decisions access; saving does not enable decisions. Cancel with Ctrl+C.\nOpenAI API key (hidden): ':isJev?'This checks TypeSafe authentication without inference and saves an owner-only local key outside the project (not encrypted). TYPESAFE_API_KEY overrides it. Saving does not enable Jev. Cancel with Ctrl+C.\nTypeSafe API key (hidden): ':'This checks xAI authentication without generated output and saves the key for your account on this computer, outside the project, in an owner-only local file (not encrypted). XAI_API_KEY overrides the saved key. Cancel with Ctrl+C.\nxAI API key (hidden): ');
|
|
931
946
|
apiKey=await prompt.question('');process.stdout.write('\n');
|
|
932
|
-
result=await
|
|
947
|
+
result=await configureCredential({confirmed:true,expectedRevision:previous.revision,apiKey},credentialOptions);
|
|
933
948
|
}finally{apiKey=undefined;prompt.close();}
|
|
934
949
|
}
|
|
935
|
-
print(json?result:result.message??('Grok credential: '+result.source+'. Saved key: '+(result.saved?'available':'absent')+'.'),json);
|
|
950
|
+
print(json?result:result.message??((isJev?'Jev':isOpenai?'OpenAI':'Grok')+' credential: '+result.source+'. Saved key: '+(result.saved?'available':'absent')+'.'),json);
|
|
936
951
|
if(result.status==='failed')process.exitCode=1;return;
|
|
937
952
|
}
|
|
953
|
+
|
|
954
|
+
if(['jev','decisions'].includes(command)){
|
|
955
|
+
const actions=['status','configure','measurements','decide','experiment','personas'];
|
|
956
|
+
if(!actions.includes(subcommand))throw new Error('Choose Jev status, configure, measurements, decide, experiment or credentials.');
|
|
957
|
+
const valued=['--project',...(subcommand==='configure'?['--provider','--mode','--use-cases','--max-calls','--max-input-tokens','--timeout-ms','--minimum-confidence','--expected-revision']:[]),...(subcommand==='decide'?['--input']:[]),...(subcommand==='experiment'?['--limit']:[]),...(subcommand==='personas'?['--focus','--limit']:[])];
|
|
958
|
+
const flags=['--json',...(subcommand==='configure'?['--yes','--acknowledge-cloud-processing','--acknowledge-openai-processing','--revoke-cloud-processing','--revoke-openai-processing']:[])],seen=new Set();
|
|
959
|
+
for(let i=2;i<args.length;i++){const argument=args[i];if(seen.has(argument))throw new Error('Do not repeat Jev options.');seen.add(argument);if(valued.includes(argument)){if(!args[i+1]||args[i+1].startsWith('--'))throw new Error('A Jev option requires a value.');i++;}else if(!flags.includes(argument))throw new Error('Unsupported Jev option. Never put API keys in command arguments.');}
|
|
960
|
+
const root=selectedProject(args),current=readJevSettings(root);let value;
|
|
961
|
+
if(subcommand==='status')value={settings:current,...readDecisionCredentials(root),measurements:readJevMeasurements(root)};
|
|
962
|
+
if(subcommand==='measurements')value=readJevMeasurements(root);
|
|
963
|
+
if(subcommand==='configure'){
|
|
964
|
+
const policy=structuredClone(current.policy);
|
|
965
|
+
for(const vendor of ['openai','cloud'])if(has(args,'--acknowledge-'+vendor+'-processing')&&has(args,'--revoke-'+vendor+'-processing'))throw new Error('Choose acknowledgement or revocation for each provider.');
|
|
966
|
+
if(has(args,'--mode'))policy.mode=option(args,'--mode');
|
|
967
|
+
if(has(args,'--provider'))policy.provider=option(args,'--provider');
|
|
968
|
+
if(has(args,'--acknowledge-openai-processing'))policy.openaiConsent=true;
|
|
969
|
+
if(has(args,'--revoke-openai-processing'))policy.openaiConsent=false;
|
|
970
|
+
if(has(args,'--revoke-cloud-processing'))policy.cloudConsent=false;
|
|
971
|
+
if(has(args,'--use-cases'))policy.useCases=option(args,'--use-cases').split(',').map(s=>s.trim());
|
|
972
|
+
for(const [flag,key]of [['--max-calls','maxCalls'],['--max-input-tokens','maxInputTokens'],['--timeout-ms','timeoutMs'],['--minimum-confidence','minConfidence']])if(has(args,flag))policy[key]=Number(option(args,flag));
|
|
973
|
+
if(has(args,'--acknowledge-cloud-processing'))policy.cloudConsent=true;
|
|
974
|
+
value=saveJevSettings(root,{confirmed:has(args,'--yes'),expectedRevision:option(args,'--expected-revision',current.revision),policy});
|
|
975
|
+
}
|
|
976
|
+
if(subcommand==='decide'){const file=option(args,'--input');if(!file)throw new Error('Provide a project-local JSON decision input.');value=await decideWithJev(root,readProjectJsonInput(root,file,'Jev decision'));}
|
|
977
|
+
if(subcommand==='personas')value=await recommendPersonasWithJev(root,{focus:option(args,'--focus'),limit:Number(option(args,'--limit','4'))},listPersonas(availablePersonaRoots(root)));
|
|
978
|
+
if(subcommand==='experiment')value=await runJevExperiment(root,{limit:has(args,'--limit')?Number(option(args,'--limit')):6});
|
|
979
|
+
print(json?value:JSON.stringify(value,null,2),json);return;
|
|
980
|
+
}
|
|
938
981
|
if(command==='providers'){
|
|
939
982
|
if(!['show','set','auto','defaults','model'].includes(subcommand))throw new Error('Choose providers show, set, auto, model or defaults.');
|
|
940
983
|
const valued=new Set(['--project','--expected-digest',...(subcommand==='set'?['--primary','--secondary','--tertiary','--pool','--input']:[]),...(subcommand==='auto'?['--pool']:[]),...(subcommand==='model'?['--suggest','--allow-model']:[])]);
|
|
@@ -1127,7 +1170,7 @@ export async function run(args) {
|
|
|
1127
1170
|
if (command === 'companion' && subcommand === 'status') {
|
|
1128
1171
|
assertCompanionArguments(args);
|
|
1129
1172
|
const project = selectedProject(args);
|
|
1130
|
-
print(
|
|
1173
|
+
print(await readCompanionWithJev(project, {
|
|
1131
1174
|
focus: option(args, '--focus'),
|
|
1132
1175
|
personas: listPersonas(availablePersonaRoots(project)),
|
|
1133
1176
|
}), json);
|
|
@@ -1316,7 +1359,7 @@ export async function run(args) {
|
|
|
1316
1359
|
const profile = args[2] ?? '';
|
|
1317
1360
|
const budgetValue = option(args, '--budget');
|
|
1318
1361
|
const projectRoot = selectedProject(args);
|
|
1319
|
-
print(
|
|
1362
|
+
print(await prepareProjectContextWithJev(projectRoot, {
|
|
1320
1363
|
profile,
|
|
1321
1364
|
slug: option(args, '--slug'),
|
|
1322
1365
|
taskId: option(args, '--task'),
|
|
@@ -2518,6 +2561,8 @@ export async function run(args) {
|
|
|
2518
2561
|
|
|
2519
2562
|
throw new Error(`Unknown command: ${args.join(' ')}`);
|
|
2520
2563
|
} catch (error) {
|
|
2564
|
+
if(command==='providers'&&subcommand==='credentials'&&args[2]==='openai'){const safe=safeOpenaiCredentialError(error);console.error(json?JSON.stringify(safe):safe.error);process.exitCode=1;return;}
|
|
2565
|
+
if(['jev','decisions'].includes(command)){const safe=safeJevError(error);console.error(json?JSON.stringify(safe):safe.error);process.exitCode=1;return;}
|
|
2521
2566
|
if (command !== 'error-report') {
|
|
2522
2567
|
try {
|
|
2523
2568
|
const projectRoot = selectedProject(args);
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// TypeSafe uses the same audited private storage safeguards as Grok, with a separate key lifecycle.
|
|
2
|
+
import {constants,openSync,closeSync,fstatSync,lstatSync,realpathSync,readFileSync,mkdirSync,writeFileSync,renameSync,unlinkSync,rmSync} from 'node:fs';
|
|
3
|
+
import {homedir} from 'node:os';
|
|
4
|
+
import {resolve,relative,isAbsolute} from 'node:path';
|
|
5
|
+
import {randomUUID,createHash} from 'node:crypto';
|
|
6
|
+
|
|
7
|
+
const messages={
|
|
8
|
+
'jev-credential-invalid':'Provide a valid TypeSafe API key using the private setup form or hidden terminal prompt.',
|
|
9
|
+
'jev-credential-input':'Use the supported credential action and confirm the change.',
|
|
10
|
+
'jev-credential-unsafe-storage':'Private credential storage is unsafe or unavailable. Repair its permissions or use TYPESAFE_API_KEY in the launching environment.',
|
|
11
|
+
'jev-credential-invalid-storage':'The saved credential could not be read safely. Repair the private credential file before continuing.',
|
|
12
|
+
'jev-credential-stale':'The saved credential changed. Refresh its status and try again.',
|
|
13
|
+
'jev-credential-busy':'Another credential change is running. Retry after it finishes.',
|
|
14
|
+
'jev-credential-missing':'No Jev key is configured. Use private credential setup or TYPESAFE_API_KEY in the launching environment.',
|
|
15
|
+
'jev-key-rejected':'The key was not accepted. Check the key and its API permissions, then try again. The previous saved key was retained.',
|
|
16
|
+
'jev-check-rate-limited':'TypeSafe limited the authentication check. Wait before trying again. The previous saved key was retained.',
|
|
17
|
+
'jev-check-unavailable':'The authentication check could not complete. Check your connection and try again. The previous saved key was retained.',
|
|
18
|
+
};
|
|
19
|
+
function fail(code,statusCode=400){const error=new Error(messages[code]??messages['jev-credential-input']);error.code=code;error.statusCode=statusCode;throw error;}
|
|
20
|
+
function key(value){if(typeof value!=='string'||value.length>512||!/^apikey_[A-Za-z0-9_-]{16,508}$/.test(value.trim()))fail('jev-credential-invalid');return value.trim();}
|
|
21
|
+
function request(input,fields){if(!input||typeof input!=='object'||Array.isArray(input)||Object.keys(input).some(k=>!fields.includes(k))||input.confirmed!==true||typeof input.expectedRevision!=='string'||input.expectedRevision.length>80)fail('jev-credential-input');}
|
|
22
|
+
function stat(path){try{return lstatSync(path);}catch(error){if(error.code==='ENOENT')return null;fail('jev-credential-unsafe-storage');}}
|
|
23
|
+
function owned(s){return s&&!s.isSymbolicLink()&&(process.getuid===undefined||s.uid===process.getuid());}
|
|
24
|
+
function paths(options={},create=false){
|
|
25
|
+
const declaredHome=resolve(options.home??homedir());
|
|
26
|
+
const root=stat(declaredHome);if(!owned(root)||!root.isDirectory()||(root.mode&0o022))fail('jev-credential-unsafe-storage');
|
|
27
|
+
let home,project;try{home=realpathSync(declaredHome);if(options.projectRoot)project=realpathSync(resolve(options.projectRoot));}catch{fail('jev-credential-unsafe-storage');}
|
|
28
|
+
const folder=resolve(home,'.ewai/credentials'),file=resolve(folder,'jev.json');
|
|
29
|
+
if(project){const rel=relative(project,file);if(rel===''||(!isAbsolute(rel)&&rel!=='..'&&!rel.startsWith('../')))fail('jev-credential-unsafe-storage');}
|
|
30
|
+
const canonicalRoot=stat(home);if(!owned(canonicalRoot)||canonicalRoot.ino!==root.ino||canonicalRoot.dev!==root.dev)fail('jev-credential-unsafe-storage');
|
|
31
|
+
for(const path of [resolve(home,'.ewai'),folder]){
|
|
32
|
+
let s=stat(path);if(!s&&create){try{mkdirSync(path,{mode:0o700});}catch(error){if(error.code!=='EEXIST')fail('jev-credential-unsafe-storage');}s=stat(path);}
|
|
33
|
+
if(s&&(!owned(s)||!s.isDirectory()||(s.mode&(path===folder?0o077:0o022))))fail('jev-credential-unsafe-storage');
|
|
34
|
+
}
|
|
35
|
+
return {folder,file,location:JSON.stringify([home,root.dev,root.ino])};
|
|
36
|
+
}
|
|
37
|
+
function stored(options={}){
|
|
38
|
+
const {file,location}=paths(options),before=stat(file);if(!before)return {value:null,signature:'missing',location};
|
|
39
|
+
if(process.platform==='win32'||!owned(before)||!before.isFile()||before.nlink!==1||(before.mode&0o077)||before.size>4096)fail('jev-credential-unsafe-storage');
|
|
40
|
+
let fd;try{
|
|
41
|
+
fd=openSync(file,constants.O_RDONLY|constants.O_NOFOLLOW);const bytes=readFileSync(fd),after=fstatSync(fd),current=stat(file);
|
|
42
|
+
if(bytes.length>4096||!current||before.ino!==after.ino||before.dev!==after.dev||before.mtimeMs!==after.mtimeMs||after.ino!==current.ino||after.mtimeMs!==current.mtimeMs)fail('jev-credential-unsafe-storage');
|
|
43
|
+
let value;try{value=JSON.parse(bytes.toString('utf8'));}catch{fail('jev-credential-invalid-storage');}
|
|
44
|
+
if(value?.schema!=='ewai.jev-credential/v1'||typeof value.revision!=='string'||!/^[a-f0-9-]{36}$/.test(value.revision)||Object.keys(value).some(k=>!['schema','revision','apiKey'].includes(k)))fail('jev-credential-invalid-storage');
|
|
45
|
+
try{key(value.apiKey);}catch{fail('jev-credential-invalid-storage');}
|
|
46
|
+
return {value,location,signature:JSON.stringify([after.dev,after.ino,after.mtimeMs,after.ctimeMs,createHash('sha256').update(bytes).digest('hex')])};
|
|
47
|
+
}catch(error){if(error.code?.startsWith('jev-'))throw error;fail('jev-credential-unsafe-storage');}finally{if(fd!==undefined)closeSync(fd);}
|
|
48
|
+
}
|
|
49
|
+
function environment(options){const env=options.env??process.env;return env.TYPESAFE_API_KEY?key(env.TYPESAFE_API_KEY):null;}
|
|
50
|
+
export function jevCredentialStatus(options={}){
|
|
51
|
+
const override=environment(options);let value,storageIssue=null;
|
|
52
|
+
try{({value}=stored(options));}catch(error){if(!override)throw error;storageIssue='unsafe-saved-storage';}
|
|
53
|
+
|
|
54
|
+
return {schema:'ewai.jev-credential-status/v1',provider:'jev',configured:Boolean(override||value),saved:storageIssue?null:Boolean(value),storageIssue,source:override?'environment':value?'saved':'missing',revision:value?.revision??(storageIssue?'unavailable':'missing'),storage:'owner-only-local-file',encrypted:false,storageSupported:!storageIssue&&process.platform!=='win32',authority:'credentials-only'};
|
|
55
|
+
}
|
|
56
|
+
export function resolveJevApiKey(options={}){const override=environment(options);return override??stored(options).value?.apiKey??null;}
|
|
57
|
+
function predecessor(input,options,expected){const current=stored(options);if((current.value?.revision??'missing')!==input.expectedRevision||(expected&&(current.signature!==expected.signature||current.location!==expected.location)))fail('jev-credential-stale',409);return current;}
|
|
58
|
+
function mutation(options,operation){
|
|
59
|
+
if(process.platform==='win32')fail('jev-credential-unsafe-storage');
|
|
60
|
+
const {folder}=paths(options,true),lock=resolve(folder,'.jev.lock');
|
|
61
|
+
try{mkdirSync(lock,{mode:0o700});}catch(error){fail(error.code==='EEXIST'?'jev-credential-busy':'jev-credential-unsafe-storage',409);}
|
|
62
|
+
const identity=lstatSync(lock);
|
|
63
|
+
try{return operation();}finally{const current=stat(lock);if(owned(current)&¤t.isDirectory()&¤t.ino===identity.ino)rmSync(lock,{recursive:true});}
|
|
64
|
+
}
|
|
65
|
+
async function authenticate(apiKey,options={}){
|
|
66
|
+
const controller=new AbortController();let timer;
|
|
67
|
+
try{
|
|
68
|
+
const timeout=Math.min(10000,Math.max(10,options.timeoutMs??10000));
|
|
69
|
+
const response=await Promise.race([(options.fetchImpl??fetch)('https://api.typesafe.ai/v1/models',{method:'GET',headers:{authorization:'Bearer '+apiKey},redirect:'manual',signal:controller.signal}),new Promise((_,reject)=>{timer=setTimeout(()=>{controller.abort();reject(Error('deadline'));},timeout);})]);
|
|
70
|
+
// Discard provider bodies, including errors. They are not safe evidence.
|
|
71
|
+
if(response.body)void response.body.cancel().catch(()=>{});
|
|
72
|
+
if(response.status===200)return {status:'verified',code:'jev-authentication-verified',message:'TypeSafe authentication check passed. No output was generated; credit and coding readiness remain unverified.'};
|
|
73
|
+
const code=[401,403].includes(response.status)?'jev-key-rejected':response.status===429?'jev-check-rate-limited':'jev-check-unavailable';return {status:'failed',code,message:messages[code]};
|
|
74
|
+
}catch{return {status:'failed',code:'jev-check-unavailable',message:messages['jev-check-unavailable']};}
|
|
75
|
+
finally{clearTimeout(timer);controller.abort();}
|
|
76
|
+
}
|
|
77
|
+
export async function checkJevConnection(options={}){
|
|
78
|
+
const apiKey=resolveJevApiKey(options);
|
|
79
|
+
if(!apiKey)return {...jevCredentialStatus(options),status:'failed',code:'jev-credential-missing',message:messages['jev-credential-missing']};
|
|
80
|
+
return {...jevCredentialStatus(options),...await authenticate(apiKey,options)};
|
|
81
|
+
}
|
|
82
|
+
// Internal cache identity includes physical file identity and exact bytes; it
|
|
83
|
+
// is never included in status, diagnostics or model-facing credential tools.
|
|
84
|
+
export function jevCredentialIdentity(options={}) {
|
|
85
|
+
const override=environment(options);if(override)return createHash('sha256').update('environment '+override).digest('hex');
|
|
86
|
+
const value=stored(options);return createHash('sha256').update(value.location+' '+value.signature).digest('hex');
|
|
87
|
+
}
|
|
88
|
+
export async function configureJevCredentials(input,options={}){
|
|
89
|
+
request(input,['confirmed','expectedRevision','apiKey']);const apiKey=key(input.apiKey);environment(options);const previous=predecessor(input,options);
|
|
90
|
+
const check=await authenticate(apiKey,options);if(check.status!=='verified')fail(check.code);
|
|
91
|
+
mutation(options,()=>{
|
|
92
|
+
predecessor(input,options,previous);const {file}=paths(options,true),temporary=file+'.'+randomUUID()+'.tmp';
|
|
93
|
+
try{writeFileSync(temporary,JSON.stringify({schema:'ewai.jev-credential/v1',revision:randomUUID(),apiKey})+'\n',{flag:'wx',mode:0o600});paths(options);predecessor(input,options,previous);renameSync(temporary,file);}
|
|
94
|
+
catch(error){if(error.code?.startsWith('jev-'))throw error;fail('jev-credential-unsafe-storage');}
|
|
95
|
+
finally{if(stat(temporary))unlinkSync(temporary);}
|
|
96
|
+
});
|
|
97
|
+
return {...jevCredentialStatus(options),check,message:'Jev key checked and saved for your account on this computer. No work started.'};
|
|
98
|
+
}
|
|
99
|
+
export function removeJevCredentials(input,options={}){
|
|
100
|
+
request(input,['confirmed','expectedRevision']);environment(options);const previous=predecessor(input,options);
|
|
101
|
+
mutation(options,()=>{const current=predecessor(input,options,previous);if(current.value)unlinkSync(paths(options).file);});
|
|
102
|
+
const status=jevCredentialStatus(options);return {...status,message:status.source==='environment'?'Saved key removed. TYPESAFE_API_KEY still supplies the active credential.':'Saved Jev key removed. No work started.'};
|
|
103
|
+
}
|
|
104
|
+
export function safeJevCredentialError(error){const code=Object.hasOwn(messages,error?.code)?error.code:'jev-credential-input';return {error:messages[code],code};}
|
package/src/jev.mjs
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { constants,openSync,closeSync,readFileSync,writeFileSync,lstatSync,fstatSync,realpathSync,mkdirSync,renameSync,unlinkSync,rmSync } from 'node:fs';
|
|
2
|
+
import { resolve,dirname,relative } from 'node:path';
|
|
3
|
+
import { randomUUID,createHash } from 'node:crypto';
|
|
4
|
+
import { performance } from 'node:perf_hooks';
|
|
5
|
+
import { resolveJevApiKey,jevCredentialIdentity,jevCredentialStatus } from './jev-credentials.mjs';
|
|
6
|
+
import {resolveOpenaiApiKey,openaiCredentialIdentity,openaiCredentialStatus} from './openai-credentials.mjs';
|
|
7
|
+
import {OPENAI_DECISIONS_MODEL,buildOpenaiDecisionPayload,normaliseOpenaiDecisionResponse,openaiDecisionUsage} from './openai-decisions.mjs';
|
|
8
|
+
|
|
9
|
+
export const JEV_MODEL='jev-1.13.0';
|
|
10
|
+
export const JEV_USE_CASES=Object.freeze(['context','personas','skill','claim','failure','impact','tests']);
|
|
11
|
+
const defaultPolicy=Object.freeze({mode:'off',provider:'auto',openaiConsent:false,useCases:['context','personas'],cloudConsent:false,maxCalls:100,maxInputTokens:100000,timeoutMs:3000,minConfidence:0.65});
|
|
12
|
+
const sources=new Set(['public','synthetic','cloud-approved','metadata-only']);
|
|
13
|
+
const validId=value=>/^[-a-zA-Z0-9_.]{1,80}$/.test(value)&&!['__proto__','constructor','prototype'].includes(value);
|
|
14
|
+
const sensitive=/(?:apikey_[A-Za-z0-9_-]{16,}|xai-[A-Za-z0-9_-]{16,}|sk-[A-Za-z0-9_-]{16,}|AKIA[A-Z0-9]{16}|-----BEGIN[^\n]*PRIVATE KEY-----|authorization\s*[:=]|(?:password|api[_-]?key|access[_-]?token)["']?\s*[:=]\s*["']?\S{4,})/i;
|
|
15
|
+
function sensitiveInput(value,depth=0){
|
|
16
|
+
if(depth>12)return true;
|
|
17
|
+
if(typeof value==='string'){
|
|
18
|
+
if(sensitive.test(value)||sensitive.test(value.replace(/\\u([a-f0-9]{4})/gi,(_,hex)=>String.fromCharCode(parseInt(hex,16))).replace(/\\["\\]/g,'')))return true;
|
|
19
|
+
try{const decoded=JSON.parse(value);if(typeof decoded!=='number'&&typeof decoded!=='boolean'&&decoded!==null&&decoded!==value)return sensitiveInput(decoded,depth+1);}catch{/* Ordinary text is checked above. */}
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
if(value&&typeof value==='object')return Object.entries(value).some(([key,item])=>/^(?:password|api[_-]?key|access[_-]?token|authorization)$/i.test(key)||sensitiveInput(item,depth+1));
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
const digest=value=>createHash('sha256').update(JSON.stringify(value)).digest('hex');
|
|
26
|
+
const plain=value=>Boolean(value&&typeof value==='object'&&!Array.isArray(value));
|
|
27
|
+
const own=(value,fields)=>plain(value)&&Object.keys(value).every(k=>fields.includes(k));
|
|
28
|
+
function fail(code,message){const error=new Error(message);error.code=code;error.statusCode=code==='jev-settings-stale'||code==='jev-storage-busy'?409:400;throw error;}
|
|
29
|
+
function invalid(){fail('jev-input','Use a supported, bounded Jev request.');}
|
|
30
|
+
function stat(path){try{return lstatSync(path);}catch(e){if(e.code==='ENOENT')return null;fail('jev-storage','Jev local state could not be read safely.');}}
|
|
31
|
+
function owned(s){return s&&!s.isSymbolicLink()&&(!process.getuid||s.uid===process.getuid());}
|
|
32
|
+
|
|
33
|
+
// No source or prompt is stored here. Every file and ancestor is checked before
|
|
34
|
+
// no-follow reads/atomic writes; unsafe operational state prevents paid calls.
|
|
35
|
+
function pathFor(root,name,create=false){
|
|
36
|
+
const base=realpathSync(resolve(root));
|
|
37
|
+
const folders=name==='settings'?[resolve(base,'.ewai-pipeline')]:[resolve(base,'.ewai-pipeline'),resolve(base,'.ewai-pipeline/runtime'),resolve(base,'.ewai-pipeline/runtime/jev')];
|
|
38
|
+
for(const path of folders){let s=stat(path);if(!s&&create){mkdirSync(path,{mode:0o700});s=stat(path);}if(s&&(!owned(s)||!s.isDirectory()||(s.mode&0o022)))fail('jev-storage','Jev local state could not be read safely.');}
|
|
39
|
+
return resolve(folders.at(-1),name==='settings'?'jev.json':name+'.json');
|
|
40
|
+
}
|
|
41
|
+
function read(root,name,fallback){
|
|
42
|
+
const path=pathFor(root,name),s=stat(path);if(!s)return structuredClone(fallback);
|
|
43
|
+
if(!owned(s)||!s.isFile()||s.nlink!==1||(s.mode&0o077)||s.size>1048576)fail('jev-storage','Jev local state could not be read safely.');
|
|
44
|
+
let fd;try{fd=openSync(path,constants.O_RDONLY|constants.O_NOFOLLOW);const bytes=readFileSync(fd),opened=fstatSync(fd),current=stat(path);if(!current||opened.ino!==s.ino||opened.dev!==s.dev||opened.nlink!==1||current.ino!==s.ino||current.mtimeMs!==s.mtimeMs||opened.mtimeMs!==s.mtimeMs||bytes.length>1048576)fail('jev-storage','Jev local state changed while reading.');return JSON.parse(bytes);}catch(e){if(e.code?.startsWith('jev-'))throw e;fail('jev-storage','Jev local state could not be read safely.');}finally{if(fd!==undefined)closeSync(fd);}
|
|
45
|
+
}
|
|
46
|
+
function write(root,name,value){
|
|
47
|
+
const path=pathFor(root,name,true),before=stat(path);if(before&&(!owned(before)||!before.isFile()||before.nlink!==1||(before.mode&0o077)))fail('jev-storage','Jev local state could not be written safely.');
|
|
48
|
+
const temporary=path+'.'+randomUUID()+'.tmp';
|
|
49
|
+
try{writeFileSync(temporary,JSON.stringify(value)+'\n',{flag:'wx',mode:0o600});pathFor(root,name,true);renameSync(temporary,path);}finally{if(stat(temporary))unlinkSync(temporary);}
|
|
50
|
+
}
|
|
51
|
+
function locked(root,fn){
|
|
52
|
+
const path=pathFor(root,'state',true),lock=resolve(dirname(path),'.mutation-lock');
|
|
53
|
+
try{mkdirSync(lock,{mode:0o700});}catch(e){fail(e.code==='EEXIST'?'jev-storage-busy':'jev-storage','Another Jev state change is running or local state is unavailable.');}
|
|
54
|
+
const identity=lstatSync(lock);
|
|
55
|
+
try{return fn();}finally{const current=stat(lock);if(owned(current)&¤t.ino===identity.ino&¤t.dev===identity.dev)rmSync(lock,{recursive:true});}
|
|
56
|
+
}
|
|
57
|
+
function policy(value){
|
|
58
|
+
// Historical policies disclosed only TypeSafe. Reading or saving that shape
|
|
59
|
+
// never opts the account into a second vendor or changes its preference.
|
|
60
|
+
if(plain(value)&&!Object.hasOwn(value,'provider')&&!Object.hasOwn(value,'openaiConsent'))value={...value,provider:'jev',openaiConsent:false};
|
|
61
|
+
if(!own(value,Object.keys(defaultPolicy))||Object.keys(value).length!==Object.keys(defaultPolicy).length||!['off','shadow','active'].includes(value.mode)||!['auto','openai','jev'].includes(value.provider)||typeof value.openaiConsent!=='boolean'||typeof value.cloudConsent!=='boolean'||!Array.isArray(value.useCases)||value.useCases.length>7||new Set(value.useCases).size!==value.useCases.length||value.useCases.some(v=>!JEV_USE_CASES.includes(v)))invalid();
|
|
62
|
+
for(const [key,min,max]of [['maxCalls',1,1000],['maxInputTokens',2000,1000000],['timeoutMs',100,10000]])if(!Number.isSafeInteger(value[key])||value[key]<min||value[key]>max)invalid();
|
|
63
|
+
if(!Number.isFinite(value.minConfidence)||value.minConfidence<0.5||value.minConfidence>1)invalid();
|
|
64
|
+
if(value.mode!=='off'&&!(value.provider==='openai'?value.openaiConsent:value.provider==='jev'?value.cloudConsent:value.openaiConsent||value.cloudConsent))fail('jev-consent','Confirm the selected provider text disclosure consent before enabling decision assistance.');
|
|
65
|
+
return structuredClone(value);
|
|
66
|
+
}
|
|
67
|
+
function selectedProvider(root,settings,options={}){
|
|
68
|
+
const order=settings.policy.provider==='auto'?['openai','jev']:[settings.policy.provider];
|
|
69
|
+
for(const provider of order){
|
|
70
|
+
if(!(provider==='openai'?settings.policy.openaiConsent:settings.policy.cloudConsent))continue;
|
|
71
|
+
try{
|
|
72
|
+
const o={...options,projectRoot:root},apiKey=provider==='openai'?resolveOpenaiApiKey(o):resolveJevApiKey(o);if(!apiKey)continue;
|
|
73
|
+
return {provider,apiKey,model:provider==='openai'?OPENAI_DECISIONS_MODEL:JEV_MODEL,endpoint:provider==='openai'?'https://api.openai.com/v1/decisions':'https://api.typesafe.ai/v1/systemone',price:provider==='openai'?0.10:0.042,identity:provider==='openai'?openaiCredentialIdentity(o):jevCredentialIdentity(o)};
|
|
74
|
+
}catch(error){if(settings.policy.provider!=='auto')throw error;/* An unusable, unselected credential must not disable another authorised vendor. */}
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
const selectedIdentity=selected=>selected?digest({provider:selected.provider,model:selected.model,credential:selected.identity,keyDigest:digest(selected.apiKey)}):'local';
|
|
79
|
+
export function decisionCredentialIdentity(root,options={}){
|
|
80
|
+
const settings=readJevSettings(root);
|
|
81
|
+
if(settings.policy.mode==='off')return 'local';
|
|
82
|
+
// Credential failures cannot interrupt optional persona/context assistance.
|
|
83
|
+
// A changed identity still invalidates any advice from an earlier request.
|
|
84
|
+
try{return selectedIdentity(selectedProvider(root,settings,options));}catch{return 'credential-unavailable';}
|
|
85
|
+
}
|
|
86
|
+
export function readDecisionCredentials(root,options={}){
|
|
87
|
+
const readStatus=(provider,fn)=>{try{return fn({...options,projectRoot:root});}catch{return {provider,configured:null,saved:null,source:'unavailable',storageSupported:false,storageIssue:'credential-unavailable',authority:'credentials-only'};}};
|
|
88
|
+
return {credentials:readStatus('jev',jevCredentialStatus),openaiCredentials:readStatus('openai',openaiCredentialStatus)};
|
|
89
|
+
}
|
|
90
|
+
export function readJevSettings(root){
|
|
91
|
+
const value=read(root,'settings',{schema:'ewai.jev-settings/v1',revision:'missing',policy:structuredClone(defaultPolicy)});
|
|
92
|
+
if(!own(value,['schema','revision','policy'])||value.schema!=='ewai.jev-settings/v1'||typeof value.revision!=='string'||!(/^[a-f0-9-]{36}$/.test(value.revision)||value.revision==='missing'))invalid();
|
|
93
|
+
return {...value,policy:policy(value.policy)};
|
|
94
|
+
}
|
|
95
|
+
export function saveJevSettings(root,input){
|
|
96
|
+
if(!own(input,['confirmed','expectedRevision','policy'])||input.confirmed!==true||typeof input.expectedRevision!=='string')invalid();
|
|
97
|
+
const next=policy(input.policy);
|
|
98
|
+
return locked(root,()=>{const current=readJevSettings(root);if(current.revision!==input.expectedRevision)fail('jev-settings-stale','Jev settings changed. Refresh before saving again.');const value={schema:'ewai.jev-settings/v1',revision:randomUUID(),policy:next};write(root,'settings',value);return value;});
|
|
99
|
+
}
|
|
100
|
+
function emptyState(revision){return{schema:'ewai.jev-runtime/v1',revision,calls:0,reservedTokens:0,inputTokens:0,estimatedUSD:0,unknownUsage:false,budgetBlocked:false,observations:[],cache:{},pending:{}};}
|
|
101
|
+
function state(root,revision){const value=read(root,'state',emptyState(revision));if(!Object.hasOwn(value,'pending'))value.pending={};if(!Object.hasOwn(value,'estimatedUSD'))value.estimatedUSD=value.inputTokens*0.042/1000000;if(!plain(value.pending)||Object.keys(value.pending).length>1000||Object.entries(value.pending).some(([id,p])=>!validId(id)||!own(p,['settingsRevision'])||typeof p.settingsRevision!=='string'))fail('jev-storage','Jev pending usage could not be read safely.');if(value.schema!=='ewai.jev-runtime/v1'||!Number.isSafeInteger(value.calls)||value.calls<0||!Number.isSafeInteger(value.reservedTokens)||value.reservedTokens<0||!Number.isSafeInteger(value.inputTokens)||value.inputTokens<0||!Number.isFinite(value.estimatedUSD)||value.estimatedUSD<0||typeof value.unknownUsage!=='boolean'||typeof value.budgetBlocked!=='boolean'||!Array.isArray(value.observations)||value.observations.length>500||!plain(value.cache))fail('jev-storage','Jev measurements could not be read safely.');return value.revision===revision?value:{...value,revision,calls:0,reservedTokens:0,budgetBlocked:false,cache:{},pending:{},unknownUsage:value.unknownUsage||Object.keys(value.pending).length>0};}
|
|
102
|
+
function observe(root,revision,observation,cacheEntry){
|
|
103
|
+
locked(root,()=>{const current=readJevSettings(root),value=state(root,current.revision);delete value.pending[observation.id];value.observations.push({...observation,id:observation.id??randomUUID(),settingsRevision:revision});value.observations=value.observations.slice(-500);if(observation.providerCall){if(observation.inputTokens===null){value.unknownUsage=true;if(current.revision===revision)value.budgetBlocked=true;}else {value.inputTokens+=observation.inputTokens;value.estimatedUSD+=observation.estimatedUSD;}}if(observation.reason==='token-reservation-exceeded'&¤t.revision===revision)value.budgetBlocked=true;if(cacheEntry&¤t.revision===revision){value.cache[cacheEntry.key]=cacheEntry.value;while(Buffer.byteLength(JSON.stringify(value.cache))>131072)delete value.cache[Object.keys(value.cache)[0]];}write(root,'state',value);});
|
|
104
|
+
}
|
|
105
|
+
export function readJevMeasurements(root){
|
|
106
|
+
const settings=readJevSettings(root),value=state(root,settings.revision),latencies=value.observations.filter(o=>o.providerCall).map(o=>o.elapsedMs).sort((a,b)=>a-b);
|
|
107
|
+
return {schema:'ewai.jev-measurements/v1',revision:settings.revision,budget:{callsUsed:value.calls,maxCalls:settings.policy.maxCalls,reservedTokens:value.reservedTokens,maxInputTokens:settings.policy.maxInputTokens},summary:{unresolvedCalls:Object.keys(value.pending).length,historyLimit:500,historyScope:'latest 500 observation events; input/cost totals cover all recorded provider calls',providerCalls:value.observations.filter(o=>o.providerCall).length,cacheHits:value.observations.filter(o=>o.cacheHit).length,fallbacks:value.observations.filter(o=>o.status==='fallback').length,inputTokens:value.unknownUsage||Object.keys(value.pending).length?null:value.inputTokens,estimatedUSD:value.unknownUsage||Object.keys(value.pending).length?null:value.estimatedUSD,medianMs:latencies.length?latencies[Math.floor(latencies.length/2)]:null,p95Ms:latencies.length?latencies[Math.min(latencies.length-1,Math.ceil(latencies.length*0.95)-1)]:null,downstreamSavings:null,priceSource:'Base-rate estimates: TypeSafe models and OpenAI Decisions guide; OpenAI estimate precedes free cache adjustments and regional/long-context premiums',priceUSDPerMillionInputTokens:null,priceByProvider:{jev:0.042,openai:0.10}},observations:value.observations};
|
|
108
|
+
}
|
|
109
|
+
export function recordJevEffect(root,decision,details){
|
|
110
|
+
if(!decision?.measurement?.id)return;
|
|
111
|
+
if(!own(details,['effect','baselineIds','suggestedIds','actualIds'])||!['unchanged','context-ordering','persona-additions','review-addition'].includes(details.effect))invalid();
|
|
112
|
+
for(const ids of [details.baselineIds,details.suggestedIds,details.actualIds])if(!Array.isArray(ids)||ids.length>64||ids.some(id=>!validId(id)))invalid();
|
|
113
|
+
const revision=readJevSettings(root).revision;
|
|
114
|
+
observe(root,revision,{id:randomUUID(),decisionId:decision.measurement.id,useCase:decision.measurement.useCase,provider:decision.measurement.provider??'jev',model:decision.measurement.model??JEV_MODEL,mode:decision.mode,status:'effect-recorded',providerCall:false,cacheHit:false,elapsedMs:0,inputTokens:0,outputTokens:0,estimatedUSD:0,downstreamSavings:null,...details});
|
|
115
|
+
}
|
|
116
|
+
function question(q){
|
|
117
|
+
if(!own(q,['type','instructions','criteria'])||!['choice','score','noul'].includes(q.type)||typeof q.instructions!=='string'||!q.instructions.trim()||q.instructions.length>2000)invalid();
|
|
118
|
+
if(q.type==='choice'){if(!plain(q.criteria)||Object.keys(q.criteria).length<2||Object.keys(q.criteria).length>64||Object.entries(q.criteria).some(([k,v])=>!validId(k)||typeof v!=='string'||v.length>1000))invalid();}
|
|
119
|
+
if(q.type==='score'&&(!Array.isArray(q.criteria)||q.criteria.length<2||q.criteria.length>10||q.criteria.some(v=>typeof v!=='string'||!v.trim()||v.length>1000)))invalid();
|
|
120
|
+
if(q.type==='noul'&&q.criteria!==undefined&&(!own(q.criteria,['true','false'])||Object.values(q.criteria).some(v=>typeof v!=='string'||v.length>1000)))invalid();
|
|
121
|
+
return q;
|
|
122
|
+
}
|
|
123
|
+
function usage(body){const u=body?.usage;return own(u,['input_tokens','output_tokens'])&&Number.isSafeInteger(u.input_tokens)&&u.input_tokens>=0&&Number.isSafeInteger(u.output_tokens)&&u.output_tokens>=0?{inputTokens:u.input_tokens,outputTokens:u.output_tokens}:null;}
|
|
124
|
+
function validatedAnswers(body,questions){
|
|
125
|
+
if(body?.model!==JEV_MODEL||!plain(body.answers)||Object.keys(body.answers).length!==Object.keys(questions).length)throw Error('invalid-response');
|
|
126
|
+
const output={};
|
|
127
|
+
for(const [id,q]of Object.entries(questions)){
|
|
128
|
+
const a=body.answers[id];if(!plain(a)||a.type!==q.type)throw Error('invalid-response');
|
|
129
|
+
if(q.type==='noul'){if(!own(a,['type','noul'])||!Number.isFinite(a.noul)||a.noul<0||a.noul>1)throw Error('invalid-response');output[id]={type:'noul',noul:a.noul};continue;}
|
|
130
|
+
const keys=q.type==='choice'?Object.keys(q.criteria):q.criteria.map((_,i)=>String(i));
|
|
131
|
+
if(!own(a,q.type==='choice'?['type','choice','probabilities','confidence']:['type','score','legend','probabilities','confidence'])||!plain(a.probabilities)||Object.keys(a.probabilities).length!==keys.length||!keys.every(k=>Object.hasOwn(a.probabilities,k))||!Object.values(a.probabilities).every(p=>Number.isFinite(p)&&p>=0&&p<=1)||Math.abs(Object.values(a.probabilities).reduce((s,p)=>s+p,0)-1)>0.001||!Number.isFinite(a.confidence)||a.confidence<0||a.confidence>1)throw Error('invalid-response');
|
|
132
|
+
if(q.type==='choice'){
|
|
133
|
+
if(!keys.includes(a.choice)||a.probabilities[a.choice]+1e-9<Math.max(...Object.values(a.probabilities)))throw Error('invalid-response');output[id]={type:'choice',choice:a.choice,probabilities:a.probabilities,confidence:a.confidence};
|
|
134
|
+
}else{
|
|
135
|
+
const expected=keys.reduce((s,k)=>s+Number(k)*a.probabilities[k],0);
|
|
136
|
+
if(!Number.isFinite(a.score)||a.score<0||a.score>q.criteria.length-1||Math.abs(a.score-expected)>0.01||!plain(a.legend)||Object.keys(a.legend).length!==keys.length||!keys.every(k=>a.legend[k]===q.criteria[Number(k)]))throw Error('invalid-response');output[id]={type:'score',score:a.score,confidence:a.confidence,probabilities:a.probabilities,legend:a.legend};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return output;
|
|
140
|
+
}
|
|
141
|
+
async function boundedResponse(response){
|
|
142
|
+
const chunks=[];let size=0;
|
|
143
|
+
if(!response.body)throw Error('invalid-response');
|
|
144
|
+
for await(const chunk of response.body){size+=chunk.length;if(size>65536)throw Error('response-too-large');chunks.push(chunk);}
|
|
145
|
+
try{return JSON.parse(Buffer.concat(chunks).toString('utf8'));}catch{throw Error('invalid-response');}
|
|
146
|
+
}
|
|
147
|
+
export async function evaluateJev(root,input,options={}){
|
|
148
|
+
const started=performance.now();let settings,selected,apiKey,reservation,controller,timer,watcher,providerUsage=null,dispatched=false;
|
|
149
|
+
const fallback=(reason,status='fallback',extra={})=>({schema:'ewai.jev-decision/v1',status,reason,mode:settings?.policy.mode??'off',authority:'advisory',...extra});
|
|
150
|
+
try{
|
|
151
|
+
settings=readJevSettings(root);
|
|
152
|
+
if(settings.policy.mode==='off')return fallback('off','disabled');
|
|
153
|
+
if(!own(input,['useCase','source','state','questions'])||!JEV_USE_CASES.includes(input.useCase))invalid();
|
|
154
|
+
if(!settings.policy.useCases.includes(input.useCase))return fallback('use-case-disabled','disabled');
|
|
155
|
+
if(!sources.has(input.source))return fallback('source-ineligible');
|
|
156
|
+
|
|
157
|
+
if(!plain(input.questions)||Object.keys(input.questions).length<1||Object.keys(input.questions).length>32||Object.keys(input.questions).some(id=>!validId(id)))invalid();
|
|
158
|
+
Object.values(input.questions).forEach(question);
|
|
159
|
+
const neutralPayload={model:JEV_MODEL,state:input.state,questions:input.questions};
|
|
160
|
+
if(sensitiveInput(neutralPayload))return fallback('sensitive-input');
|
|
161
|
+
selected=selectedProvider(root,settings,options);if(!selected)return fallback('missing-key');apiKey=selected.apiKey;
|
|
162
|
+
const payload=selected.provider==='openai'?buildOpenaiDecisionPayload(input.state,input.questions):neutralPayload,serialised=JSON.stringify(payload);
|
|
163
|
+
if(!['string','object'].includes(typeof input.state)||input.state===null||Buffer.byteLength(serialised)>16000)invalid();
|
|
164
|
+
if(sensitiveInput(payload))return fallback('sensitive-input');
|
|
165
|
+
|
|
166
|
+
if(serialised.includes(apiKey))return fallback('sensitive-input');
|
|
167
|
+
// UTF-8 bytes plus headroom is a conservative local reservation, not a
|
|
168
|
+
// claimed token count. Unknown/over-reservation usage exhausts the budget.
|
|
169
|
+
const namespace=selectedIdentity(selected);
|
|
170
|
+
const reserve=Buffer.byteLength(serialised)+1024,cacheKey=digest({revision:settings.revision,credential:namespace,provider:selected.provider,model:selected.model,source:input.source,useCase:input.useCase,payload});
|
|
171
|
+
reservation=locked(root,()=>{
|
|
172
|
+
if(digest(readJevSettings(root))!==digest(settings))return {reason:'policy-changed'};
|
|
173
|
+
const value=state(root,settings.revision),cached=value.cache[cacheKey];
|
|
174
|
+
if(cached){try{const answers=Object.fromEntries(Object.entries(cached.answers).map(([id,a])=>[id,a.type==='score'?{...a,legend:Object.fromEntries(input.questions[id].criteria.map((label,index)=>[String(index),label]))}:a]));validatedAnswers({model:JEV_MODEL,answers},input.questions);return {cached:{answers}};}catch{return {reason:'invalid-cache'};}}
|
|
175
|
+
if(value.budgetBlocked||Object.keys(value.pending).length||value.calls>=settings.policy.maxCalls||value.reservedTokens+reserve>settings.policy.maxInputTokens)return {reason:'budget-exhausted'};
|
|
176
|
+
const id=randomUUID();value.calls++;value.reservedTokens+=reserve;value.pending[id]={settingsRevision:settings.revision};write(root,'state',value);return {id,reserve,cacheKey};
|
|
177
|
+
});
|
|
178
|
+
if(reservation.reason)return fallback(reservation.reason);
|
|
179
|
+
const stillCurrent=()=>digest(readJevSettings(root))===digest(settings)&&decisionCredentialIdentity(root,options)===namespace;
|
|
180
|
+
if(reservation.cached){
|
|
181
|
+
if(!stillCurrent())return fallback('policy-changed');
|
|
182
|
+
const measurement={id:randomUUID(),useCase:input.useCase,provider:selected?.provider??null,model:selected?.model??null,mode:settings.policy.mode,status:'suggested',providerCall:false,cacheHit:true,elapsedMs:Math.round(performance.now()-started),inputTokens:0,outputTokens:0,estimatedUSD:0,effect:settings.policy.mode==='shadow'?'shadow':'advisory',downstreamSavings:null};
|
|
183
|
+
observe(root,settings.revision,measurement);return {...fallback('cached','suggested'),answers:reservation.cached.answers,cacheHit:true,measurement};
|
|
184
|
+
}
|
|
185
|
+
if(!stillCurrent())throw Error('policy-changed');
|
|
186
|
+
controller=new AbortController();let rejectDeadline;
|
|
187
|
+
const deadline=new Promise((_,reject)=>{rejectDeadline=reject;timer=setTimeout(()=>{controller.abort();reject(Error('timeout'));},settings.policy.timeoutMs);});
|
|
188
|
+
watcher=setInterval(()=>{try{if(!stillCurrent()){controller.abort();rejectDeadline(Error('policy-changed'));}}catch{controller.abort();rejectDeadline(Error('policy-changed'));}},100);
|
|
189
|
+
const operation=(async()=>{
|
|
190
|
+
dispatched=true;const response=await(options.fetchImpl??fetch)(selected.endpoint,{method:'POST',headers:{authorization:'Bearer '+apiKey,'content-type':'application/json'},body:serialised,redirect:'manual',signal:controller.signal});
|
|
191
|
+
if(!response.ok){if(response.body)void response.body.cancel().catch(()=>{});throw Error('http-'+response.status);}
|
|
192
|
+
return boundedResponse(response);
|
|
193
|
+
})();
|
|
194
|
+
const body=await Promise.race([operation,deadline]);providerUsage=selected.provider==='openai'?openaiDecisionUsage(body):usage(body);
|
|
195
|
+
if(!providerUsage)throw Error('missing-usage');
|
|
196
|
+
if(providerUsage.inputTokens>reserve)throw Error('token-reservation-exceeded');
|
|
197
|
+
const answers=validatedAnswers(selected.provider==='openai'?normaliseOpenaiDecisionResponse(body,input.questions):body,input.questions);
|
|
198
|
+
if(!stillCurrent())throw Error('policy-changed');
|
|
199
|
+
if(Object.values(answers).some(a=>a.type!=='noul'&&a.confidence<settings.policy.minConfidence))throw Error('low-confidence');
|
|
200
|
+
const measurement={id:reservation.id,useCase:input.useCase,provider:selected?.provider??null,model:selected?.model??null,mode:settings.policy.mode,status:'suggested',providerCall:true,cacheHit:false,elapsedMs:Math.round(performance.now()-started),...providerUsage,estimatedUSD:providerUsage.inputTokens*selected.price/1000000,effect:settings.policy.mode==='shadow'?'shadow':'advisory',downstreamSavings:null};
|
|
201
|
+
// Choice cache carries bounded IDs and numeric probabilities/confidence,
|
|
202
|
+
// never rubrics, instructions or source bodies.
|
|
203
|
+
const cachedAnswers=Object.fromEntries(Object.entries(answers).map(([id,a])=>[id,a.type==='choice'?{...a}:a.type==='noul'?{...a}:{...a,legend:{}}]));
|
|
204
|
+
// Score legends are reconstructed from the caller's matching hashed rubric.
|
|
205
|
+
// Persist only numeric results and bounded IDs, never catalogue descriptions.
|
|
206
|
+
const cacheEntry={key:cacheKey,value:{answers:cachedAnswers}};
|
|
207
|
+
observe(root,settings.revision,measurement,cacheEntry);
|
|
208
|
+
return {...fallback('evaluated','suggested'),answers,cacheHit:false,measurement};
|
|
209
|
+
}catch(error){
|
|
210
|
+
const allowed=/^(?:timeout|policy-changed|invalid-response|refusal|response-too-large|missing-usage|token-reservation-exceeded|low-confidence|http-\d{3})$/;
|
|
211
|
+
const reason=allowed.test(error?.message??'')?error.message:error.code?.startsWith('jev-')?'local-state-or-input-unavailable':'provider-unavailable';
|
|
212
|
+
const result=fallback(reason);
|
|
213
|
+
if(reservation?.reserve){
|
|
214
|
+
const measurement={id:reservation.id,useCase:input.useCase,provider:selected?.provider??null,model:selected?.model??null,mode:settings.policy.mode,status:'fallback',reason,providerCall:dispatched,cacheHit:false,elapsedMs:Math.round(performance.now()-started),...providerUsage,inputTokens:providerUsage?.inputTokens??(dispatched?null:0),outputTokens:providerUsage?.outputTokens??(dispatched?null:0),estimatedUSD:providerUsage?providerUsage.inputTokens*selected.price/1000000:dispatched?null:0,effect:'none',downstreamSavings:null};
|
|
215
|
+
try{observe(root,settings.revision,measurement);}catch{/* No unsafe diagnostic or source is emitted. */}result.measurement=measurement;
|
|
216
|
+
}
|
|
217
|
+
return result;
|
|
218
|
+
}finally{clearTimeout(timer);clearInterval(watcher);controller?.abort();apiKey=undefined;}
|
|
219
|
+
}
|
|
220
|
+
export function safeJevError(error){return {code:error?.code?.startsWith('jev-')?error.code:'jev-input',error:error?.code?.startsWith('jev-')?error.message:'Jev could not complete this request. Refresh status and try again.'};}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// OpenAI uses the same audited private storage safeguards as Grok, with a separate key lifecycle.
|
|
2
|
+
import {constants,openSync,closeSync,fstatSync,lstatSync,realpathSync,readFileSync,mkdirSync,writeFileSync,renameSync,unlinkSync,rmSync} from 'node:fs';
|
|
3
|
+
import {homedir} from 'node:os';
|
|
4
|
+
import {resolve,relative,isAbsolute} from 'node:path';
|
|
5
|
+
import {randomUUID,createHash} from 'node:crypto';
|
|
6
|
+
|
|
7
|
+
const messages={
|
|
8
|
+
'openai-credential-invalid':'Provide a valid OpenAI API key using the private setup form or hidden terminal prompt.',
|
|
9
|
+
'openai-credential-input':'Use the supported credential action and confirm the change.',
|
|
10
|
+
'openai-credential-unsafe-storage':'Private credential storage is unsafe or unavailable. Repair its permissions or use OPENAI_API_KEY in the launching environment.',
|
|
11
|
+
'openai-credential-invalid-storage':'The saved credential could not be read safely. Repair the private credential file before continuing.',
|
|
12
|
+
'openai-credential-stale':'The saved credential changed. Refresh its status and try again.',
|
|
13
|
+
'openai-credential-busy':'Another credential change is running. Retry after it finishes.',
|
|
14
|
+
'openai-credential-missing':'No OpenAI key is configured. Use private credential setup or OPENAI_API_KEY in the launching environment.',
|
|
15
|
+
'openai-key-rejected':'The metadata authentication check was not accepted. Check the key and its models-read permission, then try again. This does not determine Decisions endpoint access. The previous saved key was retained.',
|
|
16
|
+
'openai-check-rate-limited':'OpenAI limited the authentication check. Wait before trying again. The previous saved key was retained.',
|
|
17
|
+
'openai-check-unavailable':'The authentication check could not complete. Check your connection and try again. The previous saved key was retained.',
|
|
18
|
+
};
|
|
19
|
+
function fail(code,statusCode=400){const error=new Error(messages[code]??messages['openai-credential-input']);error.code=code;error.statusCode=statusCode;throw error;}
|
|
20
|
+
function key(value){if(typeof value!=='string'||value.length>512||!/^sk-[A-Za-z0-9_-]{16,508}$/.test(value.trim()))fail('openai-credential-invalid');return value.trim();}
|
|
21
|
+
function request(input,fields){if(!input||typeof input!=='object'||Array.isArray(input)||Object.keys(input).some(k=>!fields.includes(k))||input.confirmed!==true||typeof input.expectedRevision!=='string'||input.expectedRevision.length>80)fail('openai-credential-input');}
|
|
22
|
+
function stat(path){try{return lstatSync(path);}catch(error){if(error.code==='ENOENT')return null;fail('openai-credential-unsafe-storage');}}
|
|
23
|
+
function owned(s){return s&&!s.isSymbolicLink()&&(process.getuid===undefined||s.uid===process.getuid());}
|
|
24
|
+
function paths(options={},create=false){
|
|
25
|
+
const declaredHome=resolve(options.home??homedir());
|
|
26
|
+
const root=stat(declaredHome);if(!owned(root)||!root.isDirectory()||(root.mode&0o022))fail('openai-credential-unsafe-storage');
|
|
27
|
+
let home,project;try{home=realpathSync(declaredHome);if(options.projectRoot)project=realpathSync(resolve(options.projectRoot));}catch{fail('openai-credential-unsafe-storage');}
|
|
28
|
+
const folder=resolve(home,'.ewai/credentials'),file=resolve(folder,'openai.json');
|
|
29
|
+
if(project){const rel=relative(project,file);if(rel===''||(!isAbsolute(rel)&&rel!=='..'&&!rel.startsWith('../')))fail('openai-credential-unsafe-storage');}
|
|
30
|
+
const canonicalRoot=stat(home);if(!owned(canonicalRoot)||canonicalRoot.ino!==root.ino||canonicalRoot.dev!==root.dev)fail('openai-credential-unsafe-storage');
|
|
31
|
+
for(const path of [resolve(home,'.ewai'),folder]){
|
|
32
|
+
let s=stat(path);if(!s&&create){try{mkdirSync(path,{mode:0o700});}catch(error){if(error.code!=='EEXIST')fail('openai-credential-unsafe-storage');}s=stat(path);}
|
|
33
|
+
if(s&&(!owned(s)||!s.isDirectory()||(s.mode&(path===folder?0o077:0o022))))fail('openai-credential-unsafe-storage');
|
|
34
|
+
}
|
|
35
|
+
return {folder,file,location:JSON.stringify([home,root.dev,root.ino])};
|
|
36
|
+
}
|
|
37
|
+
function stored(options={}){
|
|
38
|
+
const {file,location}=paths(options),before=stat(file);if(!before)return {value:null,signature:'missing',location};
|
|
39
|
+
if(process.platform==='win32'||!owned(before)||!before.isFile()||before.nlink!==1||(before.mode&0o077)||before.size>4096)fail('openai-credential-unsafe-storage');
|
|
40
|
+
let fd;try{
|
|
41
|
+
fd=openSync(file,constants.O_RDONLY|constants.O_NOFOLLOW);const bytes=readFileSync(fd),after=fstatSync(fd),current=stat(file);
|
|
42
|
+
if(bytes.length>4096||!current||before.ino!==after.ino||before.dev!==after.dev||before.mtimeMs!==after.mtimeMs||after.ino!==current.ino||after.mtimeMs!==current.mtimeMs)fail('openai-credential-unsafe-storage');
|
|
43
|
+
let value;try{value=JSON.parse(bytes.toString('utf8'));}catch{fail('openai-credential-invalid-storage');}
|
|
44
|
+
if(value?.schema!=='ewai.openai-credential/v1'||typeof value.revision!=='string'||!/^[a-f0-9-]{36}$/.test(value.revision)||Object.keys(value).some(k=>!['schema','revision','apiKey'].includes(k)))fail('openai-credential-invalid-storage');
|
|
45
|
+
try{key(value.apiKey);}catch{fail('openai-credential-invalid-storage');}
|
|
46
|
+
return {value,location,signature:JSON.stringify([after.dev,after.ino,after.mtimeMs,after.ctimeMs,createHash('sha256').update(bytes).digest('hex')])};
|
|
47
|
+
}catch(error){if(error.code?.startsWith('openai-'))throw error;fail('openai-credential-unsafe-storage');}finally{if(fd!==undefined)closeSync(fd);}
|
|
48
|
+
}
|
|
49
|
+
function environment(options){const env=options.env??process.env;return env.OPENAI_API_KEY?key(env.OPENAI_API_KEY):null;}
|
|
50
|
+
export function openaiCredentialStatus(options={}){
|
|
51
|
+
const override=environment(options);let value,storageIssue=null;
|
|
52
|
+
try{({value}=stored(options));}catch(error){if(!override)throw error;storageIssue='unsafe-saved-storage';}
|
|
53
|
+
|
|
54
|
+
return {schema:'ewai.openai-credential-status/v1',provider:'openai',configured:Boolean(override||value),saved:storageIssue?null:Boolean(value),storageIssue,source:override?'environment':value?'saved':'missing',revision:value?.revision??(storageIssue?'unavailable':'missing'),storage:'owner-only-local-file',encrypted:false,storageSupported:!storageIssue&&process.platform!=='win32',authority:'credentials-only'};
|
|
55
|
+
}
|
|
56
|
+
export function resolveOpenaiApiKey(options={}){const override=environment(options);return override??stored(options).value?.apiKey??null;}
|
|
57
|
+
function predecessor(input,options,expected){const current=stored(options);if((current.value?.revision??'missing')!==input.expectedRevision||(expected&&(current.signature!==expected.signature||current.location!==expected.location)))fail('openai-credential-stale',409);return current;}
|
|
58
|
+
function mutation(options,operation){
|
|
59
|
+
if(process.platform==='win32')fail('openai-credential-unsafe-storage');
|
|
60
|
+
const {folder}=paths(options,true),lock=resolve(folder,'.openai.lock');
|
|
61
|
+
try{mkdirSync(lock,{mode:0o700});}catch(error){fail(error.code==='EEXIST'?'openai-credential-busy':'openai-credential-unsafe-storage',409);}
|
|
62
|
+
const identity=lstatSync(lock);
|
|
63
|
+
try{return operation();}finally{const current=stat(lock);if(owned(current)&¤t.isDirectory()&¤t.ino===identity.ino)rmSync(lock,{recursive:true});}
|
|
64
|
+
}
|
|
65
|
+
async function authenticate(apiKey,options={}){
|
|
66
|
+
const controller=new AbortController();let timer;
|
|
67
|
+
try{
|
|
68
|
+
const timeout=Math.min(10000,Math.max(10,options.timeoutMs??10000));
|
|
69
|
+
const response=await Promise.race([(options.fetchImpl??fetch)('https://api.openai.com/v1/models',{method:'GET',headers:{authorization:'Bearer '+apiKey},redirect:'manual',signal:controller.signal}),new Promise((_,reject)=>{timer=setTimeout(()=>{controller.abort();reject(Error('deadline'));},timeout);})]);
|
|
70
|
+
// Discard provider bodies, including errors. They are not safe evidence.
|
|
71
|
+
if(response.body)void response.body.cancel().catch(()=>{});
|
|
72
|
+
if(response.status===200)return {status:'verified',code:'openai-authentication-verified',message:'OpenAI authentication check passed. No output was generated; Decisions endpoint access, credit and decision quality remain unverified.'};
|
|
73
|
+
const code=[401,403].includes(response.status)?'openai-key-rejected':response.status===429?'openai-check-rate-limited':'openai-check-unavailable';return {status:'failed',code,message:messages[code]};
|
|
74
|
+
}catch{return {status:'failed',code:'openai-check-unavailable',message:messages['openai-check-unavailable']};}
|
|
75
|
+
finally{clearTimeout(timer);controller.abort();}
|
|
76
|
+
}
|
|
77
|
+
export async function checkOpenaiConnection(options={}){
|
|
78
|
+
const apiKey=resolveOpenaiApiKey(options);
|
|
79
|
+
if(!apiKey)return {...openaiCredentialStatus(options),status:'failed',code:'openai-credential-missing',message:messages['openai-credential-missing']};
|
|
80
|
+
return {...openaiCredentialStatus(options),...await authenticate(apiKey,options)};
|
|
81
|
+
}
|
|
82
|
+
// Internal cache identity includes physical file identity and exact bytes; it
|
|
83
|
+
// is never included in status, diagnostics or model-facing credential tools.
|
|
84
|
+
export function openaiCredentialIdentity(options={}) {
|
|
85
|
+
const override=environment(options);if(override)return createHash('sha256').update('environment '+override).digest('hex');
|
|
86
|
+
const value=stored(options);return createHash('sha256').update(value.location+' '+value.signature).digest('hex');
|
|
87
|
+
}
|
|
88
|
+
export async function configureOpenaiCredentials(input,options={}){
|
|
89
|
+
request(input,['confirmed','expectedRevision','apiKey']);const apiKey=key(input.apiKey);environment(options);const previous=predecessor(input,options);
|
|
90
|
+
const check=await authenticate(apiKey,options);if(check.status!=='verified')fail(check.code);
|
|
91
|
+
mutation(options,()=>{
|
|
92
|
+
predecessor(input,options,previous);const {file}=paths(options,true),temporary=file+'.'+randomUUID()+'.tmp';
|
|
93
|
+
try{writeFileSync(temporary,JSON.stringify({schema:'ewai.openai-credential/v1',revision:randomUUID(),apiKey})+'\n',{flag:'wx',mode:0o600});paths(options);predecessor(input,options,previous);renameSync(temporary,file);}
|
|
94
|
+
catch(error){if(error.code?.startsWith('openai-'))throw error;fail('openai-credential-unsafe-storage');}
|
|
95
|
+
finally{if(stat(temporary))unlinkSync(temporary);}
|
|
96
|
+
});
|
|
97
|
+
return {...openaiCredentialStatus(options),check,message:'OpenAI key checked and saved for your account on this computer. No work started.'};
|
|
98
|
+
}
|
|
99
|
+
export function removeOpenaiCredentials(input,options={}){
|
|
100
|
+
request(input,['confirmed','expectedRevision']);environment(options);const previous=predecessor(input,options);
|
|
101
|
+
mutation(options,()=>{const current=predecessor(input,options,previous);if(current.value)unlinkSync(paths(options).file);});
|
|
102
|
+
const status=openaiCredentialStatus(options);return {...status,message:status.source==='environment'?'Saved key removed. OPENAI_API_KEY still supplies the active credential.':'Saved OpenAI key removed. No work started.'};
|
|
103
|
+
}
|
|
104
|
+
export function safeOpenaiCredentialError(error){const code=Object.hasOwn(messages,error?.code)?error.code:'openai-credential-input';return {error:messages[code],code};}
|