mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-28 16:32:28 +00:00
110 lines
4.4 KiB
JavaScript
110 lines
4.4 KiB
JavaScript
// adapters/custom.mjs — a provider defined entirely in fleet.config.json.
|
|
//
|
|
// harnesses.<name>.argv is a list of command templates. A whole-token placeholder
|
|
// that is empty is omitted. {{extra}} (or, if absent, the spot before {{prompt}})
|
|
// receives headlessArgs. done.kind is exit | regex | json-type.
|
|
// See docs/adapters.md. There is no background mode; fleet up uses tmux.
|
|
import { messaging as mailboxMessaging } from './mailbox.mjs';
|
|
import { baseVerdict, parseJsonRecords } from './contract.mjs';
|
|
|
|
const VARS = ['bin', 'model', 'context', 'sessionId', 'sessionName', 'promptFile', 'firstPrompt', 'prompt', 'remoteControl', 'cwd'];
|
|
|
|
export function fillTemplate(template, vars, extra = []) {
|
|
const out = [];
|
|
let usedExtra = false;
|
|
const extras = extra.filter((part) => part != null && part !== '');
|
|
for (const part of template) {
|
|
if (part === '{{extra}}') { out.push(...extras); usedExtra = true; continue; }
|
|
if (part === '{{prompt}}' && !usedExtra) { out.push(...extras); usedExtra = true; }
|
|
const whole = typeof part === 'string' && part.match(/^\{\{(\w+)\}\}$/);
|
|
if (whole) {
|
|
if (!VARS.includes(whole[1])) { out.push(part); continue; }
|
|
const value = vars[whole[1]];
|
|
if (value == null || value === '') continue;
|
|
out.push(String(value));
|
|
continue;
|
|
}
|
|
out.push(String(part).replace(/\{\{(\w+)\}\}/g, (_, key) => (vars[key] == null ? '' : String(vars[key]))));
|
|
}
|
|
if (!usedExtra) out.push(...extras);
|
|
return out;
|
|
}
|
|
|
|
function varsOf(opts, config) {
|
|
return {
|
|
bin: config.bin ?? 'custom',
|
|
model: opts.model ?? '',
|
|
context: opts.context ?? '',
|
|
sessionId: opts.sessionId ?? '',
|
|
sessionName: opts.sessionName ?? '',
|
|
promptFile: opts.promptFile ?? '',
|
|
firstPrompt: opts.firstPrompt ?? '',
|
|
prompt: opts.prompt ?? '',
|
|
remoteControl: opts.remoteControl ? 'on' : '',
|
|
cwd: opts.cwd ?? '',
|
|
};
|
|
}
|
|
|
|
export function interpretDone(rule, output, meta = {}) {
|
|
const base = baseVerdict(meta);
|
|
if (base) return base;
|
|
const kind = rule?.kind ?? 'exit';
|
|
if (meta.status !== 0) return { ok: false, reason: `exit:${meta.status}` };
|
|
if (kind === 'exit') {
|
|
if (rule?.requireOutput && !String(output ?? '').trim()) return { ok: false, reason: 'no-completed-result' };
|
|
return { ok: true, reason: 'exit' };
|
|
}
|
|
if (kind === 'regex') {
|
|
let re;
|
|
try { re = new RegExp(rule.pattern ?? '', rule.flags ?? ''); }
|
|
catch { return { ok: false, reason: 'bad-done-pattern' }; }
|
|
if (!re.test(String(output ?? ''))) return { ok: false, reason: 'no-completed-result' };
|
|
return { ok: true, reason: 'regex' };
|
|
}
|
|
if (kind === 'json-type') {
|
|
const hit = parseJsonRecords(output).find((record) => record && record.type === rule.type);
|
|
if (!hit) return { ok: false, reason: 'no-completed-result' };
|
|
const sessionId = typeof hit.thread_id === 'string' ? hit.thread_id
|
|
: (typeof hit.sessionId === 'string' ? hit.sessionId : null);
|
|
return { ok: true, reason: rule.type || 'json-type', ...(sessionId ? { sessionId } : {}) };
|
|
}
|
|
return { ok: false, reason: `bad-done-kind:${kind}` };
|
|
}
|
|
|
|
export function bind(config = {}, name = 'custom') {
|
|
const argv = config.argv ?? {};
|
|
const has = (key) => Array.isArray(argv[key]);
|
|
const build = (key, opts, extra) => fillTemplate(argv[key], varsOf(opts ?? {}, { ...config, bin: config.bin ?? name }), extra);
|
|
return {
|
|
name,
|
|
bin: config.bin ?? name,
|
|
complete: has('start') && has('headless'),
|
|
experimental: false,
|
|
tiers: config.tiers ?? { strong: null, standard: null, light: null },
|
|
capabilities: {
|
|
interactive: has('start'),
|
|
resume: has('resume'),
|
|
background: false,
|
|
headless: has('headless'),
|
|
doneSignal: config.done?.kind ?? 'exit',
|
|
prompt: 'template',
|
|
autoMode: config.headlessArgs ?? [],
|
|
contextFlag: null,
|
|
remoteControl: null,
|
|
nativeMessaging: false,
|
|
},
|
|
start: has('start') ? (opts) => build('start', opts) : null,
|
|
resume: has('resume') ? (opts) => build('resume', opts) : null,
|
|
background: null,
|
|
headless: has('headless') ? ({ prompt, model, extraArgs = [] } = {}) => build('headless', { prompt, model }, extraArgs) : null,
|
|
completed: (output, meta) => interpretDone(config.done, output, meta),
|
|
canResume: () => has('resume'),
|
|
list: () => null,
|
|
stop: null,
|
|
cleanEnv: (env = process.env) => ({ ...env }),
|
|
messaging: mailboxMessaging,
|
|
skillsDir: config.skillsDir ?? null,
|
|
usage: () => null,
|
|
};
|
|
}
|