Agent Config CLI
This commit is contained in:
@@ -178,12 +178,140 @@ function bareAgentResolveBackend(config) {
|
||||
if (b === 'qvac') return 'qvac'
|
||||
const p = String(config?.provider || '').trim().toLowerCase()
|
||||
if (p === 'qvac') return 'qvac'
|
||||
if (p === 'groq' || p === 'xai') return 'rest'
|
||||
if (p === 'groq' || p === 'xai' || p === 'openai' || p === 'custom') return 'rest'
|
||||
// Default: qvac when key unset; rest when key present (legacy configs)
|
||||
if (config?.rest_api_key && String(config.rest_api_key).trim()) return 'rest'
|
||||
return 'qvac'
|
||||
}
|
||||
|
||||
/** @type {readonly string[]} */
|
||||
var BARE_AGENT_QVAC_ONLY_KEYS = Object.freeze([
|
||||
'qvac_model',
|
||||
'qvac_profile',
|
||||
'qvac_ctx_size',
|
||||
'qvac_device',
|
||||
'qvac_main_gpu',
|
||||
'qvac_gpu_layers'
|
||||
])
|
||||
|
||||
/** @type {readonly string[]} */
|
||||
var BARE_AGENT_REST_ONLY_KEYS = Object.freeze(['rest_base_url', 'rest_api_key'])
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* id: string,
|
||||
* label: string,
|
||||
* rest_base_url: string,
|
||||
* default_model: string,
|
||||
* models: string[]
|
||||
* }} BareAgentRestProvider
|
||||
*/
|
||||
|
||||
/** @type {Record<string, BareAgentRestProvider>} */
|
||||
const BARE_AGENT_REST_PROVIDERS = {
|
||||
groq: {
|
||||
id: 'groq',
|
||||
label: 'Groq',
|
||||
rest_base_url: 'https://api.groq.com/openai/v1',
|
||||
default_model: 'llama-3.3-70b-versatile',
|
||||
models: [
|
||||
'llama-3.3-70b-versatile',
|
||||
'llama-3.1-8b-instant',
|
||||
'openai/gpt-oss-120b'
|
||||
]
|
||||
},
|
||||
xai: {
|
||||
id: 'xai',
|
||||
label: 'xAI (Grok)',
|
||||
rest_base_url: 'https://api.x.ai/v1',
|
||||
default_model: 'grok-4',
|
||||
models: ['grok-4', 'grok-3', 'grok-3-mini']
|
||||
},
|
||||
openai: {
|
||||
id: 'openai',
|
||||
label: 'OpenAI',
|
||||
rest_base_url: 'https://api.openai.com/v1',
|
||||
default_model: 'gpt-4.1',
|
||||
models: ['gpt-4.1', 'gpt-4.1-mini', 'o4-mini']
|
||||
},
|
||||
custom: {
|
||||
id: 'custom',
|
||||
label: 'Custom OpenAI-compatible',
|
||||
rest_base_url: '',
|
||||
default_model: '',
|
||||
models: []
|
||||
}
|
||||
}
|
||||
|
||||
/** @returns {BareAgentRestProvider[]} */
|
||||
function bareAgentRestProviderList() {
|
||||
return [BARE_AGENT_REST_PROVIDERS.groq, BARE_AGENT_REST_PROVIDERS.xai, BARE_AGENT_REST_PROVIDERS.openai, BARE_AGENT_REST_PROVIDERS.custom]
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} [id]
|
||||
* @returns {BareAgentRestProvider}
|
||||
*/
|
||||
function bareAgentRestGetProvider(id) {
|
||||
const key = String(id || '').trim().toLowerCase()
|
||||
if (key === 'http' || key === 'rest') return BARE_AGENT_REST_PROVIDERS.custom
|
||||
return BARE_AGENT_REST_PROVIDERS[key] || BARE_AGENT_REST_PROVIDERS.groq
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} [model]
|
||||
*/
|
||||
function bareAgentIsQvacModelId(model) {
|
||||
const m = String(model || '').trim()
|
||||
if (!m) return false
|
||||
return (
|
||||
/^QWEN/i.test(m) ||
|
||||
/^LLAMA_TOOL/i.test(m) ||
|
||||
/QWEN3/i.test(m) ||
|
||||
/LLAMA_TOOL_CALLING/i.test(m)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop keys that belong to the other backend so config.json matches the choice.
|
||||
* @param {Record<string, unknown>} config
|
||||
* @returns {Record<string, unknown>}
|
||||
*/
|
||||
function bareAgentSanitizeConfigForBackend(config) {
|
||||
const out = { ...(config && typeof config === 'object' ? config : {}) }
|
||||
const backend = bareAgentResolveBackend(out)
|
||||
out.backend = backend
|
||||
if (backend === 'rest') {
|
||||
for (let i = 0; i < BARE_AGENT_QVAC_ONLY_KEYS.length; i++) {
|
||||
delete out[BARE_AGENT_QVAC_ONLY_KEYS[i]]
|
||||
}
|
||||
const prov = String(out.provider || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
if (!prov || prov === 'qvac') out.provider = 'groq'
|
||||
if (bareAgentIsQvacModelId(String(out.model || ''))) {
|
||||
const spec = bareAgentRestGetProvider(String(out.provider || 'groq'))
|
||||
if (spec.default_model) out.model = spec.default_model
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < BARE_AGENT_REST_ONLY_KEYS.length; i++) {
|
||||
delete out[BARE_AGENT_REST_ONLY_KEYS[i]]
|
||||
}
|
||||
out.provider = 'qvac'
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} [secret]
|
||||
*/
|
||||
function bareAgentMaskSecretPreview(secret) {
|
||||
const t = String(secret || '')
|
||||
if (!t.trim()) return '(not set)'
|
||||
if (t.length <= 8) return '********'
|
||||
return t.slice(0, 3) + '…' + t.slice(-4)
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve context window for QVAC load from the model card / profile.
|
||||
* Legacy undersized `qvac_ctx_size` values (e.g. 4096/8192) are ignored so
|
||||
|
||||
@@ -408,6 +408,9 @@ async function bareAgentLoadOrCreateConfig(ctx, paths) {
|
||||
|
||||
if (missingOrEmpty || Object.keys(raw).length === 0) {
|
||||
raw = bareAgentDefaultConfig()
|
||||
if (typeof bareAgentSanitizeConfigForBackend === 'function') {
|
||||
raw = bareAgentSanitizeConfigForBackend(raw)
|
||||
}
|
||||
await bareAgentSaveConfig(ctx, paths, /** @type {any} */ (raw))
|
||||
return { config: /** @type {any} */ (raw), created: true }
|
||||
}
|
||||
@@ -415,14 +418,21 @@ async function bareAgentLoadOrCreateConfig(ctx, paths) {
|
||||
bareAgentValidateConfigShape(raw)
|
||||
const merged = bareAgentMergeConfig(bareAgentDefaultConfig(), raw)
|
||||
const applied = bareAgentApplyAccessPolicyUpgrade(raw, merged)
|
||||
if (applied.upgraded) {
|
||||
let next = applied.config
|
||||
if (typeof bareAgentSanitizeConfigForBackend === 'function') {
|
||||
next = bareAgentSanitizeConfigForBackend(next)
|
||||
}
|
||||
const dirty =
|
||||
applied.upgraded ||
|
||||
JSON.stringify(next) !== JSON.stringify(merged)
|
||||
if (dirty) {
|
||||
try {
|
||||
await bareAgentSaveConfig(ctx, paths, /** @type {any} */ (applied.config))
|
||||
await bareAgentSaveConfig(ctx, paths, /** @type {any} */ (next))
|
||||
} catch {
|
||||
/* keep upgraded in-memory even if persist fails */
|
||||
}
|
||||
}
|
||||
return { config: /** @type {any} */ (applied.config), created: false }
|
||||
return { config: /** @type {any} */ (next), created: false }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -434,8 +444,18 @@ async function bareAgentSaveConfig(ctx, paths, config) {
|
||||
const vfs = ctx.vfs
|
||||
if (!vfs || typeof vfs.writeFile !== 'function')
|
||||
throw new Error('agent: vfs.writeFile unavailable')
|
||||
bareAgentValidateConfigShape(config)
|
||||
const json = JSON.stringify(config, null, 2) + '\n'
|
||||
let next = config
|
||||
if (typeof bareAgentSanitizeConfigForBackend === 'function') {
|
||||
next = bareAgentSanitizeConfigForBackend(config)
|
||||
if (config && typeof config === 'object' && config !== next) {
|
||||
for (const k of Object.keys(config)) {
|
||||
if (!Object.prototype.hasOwnProperty.call(next, k)) delete config[k]
|
||||
}
|
||||
Object.assign(config, next)
|
||||
}
|
||||
}
|
||||
bareAgentValidateConfigShape(next)
|
||||
const json = JSON.stringify(next, null, 2) + '\n'
|
||||
const body =
|
||||
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.from === 'function'
|
||||
? ctx.b4a.from(json)
|
||||
|
||||
@@ -3294,6 +3294,10 @@ function bareAgentMergeConfigPatch(base, patch) {
|
||||
* @param {Record<string, unknown>} config
|
||||
*/
|
||||
async function bareAgentSaveConfigFromTools(ctx, paths, config) {
|
||||
if (typeof bareAgentSaveConfig === 'function') {
|
||||
await bareAgentSaveConfig(ctx, paths, config)
|
||||
return
|
||||
}
|
||||
const vfs = ctx.vfs
|
||||
if (!vfs?.writeFile) return
|
||||
const json = JSON.stringify(config, null, 2) + '\n'
|
||||
|
||||
@@ -411,66 +411,102 @@ function bareAgentApplyProviderProfile(cfg) {
|
||||
const backend = bareAgentResolveBackend(out)
|
||||
out.backend = backend
|
||||
if (backend === 'qvac') {
|
||||
// Automatic default: recommended unless an explicit non-default profile is set.
|
||||
// Migrate stale "lite" installs that still pin 0.6B + tiny ctx from older wizards.
|
||||
let profileId = String(out.qvac_profile || 'recommended')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
if (!profileId || profileId === 'lite') profileId = 'recommended'
|
||||
if (!profileId) profileId = 'recommended'
|
||||
const profile = bareAgentQvacGetProfile(profileId)
|
||||
out.qvac_profile = profile.id
|
||||
out.qvac_model = profile.chatModel
|
||||
out.model = profile.chatModel
|
||||
out.provider = 'qvac'
|
||||
// Auto model-card ctx: clear legacy undersized overrides (0 / 4k / 8k).
|
||||
const rawCtx = Number(out.qvac_ctx_size)
|
||||
if (!Number.isFinite(rawCtx) || rawCtx < profile.ctxSize) {
|
||||
out.qvac_ctx_size = 0
|
||||
}
|
||||
if (!String(out.qvac_main_gpu || '').trim()) out.qvac_main_gpu = 'auto'
|
||||
if (typeof bareAgentSanitizeConfigForBackend === 'function') {
|
||||
return bareAgentSanitizeConfigForBackend(out)
|
||||
}
|
||||
return out
|
||||
}
|
||||
const provider = String(out.provider || '')
|
||||
let provider = String(out.provider || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
const model = String(out.model || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
const defaultGroq = 'https://api.groq.com/openai/v1'
|
||||
if (provider === 'groq' || provider === '' || provider === 'qvac') {
|
||||
if (provider === 'qvac' || provider === '') out.provider = 'groq'
|
||||
const base = String(out.rest_base_url || '').trim()
|
||||
if (!base || base === 'https://api.x.ai/v1') out.rest_base_url = defaultGroq
|
||||
const cur =
|
||||
typeof out.request_timeout_ms === 'number' &&
|
||||
Number.isFinite(out.request_timeout_ms)
|
||||
? out.request_timeout_ms
|
||||
: 120000
|
||||
if (cur < 120000) out.request_timeout_ms = 120000
|
||||
if (
|
||||
!String(out.model || '').trim() ||
|
||||
String(out.model).startsWith('QWEN') ||
|
||||
String(out.model).startsWith('LLAMA')
|
||||
) {
|
||||
out.model = 'llama-3.3-70b-versatile'
|
||||
}
|
||||
if (!provider || provider === 'qvac' || provider === 'rest' || provider === 'http') {
|
||||
provider = 'groq'
|
||||
}
|
||||
if (provider === 'xai') {
|
||||
const base = String(out.rest_base_url || '').trim()
|
||||
if (!base || base === defaultGroq) out.rest_base_url = 'https://api.x.ai/v1'
|
||||
const isReasoningModel =
|
||||
model.includes('reasoning') || model.includes('grok-4.20')
|
||||
if (isReasoningModel) {
|
||||
const cur =
|
||||
typeof out.request_timeout_ms === 'number' &&
|
||||
Number.isFinite(out.request_timeout_ms)
|
||||
? out.request_timeout_ms
|
||||
: 120000
|
||||
if (cur < 300000) out.request_timeout_ms = 300000
|
||||
}
|
||||
const spec =
|
||||
typeof bareAgentRestGetProvider === 'function'
|
||||
? bareAgentRestGetProvider(provider)
|
||||
: { id: 'groq', rest_base_url: 'https://api.groq.com/openai/v1', default_model: 'llama-3.3-70b-versatile' }
|
||||
out.provider = spec.id
|
||||
const base = String(out.rest_base_url || '').trim()
|
||||
const knownDefaults = [
|
||||
'https://api.groq.com/openai/v1',
|
||||
'https://api.x.ai/v1',
|
||||
'https://api.openai.com/v1'
|
||||
]
|
||||
if (!base || (spec.rest_base_url && knownDefaults.indexOf(base) !== -1 && base !== spec.rest_base_url)) {
|
||||
if (spec.rest_base_url) out.rest_base_url = spec.rest_base_url
|
||||
}
|
||||
if (!String(out.model || '').trim() || bareAgentIsQvacModelId(String(out.model || ''))) {
|
||||
if (spec.default_model) out.model = spec.default_model
|
||||
}
|
||||
const modelLow = String(out.model || '').trim().toLowerCase()
|
||||
const cur =
|
||||
typeof out.request_timeout_ms === 'number' && Number.isFinite(out.request_timeout_ms)
|
||||
? out.request_timeout_ms
|
||||
: 120000
|
||||
if (spec.id === 'groq' && cur < 120000) out.request_timeout_ms = 120000
|
||||
if (spec.id === 'xai') {
|
||||
const isReasoning =
|
||||
modelLow.includes('reasoning') || modelLow.includes('grok-4')
|
||||
if (isReasoning && cur < 300000) out.request_timeout_ms = 300000
|
||||
}
|
||||
if (typeof bareAgentSanitizeConfigForBackend === 'function') {
|
||||
return bareAgentSanitizeConfigForBackend(out)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, unknown>} config
|
||||
*/
|
||||
function bareAgentFormatConfigSummary(config) {
|
||||
const cfg = config && typeof config === 'object' ? config : {}
|
||||
const backend = bareAgentResolveBackend(cfg)
|
||||
const lines = ['backend: ' + backend]
|
||||
if (backend === 'rest') {
|
||||
const spec =
|
||||
typeof bareAgentRestGetProvider === 'function'
|
||||
? bareAgentRestGetProvider(String(cfg.provider || 'groq'))
|
||||
: { label: String(cfg.provider || 'groq') }
|
||||
lines.push('provider: ' + (spec.label || cfg.provider || 'groq'))
|
||||
lines.push('rest_base_url: ' + String(cfg.rest_base_url || '(not set)'))
|
||||
lines.push('model: ' + String(cfg.model || '(not set)'))
|
||||
lines.push(
|
||||
'api_key: ' +
|
||||
(typeof bareAgentMaskSecretPreview === 'function'
|
||||
? bareAgentMaskSecretPreview(cfg.rest_api_key)
|
||||
: cfg.rest_api_key
|
||||
? '(set)'
|
||||
: '(not set)')
|
||||
)
|
||||
} else {
|
||||
lines.push('profile: ' + String(cfg.qvac_profile || 'recommended'))
|
||||
lines.push('model: ' + String(cfg.qvac_model || cfg.model || '(not set)'))
|
||||
lines.push(
|
||||
'device: ' +
|
||||
String(cfg.qvac_device || 'auto') +
|
||||
(cfg.qvac_main_gpu && String(cfg.qvac_main_gpu) !== 'auto'
|
||||
? ' gpu=' + String(cfg.qvac_main_gpu)
|
||||
: '')
|
||||
)
|
||||
}
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, unknown>} cfg
|
||||
*/
|
||||
@@ -588,7 +624,7 @@ async function bareAgentInteractiveSetupTui(ctx, argv0, paths, config) {
|
||||
value: 'qvac'
|
||||
},
|
||||
{
|
||||
label: 'REST API — OpenAI-compatible (Groq, xAI, …)',
|
||||
label: 'REST API — OpenAI-compatible (Groq, xAI, OpenAI, custom)',
|
||||
value: 'rest'
|
||||
}
|
||||
],
|
||||
@@ -607,51 +643,160 @@ async function bareAgentInteractiveSetupTui(ctx, argv0, paths, config) {
|
||||
const backend = String(values.backend || curBackend || 'qvac')
|
||||
if (backend === 'rest') {
|
||||
config.backend = 'rest'
|
||||
if (
|
||||
String(config.provider || '')
|
||||
.trim()
|
||||
.toLowerCase() === 'qvac'
|
||||
) {
|
||||
config.provider = 'groq'
|
||||
}
|
||||
config = await bareAgentSetupRestFieldsTui(ctx, argv0, paths, config)
|
||||
} else {
|
||||
config.backend = 'qvac'
|
||||
config.provider = 'qvac'
|
||||
}
|
||||
if (bareAgentResolveBackend(config) === 'qvac') {
|
||||
if (!qvacOk) {
|
||||
config.backend = 'rest'
|
||||
if (
|
||||
String(config.provider || '')
|
||||
.trim()
|
||||
.toLowerCase() === 'qvac'
|
||||
) {
|
||||
config.provider = 'groq'
|
||||
}
|
||||
} else {
|
||||
const chosen = bareAgentQvacGetProfile('recommended')
|
||||
config.qvac_profile = chosen.id
|
||||
config.qvac_model = chosen.chatModel
|
||||
config.model = chosen.chatModel
|
||||
config.provider = 'qvac'
|
||||
config.backend = 'qvac'
|
||||
config.qvac_ctx_size = 0
|
||||
config.qvac_device = ''
|
||||
config.qvac_main_gpu = 'auto'
|
||||
bareAgentErr(
|
||||
ctx,
|
||||
argv0 +
|
||||
': QVAC host bridge unavailable. Choose REST or enable the QVAC host bridge.'
|
||||
)
|
||||
return config
|
||||
}
|
||||
}
|
||||
if (bareAgentResolveBackend(config) === 'rest') {
|
||||
if (
|
||||
!String(config.provider || '').trim() ||
|
||||
String(config.provider).toLowerCase() === 'qvac'
|
||||
) {
|
||||
config.provider = 'groq'
|
||||
}
|
||||
config.backend = 'rest'
|
||||
config = await bareAgentSetupQvacFieldsTui(ctx, config)
|
||||
}
|
||||
config = bareAgentApplyProviderProfile(config)
|
||||
await bareAgentSaveConfig(ctx, paths, config)
|
||||
bareAgentLog(ctx, 'Configuration saved.')
|
||||
bareAgentLog(ctx, 'Configuration saved.\n' + bareAgentFormatConfigSummary(config))
|
||||
return config
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {Record<string, unknown>} config
|
||||
*/
|
||||
async function bareAgentSetupQvacFieldsTui(ctx, config) {
|
||||
const profiles = bareAgentQvacProfileList()
|
||||
const cur = String(config.qvac_profile || 'recommended').trim().toLowerCase()
|
||||
let selected = 0
|
||||
for (let i = 0; i < profiles.length; i++) {
|
||||
if (profiles[i].id === cur) selected = i
|
||||
}
|
||||
const deviceCur = String(config.qvac_device || 'auto').trim().toLowerCase() || 'auto'
|
||||
const form = ctx.tui.form.create({
|
||||
title: 'QVAC (local on-device)',
|
||||
fields: [
|
||||
{
|
||||
type: 'radio',
|
||||
name: 'qvac_profile',
|
||||
label: 'Model profile',
|
||||
options: profiles.map(function (p) {
|
||||
return {
|
||||
label: p.label + ' — ' + p.chatModel + ' (' + p.description + ')',
|
||||
value: p.id
|
||||
}
|
||||
}),
|
||||
selected: selected
|
||||
},
|
||||
{
|
||||
type: 'radio',
|
||||
name: 'qvac_device',
|
||||
label: 'Device',
|
||||
options: [
|
||||
{ label: 'Auto (detect GPU)', value: 'auto' },
|
||||
{ label: 'CPU', value: 'cpu' },
|
||||
{ label: 'GPU', value: 'gpu' }
|
||||
],
|
||||
selected: deviceCur === 'cpu' ? 1 : deviceCur === 'gpu' ? 2 : 0
|
||||
}
|
||||
]
|
||||
})
|
||||
const values = await ctx.tui.form.run(form)
|
||||
if (!values) return config
|
||||
const chosen = bareAgentQvacGetProfile(String(values.qvac_profile || 'recommended'))
|
||||
config.backend = 'qvac'
|
||||
config.provider = 'qvac'
|
||||
config.qvac_profile = chosen.id
|
||||
config.qvac_model = chosen.chatModel
|
||||
config.model = chosen.chatModel
|
||||
config.qvac_ctx_size = 0
|
||||
config.qvac_device = String(values.qvac_device || 'auto').trim() || 'auto'
|
||||
config.qvac_main_gpu = 'auto'
|
||||
return config
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string} argv0
|
||||
* @param {{ config: string }} paths
|
||||
* @param {Record<string, unknown>} config
|
||||
*/
|
||||
async function bareAgentSetupRestFieldsTui(ctx, argv0, paths, config) {
|
||||
const providers = bareAgentRestProviderList()
|
||||
const curProv = String(config.provider || 'groq').trim().toLowerCase()
|
||||
let selected = 0
|
||||
for (let i = 0; i < providers.length; i++) {
|
||||
if (providers[i].id === curProv) selected = i
|
||||
}
|
||||
const form = ctx.tui.form.create({
|
||||
title: 'REST API (OpenAI-compatible)',
|
||||
fields: [
|
||||
{
|
||||
type: 'radio',
|
||||
name: 'provider',
|
||||
label: 'Provider',
|
||||
options: providers.map(function (p) {
|
||||
return { label: p.label, value: p.id }
|
||||
}),
|
||||
selected: selected
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'rest_base_url',
|
||||
label: 'Base URL (blank = provider default)',
|
||||
value: String(config.rest_base_url || '')
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'rest_api_key',
|
||||
label:
|
||||
'API key' +
|
||||
(String(config.rest_api_key || '').trim()
|
||||
? ' [leave blank to keep ' +
|
||||
bareAgentMaskSecretPreview(config.rest_api_key) +
|
||||
']'
|
||||
: ' (required)'),
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'model',
|
||||
label: 'Model id (blank = provider default)',
|
||||
value: bareAgentIsQvacModelId(String(config.model || ''))
|
||||
? ''
|
||||
: String(config.model || '')
|
||||
}
|
||||
]
|
||||
})
|
||||
const values = await ctx.tui.form.run(form)
|
||||
if (!values) return config
|
||||
const spec = bareAgentRestGetProvider(String(values.provider || 'groq'))
|
||||
config.backend = 'rest'
|
||||
config.provider = spec.id
|
||||
const url = String(values.rest_base_url || '').trim()
|
||||
config.rest_base_url = url || spec.rest_base_url || String(config.rest_base_url || '')
|
||||
const typedKey = String(values.rest_api_key || '').trim()
|
||||
if (typedKey) config.rest_api_key = typedKey
|
||||
const model = String(values.model || '').trim()
|
||||
if (model) config.model = model
|
||||
else if (!String(config.model || '').trim() || bareAgentIsQvacModelId(String(config.model || ''))) {
|
||||
config.model = spec.default_model || config.model
|
||||
}
|
||||
if (!String(config.rest_api_key || '').trim() && bareAgentCanPlainSetup(ctx)) {
|
||||
const key =
|
||||
(await bareAgentPromptSetupLine(
|
||||
ctx,
|
||||
'API key (required, input hidden): ',
|
||||
{ mask: true }
|
||||
)) || ''
|
||||
if (key.trim()) config.rest_api_key = key.trim()
|
||||
}
|
||||
if (spec.id === 'custom' && !String(config.rest_base_url || '').trim()) {
|
||||
bareAgentErr(ctx, argv0 + ': custom REST provider needs a base URL (e.g. https://host/v1).')
|
||||
}
|
||||
void paths
|
||||
return config
|
||||
}
|
||||
|
||||
@@ -684,7 +829,7 @@ async function bareAgentInteractiveSetup(ctx, argv0, paths, config, opts) {
|
||||
'\n=== ' +
|
||||
argv0 +
|
||||
' configuration ===\n' +
|
||||
'Only identity + backend. Models, context, and tools are chosen automatically.\n' +
|
||||
'Pick a backend, then only that backend is stored in ~/.agent/config.json.\n' +
|
||||
'Enter keeps the [default].\n\n'
|
||||
)
|
||||
try {
|
||||
@@ -711,10 +856,10 @@ async function bareAgentInteractiveSetup(ctx, argv0, paths, config, opts) {
|
||||
ctx,
|
||||
stdout,
|
||||
'\nInference backend:\n' +
|
||||
' 1) QVAC — local on-device' +
|
||||
' 1) QVAC — local on-device models' +
|
||||
(qvacOk ? '' : ' [host bridge unavailable]') +
|
||||
'\n' +
|
||||
' 2) REST API — OpenAI-compatible (Groq, xAI, …)\n'
|
||||
' 2) REST API — Groq, xAI, OpenAI, or any OpenAI-compatible URL\n'
|
||||
)
|
||||
const backendRaw =
|
||||
(await bareAgentPromptSetupLine(
|
||||
@@ -725,22 +870,9 @@ async function bareAgentInteractiveSetup(ctx, argv0, paths, config, opts) {
|
||||
)) || ''
|
||||
{
|
||||
const v = backendRaw.trim().toLowerCase()
|
||||
if (v === '2' || v === 'rest' || v === 'r') {
|
||||
config.backend = 'rest'
|
||||
if (
|
||||
String(config.provider || '')
|
||||
.trim()
|
||||
.toLowerCase() === 'qvac'
|
||||
) {
|
||||
config.provider = 'groq'
|
||||
}
|
||||
} else if (v === '1' || v === 'qvac' || v === 'q') {
|
||||
config.backend = 'qvac'
|
||||
config.provider = 'qvac'
|
||||
}
|
||||
}
|
||||
if (!String(config.backend || '').trim()) {
|
||||
config.backend = curBackend
|
||||
if (v === '2' || v === 'rest' || v === 'r') config.backend = 'rest'
|
||||
else if (v === '1' || v === 'qvac' || v === 'q') config.backend = 'qvac'
|
||||
else if (!String(config.backend || '').trim()) config.backend = curBackend
|
||||
}
|
||||
|
||||
if (bareAgentResolveBackend(config) === 'qvac') {
|
||||
@@ -748,65 +880,27 @@ async function bareAgentInteractiveSetup(ctx, argv0, paths, config, opts) {
|
||||
bareAgentWriteOut(
|
||||
ctx,
|
||||
stdout,
|
||||
'\nQVAC host bridge unavailable — switching to REST (keep an API key in config or env).\n'
|
||||
'\nQVAC host bridge is unavailable on this boot.\n'
|
||||
)
|
||||
config.backend = 'rest'
|
||||
if (
|
||||
String(config.provider || '')
|
||||
.trim()
|
||||
.toLowerCase() === 'qvac'
|
||||
) {
|
||||
config.provider = 'groq'
|
||||
const sw =
|
||||
(await bareAgentPromptSetupLine(ctx, 'Switch to REST API instead? [Y/n]: ')) ||
|
||||
''
|
||||
const ans = sw.trim().toLowerCase()
|
||||
if (ans === 'n' || ans === 'no') {
|
||||
bareAgentErr(
|
||||
ctx,
|
||||
argv0 + ': QVAC selected but the host bridge is unavailable.'
|
||||
)
|
||||
return config
|
||||
}
|
||||
config.backend = 'rest'
|
||||
} else {
|
||||
// Automatic: recommended profile + model-card context (no prompts).
|
||||
const chosen = bareAgentQvacGetProfile('recommended')
|
||||
config.qvac_profile = chosen.id
|
||||
config.qvac_model = chosen.chatModel
|
||||
config.model = chosen.chatModel
|
||||
config.provider = 'qvac'
|
||||
config.backend = 'qvac'
|
||||
config.qvac_ctx_size = 0
|
||||
config.qvac_device = ''
|
||||
config.qvac_main_gpu = 'auto'
|
||||
bareAgentWriteOut(
|
||||
ctx,
|
||||
stdout,
|
||||
'\nQVAC auto: profile "' +
|
||||
chosen.label +
|
||||
'" → ' +
|
||||
chosen.chatModel +
|
||||
' (ctx ' +
|
||||
chosen.ctxSize +
|
||||
' from model card; GPU auto).\n'
|
||||
)
|
||||
config = await bareAgentSetupQvacFieldsPlain(ctx, stdout, config)
|
||||
}
|
||||
}
|
||||
|
||||
if (bareAgentResolveBackend(config) === 'rest') {
|
||||
// Automatic REST defaults; preserve an existing API key if present.
|
||||
if (
|
||||
!String(config.provider || '').trim() ||
|
||||
String(config.provider).toLowerCase() === 'qvac'
|
||||
) {
|
||||
config.provider = 'groq'
|
||||
}
|
||||
config.backend = 'rest'
|
||||
if (!String(config.rest_api_key || '').trim()) {
|
||||
bareAgentWriteOut(
|
||||
ctx,
|
||||
stdout,
|
||||
'\nREST selected: set rest_api_key in ' +
|
||||
paths.config +
|
||||
' (or keep a previous key). Defaults: Groq OpenAI-compatible URL + model.\n'
|
||||
)
|
||||
} else {
|
||||
bareAgentWriteOut(
|
||||
ctx,
|
||||
stdout,
|
||||
'\nREST auto: provider/model/URL defaults applied; existing API key kept.\n'
|
||||
)
|
||||
}
|
||||
config = await bareAgentSetupRestFieldsPlain(ctx, stdout, config)
|
||||
}
|
||||
} catch (e) {
|
||||
const msg =
|
||||
@@ -818,7 +912,159 @@ async function bareAgentInteractiveSetup(ctx, argv0, paths, config, opts) {
|
||||
}
|
||||
config = bareAgentApplyProviderProfile(config)
|
||||
await bareAgentSaveConfig(ctx, paths, config)
|
||||
bareAgentLog(ctx, 'Configuration saved.')
|
||||
bareAgentLog(ctx, 'Configuration saved.\n' + bareAgentFormatConfigSummary(config))
|
||||
return config
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {unknown} stdout
|
||||
* @param {Record<string, unknown>} config
|
||||
*/
|
||||
async function bareAgentSetupQvacFieldsPlain(ctx, stdout, config) {
|
||||
const profiles = bareAgentQvacProfileList()
|
||||
const cur = String(config.qvac_profile || 'recommended').trim().toLowerCase()
|
||||
let lines = '\nQVAC model profile:\n'
|
||||
for (let i = 0; i < profiles.length; i++) {
|
||||
const p = profiles[i]
|
||||
lines +=
|
||||
' ' +
|
||||
String(i + 1) +
|
||||
') ' +
|
||||
p.label +
|
||||
' — ' +
|
||||
p.chatModel +
|
||||
(p.id === cur ? ' [current]' : '') +
|
||||
'\n'
|
||||
}
|
||||
bareAgentWriteOut(ctx, stdout, lines)
|
||||
const raw =
|
||||
(await bareAgentPromptSetupLine(
|
||||
ctx,
|
||||
'Profile [1-' + String(profiles.length) + '] [recommended]: '
|
||||
)) || ''
|
||||
let chosen = bareAgentQvacGetProfile(cur || 'recommended')
|
||||
const n = Number(raw.trim())
|
||||
if (Number.isFinite(n) && n >= 1 && n <= profiles.length) {
|
||||
chosen = profiles[n - 1]
|
||||
} else if (raw.trim()) {
|
||||
chosen = bareAgentQvacGetProfile(raw.trim())
|
||||
}
|
||||
bareAgentWriteOut(
|
||||
ctx,
|
||||
stdout,
|
||||
'\nDevice:\n 1) auto (detect GPU)\n 2) cpu\n 3) gpu\n'
|
||||
)
|
||||
const devRaw = (await bareAgentPromptSetupLine(ctx, 'Device [1=auto, 2=cpu, 3=gpu] [1]: ')) || ''
|
||||
const dv = devRaw.trim().toLowerCase()
|
||||
const device = dv === '2' || dv === 'cpu' ? 'cpu' : dv === '3' || dv === 'gpu' ? 'gpu' : 'auto'
|
||||
config.backend = 'qvac'
|
||||
config.provider = 'qvac'
|
||||
config.qvac_profile = chosen.id
|
||||
config.qvac_model = chosen.chatModel
|
||||
config.model = chosen.chatModel
|
||||
config.qvac_ctx_size = 0
|
||||
config.qvac_device = device
|
||||
config.qvac_main_gpu = 'auto'
|
||||
bareAgentWriteOut(
|
||||
ctx,
|
||||
stdout,
|
||||
'\nQVAC: ' + chosen.label + ' → ' + chosen.chatModel + ' (device ' + device + ').\n'
|
||||
)
|
||||
return config
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {unknown} stdout
|
||||
* @param {Record<string, unknown>} config
|
||||
*/
|
||||
async function bareAgentSetupRestFieldsPlain(ctx, stdout, config) {
|
||||
const providers = bareAgentRestProviderList()
|
||||
let lines = '\nREST provider:\n'
|
||||
for (let i = 0; i < providers.length; i++) {
|
||||
lines += ' ' + String(i + 1) + ') ' + providers[i].label + '\n'
|
||||
}
|
||||
bareAgentWriteOut(ctx, stdout, lines)
|
||||
const curProv = String(config.provider || 'groq').trim().toLowerCase()
|
||||
const defIdx =
|
||||
providers.findIndex(function (p) {
|
||||
return p.id === curProv
|
||||
}) + 1
|
||||
const raw =
|
||||
(await bareAgentPromptSetupLine(
|
||||
ctx,
|
||||
'Provider [1=Groq, 2=xAI, 3=OpenAI, 4=Custom] [' +
|
||||
String(defIdx > 0 ? defIdx : 1) +
|
||||
']: '
|
||||
)) || ''
|
||||
let spec = bareAgentRestGetProvider(curProv === 'qvac' ? 'groq' : curProv)
|
||||
const n = Number(raw.trim())
|
||||
if (Number.isFinite(n) && n >= 1 && n <= providers.length) spec = providers[n - 1]
|
||||
else if (raw.trim()) spec = bareAgentRestGetProvider(raw.trim())
|
||||
config.backend = 'rest'
|
||||
config.provider = spec.id
|
||||
|
||||
const urlDefault = spec.rest_base_url || String(config.rest_base_url || '')
|
||||
const urlRaw =
|
||||
(await bareAgentPromptSetupLine(
|
||||
ctx,
|
||||
'Base URL [' + (urlDefault || 'required for custom') + ']: '
|
||||
)) || ''
|
||||
const url = urlRaw.trim() || urlDefault
|
||||
if (!url) {
|
||||
throw new Error('REST base URL is required for provider ' + spec.id)
|
||||
}
|
||||
config.rest_base_url = url.replace(/\/+$/, '')
|
||||
|
||||
const haveKey = Boolean(String(config.rest_api_key || '').trim())
|
||||
const keyPrompt = haveKey
|
||||
? 'API key [Enter keeps ' + bareAgentMaskSecretPreview(config.rest_api_key) + ']: '
|
||||
: 'API key (required, input hidden): '
|
||||
const keyRaw = (await bareAgentPromptSetupLine(ctx, keyPrompt, { mask: true })) || ''
|
||||
if (keyRaw.trim()) config.rest_api_key = keyRaw.trim()
|
||||
if (!String(config.rest_api_key || '').trim()) {
|
||||
throw new Error('REST API key is required')
|
||||
}
|
||||
|
||||
if (spec.models.length) {
|
||||
let mlines = '\nSuggested models for ' + spec.label + ':\n'
|
||||
for (let i = 0; i < spec.models.length; i++) {
|
||||
mlines += ' ' + String(i + 1) + ') ' + spec.models[i] + '\n'
|
||||
}
|
||||
bareAgentWriteOut(ctx, stdout, mlines)
|
||||
}
|
||||
const modelDefault =
|
||||
!String(config.model || '').trim() || bareAgentIsQvacModelId(String(config.model || ''))
|
||||
? spec.default_model
|
||||
: String(config.model)
|
||||
const modelRaw =
|
||||
(await bareAgentPromptSetupLine(
|
||||
ctx,
|
||||
'Model [' + (modelDefault || 'required') + ']: '
|
||||
)) || ''
|
||||
const modelPick = modelRaw.trim()
|
||||
const modelNum = Number(modelPick)
|
||||
if (Number.isFinite(modelNum) && spec.models[modelNum - 1]) {
|
||||
config.model = spec.models[modelNum - 1]
|
||||
} else if (modelPick) {
|
||||
config.model = modelPick
|
||||
} else if (modelDefault) {
|
||||
config.model = modelDefault
|
||||
} else {
|
||||
throw new Error('REST model id is required')
|
||||
}
|
||||
bareAgentWriteOut(
|
||||
ctx,
|
||||
stdout,
|
||||
'\nREST: ' +
|
||||
spec.label +
|
||||
' → ' +
|
||||
config.model +
|
||||
'\n ' +
|
||||
config.rest_base_url +
|
||||
'\n'
|
||||
)
|
||||
return config
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,8 @@ async function run(ctx, argv) {
|
||||
'Runs an autonomous coding/OS agent. Default backend is QVAC (local on-device);\n' +
|
||||
'or configure any OpenAI-compatible HTTPS REST API.\n' +
|
||||
'Configuration: ~/.agent/config.json on your personal drive (created on first run).\n' +
|
||||
'Use --setup or --config to choose QVAC vs REST, model profile / API URL+key (plain TTY prompts).\n' +
|
||||
'Use --setup or --config to choose QVAC or REST, then walk through only that backend\n' +
|
||||
'(QVAC profile/device, or REST provider + URL + API key + model). Unused keys are not stored.\n' +
|
||||
'Use --reset or `reset` to clear ~/.agent/history.json and start a fresh chat session.\n' +
|
||||
'Use --auto / --autonomous GOAL to keep the tool loop running until task_complete,\n' +
|
||||
'stop, or the timebox (default 30m). --status prints the current run + compaction mode.\n' +
|
||||
@@ -76,28 +77,42 @@ async function run(ctx, argv) {
|
||||
const home = bareAgentResolveHome(ctx)
|
||||
const paths = bareAgentPaths(home)
|
||||
const loaded = await bareAgentLoadOrCreateConfig(ctx, paths)
|
||||
const cfg = loaded && loaded.config ? loaded.config : loaded
|
||||
let cfg = loaded && loaded.config ? loaded.config : loaded
|
||||
if (typeof bareAgentSanitizeConfigForBackend === 'function') {
|
||||
cfg = bareAgentSanitizeConfigForBackend(cfg || {})
|
||||
}
|
||||
const started = Number(cfg && cfg.autonomous_started_at_ms) || 0
|
||||
const maxRt = Number(cfg && cfg.autonomous_max_runtime_ms) || 0
|
||||
const elapsed = started > 0 ? Math.max(0, Date.now() - started) : 0
|
||||
ctx.console.log(
|
||||
[
|
||||
'agent status',
|
||||
' backend: ' + String((cfg && (cfg.backend || cfg.provider)) || 'qvac'),
|
||||
' compaction: ' + String((cfg && cfg.context_compaction) || 'auto'),
|
||||
' autonomous_enabled: ' + String(Boolean(cfg && cfg.autonomous_mode_enabled)),
|
||||
' autonomous_active: ' + String(Boolean(cfg && cfg.autonomous_active)),
|
||||
' status: ' + String((cfg && cfg.autonomous_status) || 'idle'),
|
||||
' goal: ' + String((cfg && cfg.autonomous_goal) || ''),
|
||||
' elapsed_s: ' + String(Math.round(elapsed / 1000)),
|
||||
' remaining_s: ' +
|
||||
String(maxRt > 0 ? Math.max(0, Math.round((maxRt - elapsed) / 1000)) : 0),
|
||||
' last_error: ' + String((cfg && cfg.autonomous_last_error) || ''),
|
||||
' plan_mode: ' + String(Boolean(cfg && cfg.plan_mode_active)),
|
||||
' access_policy: ' + String((cfg && cfg.access_policy) || 'full'),
|
||||
' allow_delete: ' + String(cfg && cfg.allow_delete !== false)
|
||||
].join('\n')
|
||||
const backend = typeof bareAgentResolveBackend === 'function'
|
||||
? bareAgentResolveBackend(cfg || {})
|
||||
: String((cfg && (cfg.backend || cfg.provider)) || 'qvac')
|
||||
/** @type {string[]} */
|
||||
const lines = ['agent status']
|
||||
if (typeof bareAgentFormatConfigSummary === 'function') {
|
||||
String(bareAgentFormatConfigSummary(cfg || {}))
|
||||
.split('\n')
|
||||
.forEach(function (row) {
|
||||
lines.push(' ' + row)
|
||||
})
|
||||
} else {
|
||||
lines.push(' backend: ' + backend)
|
||||
}
|
||||
lines.push(
|
||||
' compaction: ' + String((cfg && cfg.context_compaction) || 'auto'),
|
||||
' autonomous_enabled: ' + String(Boolean(cfg && cfg.autonomous_mode_enabled)),
|
||||
' autonomous_active: ' + String(Boolean(cfg && cfg.autonomous_active)),
|
||||
' status: ' + String((cfg && cfg.autonomous_status) || 'idle'),
|
||||
' goal: ' + String((cfg && cfg.autonomous_goal) || ''),
|
||||
' elapsed_s: ' + String(Math.round(elapsed / 1000)),
|
||||
' remaining_s: ' +
|
||||
String(maxRt > 0 ? Math.max(0, Math.round((maxRt - elapsed) / 1000)) : 0),
|
||||
' last_error: ' + String((cfg && cfg.autonomous_last_error) || ''),
|
||||
' plan_mode: ' + String(Boolean(cfg && cfg.plan_mode_active)),
|
||||
' access_policy: ' + String((cfg && cfg.access_policy) || 'full'),
|
||||
' allow_delete: ' + String(cfg && cfg.allow_delete !== false)
|
||||
)
|
||||
ctx.console.log(lines.join('\n'))
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
|
||||
@@ -35,12 +35,14 @@ test('agent-state exposes QVAC backend config keys', async (t) => {
|
||||
t.ok(STATE.includes('QWEN3_1_7B_INST_Q4'), 'default qvac model id')
|
||||
})
|
||||
|
||||
test('agent-tui wizard offers QVAC vs REST and auto model selection', async (t) => {
|
||||
test('agent-tui wizard offers QVAC vs REST and backend-specific setup', async (t) => {
|
||||
t.ok(TUI.includes('Backend [1=QVAC, 2=REST]'))
|
||||
t.ok(TUI.includes('Only identity + backend'))
|
||||
t.ok(TUI.includes('QVAC auto: profile'))
|
||||
t.ok(!TUI.includes('Model profile number'))
|
||||
t.ok(!TUI.includes('REST base URL'))
|
||||
t.ok(TUI.includes('bareAgentSetupRestFieldsPlain'))
|
||||
t.ok(TUI.includes('bareAgentSetupQvacFieldsPlain'))
|
||||
t.ok(TUI.includes('Provider [1=Groq, 2=xAI, 3=OpenAI, 4=Custom]'))
|
||||
t.ok(TUI.includes('API key (required, input hidden)'))
|
||||
t.ok(TUI.includes('Base URL ['))
|
||||
t.ok(TUI.includes('QVAC model profile'))
|
||||
t.ok(!TUI.includes('Enable autonomous coding mode?'))
|
||||
t.ok(TUI.includes('bareOsQvacComplete'))
|
||||
t.ok(TUI.includes("backendIter === 'qvac'"))
|
||||
@@ -49,6 +51,7 @@ test('agent-tui wizard offers QVAC vs REST and auto model selection', async (t)
|
||||
'setup prefers ctx.tui.form'
|
||||
)
|
||||
t.ok(TUI.includes('ctx.tui.form.run'), 'setup form run')
|
||||
t.ok(TUI.includes('bareAgentSanitizeConfigForBackend'))
|
||||
})
|
||||
|
||||
test('agent-tools merge patch allows backend / qvac keys', async (t) => {
|
||||
@@ -153,7 +156,7 @@ test('agent-openai parses reasoning deltas when present', async (t) => {
|
||||
})
|
||||
|
||||
test('agent-tui contains groq provider profile/request shaping', async (t) => {
|
||||
t.ok(TUI.includes("provider === 'groq'"))
|
||||
t.ok(TUI.includes("spec.id === 'groq'") || TUI.includes("provider === 'groq'"))
|
||||
t.ok(TUI.includes('https://api.groq.com/openai/v1'))
|
||||
t.ok(TUI.includes('body.parallel_tool_calls'))
|
||||
t.ok(TUI.includes('body.max_completion_tokens'))
|
||||
|
||||
@@ -10,7 +10,7 @@ function loadAgentQvacHelpers() {
|
||||
vm.createContext(sandbox)
|
||||
vm.runInContext(
|
||||
QVAC_SRC +
|
||||
'\n;this.__exports = { bareAgentFlattenToolsForQvac, bareAgentQvacGetProfile, bareAgentQvacProfileList, bareAgentResolveBackend, bareAgentQvacBridgeAvailable, bareAgentQvacResolveCtxSize, bareAgentQvacResolveDeviceOpts, bareAgentQvacModelCardCtxSize, BARE_AGENT_QVAC_CTX_QWEN3, BARE_AGENT_QVAC_CTX_LLAMA32_1B }',
|
||||
'\n;this.__exports = { bareAgentFlattenToolsForQvac, bareAgentQvacGetProfile, bareAgentQvacProfileList, bareAgentResolveBackend, bareAgentQvacBridgeAvailable, bareAgentQvacResolveCtxSize, bareAgentQvacResolveDeviceOpts, bareAgentQvacModelCardCtxSize, bareAgentSanitizeConfigForBackend, bareAgentRestGetProvider, bareAgentRestProviderList, bareAgentIsQvacModelId, bareAgentMaskSecretPreview, BARE_AGENT_QVAC_CTX_QWEN3, BARE_AGENT_QVAC_CTX_LLAMA32_1B }',
|
||||
sandbox
|
||||
)
|
||||
return sandbox.__exports
|
||||
@@ -87,9 +87,58 @@ test('resolve backend defaults to qvac', async (t) => {
|
||||
t.is(bareAgentResolveBackend({ backend: 'qvac' }), 'qvac')
|
||||
t.is(bareAgentResolveBackend({ backend: 'rest' }), 'rest')
|
||||
t.is(bareAgentResolveBackend({ provider: 'groq', rest_api_key: 'x' }), 'rest')
|
||||
t.is(bareAgentResolveBackend({ provider: 'openai' }), 'rest')
|
||||
t.is(bareAgentResolveBackend({ provider: 'custom' }), 'rest')
|
||||
t.is(bareAgentResolveBackend({ provider: 'qvac' }), 'qvac')
|
||||
})
|
||||
|
||||
test('sanitize drops the other backend keys', async (t) => {
|
||||
const { bareAgentSanitizeConfigForBackend } = loadAgentQvacHelpers()
|
||||
const rest = bareAgentSanitizeConfigForBackend({
|
||||
backend: 'rest',
|
||||
provider: 'qvac',
|
||||
model: 'QWEN3_1_7B_INST_Q4',
|
||||
qvac_model: 'QWEN3_1_7B_INST_Q4',
|
||||
qvac_profile: 'recommended',
|
||||
qvac_ctx_size: 0,
|
||||
rest_api_key: 'gsk_test',
|
||||
rest_base_url: 'https://api.groq.com/openai/v1'
|
||||
})
|
||||
t.is(rest.backend, 'rest')
|
||||
t.is(rest.provider, 'groq')
|
||||
t.is(rest.model, 'llama-3.3-70b-versatile')
|
||||
t.absent('qvac_model' in rest)
|
||||
t.absent('qvac_profile' in rest)
|
||||
t.ok(rest.rest_api_key)
|
||||
|
||||
const qvac = bareAgentSanitizeConfigForBackend({
|
||||
backend: 'qvac',
|
||||
provider: 'groq',
|
||||
rest_api_key: 'secret',
|
||||
rest_base_url: 'https://api.groq.com/openai/v1',
|
||||
qvac_profile: 'strong',
|
||||
qvac_model: 'QWEN3_4B_INST_Q4_K_M'
|
||||
})
|
||||
t.is(qvac.backend, 'qvac')
|
||||
t.is(qvac.provider, 'qvac')
|
||||
t.absent('rest_api_key' in qvac)
|
||||
t.absent('rest_base_url' in qvac)
|
||||
t.is(qvac.qvac_profile, 'strong')
|
||||
})
|
||||
|
||||
test('REST provider catalog has groq xai openai custom', async (t) => {
|
||||
const { bareAgentRestProviderList, bareAgentRestGetProvider, bareAgentMaskSecretPreview } =
|
||||
loadAgentQvacHelpers()
|
||||
const list = bareAgentRestProviderList()
|
||||
t.ok(list.some((p) => p.id === 'groq'))
|
||||
t.ok(list.some((p) => p.id === 'xai'))
|
||||
t.ok(list.some((p) => p.id === 'openai'))
|
||||
t.ok(list.some((p) => p.id === 'custom'))
|
||||
t.is(bareAgentRestGetProvider('xai').rest_base_url, 'https://api.x.ai/v1')
|
||||
t.is(bareAgentMaskSecretPreview('gsk_abcdefghijk'), 'gsk…hijk')
|
||||
t.is(bareAgentMaskSecretPreview(''), '(not set)')
|
||||
})
|
||||
|
||||
test('qvac bridge available checks ctx hooks', async (t) => {
|
||||
const { bareAgentQvacBridgeAvailable } = loadAgentQvacHelpers()
|
||||
t.is(bareAgentQvacBridgeAvailable({}), false)
|
||||
|
||||
Reference in New Issue
Block a user