fix(local): honor server LLM for keyless missions
This commit is contained in:
1 parent
600f43b8b1
commit
4b81bf6436
5 files changed
+91
-33
No files matched your search
+21
-8
@@ -5714,6 +5714,7 @@ tempest.operators.<span class="function">spawn</span>(<span class="string">'scan
|
|||||||
|
|
||||||
connected: false,
|
connected: false,
|
||||||
llmAvailable: false,
|
llmAvailable: false,
|
||||||
|
serverLLM: null,
|
||||||
|
|
||||||
// Check server health
|
// Check server health
|
||||||
async checkHealth() {
|
async checkHealth() {
|
||||||
@@ -5747,6 +5748,7 @@ tempest.operators.<span class="function">spawn</span>(<span class="string">'scan
|
|||||||
this.connected = data.status === 'operational';
|
this.connected = data.status === 'operational';
|
||||||
// /api/health returns llm as an object ({connected:true,...}); accept that or the legacy string
|
// /api/health returns llm as an object ({connected:true,...}); accept that or the legacy string
|
||||||
this.llmAvailable = data.llm === 'connected' || !!(data.llm && data.llm.connected);
|
this.llmAvailable = data.llm === 'connected' || !!(data.llm && data.llm.connected);
|
||||||
|
this.serverLLM = data.llm && typeof data.llm === 'object' ? data.llm : null;
|
||||||
// Keep the dispatch latch honest: BackendDispatch.available is otherwise write-only-true.
|
// Keep the dispatch latch honest: BackendDispatch.available is otherwise write-only-true.
|
||||||
// A server that's down / lost mission support must clear it; a healthy one re-arms it.
|
// A server that's down / lost mission support must clear it; a healthy one re-arms it.
|
||||||
if (typeof BackendDispatch !== 'undefined') {
|
if (typeof BackendDispatch !== 'undefined') {
|
||||||
@@ -15725,12 +15727,14 @@ ACTION_INPUT: {"output":"Found confirmed SQLi on /login endpoint..."}`;
|
|||||||
|
|
||||||
function getGeneralConfig() {
|
function getGeneralConfig() {
|
||||||
const apiKey = getApiKey();
|
const apiKey = getApiKey();
|
||||||
const provider = localStorage.getItem('t3mp3st_general_provider') || (T3MP3ST_API?.connected ? 'codex' : 'openrouter');
|
const savedProvider = localStorage.getItem('t3mp3st_general_provider');
|
||||||
const model = provider === 'codex' ? 'codex-default' : (state.settings?.selectedModel || 'anthropic/claude-sonnet-4');
|
const provider = savedProvider || (T3MP3ST_API?.connected && !T3MP3ST_API?.llmAvailable ? 'codex' : undefined);
|
||||||
|
const model = provider === 'codex' ? 'codex-default' : (provider ? (state.settings?.selectedModel || 'anthropic/claude-sonnet-4') : undefined);
|
||||||
return { apiKey, provider, model };
|
return { apiKey, provider, model };
|
||||||
}
|
}
|
||||||
|
|
||||||
function _generalNeedsApiKey(provider) {
|
function _generalNeedsApiKey(provider) {
|
||||||
|
if (!provider) return false;
|
||||||
return !['codex', 'mock', 'local'].includes(provider);
|
return !['codex', 'mock', 'local'].includes(provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18181,19 +18185,27 @@ IMPORTANT: This is an authorized security testing/red team platform. All operati
|
|||||||
// Determine the LLM backend. Priority: a configured API key → its provider; else a
|
// Determine the LLM backend. Priority: a configured API key → its provider; else a
|
||||||
// connected local agent (Claude Code / Codex / Hermes) which needs NO key (uses its own
|
// connected local agent (Claude Code / Codex / Hermes) which needs NO key (uses its own
|
||||||
// login); else fall through to the server's own configured LLM.
|
// login); else fall through to the server's own configured LLM.
|
||||||
let provider = 'openrouter';
|
let provider;
|
||||||
let model = state.settings.selectedModel || 'anthropic/claude-sonnet-4';
|
let model;
|
||||||
const agentBackend = (!apiKey && typeof window.t3mpPreferredAgent === 'function') ? window.t3mpPreferredAgent() : null;
|
const agentBackend = (!apiKey && typeof window.t3mpPreferredAgent === 'function') ? window.t3mpPreferredAgent() : null;
|
||||||
if (!apiKey && agentBackend) {
|
if (!apiKey && agentBackend) {
|
||||||
provider = 'local-agent';
|
provider = 'local-agent';
|
||||||
model = agentBackend; // the agent id (codex|claude|hermes) travels in `model`
|
model = agentBackend; // the agent id (codex|claude|hermes) travels in `model`
|
||||||
addIntel('BACKEND', `No API key — routing the mission through your connected agent: ${agentBackend.toUpperCase()}`, 'success');
|
addIntel('BACKEND', `No API key — routing the mission through your connected agent: ${agentBackend.toUpperCase()}`, 'success');
|
||||||
addMissionLog('info', `LLM backend: local agent (${agentBackend}) — no API key needed`);
|
addMissionLog('info', `LLM backend: local agent (${agentBackend}) — no API key needed`);
|
||||||
|
} else if (!apiKey) {
|
||||||
|
addIntel('BACKEND', 'No browser API key — using the server-configured LLM backend', 'success');
|
||||||
|
addMissionLog('info', 'LLM backend: server default — no browser API key sent');
|
||||||
} else if (apiKey && apiKey.startsWith('sk-ant-')) {
|
} else if (apiKey && apiKey.startsWith('sk-ant-')) {
|
||||||
provider = 'anthropic';
|
provider = 'anthropic';
|
||||||
|
model = state.settings.selectedModel || 'anthropic/claude-sonnet-4';
|
||||||
model = model.replace('anthropic/', ''); // Anthropic API doesn't use prefix
|
model = model.replace('anthropic/', ''); // Anthropic API doesn't use prefix
|
||||||
} else if (apiKey && apiKey.startsWith('sk-') && !apiKey.startsWith('sk-ant-') && !apiKey.startsWith('sk-or-')) {
|
} else if (apiKey && apiKey.startsWith('sk-') && !apiKey.startsWith('sk-ant-') && !apiKey.startsWith('sk-or-')) {
|
||||||
provider = 'openai';
|
provider = 'openai';
|
||||||
|
model = state.settings.selectedModel || 'anthropic/claude-sonnet-4';
|
||||||
|
} else {
|
||||||
|
provider = 'openrouter';
|
||||||
|
model = state.settings.selectedModel || 'anthropic/claude-sonnet-4';
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -25104,10 +25116,11 @@ Be specific and actionable. Format as structured JSON.`;
|
|||||||
function _admBackbone(){
|
function _admBackbone(){
|
||||||
const apiKey = (typeof getApiKey === 'function') ? (getApiKey() || '') : '';
|
const apiKey = (typeof getApiKey === 'function') ? (getApiKey() || '') : '';
|
||||||
const agent = (!apiKey && typeof window.t3mpPreferredAgent === 'function') ? window.t3mpPreferredAgent() : null;
|
const agent = (!apiKey && typeof window.t3mpPreferredAgent === 'function') ? window.t3mpPreferredAgent() : null;
|
||||||
let provider = 'openrouter', model = ((typeof state !== 'undefined' && state.settings && state.settings.selectedModel) || 'anthropic/claude-sonnet-4');
|
let provider, model;
|
||||||
if (!apiKey && agent) { provider = 'local-agent'; model = agent; }
|
if (!apiKey && agent) { provider = 'local-agent'; model = agent; }
|
||||||
else if (apiKey && apiKey.startsWith('sk-ant-')) { provider = 'anthropic'; model = model.replace('anthropic/', ''); }
|
else if (apiKey && apiKey.startsWith('sk-ant-')) { provider = 'anthropic'; model = ((typeof state !== 'undefined' && state.settings && state.settings.selectedModel) || 'anthropic/claude-sonnet-4').replace('anthropic/', ''); }
|
||||||
else if (apiKey && apiKey.startsWith('sk-') && !apiKey.startsWith('sk-or-')) { provider = 'openai'; }
|
else if (apiKey && apiKey.startsWith('sk-') && !apiKey.startsWith('sk-or-')) { provider = 'openai'; model = ((typeof state !== 'undefined' && state.settings && state.settings.selectedModel) || 'anthropic/claude-sonnet-4'); }
|
||||||
|
else if (apiKey) { provider = 'openrouter'; model = ((typeof state !== 'undefined' && state.settings && state.settings.selectedModel) || 'anthropic/claude-sonnet-4'); }
|
||||||
return { provider: provider, model: model, apiKey: apiKey || undefined };
|
return { provider: provider, model: model, apiKey: apiKey || undefined };
|
||||||
}
|
}
|
||||||
window.admiralPreviewPlan = async function(){
|
window.admiralPreviewPlan = async function(){
|
||||||
@@ -25297,7 +25310,7 @@ Be specific and actionable. Format as structured JSON.`;
|
|||||||
const cur = (document.getElementById('opPromptEdit')||{}).value || '';
|
const cur = (document.getElementById('opPromptEdit')||{}).value || '';
|
||||||
const apiKey = (typeof getApiKey==='function')?(getApiKey()||''):'';
|
const apiKey = (typeof getApiKey==='function')?(getApiKey()||''):'';
|
||||||
const agent = (!apiKey && typeof window.t3mpPreferredAgent==='function')?window.t3mpPreferredAgent():null;
|
const agent = (!apiKey && typeof window.t3mpPreferredAgent==='function')?window.t3mpPreferredAgent():null;
|
||||||
let provider='openrouter', model; if(!apiKey&&agent){provider='local-agent';model=agent;}
|
let provider, model; if(!apiKey&&agent){provider='local-agent';model=agent;} else if(apiKey){provider='openrouter';}
|
||||||
try { const d = await (await fetch(base()+'/api/admiral/suggest',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({operatorPrompt:cur,archetype:SELECTED,provider,model,apiKey:apiKey||undefined})})).json();
|
try { const d = await (await fetch(base()+'/api/admiral/suggest',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({operatorPrompt:cur,archetype:SELECTED,provider,model,apiKey:apiKey||undefined})})).json();
|
||||||
if(d.error) throw new Error(d.error); ADVICE=d; renderAdvice();
|
if(d.error) throw new Error(d.error); ADVICE=d; renderAdvice();
|
||||||
} catch(e){ if(box) box.innerHTML='<div style="font-size:12px;color:#ff6b6b;padding:8px;">Suggestion failed: '+esc(e.message)+'</div>'; }
|
} catch(e){ if(box) box.innerHTML='<div style="font-size:12px;color:#ff6b6b;padding:8px;">Suggestion failed: '+esc(e.message)+'</div>'; }
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { readFileSync } from 'fs';
|
|||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
|
|
||||||
const serverSource = readFileSync(join(process.cwd(), 'src/server.ts'), 'utf8');
|
const serverSource = readFileSync(join(process.cwd(), 'src/server.ts'), 'utf8');
|
||||||
|
const uiSource = readFileSync(join(process.cwd(), 'docs/index.html'), 'utf8');
|
||||||
|
|
||||||
function sourceBlock(startMarker: string, endMarker: string): string {
|
function sourceBlock(startMarker: string, endMarker: string): string {
|
||||||
const start = serverSource.indexOf(startMarker);
|
const start = serverSource.indexOf(startMarker);
|
||||||
@@ -17,6 +18,34 @@ function routeBlock(startMarker: string, endMarker: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe('local API authorization hardening invariants', () => {
|
describe('local API authorization hardening invariants', () => {
|
||||||
|
it('server mission/general routes default to the configured LLM instead of hardcoded OpenRouter', () => {
|
||||||
|
const resolver = routeBlock('function resolveGeneralLLMConfig(', '/**\n * Bring a planned mission UP for real');
|
||||||
|
const missionRoute = routeBlock("app.post('/api/mission/start'", "app.get('/api/mission/report'");
|
||||||
|
const generalRoutes = routeBlock("app.post('/api/general/plan'", '// =============================================================================\n// THE ADMIRAL');
|
||||||
|
|
||||||
|
expect(resolver).toContain('config.getLLMConfig()');
|
||||||
|
expect(resolver).toContain('provider || defaultConfig.provider');
|
||||||
|
expect(missionRoute).toContain('resolveGeneralLLMConfig(provider, model, apiKey)');
|
||||||
|
expect(`${missionRoute}\n${generalRoutes}`).not.toMatch(/provider\s*=\s*['"]openrouter['"]/);
|
||||||
|
expect(`${missionRoute}\n${generalRoutes}`).not.toMatch(/model\s*=\s*['"]anthropic\/claude-sonnet-4['"]/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('health and LLM status count keyless local backends as configured', () => {
|
||||||
|
const health = routeBlock('function healthPayload()', '// =============================================================================\n// API ENDPOINTS - HEALTH & STATUS');
|
||||||
|
const llmStatus = routeBlock("app.get('/api/llm/status'", '// =============================================================================\n// TOOL EXECUTION ENDPOINTS');
|
||||||
|
|
||||||
|
expect(health).toContain('providerRunsKeyless(llmConfig.provider)');
|
||||||
|
expect(llmStatus).toContain('providerRunsKeyless(llmConfig.provider)');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('War Room no-key/no-agent backend dispatch defers to the server-configured LLM', () => {
|
||||||
|
expect(uiSource).toContain("serverLLM: null");
|
||||||
|
expect(uiSource).toContain('this.serverLLM = data.llm');
|
||||||
|
expect(uiSource).toContain('No browser API key — using the server-configured LLM backend');
|
||||||
|
expect(uiSource).toContain('LLM backend: server default — no browser API key sent');
|
||||||
|
expect(uiSource).not.toMatch(/let provider = ['"]openrouter['"];\s*\n\s*let model = state\.settings\.selectedModel/);
|
||||||
|
});
|
||||||
|
|
||||||
it('/api/events does not grant wildcard CORS and rejects foreign browser origins before opening SSE', () => {
|
it('/api/events does not grant wildcard CORS and rejects foreign browser origins before opening SSE', () => {
|
||||||
const route = routeBlock("app.get('/api/events'", '// =============================================================================\n// API ENDPOINTS - HEALTH & STATUS');
|
const route = routeBlock("app.get('/api/events'", '// =============================================================================\n// API ENDPOINTS - HEALTH & STATUS');
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,14 @@ describe('local OpenAI-compatible provider hardening', () => {
|
|||||||
expect(localCase).toMatch(/ZHIPUAI_API_KEY/);
|
expect(localCase).toMatch(/ZHIPUAI_API_KEY/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('getLLMConfig treats UI local placeholders as unset model names', () => {
|
||||||
|
const localCase = block(configSource, "case 'local':", 'break;');
|
||||||
|
|
||||||
|
expect(localCase).toContain('local-model');
|
||||||
|
expect(localCase).toContain('local/ollama');
|
||||||
|
expect(localCase).toContain('TEMPEST_LOCAL_MODEL');
|
||||||
|
});
|
||||||
|
|
||||||
it('getApiKey resolves a local key from env without persisting it', () => {
|
it('getApiKey resolves a local key from env without persisting it', () => {
|
||||||
// The local key is read in-memory from env, matching the env-key hardening
|
// The local key is read in-memory from env, matching the env-key hardening
|
||||||
// invariant (env keys are never written to the store).
|
// invariant (env keys are never written to the store).
|
||||||
|
|||||||
+4
-3
@@ -726,9 +726,10 @@ class ConfigManager {
|
|||||||
// Some OpenAI-compatible servers require a real bearer (Zhipu, Together, etc.) —
|
// Some OpenAI-compatible servers require a real bearer (Zhipu, Together, etc.) —
|
||||||
// TEMPEST_LOCAL_API_KEY (or a provider-specific env like ZAI_API_KEY) provides it.
|
// TEMPEST_LOCAL_API_KEY (or a provider-specific env like ZAI_API_KEY) provides it.
|
||||||
baseUrl = process.env.TEMPEST_LOCAL_BASE_URL?.trim() || 'http://localhost:11434/api';
|
baseUrl = process.env.TEMPEST_LOCAL_BASE_URL?.trim() || 'http://localhost:11434/api';
|
||||||
// 'local-model' is the placeholder id from AVAILABLE_MODELS — treat it as unset so
|
// Placeholder ids from AVAILABLE_MODELS / the static UI are not real served model tags.
|
||||||
// TEMPEST_LOCAL_MODEL (or the llama3 default) wins; a real tag passed in still takes priority.
|
// Treat them as unset so TEMPEST_LOCAL_MODEL (or the llama3 default) wins; a real tag
|
||||||
actualModel = (model && model !== 'local-model' ? model : undefined) || process.env.TEMPEST_LOCAL_MODEL?.trim() || 'llama3';
|
// passed in still takes priority.
|
||||||
|
actualModel = (model && !['local-model', 'local/ollama'].includes(model) ? model : undefined) || process.env.TEMPEST_LOCAL_MODEL?.trim() || 'llama3';
|
||||||
apiKey = process.env.TEMPEST_LOCAL_API_KEY?.trim() || process.env.ZAI_API_KEY?.trim() || process.env.ZHIPUAI_API_KEY?.trim();
|
apiKey = process.env.TEMPEST_LOCAL_API_KEY?.trim() || process.env.ZAI_API_KEY?.trim() || process.env.ZHIPUAI_API_KEY?.trim();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
+29
-22
@@ -4322,7 +4322,7 @@ function healthPayload(): Record<string, unknown> {
|
|||||||
version: '0.2.1',
|
version: '0.2.1',
|
||||||
apiVersion: 'v1',
|
apiVersion: 'v1',
|
||||||
llm: {
|
llm: {
|
||||||
configured: Boolean(llmConfig.apiKey) || llmConfig.provider === 'codex',
|
configured: Boolean(llmConfig.apiKey) || providerRunsKeyless(llmConfig.provider),
|
||||||
connected: Boolean(llm) || llmConfig.provider === 'codex',
|
connected: Boolean(llm) || llmConfig.provider === 'codex',
|
||||||
provider: llmConfig.provider,
|
provider: llmConfig.provider,
|
||||||
model: llmConfig.model,
|
model: llmConfig.model,
|
||||||
@@ -5953,7 +5953,8 @@ app.get('/api/llm/status', (_req: Request, res: Response) => {
|
|||||||
connected: !!llm,
|
connected: !!llm,
|
||||||
provider: llmConfig.provider,
|
provider: llmConfig.provider,
|
||||||
model: llmConfig.model,
|
model: llmConfig.model,
|
||||||
hasApiKey: !!llmConfig.apiKey
|
hasApiKey: !!llmConfig.apiKey,
|
||||||
|
configured: Boolean(llmConfig.apiKey) || providerRunsKeyless(llmConfig.provider),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -6045,8 +6046,8 @@ app.post('/api/mission/start', async (req: Request, res: Response): Promise<void
|
|||||||
targets = [],
|
targets = [],
|
||||||
operators = [],
|
operators = [],
|
||||||
apiKey,
|
apiKey,
|
||||||
provider = 'openrouter',
|
provider,
|
||||||
model = 'anthropic/claude-sonnet-4',
|
model,
|
||||||
// OPTIONAL white-box source: an absolute path to a LOCAL repo you own. When
|
// OPTIONAL white-box source: an absolute path to a LOCAL repo you own. When
|
||||||
// present, we ingest + security-rank it and hand the packed source to the
|
// present, we ingest + security-rank it and hand the packed source to the
|
||||||
// command via setWhiteboxSource BEFORE start(), so operators reason over the
|
// command via setWhiteboxSource BEFORE start(), so operators reason over the
|
||||||
@@ -6054,18 +6055,19 @@ app.post('/api/mission/start', async (req: Request, res: Response): Promise<void
|
|||||||
repoPath,
|
repoPath,
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
||||||
// Use provided apiKey or fall back to server-configured one. Local-agent backends
|
// Use the request-selected backend, or fall back to the server's configured default.
|
||||||
// (Claude Code / Codex / Hermes) need NO key — the agent uses its own login.
|
// Local/local-agent backends need no hosted API key.
|
||||||
// SECURITY NOTE: apiKey is read from the request body (Authorization header is
|
// SECURITY NOTE: apiKey is read from the request body (Authorization header is
|
||||||
// preferred). Kept body-accepted for the same-origin UI; only reachable from
|
// preferred). Kept body-accepted for the same-origin UI; only reachable from
|
||||||
// the local operator (loopback bind + origin guard). Header move is out of scope.
|
// the local operator (loopback bind + origin guard). Header move is out of scope.
|
||||||
const effectiveKey = apiKey || config.getLLMConfig().apiKey;
|
const missionLLMConfig = resolveGeneralLLMConfig(provider, model, apiKey);
|
||||||
if (providerNeedsApiKey(provider) && !effectiveKey) {
|
const effectiveKey = missionLLMConfig.apiKey;
|
||||||
|
if (providerNeedsApiKey(missionLLMConfig.provider) && !effectiveKey) {
|
||||||
res.status(400).json({ error: 'API key required — pass apiKey, configure one on the server, or connect a local agent (Claude Code / Codex / Hermes)' });
|
res.status(400).json({ error: 'API key required — pass apiKey, configure one on the server, or connect a local agent (Claude Code / Codex / Hermes)' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (provider === 'local-agent') {
|
if (missionLLMConfig.provider === 'local-agent') {
|
||||||
const localAgent = await requireLiveLocalAgent(model);
|
const localAgent = await requireLiveLocalAgent(missionLLMConfig.model);
|
||||||
if (!localAgent.ok) {
|
if (!localAgent.ok) {
|
||||||
res.status(503).json({ error: localAgent.error });
|
res.status(503).json({ error: localAgent.error });
|
||||||
return;
|
return;
|
||||||
@@ -6101,7 +6103,7 @@ app.post('/api/mission/start', async (req: Request, res: Response): Promise<void
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const cmd = createTempestCommandInstance(name, effectiveKey, provider, model);
|
const cmd = createTempestCommandInstance(name, effectiveKey, missionLLMConfig.provider, missionLLMConfig.model);
|
||||||
|
|
||||||
// Add targets
|
// Add targets
|
||||||
for (const t of targets) {
|
for (const t of targets) {
|
||||||
@@ -6560,6 +6562,10 @@ function providerNeedsApiKey(provider: string): boolean {
|
|||||||
return !['codex', 'mock', 'local', 'local-agent'].includes(provider);
|
return !['codex', 'mock', 'local', 'local-agent'].includes(provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function providerRunsKeyless(provider: string): boolean {
|
||||||
|
return !providerNeedsApiKey(provider);
|
||||||
|
}
|
||||||
|
|
||||||
function readPositiveTimeoutEnv(name: string): number | undefined {
|
function readPositiveTimeoutEnv(name: string): number | undefined {
|
||||||
const raw = process.env[name];
|
const raw = process.env[name];
|
||||||
if (raw == null || raw.trim() === '') return undefined;
|
if (raw == null || raw.trim() === '') return undefined;
|
||||||
@@ -6578,7 +6584,7 @@ function readGeneralTimeoutEnv(): number | undefined {
|
|||||||
// the key in the body, so we accept it to avoid breaking it. Moving to a header
|
// the key in the body, so we accept it to avoid breaking it. Moving to a header
|
||||||
// needs a coordinated UI change and is out of scope. The body key is only ever
|
// needs a coordinated UI change and is out of scope. The body key is only ever
|
||||||
// reachable from the local operator (loopback bind + origin guard).
|
// reachable from the local operator (loopback bind + origin guard).
|
||||||
function resolveGeneralLLMConfig(provider: string, model: string | undefined, apiKey: string | undefined): {
|
function resolveGeneralLLMConfig(provider: string | undefined, model: string | undefined, apiKey: string | undefined): {
|
||||||
provider: any;
|
provider: any;
|
||||||
model: string;
|
model: string;
|
||||||
apiKey?: string;
|
apiKey?: string;
|
||||||
@@ -6586,7 +6592,8 @@ function resolveGeneralLLMConfig(provider: string, model: string | undefined, ap
|
|||||||
temperature: number;
|
temperature: number;
|
||||||
timeout: number;
|
timeout: number;
|
||||||
} {
|
} {
|
||||||
const selectedProvider = provider || 'openrouter';
|
const defaultConfig = config.getLLMConfig();
|
||||||
|
const selectedProvider = provider || defaultConfig.provider;
|
||||||
// Local-agent backends (Claude Code / Codex / Hermes via the connector) need NO API key — the
|
// Local-agent backends (Claude Code / Codex / Hermes via the connector) need NO API key — the
|
||||||
// agent uses its own login. The agent id (codex|claude|hermes) travels in `model`.
|
// agent uses its own login. The agent id (codex|claude|hermes) travels in `model`.
|
||||||
if (selectedProvider === 'local-agent') {
|
if (selectedProvider === 'local-agent') {
|
||||||
@@ -6779,8 +6786,8 @@ app.post('/api/general/plan', async (req: Request, res: Response): Promise<void>
|
|||||||
urgency,
|
urgency,
|
||||||
opsecPreference,
|
opsecPreference,
|
||||||
apiKey,
|
apiKey,
|
||||||
provider = 'openrouter',
|
provider,
|
||||||
model = 'anthropic/claude-sonnet-4',
|
model,
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
||||||
if (!objective) {
|
if (!objective) {
|
||||||
@@ -6853,8 +6860,8 @@ app.post('/api/general/execute', async (req: Request, res: Response): Promise<vo
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
apiKey,
|
apiKey,
|
||||||
provider = 'openrouter',
|
provider,
|
||||||
model = 'anthropic/claude-sonnet-4',
|
model,
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
||||||
let generalConfig;
|
let generalConfig;
|
||||||
@@ -6966,8 +6973,8 @@ app.post('/api/general/auto', async (req: Request, res: Response): Promise<void>
|
|||||||
urgency,
|
urgency,
|
||||||
opsecPreference,
|
opsecPreference,
|
||||||
apiKey,
|
apiKey,
|
||||||
provider = 'openrouter',
|
provider,
|
||||||
model = 'anthropic/claude-sonnet-4',
|
model,
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
||||||
if (!objective) {
|
if (!objective) {
|
||||||
@@ -7225,7 +7232,7 @@ import { Admiral, briefToDirective, type ChatMsg, type MissionBrief } from './ad
|
|||||||
*/
|
*/
|
||||||
app.post('/api/admiral/converse', async (req: Request, res: Response): Promise<void> => {
|
app.post('/api/admiral/converse', async (req: Request, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const { messages, provider = 'openrouter', model, apiKey } = req.body as {
|
const { messages, provider, model, apiKey } = req.body as {
|
||||||
messages: ChatMsg[]; provider?: string; model?: string; apiKey?: string;
|
messages: ChatMsg[]; provider?: string; model?: string; apiKey?: string;
|
||||||
};
|
};
|
||||||
if (!Array.isArray(messages) || messages.length === 0) {
|
if (!Array.isArray(messages) || messages.length === 0) {
|
||||||
@@ -7255,7 +7262,7 @@ app.post('/api/admiral/converse', async (req: Request, res: Response): Promise<v
|
|||||||
*/
|
*/
|
||||||
app.post('/api/admiral/suggest', async (req: Request, res: Response): Promise<void> => {
|
app.post('/api/admiral/suggest', async (req: Request, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const { operatorPrompt, archetype, failureSignal, provider = 'openrouter', model, apiKey } = req.body as {
|
const { operatorPrompt, archetype, failureSignal, provider, model, apiKey } = req.body as {
|
||||||
operatorPrompt?: string; archetype?: string; failureSignal?: string; provider?: string; model?: string; apiKey?: string;
|
operatorPrompt?: string; archetype?: string; failureSignal?: string; provider?: string; model?: string; apiKey?: string;
|
||||||
};
|
};
|
||||||
let prompt = typeof operatorPrompt === 'string' ? operatorPrompt : '';
|
let prompt = typeof operatorPrompt === 'string' ? operatorPrompt : '';
|
||||||
@@ -7290,7 +7297,7 @@ app.post('/api/admiral/suggest', async (req: Request, res: Response): Promise<vo
|
|||||||
*/
|
*/
|
||||||
app.post('/api/admiral/launch', async (req: Request, res: Response): Promise<void> => {
|
app.post('/api/admiral/launch', async (req: Request, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const { brief, confirmed, provider = 'openrouter', model, apiKey } = req.body as {
|
const { brief, confirmed, provider, model, apiKey } = req.body as {
|
||||||
brief: MissionBrief; confirmed?: boolean; provider?: string; model?: string; apiKey?: string;
|
brief: MissionBrief; confirmed?: boolean; provider?: string; model?: string; apiKey?: string;
|
||||||
};
|
};
|
||||||
if (!brief || !brief.objective || !brief.target) {
|
if (!brief || !brief.objective || !brief.target) {
|
||||||
|
|||||||
Reference in new issue
Block a user