f363717f5f
Add a new 'github' model provider backed by the Copilot API (api.githubcopilot.com), with OAuth device flow for authentication. - New src/auth/github.ts: device flow login, token storage at ~/.config/flynn/auth.json with 0600 permissions - New src/models/github.ts: OpenAI-compatible client with streaming, tool calling, and Copilot-specific headers - Add 'github' to provider enum in config schema - Register provider in daemon factory and TUI client factory - Refactor TUI to use provider-agnostic client factory (was hardcoded to AnthropicClient for all tiers) - Add /login command to TUI for interactive OAuth authorization - Add Copilot model cost tracking entries
291 lines
11 KiB
TypeScript
291 lines
11 KiB
TypeScript
import { z } from 'zod';
|
|
|
|
const telegramSchema = z.object({
|
|
bot_token: z.string().min(1, 'Bot token is required'),
|
|
allowed_chat_ids: z.array(z.number()).min(1, 'At least one chat ID required'),
|
|
require_mention: z.boolean().default(true),
|
|
});
|
|
|
|
const serverSchema = z.object({
|
|
tailscale_only: z.boolean().default(true),
|
|
localhost: z.boolean().default(true),
|
|
port: z.number().default(18800),
|
|
/** Static bearer token for gateway auth. If set, all connections must provide it. */
|
|
token: z.string().optional(),
|
|
/** Trust Tailscale-User-Login header for identity. */
|
|
tailscale_identity: z.boolean().default(false),
|
|
/** Apply token auth to HTTP requests too (not just WebSocket). Default: true when token is set. */
|
|
auth_http: z.boolean().default(true),
|
|
});
|
|
|
|
const modelConfigSchema = z.object({
|
|
provider: z.enum(['anthropic', 'openai', 'gemini', 'ollama', 'llamacpp', 'openrouter', 'bedrock', 'github']),
|
|
model: z.string(),
|
|
endpoint: z.string().optional(),
|
|
api_key: z.string().optional(),
|
|
auth_token: z.string().optional(),
|
|
for: z.array(z.string()).optional(),
|
|
num_gpu: z.number().optional(),
|
|
context_window: z.number().optional(),
|
|
});
|
|
|
|
const modelsSchema = z.object({
|
|
local: modelConfigSchema.optional(),
|
|
fast: modelConfigSchema.optional(),
|
|
default: modelConfigSchema,
|
|
complex: modelConfigSchema.optional(),
|
|
fallback_chain: z.array(z.string()).default(['anthropic']),
|
|
local_providers: z.record(z.string(), modelConfigSchema).optional(),
|
|
});
|
|
|
|
const backendsSchema = z.object({
|
|
claude_code: z.object({
|
|
enabled: z.boolean().default(false),
|
|
path: z.string().optional(),
|
|
}).default({ enabled: false }),
|
|
opencode: z.object({
|
|
enabled: z.boolean().default(false),
|
|
path: z.string().optional(),
|
|
}).default({ enabled: false }),
|
|
native: z.object({
|
|
enabled: z.boolean().default(true),
|
|
}).default({ enabled: true }),
|
|
}).default({});
|
|
|
|
const hooksSchema = z.object({
|
|
confirm: z.array(z.string()).default([]),
|
|
log: z.array(z.string()).default([]),
|
|
silent: z.array(z.string()).default([]),
|
|
}).default({});
|
|
|
|
const skillsSchema = z.object({
|
|
/** Directory for user-created workspace skills. */
|
|
workspace_dir: z.string().optional(),
|
|
/** Directory for managed (installed) skills. Defaults to ~/.flynn/workspace/skills. */
|
|
managed_dir: z.string().optional(),
|
|
/** Directory for bundled skills shipped with Flynn. */
|
|
bundled_dir: z.string().optional(),
|
|
}).default({});
|
|
|
|
const mcpServerSchema = z.object({
|
|
name: z.string(),
|
|
command: z.string(),
|
|
args: z.array(z.string()).default([]),
|
|
env: z.record(z.string(), z.string()).optional(),
|
|
cwd: z.string().optional(),
|
|
});
|
|
|
|
const mcpSchema = z.object({
|
|
servers: z.array(mcpServerSchema).default([]),
|
|
}).default({ servers: [] });
|
|
|
|
const cronJobSchema = z.object({
|
|
name: z.string().min(1, 'Cron job name is required'),
|
|
schedule: z.string().min(1, 'Cron schedule is required'),
|
|
message: z.string().min(1, 'Cron message is required'),
|
|
output: z.object({
|
|
channel: z.string().min(1),
|
|
peer: z.string().min(1),
|
|
}),
|
|
enabled: z.boolean().default(true),
|
|
timezone: z.string().optional(),
|
|
});
|
|
|
|
const automationSchema = z.object({
|
|
cron: z.array(cronJobSchema).default([]),
|
|
}).default({});
|
|
|
|
const agentsSchema = z.object({
|
|
primary_tier: z.enum(['fast', 'default', 'complex', 'local']).default('default'),
|
|
delegation: z.object({
|
|
compaction: z.enum(['fast', 'default', 'complex', 'local']).default('fast'),
|
|
memory_extraction: z.enum(['fast', 'default', 'complex', 'local']).default('fast'),
|
|
classification: z.enum(['fast', 'default', 'complex', 'local']).default('fast'),
|
|
tool_summarisation: z.enum(['fast', 'default', 'complex', 'local']).default('fast'),
|
|
complex_reasoning: z.enum(['fast', 'default', 'complex', 'local']).default('complex'),
|
|
}).default({
|
|
compaction: 'fast',
|
|
memory_extraction: 'fast',
|
|
classification: 'fast',
|
|
tool_summarisation: 'fast',
|
|
complex_reasoning: 'complex',
|
|
}),
|
|
auto_escalate: z.boolean().default(false),
|
|
max_delegation_depth: z.number().min(1).max(10).default(3),
|
|
}).default({});
|
|
|
|
const memorySchema = z.object({
|
|
enabled: z.boolean().default(true),
|
|
dir: z.string().optional(), // Default: ~/.local/share/flynn/memory
|
|
auto_extract: z.boolean().default(true),
|
|
max_context_tokens: z.number().min(100).max(10000).default(2000),
|
|
}).default({});
|
|
|
|
const compactionSchema = z.object({
|
|
enabled: z.boolean().default(true),
|
|
threshold_pct: z.number().min(10).max(100).default(80),
|
|
keep_turns: z.number().min(1).max(50).default(4),
|
|
summary_max_tokens: z.number().min(128).max(4096).default(1024),
|
|
}).default({});
|
|
|
|
const discordSchema = z.object({
|
|
bot_token: z.string().min(1, 'Bot token is required'),
|
|
allowed_guild_ids: z.array(z.string()).default([]),
|
|
allowed_channel_ids: z.array(z.string()).default([]),
|
|
require_mention: z.boolean().default(true),
|
|
}).optional();
|
|
|
|
const slackSchema = z.object({
|
|
bot_token: z.string().min(1, 'Bot token is required'),
|
|
app_token: z.string().min(1, 'App token is required'),
|
|
signing_secret: z.string().min(1, 'Signing secret is required'),
|
|
allowed_channel_ids: z.array(z.string()).default([]),
|
|
require_mention: z.boolean().default(false),
|
|
}).optional();
|
|
|
|
const whatsappSchema = z.object({
|
|
allowed_numbers: z.array(z.string()).default([]),
|
|
allowed_group_ids: z.array(z.string()).default([]),
|
|
require_mention: z.boolean().default(true),
|
|
data_dir: z.string().optional(),
|
|
}).optional();
|
|
|
|
const browserSchema = z.object({
|
|
enabled: z.boolean().default(false),
|
|
executable_path: z.string().optional(),
|
|
ws_endpoint: z.string().optional(),
|
|
headless: z.boolean().default(true),
|
|
max_pages: z.number().min(1).max(20).default(5),
|
|
default_timeout: z.number().min(1000).max(120000).default(30000),
|
|
}).default({});
|
|
|
|
const processSchema = z.object({
|
|
max_concurrent: z.number().min(1).max(50).default(10),
|
|
max_runtime_minutes: z.number().min(1).max(1440).default(60),
|
|
buffer_size: z.number().min(1024).max(1048576).default(65536),
|
|
}).default({});
|
|
|
|
const retrySchema = z.object({
|
|
enabled: z.boolean().default(true),
|
|
max_retries: z.number().min(0).max(10).default(3),
|
|
initial_delay_ms: z.number().min(100).max(60000).default(1000),
|
|
backoff_multiplier: z.number().min(1).max(5).default(2),
|
|
max_delay_ms: z.number().min(1000).max(120000).default(30000),
|
|
}).default({});
|
|
|
|
const webSearchSchema = z.object({
|
|
provider: z.enum(['brave', 'searxng']).default('brave'),
|
|
api_key: z.string().optional(),
|
|
endpoint: z.string().optional(),
|
|
max_results: z.number().min(1).max(20).default(5),
|
|
}).default({});
|
|
|
|
// ── Tool policy schemas ──────────────────────────────────────────────
|
|
|
|
const toolProfileEnum = z.enum(['minimal', 'messaging', 'coding', 'full']);
|
|
|
|
const toolOverrideSchema = z.object({
|
|
profile: toolProfileEnum.optional(),
|
|
allow: z.array(z.string()).default([]),
|
|
deny: z.array(z.string()).default([]),
|
|
}).default({});
|
|
|
|
const toolsSchema = z.object({
|
|
profile: toolProfileEnum.default('full'),
|
|
allow: z.array(z.string()).default([]),
|
|
deny: z.array(z.string()).default([]),
|
|
agents: z.record(z.string(), toolOverrideSchema).default({}),
|
|
providers: z.record(z.string(), toolOverrideSchema).default({}),
|
|
}).default({});
|
|
|
|
// ── Sandbox schemas ───────────────────────────────────────────────────
|
|
|
|
const sandboxSchema = z.object({
|
|
enabled: z.boolean().default(false),
|
|
image: z.string().default('node:22-slim'),
|
|
workspace_dir: z.string().default('/workspace'),
|
|
network: z.enum(['none', 'bridge', 'host']).default('none'),
|
|
memory_limit: z.string().default('512m'),
|
|
cpu_limit: z.string().default('1.0'),
|
|
timeout_seconds: z.number().min(10).max(3600).default(300),
|
|
}).default({});
|
|
|
|
// ── Agent config + routing schemas ────────────────────────────────────
|
|
|
|
const modelTierEnum = z.enum(['fast', 'default', 'complex', 'local']);
|
|
|
|
const agentConfigEntrySchema = z.object({
|
|
system_prompt: z.string().optional(),
|
|
model_tier: modelTierEnum.optional(),
|
|
tool_profile: toolProfileEnum.optional(),
|
|
tool_overrides: toolOverrideSchema.optional(),
|
|
sandbox: z.boolean().default(false),
|
|
});
|
|
|
|
const agentConfigsSchema = z.record(z.string(), agentConfigEntrySchema).default({});
|
|
|
|
const routingSchema = z.object({
|
|
default_agent: z.string().optional(),
|
|
channels: z.record(z.string(), z.string()).default({}),
|
|
senders: z.record(z.string(), z.string()).default({}),
|
|
}).default({});
|
|
|
|
const promptSchema = z.object({
|
|
/** Additional directories to search for prompt template files. */
|
|
search_dirs: z.array(z.string()).default([]),
|
|
/** Extra named sections to include in the system prompt. */
|
|
extra_sections: z.array(z.object({
|
|
name: z.string(),
|
|
content: z.string(),
|
|
})).default([]),
|
|
}).default({});
|
|
|
|
export const configSchema = z.object({
|
|
telegram: telegramSchema,
|
|
discord: discordSchema,
|
|
slack: slackSchema,
|
|
whatsapp: whatsappSchema,
|
|
server: serverSchema.default({}),
|
|
models: modelsSchema,
|
|
backends: backendsSchema.default({}),
|
|
hooks: hooksSchema.default({}),
|
|
skills: skillsSchema.default({}),
|
|
mcp: mcpSchema.default({ servers: [] }),
|
|
automation: automationSchema,
|
|
agents: agentsSchema,
|
|
compaction: compactionSchema,
|
|
memory: memorySchema,
|
|
process: processSchema,
|
|
browser: browserSchema,
|
|
retry: retrySchema,
|
|
web_search: webSearchSchema,
|
|
prompt: promptSchema,
|
|
tools: toolsSchema,
|
|
sandbox: sandboxSchema,
|
|
agent_configs: agentConfigsSchema,
|
|
routing: routingSchema,
|
|
});
|
|
|
|
export type Config = z.infer<typeof configSchema>;
|
|
export type TelegramConfig = z.infer<typeof telegramSchema>;
|
|
export type ModelConfig = z.infer<typeof modelConfigSchema>;
|
|
export type CronJobConfig = z.infer<typeof cronJobSchema>;
|
|
export type AgentsConfig = z.infer<typeof agentsSchema>;
|
|
export type CompactionConfig = z.infer<typeof compactionSchema>;
|
|
export type MemoryConfig = z.infer<typeof memorySchema>;
|
|
export type WebSearchConfig = z.infer<typeof webSearchSchema>;
|
|
export type ProcessConfig = z.infer<typeof processSchema>;
|
|
export type BrowserConfig = z.infer<typeof browserSchema>;
|
|
export type DiscordConfig = z.infer<typeof discordSchema>;
|
|
export type SlackConfig = z.infer<typeof slackSchema>;
|
|
export type WhatsAppConfig = z.infer<typeof whatsappSchema>;
|
|
export type RetryPolicyConfig = z.infer<typeof retrySchema>;
|
|
export type PromptConfig = z.infer<typeof promptSchema>;
|
|
export type ToolProfile = z.infer<typeof toolProfileEnum>;
|
|
export type ToolOverrideConfig = z.infer<typeof toolOverrideSchema>;
|
|
export type ToolsConfig = z.infer<typeof toolsSchema>;
|
|
export type SandboxConfig = z.infer<typeof sandboxSchema>;
|
|
export type AgentConfigEntry = z.infer<typeof agentConfigEntrySchema>;
|
|
export type RoutingConfig = z.infer<typeof routingSchema>;
|
|
export type ServerConfig = z.infer<typeof serverSchema>;
|