From daf8cac3febaebb3d1b8597ee56df37b2fe4e284 Mon Sep 17 00:00:00 2001 From: William Valentin Date: Fri, 6 Feb 2026 15:48:55 -0800 Subject: [PATCH] feat: add sandbox, agent_configs, and routing config schemas --- src/config/index.ts | 2 +- src/config/schema.test.ts | 88 +++++++++++++++++++++++++++++++++++++++ src/config/schema.ts | 38 +++++++++++++++++ 3 files changed, 127 insertions(+), 1 deletion(-) diff --git a/src/config/index.ts b/src/config/index.ts index 45e4ce5..f8e6f55 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -1,2 +1,2 @@ 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 } 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 } from './schema.js'; diff --git a/src/config/schema.test.ts b/src/config/schema.test.ts index 4d3041e..7415888 100644 --- a/src/config/schema.test.ts +++ b/src/config/schema.test.ts @@ -1,6 +1,94 @@ import { describe, it, expect } from 'vitest'; import { configSchema } from './schema.js'; +describe('configSchema — sandbox', () => { + const minimalConfig = { + telegram: { bot_token: 'test', allowed_chat_ids: [1] }, + models: { default: { provider: 'anthropic', model: 'claude-3' } }, + }; + + it('defaults sandbox to disabled', () => { + const result = configSchema.parse(minimalConfig); + expect(result.sandbox.enabled).toBe(false); + expect(result.sandbox.image).toBe('node:22-slim'); + expect(result.sandbox.network).toBe('none'); + expect(result.sandbox.memory_limit).toBe('512m'); + expect(result.sandbox.cpu_limit).toBe('1.0'); + expect(result.sandbox.timeout_seconds).toBe(300); + }); + + it('accepts sandbox config', () => { + const result = configSchema.parse({ + ...minimalConfig, + sandbox: { enabled: true, image: 'ubuntu:24.04', network: 'bridge' }, + }); + expect(result.sandbox.enabled).toBe(true); + expect(result.sandbox.image).toBe('ubuntu:24.04'); + expect(result.sandbox.network).toBe('bridge'); + }); +}); + +describe('configSchema — agent_configs', () => { + const minimalConfig = { + telegram: { bot_token: 'test', allowed_chat_ids: [1] }, + models: { default: { provider: 'anthropic', model: 'claude-3' } }, + }; + + it('defaults agent_configs to empty', () => { + const result = configSchema.parse(minimalConfig); + expect(result.agent_configs).toEqual({}); + }); + + it('accepts named agent configs', () => { + const result = configSchema.parse({ + ...minimalConfig, + agent_configs: { + assistant: { + system_prompt: 'You are helpful.', + model_tier: 'default', + tool_profile: 'messaging', + }, + coder: { + model_tier: 'complex', + tool_profile: 'coding', + sandbox: true, + }, + }, + }); + expect(result.agent_configs.assistant.system_prompt).toBe('You are helpful.'); + expect(result.agent_configs.assistant.tool_profile).toBe('messaging'); + expect(result.agent_configs.coder.sandbox).toBe(true); + }); +}); + +describe('configSchema — routing', () => { + const minimalConfig = { + telegram: { bot_token: 'test', allowed_chat_ids: [1] }, + models: { default: { provider: 'anthropic', model: 'claude-3' } }, + }; + + it('defaults routing to empty', () => { + const result = configSchema.parse(minimalConfig); + expect(result.routing.default_agent).toBeUndefined(); + expect(result.routing.channels).toEqual({}); + expect(result.routing.senders).toEqual({}); + }); + + it('accepts routing config', () => { + const result = configSchema.parse({ + ...minimalConfig, + routing: { + default_agent: 'assistant', + channels: { discord: 'coder' }, + senders: { 'telegram:12345': 'coder' }, + }, + }); + expect(result.routing.default_agent).toBe('assistant'); + expect(result.routing.channels.discord).toBe('coder'); + expect(result.routing.senders['telegram:12345']).toBe('coder'); + }); +}); + describe('configSchema automation', () => { const baseConfig = { telegram: { bot_token: 'test-token', allowed_chat_ids: [123] }, diff --git a/src/config/schema.ts b/src/config/schema.ts index 1dd90bb..e3beadc 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -179,6 +179,38 @@ const toolsSchema = z.object({ 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([]), @@ -209,6 +241,9 @@ export const configSchema = z.object({ web_search: webSearchSchema, prompt: promptSchema, tools: toolsSchema, + sandbox: sandboxSchema, + agent_configs: agentConfigsSchema, + routing: routingSchema, }); export type Config = z.infer; @@ -228,3 +263,6 @@ export type PromptConfig = z.infer; export type ToolProfile = z.infer; export type ToolOverrideConfig = z.infer; export type ToolsConfig = z.infer; +export type SandboxConfig = z.infer; +export type AgentConfigEntry = z.infer; +export type RoutingConfig = z.infer;