feat: wire new providers, auth, mention-gating, and browser into daemon

Update config schema with server auth fields (token, tailscale_identity,
auth_http), channel mention settings, browser config, and openrouter/bedrock
provider enum values. Wire GeminiClient, BedrockClient, OpenRouter into
createClientFromConfig. Initialize BrowserManager and register browser tools
in daemon startup. Pass auth config and channel mention settings through to
gateway and adapters. Add puppeteer-core, @google/generative-ai, and
@aws-sdk/client-bedrock-runtime dependencies.
This commit is contained in:
William Valentin
2026-02-06 16:52:18 -08:00
parent 8c56a5a1a8
commit 880744846f
5 changed files with 1163 additions and 7 deletions
+3
View File
@@ -40,6 +40,8 @@
}, },
"dependencies": { "dependencies": {
"@anthropic-ai/sdk": "^0.39.0", "@anthropic-ai/sdk": "^0.39.0",
"@aws-sdk/client-bedrock-runtime": "^3.985.0",
"@google/generative-ai": "^0.24.1",
"@modelcontextprotocol/sdk": "^1.26.0", "@modelcontextprotocol/sdk": "^1.26.0",
"@mozilla/readability": "^0.5.0", "@mozilla/readability": "^0.5.0",
"@slack/bolt": "^4.6.0", "@slack/bolt": "^4.6.0",
@@ -56,6 +58,7 @@
"marked-terminal": "^7.3.0", "marked-terminal": "^7.3.0",
"ollama": "^0.5.0", "ollama": "^0.5.0",
"openai": "^4.0.0", "openai": "^4.0.0",
"puppeteer-core": "^24.37.2",
"react": "^19.0.0", "react": "^19.0.0",
"turndown": "^7.2.0", "turndown": "^7.2.0",
"whatsapp-web.js": "^1.34.6", "whatsapp-web.js": "^1.34.6",
+1083
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,2 +1,2 @@
export { loadConfig } from './loader.js'; export { loadConfig } from './loader.js';
export { configSchema, type Config, type TelegramConfig, type ModelConfig, type CronJobConfig, type AgentsConfig, type CompactionConfig, type ToolProfile, type ToolOverrideConfig, type ToolsConfig, type SandboxConfig, type AgentConfigEntry, type RoutingConfig } from './schema.js'; export { configSchema, type Config, type TelegramConfig, type ModelConfig, type CronJobConfig, type AgentsConfig, type CompactionConfig, type ToolProfile, type ToolOverrideConfig, type ToolsConfig, type SandboxConfig, type AgentConfigEntry, type RoutingConfig, type ServerConfig } from './schema.js';
+23 -1
View File
@@ -3,16 +3,23 @@ import { z } from 'zod';
const telegramSchema = z.object({ const telegramSchema = z.object({
bot_token: z.string().min(1, 'Bot token is required'), 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'), 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({ const serverSchema = z.object({
tailscale_only: z.boolean().default(true), tailscale_only: z.boolean().default(true),
localhost: z.boolean().default(true), localhost: z.boolean().default(true),
port: z.number().default(18800), 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({ const modelConfigSchema = z.object({
provider: z.enum(['anthropic', 'openai', 'gemini', 'ollama', 'llamacpp']), provider: z.enum(['anthropic', 'openai', 'gemini', 'ollama', 'llamacpp', 'openrouter', 'bedrock']),
model: z.string(), model: z.string(),
endpoint: z.string().optional(), endpoint: z.string().optional(),
api_key: z.string().optional(), api_key: z.string().optional(),
@@ -133,13 +140,25 @@ const slackSchema = z.object({
app_token: z.string().min(1, 'App token is required'), app_token: z.string().min(1, 'App token is required'),
signing_secret: z.string().min(1, 'Signing secret is required'), signing_secret: z.string().min(1, 'Signing secret is required'),
allowed_channel_ids: z.array(z.string()).default([]), allowed_channel_ids: z.array(z.string()).default([]),
require_mention: z.boolean().default(false),
}).optional(); }).optional();
const whatsappSchema = z.object({ const whatsappSchema = z.object({
allowed_numbers: z.array(z.string()).default([]), 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(), data_dir: z.string().optional(),
}).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({ const processSchema = z.object({
max_concurrent: z.number().min(1).max(50).default(10), max_concurrent: z.number().min(1).max(50).default(10),
max_runtime_minutes: z.number().min(1).max(1440).default(60), max_runtime_minutes: z.number().min(1).max(1440).default(60),
@@ -237,6 +256,7 @@ export const configSchema = z.object({
compaction: compactionSchema, compaction: compactionSchema,
memory: memorySchema, memory: memorySchema,
process: processSchema, process: processSchema,
browser: browserSchema,
retry: retrySchema, retry: retrySchema,
web_search: webSearchSchema, web_search: webSearchSchema,
prompt: promptSchema, prompt: promptSchema,
@@ -255,6 +275,7 @@ export type CompactionConfig = z.infer<typeof compactionSchema>;
export type MemoryConfig = z.infer<typeof memorySchema>; export type MemoryConfig = z.infer<typeof memorySchema>;
export type WebSearchConfig = z.infer<typeof webSearchSchema>; export type WebSearchConfig = z.infer<typeof webSearchSchema>;
export type ProcessConfig = z.infer<typeof processSchema>; export type ProcessConfig = z.infer<typeof processSchema>;
export type BrowserConfig = z.infer<typeof browserSchema>;
export type DiscordConfig = z.infer<typeof discordSchema>; export type DiscordConfig = z.infer<typeof discordSchema>;
export type SlackConfig = z.infer<typeof slackSchema>; export type SlackConfig = z.infer<typeof slackSchema>;
export type WhatsAppConfig = z.infer<typeof whatsappSchema>; export type WhatsAppConfig = z.infer<typeof whatsappSchema>;
@@ -266,3 +287,4 @@ export type ToolsConfig = z.infer<typeof toolsSchema>;
export type SandboxConfig = z.infer<typeof sandboxSchema>; export type SandboxConfig = z.infer<typeof sandboxSchema>;
export type AgentConfigEntry = z.infer<typeof agentConfigEntrySchema>; export type AgentConfigEntry = z.infer<typeof agentConfigEntrySchema>;
export type RoutingConfig = z.infer<typeof routingSchema>; export type RoutingConfig = z.infer<typeof routingSchema>;
export type ServerConfig = z.infer<typeof serverSchema>;
+53 -5
View File
@@ -1,11 +1,11 @@
import { Lifecycle } from './lifecycle.js'; import { Lifecycle } from './lifecycle.js';
import type { Config, ModelConfig } from '../config/index.js'; import type { Config, ModelConfig } from '../config/index.js';
import { AnthropicClient, OpenAIClient, OllamaClient, LlamaCppClient, ModelRouter, DEFAULT_RETRY_CONFIG } from '../models/index.js'; import { AnthropicClient, OpenAIClient, OllamaClient, LlamaCppClient, GeminiClient, BedrockClient, ModelRouter, DEFAULT_RETRY_CONFIG } from '../models/index.js';
import type { ModelClient, RetryConfig } from '../models/index.js'; import type { ModelClient, RetryConfig } from '../models/index.js';
import { AgentOrchestrator, type DelegationConfig } from '../backends/index.js'; import { AgentOrchestrator, type DelegationConfig } from '../backends/index.js';
import { SessionStore, SessionManager } from '../session/index.js'; import { SessionStore, SessionManager } from '../session/index.js';
import { HookEngine } from '../hooks/index.js'; import { HookEngine } from '../hooks/index.js';
import { ToolRegistry, ToolExecutor, ToolPolicy, allBuiltinTools, createWebSearchTools, createProcessTools, ProcessManager } from '../tools/index.js'; import { ToolRegistry, ToolExecutor, ToolPolicy, allBuiltinTools, createWebSearchTools, createProcessTools, ProcessManager, BrowserManager, createBrowserTools } from '../tools/index.js';
import type { Tool } from '../tools/types.js'; import type { Tool } from '../tools/types.js';
import { MemoryStore } from '../memory/index.js'; import { MemoryStore } from '../memory/index.js';
import { createMemoryTools } from '../tools/builtin/index.js'; import { createMemoryTools } from '../tools/builtin/index.js';
@@ -39,6 +39,7 @@ export interface DaemonContext {
agentConfigRegistry: AgentConfigRegistry; agentConfigRegistry: AgentConfigRegistry;
agentRouter: AgentRouter; agentRouter: AgentRouter;
sandboxManager?: SandboxManager; sandboxManager?: SandboxManager;
browserManager?: BrowserManager;
} }
function loadSystemPrompt(config: Config): string { function loadSystemPrompt(config: Config): string {
@@ -91,12 +92,23 @@ export function createClientFromConfig(cfg: ModelConfig): ModelClient {
authToken: cfg.auth_token, authToken: cfg.auth_token,
}); });
case 'gemini': case 'gemini':
// Gemini support not yet implemented — fall back to OpenAI-compatible client return new GeminiClient({
console.warn(`Gemini provider not yet implemented for model "${cfg.model}", using OpenAI-compatible client`);
return new OpenAIClient({
model: cfg.model, model: cfg.model,
apiKey: cfg.api_key, apiKey: cfg.api_key,
}); });
case 'openrouter':
return new OpenAIClient({
model: cfg.model,
apiKey: cfg.api_key ?? process.env.OPENROUTER_API_KEY,
baseURL: cfg.endpoint ?? 'https://openrouter.ai/api/v1',
});
case 'bedrock':
return new BedrockClient({
model: cfg.model,
region: cfg.endpoint,
accessKeyId: cfg.api_key,
secretAccessKey: cfg.auth_token,
});
default: default:
throw new Error(`Unknown model provider: ${(cfg as Record<string, unknown>).provider}`); throw new Error(`Unknown model provider: ${(cfg as Record<string, unknown>).provider}`);
} }
@@ -415,6 +427,28 @@ export async function startDaemon(config: Config): Promise<DaemonContext> {
console.log('Process manager stopped'); console.log('Process manager stopped');
}); });
// Initialize browser manager and register browser tools (if enabled)
let browserManager: BrowserManager | undefined;
if (config.browser?.enabled) {
browserManager = new BrowserManager({
executablePath: config.browser.executable_path,
wsEndpoint: config.browser.ws_endpoint,
headless: config.browser.headless,
maxPages: config.browser.max_pages,
defaultTimeout: config.browser.default_timeout,
});
for (const tool of createBrowserTools(browserManager)) {
toolRegistry.register(tool);
}
console.log(`Browser tools enabled (headless=${config.browser.headless})`);
lifecycle.onShutdown(async () => {
await browserManager!.shutdown();
console.log('Browser manager stopped');
});
}
const toolExecutor = new ToolExecutor(toolRegistry, hookEngine); const toolExecutor = new ToolExecutor(toolRegistry, hookEngine);
// Initialize tool policy from config // Initialize tool policy from config
@@ -505,6 +539,11 @@ export async function startDaemon(config: Config): Promise<DaemonContext> {
systemPrompt, systemPrompt,
toolRegistry, toolRegistry,
toolExecutor, toolExecutor,
auth: {
token: config.server.token,
tailscaleIdentity: config.server.tailscale_identity,
},
authHttp: config.server.auth_http,
uiDir: resolve(import.meta.dirname, '../gateway/ui'), uiDir: resolve(import.meta.dirname, '../gateway/ui'),
config, config,
restart: async () => { restart: async () => {
@@ -515,6 +554,10 @@ export async function startDaemon(config: Config): Promise<DaemonContext> {
}, },
}); });
if (config.server.token) {
console.log(`Gateway auth: token required${config.server.tailscale_identity ? ' + Tailscale identity' : ''}`);
}
// ── Channel Registry ────────────────────────────────────────── // ── Channel Registry ──────────────────────────────────────────
const channelRegistry = new ChannelRegistry(); const channelRegistry = new ChannelRegistry();
@@ -537,6 +580,7 @@ export async function startDaemon(config: Config): Promise<DaemonContext> {
const telegramAdapter = new TelegramAdapter({ const telegramAdapter = new TelegramAdapter({
botToken: config.telegram.bot_token, botToken: config.telegram.bot_token,
allowedChatIds: config.telegram.allowed_chat_ids, allowedChatIds: config.telegram.allowed_chat_ids,
requireMention: config.telegram.require_mention,
hookEngine, hookEngine,
}); });
channelRegistry.register(telegramAdapter); channelRegistry.register(telegramAdapter);
@@ -559,6 +603,7 @@ export async function startDaemon(config: Config): Promise<DaemonContext> {
appToken: config.slack.app_token, appToken: config.slack.app_token,
signingSecret: config.slack.signing_secret, signingSecret: config.slack.signing_secret,
allowedChannelIds: config.slack.allowed_channel_ids.length > 0 ? config.slack.allowed_channel_ids : undefined, allowedChannelIds: config.slack.allowed_channel_ids.length > 0 ? config.slack.allowed_channel_ids : undefined,
requireMention: config.slack.require_mention,
}); });
channelRegistry.register(slackAdapter); channelRegistry.register(slackAdapter);
} }
@@ -567,6 +612,8 @@ export async function startDaemon(config: Config): Promise<DaemonContext> {
if (config.whatsapp) { if (config.whatsapp) {
const whatsappAdapter = new WhatsAppAdapter({ const whatsappAdapter = new WhatsAppAdapter({
allowedNumbers: config.whatsapp.allowed_numbers.length > 0 ? config.whatsapp.allowed_numbers : undefined, allowedNumbers: config.whatsapp.allowed_numbers.length > 0 ? config.whatsapp.allowed_numbers : undefined,
allowedGroupIds: config.whatsapp.allowed_group_ids.length > 0 ? config.whatsapp.allowed_group_ids : undefined,
requireMention: config.whatsapp.require_mention,
dataDir: config.whatsapp.data_dir, dataDir: config.whatsapp.data_dir,
}); });
channelRegistry.register(whatsappAdapter); channelRegistry.register(whatsappAdapter);
@@ -635,6 +682,7 @@ export async function startDaemon(config: Config): Promise<DaemonContext> {
agentConfigRegistry, agentConfigRegistry,
agentRouter, agentRouter,
sandboxManager, sandboxManager,
browserManager,
}; };
} }