Files
flynn/src/councils/preflight.ts
T

348 lines
12 KiB
TypeScript

import type { AgentConfigRegistry } from '../agents/registry.js';
import {
loadStoredAnthropicAuth,
loadStoredAnthropicAuthToken,
loadStoredOpenAIApiKey,
loadStoredOpenAIAuth,
loadStoredZaiAuth,
} from '../auth/index.js';
import type { Config, ModelConfig, ModelProvider } from '../config/index.js';
import type { ModelTier } from '../models/router.js';
import type { ChatResponseFormat } from '../models/types.js';
interface DelegateRunner {
delegate(request: {
tier: ModelTier;
systemPrompt: string;
message: string;
maxTokens?: number;
responseFormat?: ChatResponseFormat;
}): Promise<{
content: string;
usage: { inputTokens: number; outputTokens: number };
tier: ModelTier;
}>;
}
interface CredentialState {
openaiOAuth: boolean;
openaiApiKeyStored: boolean;
anthropicApiKeyStored: boolean;
anthropicAuthTokenStored: boolean;
zaiStored: boolean;
}
export interface CouncilPreflightOptions {
config: Config;
registry: AgentConfigRegistry;
delegateRunner: DelegateRunner;
activeTier?: ModelTier;
includeLiveProbe?: boolean;
credentialState?: CredentialState;
}
interface TierConfigResolution {
modelConfig: ModelConfig;
fellBackToDefault: boolean;
}
interface AuthResolution {
mode: string;
source: string;
note?: string;
}
const DEFAULT_ENDPOINTS: Partial<Record<ModelProvider, string>> = {
openai: 'https://api.openai.com/v1',
openrouter: 'https://openrouter.ai/api/v1',
vercel: 'https://ai-gateway.vercel.sh/v1',
zhipuai: 'https://api.z.ai/api/paas/v4',
xai: 'https://api.x.ai/v1',
minimax: 'https://api.minimax.io/v1',
moonshot: 'https://api.moonshot.cn/v1',
ollama: 'http://localhost:11434',
llamacpp: 'http://localhost:8080',
};
function getCredentialStateFromSystem(): CredentialState {
return {
openaiOAuth: Boolean(loadStoredOpenAIAuth()),
openaiApiKeyStored: Boolean(loadStoredOpenAIApiKey()),
anthropicApiKeyStored: Boolean(loadStoredAnthropicAuth()),
anthropicAuthTokenStored: Boolean(loadStoredAnthropicAuthToken()),
zaiStored: Boolean(loadStoredZaiAuth()),
};
}
function getEffectiveAuthMode(cfg: ModelConfig): 'auto' | 'api_key' | 'oauth' {
if (cfg.auth_mode) {
return cfg.auth_mode;
}
if (cfg.use_oauth) {
return 'oauth';
}
return 'auto';
}
function resolveTierConfig(config: Config, tier: ModelTier): TierConfigResolution {
if (tier === 'default') {
return { modelConfig: config.models.default, fellBackToDefault: false };
}
const direct = config.models[tier];
if (direct) {
return { modelConfig: direct, fellBackToDefault: false };
}
return { modelConfig: config.models.default, fellBackToDefault: true };
}
function firstTruthySource(sources: Array<[boolean, string]>): string {
for (const [present, label] of sources) {
if (present) {
return label;
}
}
return 'missing';
}
function resolveAuth(cfg: ModelConfig, credentialState: CredentialState): AuthResolution {
if (cfg.provider === 'zhipuai') {
const source = firstTruthySource([
[Boolean(cfg.api_key?.trim()), 'config.api_key'],
[Boolean(cfg.auth_token?.trim()), 'config.auth_token'],
[Boolean(process.env.ZAI_API_KEY?.trim()), 'env:ZAI_API_KEY'],
[Boolean(process.env.ZHIPUAI_API_KEY?.trim()), 'env:ZHIPUAI_API_KEY'],
[Boolean(process.env.ZHIPUAI_AUTH_TOKEN?.trim()), 'env:ZHIPUAI_AUTH_TOKEN'],
[credentialState.zaiStored, 'auth.json:zai'],
]);
const note = cfg.use_oauth || cfg.auth_mode === 'oauth'
? 'use_oauth/auth_mode=oauth is ignored for zhipuai'
: undefined;
return {
mode: 'bearer_credential',
source,
note,
};
}
if (cfg.provider === 'openai') {
const authMode = getEffectiveAuthMode(cfg);
const apiSource = firstTruthySource([
[Boolean(cfg.api_keys && cfg.api_keys.length > 0), 'config.api_keys'],
[Boolean(cfg.api_key?.trim()), 'config.api_key'],
[Boolean(process.env.OPENAI_API_KEY?.trim()), 'env:OPENAI_API_KEY'],
[credentialState.openaiApiKeyStored, 'auth.json:openai.api_key'],
]);
const oauthSource = credentialState.openaiOAuth ? 'auth.json:openai.oauth' : 'missing';
if (authMode === 'oauth') {
return { mode: 'oauth', source: oauthSource };
}
if (authMode === 'api_key') {
return { mode: 'api_key', source: apiSource };
}
if (apiSource !== 'missing') {
return { mode: 'auto->api_key', source: apiSource };
}
return { mode: 'auto->oauth', source: oauthSource };
}
if (cfg.provider === 'anthropic') {
const authMode = getEffectiveAuthMode(cfg);
const apiSource = firstTruthySource([
[Boolean(cfg.api_keys && cfg.api_keys.length > 0), 'config.api_keys'],
[Boolean(cfg.api_key?.trim()), 'config.api_key'],
[Boolean(process.env.ANTHROPIC_API_KEY?.trim()), 'env:ANTHROPIC_API_KEY'],
[credentialState.anthropicApiKeyStored, 'auth.json:anthropic.api_key'],
]);
const oauthSource = firstTruthySource([
[Boolean(cfg.auth_token?.trim()), 'config.auth_token'],
[Boolean(process.env.ANTHROPIC_AUTH_TOKEN?.trim()), 'env:ANTHROPIC_AUTH_TOKEN'],
[credentialState.anthropicAuthTokenStored, 'auth.json:anthropic.auth_token'],
]);
if (authMode === 'oauth') {
return { mode: 'oauth', source: oauthSource };
}
if (authMode === 'api_key') {
return { mode: 'api_key', source: apiSource };
}
if (apiSource !== 'missing') {
return { mode: 'auto->api_key', source: apiSource };
}
return { mode: 'auto->oauth', source: oauthSource };
}
if (cfg.provider === 'gemini') {
const source = firstTruthySource([
[Boolean(cfg.api_key?.trim()), 'config.api_key'],
[Boolean(process.env.GEMINI_API_KEY?.trim()), 'env:GEMINI_API_KEY'],
[Boolean(process.env.GOOGLE_API_KEY?.trim()), 'env:GOOGLE_API_KEY'],
]);
return { mode: 'api_key', source };
}
if (cfg.provider === 'bedrock') {
const source = firstTruthySource([
[Boolean(cfg.api_key?.trim() && cfg.auth_token?.trim()), 'config.api_key+auth_token'],
[Boolean(process.env.AWS_ACCESS_KEY_ID?.trim() && process.env.AWS_SECRET_ACCESS_KEY?.trim()), 'env:AWS_ACCESS_KEY_ID+AWS_SECRET_ACCESS_KEY'],
]);
return { mode: 'aws_credentials', source };
}
if (cfg.provider === 'ollama' || cfg.provider === 'llamacpp' || cfg.provider === 'synthetic') {
return { mode: 'local', source: 'none' };
}
const source = firstTruthySource([
[Boolean(cfg.api_keys && cfg.api_keys.length > 0), 'config.api_keys'],
[Boolean(cfg.api_key?.trim()), 'config.api_key'],
]);
return { mode: 'api_key', source };
}
function resolveEndpoint(cfg: ModelConfig): string {
if (cfg.endpoint?.trim()) {
return cfg.endpoint.trim();
}
return DEFAULT_ENDPOINTS[cfg.provider] ?? '(provider default)';
}
function normalizeProbeError(error: unknown): string {
if (error instanceof Error) {
return error.message;
}
return String(error);
}
function isPreflightRequest(task: string): boolean {
return task.trim().toLowerCase() === 'preflight';
}
export function shouldRunCouncilPreflight(task: string): boolean {
return isPreflightRequest(task);
}
export async function buildCouncilPreflightReport(options: CouncilPreflightOptions): Promise<string> {
const { config, registry, delegateRunner } = options;
const councils = config.councils;
const credentialState = options.credentialState ?? getCredentialStateFromSystem();
const roles = [
{ role: 'D.arbiter', agent: councils.groups.D.arbiter_agent, tierOverride: councils.groups.D.model_tier },
{ role: 'D.freethinker', agent: councils.groups.D.freethinker_agent, tierOverride: councils.groups.D.model_tier },
...(councils.groups.D.grounder_agent
? [{ role: 'D.grounder', agent: councils.groups.D.grounder_agent, tierOverride: councils.groups.D.model_tier }]
: []),
...(councils.groups.D.writer_agent
? [{ role: 'D.writer', agent: councils.groups.D.writer_agent, tierOverride: councils.groups.D.model_tier }]
: []),
{ role: 'P.arbiter', agent: councils.groups.P.arbiter_agent, tierOverride: councils.groups.P.model_tier },
{ role: 'P.freethinker', agent: councils.groups.P.freethinker_agent, tierOverride: councils.groups.P.model_tier },
...(councils.groups.P.grounder_agent
? [{ role: 'P.grounder', agent: councils.groups.P.grounder_agent, tierOverride: councils.groups.P.model_tier }]
: []),
...(councils.groups.P.writer_agent
? [{ role: 'P.writer', agent: councils.groups.P.writer_agent, tierOverride: councils.groups.P.model_tier }]
: []),
{ role: 'Meta.arbiter', agent: councils.meta_arbiter_agent, tierOverride: councils.meta_model_tier },
...(councils.meta_writer_agent
? [{ role: 'Meta.writer', agent: councils.meta_writer_agent, tierOverride: councils.meta_model_tier }]
: []),
];
const roleLines: string[] = [];
const tiersToProbe = new Set<ModelTier>();
for (const role of roles) {
const agentConfig = registry.get(role.agent);
const agentTier = agentConfig?.modelTier ?? 'default';
const effectiveTier = role.tierOverride ?? agentTier;
const { modelConfig, fellBackToDefault } = resolveTierConfig(config, effectiveTier);
const modelLabel = `${modelConfig.provider}/${modelConfig.model}`;
const missingSuffix = agentConfig ? '' : ' [agent_missing]';
const fallbackSuffix = fellBackToDefault ? ' [tier_unconfigured->default]' : '';
roleLines.push(
`- ${role.role}: agent=${role.agent}${missingSuffix} ` +
`agent_tier=${agentTier} override_tier=${role.tierOverride ?? 'none'} ` +
`effective_tier=${effectiveTier} model=${modelLabel}${fallbackSuffix}`,
);
tiersToProbe.add(effectiveTier);
}
const tierLines: string[] = [];
for (const tier of [...tiersToProbe].sort()) {
const { modelConfig, fellBackToDefault } = resolveTierConfig(config, tier);
const auth = resolveAuth(modelConfig, credentialState);
const endpoint = resolveEndpoint(modelConfig);
tierLines.push(
`- ${tier}: provider=${modelConfig.provider} model=${modelConfig.model} ` +
`endpoint=${endpoint} auth_mode=${auth.mode} auth_source=${auth.source}` +
`${auth.note ? ` note=${auth.note}` : ''}` +
`${fellBackToDefault ? ' fallback=default' : ''}`,
);
}
const probeLines: string[] = [];
if (options.includeLiveProbe ?? true) {
const probeResults = await Promise.all(
[...tiersToProbe].sort().map(async (tier) => {
const startedAt = Date.now();
try {
await delegateRunner.delegate({
tier,
systemPrompt: 'Return exactly {"ok":true}.',
message: 'Return exactly {"ok":true}.',
maxTokens: 64,
});
return {
tier,
ok: true,
latencyMs: Math.max(0, Date.now() - startedAt),
error: '',
};
} catch (error) {
return {
tier,
ok: false,
latencyMs: Math.max(0, Date.now() - startedAt),
error: normalizeProbeError(error),
};
}
}),
);
for (const result of probeResults) {
if (result.ok) {
probeLines.push(`- ${result.tier}: ok (${result.latencyMs}ms)`);
} else {
probeLines.push(`- ${result.tier}: failed (${result.latencyMs}ms) ${result.error}`);
}
}
} else {
probeLines.push('- skipped');
}
const activeTier = options.activeTier ?? 'default';
const activeModel = resolveTierConfig(config, activeTier).modelConfig;
return [
'[Council preflight]',
`Councils enabled: ${councils.enabled ? 'yes' : 'no'}`,
`Active interactive tier: ${activeTier} (${activeModel.provider}/${activeModel.model})`,
`Scaffold path: ${councils.scaffold_path?.trim() ? councils.scaffold_path : '(none)'}`,
'',
'Role routing:',
...roleLines,
'',
'Tier provider/auth:',
...tierLines,
'',
'Live tier probes:',
...probeLines,
'',
'Path comparison:',
'- Interactive TUI messages use the active tier above.',
'- Council calls use per-role effective tiers (group override, then agent tier).',
].join('\n');
}