fix: resolve strict typecheck fallout in setup, routing, and tests
This commit is contained in:
@@ -2700,6 +2700,33 @@
|
|||||||
"src/skills/installer.test.ts"
|
"src/skills/installer.test.ts"
|
||||||
],
|
],
|
||||||
"test_status": "pnpm exec eslint on edited files + pnpm lint passing (0 errors, 0 warnings)"
|
"test_status": "pnpm exec eslint on edited files + pnpm lint passing (0 errors, 0 warnings)"
|
||||||
|
},
|
||||||
|
"audit-followup-typecheck-strict-burndown": {
|
||||||
|
"status": "completed",
|
||||||
|
"date": "2026-02-16",
|
||||||
|
"updated": "2026-02-16",
|
||||||
|
"summary": "Closed strict TypeScript fallout after lint cleanup by typing setup wizard config output (`SetupConfig`), tightening optional field handling in setup summary/tests, updating routing/tailscale/tool policy test mocks for strict compatibility, and fixing narrow production strictness in OpenAI/GitHub image URL mapping and file.list glob filtering.",
|
||||||
|
"files_modified": [
|
||||||
|
"src/cli/setup/channels.test.ts",
|
||||||
|
"src/cli/setup/config.test.ts",
|
||||||
|
"src/cli/setup/config.ts",
|
||||||
|
"src/cli/setup/integration.test.ts",
|
||||||
|
"src/cli/setup/sections.test.ts",
|
||||||
|
"src/cli/setup/summary.ts",
|
||||||
|
"src/daemon/routing.test.ts",
|
||||||
|
"src/gateway/handlers/services.test.ts",
|
||||||
|
"src/gateway/tailscale.test.ts",
|
||||||
|
"src/models/github.ts",
|
||||||
|
"src/models/local/llamacpp.test.ts",
|
||||||
|
"src/models/local/ollama.test.ts",
|
||||||
|
"src/models/openai.baseurl.test.ts",
|
||||||
|
"src/models/openai.ts",
|
||||||
|
"src/tools/builtin/file-list.ts",
|
||||||
|
"src/tools/executor.test.ts",
|
||||||
|
"src/tools/registry.test.ts",
|
||||||
|
"docs/plans/state.json"
|
||||||
|
],
|
||||||
|
"test_status": "pnpm typecheck + pnpm test:run src/cli/setup/channels.test.ts src/cli/setup/config.test.ts src/cli/setup/integration.test.ts src/cli/setup/sections.test.ts src/daemon/routing.test.ts src/gateway/handlers/services.test.ts src/gateway/tailscale.test.ts src/models/local/llamacpp.test.ts src/models/local/ollama.test.ts src/models/openai.baseurl.test.ts src/tools/executor.test.ts src/tools/registry.test.ts passing"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"overall_progress": {
|
"overall_progress": {
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ describe('setupChannels', () => {
|
|||||||
const builder = new ConfigBuilder();
|
const builder = new ConfigBuilder();
|
||||||
await setupChannels(p, builder);
|
await setupChannels(p, builder);
|
||||||
const config = builder.build();
|
const config = builder.build();
|
||||||
expect(config.telegram.bot_token).toBe('123:ABC');
|
expect(config.telegram!.bot_token).toBe('123:ABC');
|
||||||
expect(config.telegram.allowed_chat_ids).toEqual([12345, 67890]);
|
expect(config.telegram!.allowed_chat_ids).toEqual([12345, 67890]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('configures discord channel', async () => {
|
it('configures discord channel', async () => {
|
||||||
@@ -54,7 +54,7 @@ describe('setupChannels', () => {
|
|||||||
const builder = new ConfigBuilder();
|
const builder = new ConfigBuilder();
|
||||||
await setupChannels(p, builder);
|
await setupChannels(p, builder);
|
||||||
const config = builder.build();
|
const config = builder.build();
|
||||||
expect(config.discord.bot_token).toBe('MTIz.token');
|
expect(config.discord!.bot_token).toBe('MTIz.token');
|
||||||
expect(config.discord.allowed_guild_ids).toEqual(['guild1', 'guild2']);
|
expect(config.discord!.allowed_guild_ids).toEqual(['guild1', 'guild2']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ describe('ConfigBuilder', () => {
|
|||||||
builder.setProvider('default', { provider: 'anthropic', model: 'test', api_key: 'k' });
|
builder.setProvider('default', { provider: 'anthropic', model: 'test', api_key: 'k' });
|
||||||
builder.setTelegram('123:ABC', [12345]);
|
builder.setTelegram('123:ABC', [12345]);
|
||||||
const obj = builder.build();
|
const obj = builder.build();
|
||||||
expect(obj.telegram.bot_token).toBe('123:ABC');
|
expect(obj.telegram!.bot_token).toBe('123:ABC');
|
||||||
expect(obj.telegram.allowed_chat_ids).toEqual([12345]);
|
expect(obj.telegram!.allowed_chat_ids).toEqual([12345]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds discord channel', () => {
|
it('adds discord channel', () => {
|
||||||
@@ -26,8 +26,8 @@ describe('ConfigBuilder', () => {
|
|||||||
builder.setProvider('default', { provider: 'anthropic', model: 'test', api_key: 'k' });
|
builder.setProvider('default', { provider: 'anthropic', model: 'test', api_key: 'k' });
|
||||||
builder.setDiscord('MTIz.test', ['guild1']);
|
builder.setDiscord('MTIz.test', ['guild1']);
|
||||||
const obj = builder.build();
|
const obj = builder.build();
|
||||||
expect(obj.discord.bot_token).toBe('MTIz.test');
|
expect(obj.discord!.bot_token).toBe('MTIz.test');
|
||||||
expect(obj.discord.allowed_guild_ids).toEqual(['guild1']);
|
expect(obj.discord!.allowed_guild_ids).toEqual(['guild1']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds fast tier', () => {
|
it('adds fast tier', () => {
|
||||||
@@ -58,7 +58,7 @@ describe('ConfigBuilder', () => {
|
|||||||
const obj = builder.build();
|
const obj = builder.build();
|
||||||
expect(obj.models.default.provider).toBe('openai');
|
expect(obj.models.default.provider).toBe('openai');
|
||||||
expect(obj.server.port).toBe(9999);
|
expect(obj.server.port).toBe(9999);
|
||||||
expect(obj.telegram.bot_token).toBe('123:ABC');
|
expect(obj.telegram!.bot_token).toBe('123:ABC');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets memory embedding config', () => {
|
it('sets memory embedding config', () => {
|
||||||
@@ -66,16 +66,16 @@ describe('ConfigBuilder', () => {
|
|||||||
builder.setProvider('default', { provider: 'anthropic', model: 'test', api_key: 'k' });
|
builder.setProvider('default', { provider: 'anthropic', model: 'test', api_key: 'k' });
|
||||||
builder.setMemoryEmbedding({ provider: 'openai', api_key: 'sk-emb' });
|
builder.setMemoryEmbedding({ provider: 'openai', api_key: 'sk-emb' });
|
||||||
const obj = builder.build();
|
const obj = builder.build();
|
||||||
expect(obj.memory.embedding.enabled).toBe(true);
|
expect(obj.memory!.embedding!.enabled).toBe(true);
|
||||||
expect(obj.memory.embedding.provider).toBe('openai');
|
expect(obj.memory!.embedding!.provider).toBe('openai');
|
||||||
expect(obj.memory.embedding.api_key).toBe('sk-emb');
|
expect(obj.memory!.embedding!.api_key).toBe('sk-emb');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets sandbox enabled', () => {
|
it('sets sandbox enabled', () => {
|
||||||
const builder = new ConfigBuilder();
|
const builder = new ConfigBuilder();
|
||||||
builder.setSandboxEnabled(true);
|
builder.setSandboxEnabled(true);
|
||||||
const obj = builder.build();
|
const obj = builder.build();
|
||||||
expect(obj.sandbox.enabled).toBe(true);
|
expect(obj.sandbox!.enabled).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets gateway auth token', () => {
|
it('sets gateway auth token', () => {
|
||||||
|
|||||||
+38
-6
@@ -14,8 +14,40 @@ interface EmbeddingConfig {
|
|||||||
endpoint?: string;
|
endpoint?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SetupConfig {
|
||||||
|
log_level?: string;
|
||||||
|
models: Record<string, ProviderConfig & Record<string, unknown>>;
|
||||||
|
server: {
|
||||||
|
port?: number;
|
||||||
|
localhost?: boolean;
|
||||||
|
token?: string;
|
||||||
|
lock?: boolean;
|
||||||
|
tailscale?: { serve?: boolean };
|
||||||
|
} & Record<string, unknown>;
|
||||||
|
hooks?: Record<string, unknown>;
|
||||||
|
telegram?: { bot_token: string; allowed_chat_ids: number[] };
|
||||||
|
discord?: { bot_token: string; allowed_guild_ids: string[] };
|
||||||
|
slack?: { bot_token: string; app_token: string; signing_secret: string; allowed_channel_ids: string[] };
|
||||||
|
whatsapp?: { allowed_numbers: string[] };
|
||||||
|
memory?: { embedding?: { enabled?: boolean; provider?: string; api_key?: string; endpoint?: string } };
|
||||||
|
sandbox?: { enabled?: boolean };
|
||||||
|
pairing?: { enabled?: boolean };
|
||||||
|
tools?: { profile?: string };
|
||||||
|
automation?: {
|
||||||
|
cron?: Array<Record<string, unknown>>;
|
||||||
|
webhooks?: Array<Record<string, unknown>>;
|
||||||
|
gmail?: { enabled?: boolean };
|
||||||
|
gcal?: { enabled?: boolean };
|
||||||
|
gdocs?: { enabled?: boolean };
|
||||||
|
gdrive?: { enabled?: boolean };
|
||||||
|
gtasks?: { enabled?: boolean };
|
||||||
|
heartbeat?: { enabled?: boolean };
|
||||||
|
} & Record<string, unknown>;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
export class ConfigBuilder {
|
export class ConfigBuilder {
|
||||||
private config: Record<string, unknown>;
|
private config: SetupConfig;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.config = {
|
this.config = {
|
||||||
@@ -32,13 +64,13 @@ export class ConfigBuilder {
|
|||||||
|
|
||||||
static fromObject(obj: Record<string, unknown>): ConfigBuilder {
|
static fromObject(obj: Record<string, unknown>): ConfigBuilder {
|
||||||
const builder = new ConfigBuilder();
|
const builder = new ConfigBuilder();
|
||||||
builder.config = structuredClone(obj);
|
builder.config = structuredClone(obj) as SetupConfig;
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
setProvider(tier: 'default' | 'fast' | 'complex' | 'local', cfg: ProviderConfig): void {
|
setProvider(tier: 'default' | 'fast' | 'complex' | 'local', cfg: ProviderConfig): void {
|
||||||
const models = (this.config.models ?? {}) as Record<string, unknown>;
|
const models = (this.config.models ?? {}) as SetupConfig['models'];
|
||||||
const entry: Record<string, unknown> = { provider: cfg.provider, model: cfg.model };
|
const entry: ProviderConfig & Record<string, unknown> = { provider: cfg.provider, model: cfg.model };
|
||||||
if (cfg.api_key) {entry.api_key = cfg.api_key;}
|
if (cfg.api_key) {entry.api_key = cfg.api_key;}
|
||||||
if (cfg.auth_token) {entry.auth_token = cfg.auth_token;}
|
if (cfg.auth_token) {entry.auth_token = cfg.auth_token;}
|
||||||
if (cfg.endpoint) {entry.endpoint = cfg.endpoint;}
|
if (cfg.endpoint) {entry.endpoint = cfg.endpoint;}
|
||||||
@@ -155,8 +187,8 @@ export class ConfigBuilder {
|
|||||||
this.config.automation = automation;
|
this.config.automation = automation;
|
||||||
}
|
}
|
||||||
|
|
||||||
build(): Record<string, unknown> {
|
build(): SetupConfig {
|
||||||
return structuredClone(this.config) as Record<string, unknown>;
|
return structuredClone(this.config) as SetupConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
toYaml(): string {
|
toYaml(): string {
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ describe('first-run wizard integration', () => {
|
|||||||
expect(config.models.default.api_key).toBe('sk-ant-key');
|
expect(config.models.default.api_key).toBe('sk-ant-key');
|
||||||
expect(config.server.port).toBeDefined();
|
expect(config.server.port).toBeDefined();
|
||||||
|
|
||||||
const reparsed = parse(yaml);
|
const reparsed = parse(yaml) as { models?: { default?: { provider?: string } } };
|
||||||
expect(reparsed.models.default.provider).toBe('anthropic');
|
expect(reparsed.models!.default!.provider).toBe('anthropic');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('produces valid config with ollama + telegram', async () => {
|
it('produces valid config with ollama + telegram', async () => {
|
||||||
@@ -60,7 +60,7 @@ describe('first-run wizard integration', () => {
|
|||||||
const config = builder.build();
|
const config = builder.build();
|
||||||
|
|
||||||
expect(config.models.default.provider).toBe('ollama');
|
expect(config.models.default.provider).toBe('ollama');
|
||||||
expect(config.telegram.bot_token).toBe('123:ABCdef');
|
expect(config.telegram!.bot_token).toBe('123:ABCdef');
|
||||||
expect(config.telegram.allowed_chat_ids).toEqual([12345678]);
|
expect(config.telegram!.allowed_chat_ids).toEqual([12345678]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ describe('setupMemory', () => {
|
|||||||
builder.setProvider('default', { provider: 'openai', model: 'gpt-4.1', api_key: 'sk-test' });
|
builder.setProvider('default', { provider: 'openai', model: 'gpt-4.1', api_key: 'sk-test' });
|
||||||
await setupMemory(p, builder);
|
await setupMemory(p, builder);
|
||||||
const config = builder.build();
|
const config = builder.build();
|
||||||
expect(config.memory.embedding.enabled).toBe(true);
|
expect(config.memory!.embedding!.enabled).toBe(true);
|
||||||
expect(config.memory.embedding.provider).toBe('openai');
|
expect(config.memory!.embedding!.provider).toBe('openai');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('skips vector search when declined', async () => {
|
it('skips vector search when declined', async () => {
|
||||||
@@ -58,8 +58,8 @@ describe('setupSecurity', () => {
|
|||||||
const builder = new ConfigBuilder();
|
const builder = new ConfigBuilder();
|
||||||
await setupSecurity(p, builder);
|
await setupSecurity(p, builder);
|
||||||
const config = builder.build();
|
const config = builder.build();
|
||||||
expect(config.sandbox.enabled).toBe(true);
|
expect(config.sandbox!.enabled).toBe(true);
|
||||||
expect(config.pairing.enabled).toBe(true);
|
expect(config.pairing!.enabled).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
export function renderSummary(config: Record<string, unknown>): string {
|
import type { SetupConfig } from './config.js';
|
||||||
|
|
||||||
|
export function renderSummary(config: SetupConfig): string {
|
||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
|
|
||||||
const models = config.models ?? {};
|
const models = config.models ?? {};
|
||||||
@@ -22,8 +24,8 @@ export function renderSummary(config: Record<string, unknown>): string {
|
|||||||
|
|
||||||
const auto = config.automation ?? {};
|
const auto = config.automation ?? {};
|
||||||
const autoFeatures: string[] = [];
|
const autoFeatures: string[] = [];
|
||||||
if (auto.cron?.length > 0) {autoFeatures.push(`${auto.cron.length} cron jobs`);}
|
if (Array.isArray(auto.cron) && auto.cron.length > 0) {autoFeatures.push(`${auto.cron.length} cron jobs`);}
|
||||||
if (auto.webhooks?.length > 0) {autoFeatures.push('webhooks');}
|
if (Array.isArray(auto.webhooks) && auto.webhooks.length > 0) {autoFeatures.push('webhooks');}
|
||||||
if (auto.gmail?.enabled) {autoFeatures.push('gmail');}
|
if (auto.gmail?.enabled) {autoFeatures.push('gmail');}
|
||||||
if (auto.gcal?.enabled) {autoFeatures.push('gcal');}
|
if (auto.gcal?.enabled) {autoFeatures.push('gcal');}
|
||||||
if (auto.gdocs?.enabled) {autoFeatures.push('gdocs');}
|
if (auto.gdocs?.enabled) {autoFeatures.push('gdocs');}
|
||||||
|
|||||||
+38
-34
@@ -99,18 +99,18 @@ describe('daemon command fast-path integration', () => {
|
|||||||
const router = createMessageRouter({
|
const router = createMessageRouter({
|
||||||
sessionManager: {
|
sessionManager: {
|
||||||
getSession: vi.fn(() => session),
|
getSession: vi.fn(() => session),
|
||||||
} as MessageRouterDeps['sessionManager'],
|
} as unknown as MessageRouterDeps['sessionManager'],
|
||||||
modelRouter: {
|
modelRouter: {
|
||||||
getAvailableTiers: () => ['fast', 'default', 'complex', 'local'],
|
getAvailableTiers: () => ['fast', 'default', 'complex', 'local'],
|
||||||
getAllLabels: () => ({ fast: 'fast', default: 'default', complex: 'complex', local: 'local' }),
|
getAllLabels: () => ({ fast: 'fast', default: 'default', complex: 'complex', local: 'local' }),
|
||||||
getLabel: (tier: string) => tier,
|
getLabel: (tier: string) => tier,
|
||||||
} as MessageRouterDeps['modelRouter'],
|
} as unknown as MessageRouterDeps['modelRouter'],
|
||||||
systemPrompt: 'test prompt',
|
systemPrompt: 'test prompt',
|
||||||
toolRegistry: {
|
toolRegistry: {
|
||||||
clone() { return this; },
|
clone() { return this; },
|
||||||
register: vi.fn(),
|
register: vi.fn(),
|
||||||
} as MessageRouterDeps['toolRegistry'],
|
} as unknown as MessageRouterDeps['toolRegistry'],
|
||||||
toolExecutor: {} as MessageRouterDeps['toolExecutor'],
|
toolExecutor: {} as unknown as MessageRouterDeps['toolExecutor'],
|
||||||
config: {
|
config: {
|
||||||
agents: {
|
agents: {
|
||||||
primary_tier: 'default',
|
primary_tier: 'default',
|
||||||
@@ -126,16 +126,17 @@ describe('daemon command fast-path integration', () => {
|
|||||||
},
|
},
|
||||||
compaction: { enabled: false },
|
compaction: { enabled: false },
|
||||||
models: { default: { provider: 'anthropic', model: 'claude' } },
|
models: { default: { provider: 'anthropic', model: 'claude' } },
|
||||||
} as MessageRouterDeps['config'],
|
} as unknown as MessageRouterDeps['config'],
|
||||||
commandRegistry,
|
commandRegistry,
|
||||||
});
|
});
|
||||||
|
|
||||||
const reply = vi.fn(async () => {});
|
const reply = vi.fn(async (_message: OutboundMessage) => {});
|
||||||
await router.handler({
|
await router.handler({
|
||||||
id: 'm1',
|
id: 'm1',
|
||||||
channel: 'telegram',
|
channel: 'telegram',
|
||||||
senderId: 'user-1',
|
senderId: 'user-1',
|
||||||
text: '/reset',
|
text: '/reset',
|
||||||
|
timestamp: Date.now(),
|
||||||
metadata: { isCommand: true, command: 'reset' },
|
metadata: { isCommand: true, command: 'reset' },
|
||||||
} as MessageRouterInput, reply);
|
} as MessageRouterInput, reply);
|
||||||
|
|
||||||
@@ -163,18 +164,18 @@ describe('daemon command fast-path integration', () => {
|
|||||||
const router = createMessageRouter({
|
const router = createMessageRouter({
|
||||||
sessionManager: {
|
sessionManager: {
|
||||||
getSession: vi.fn(() => session),
|
getSession: vi.fn(() => session),
|
||||||
} as MessageRouterDeps['sessionManager'],
|
} as unknown as MessageRouterDeps['sessionManager'],
|
||||||
modelRouter: {
|
modelRouter: {
|
||||||
getAvailableTiers: () => ['fast', 'default', 'complex', 'local'],
|
getAvailableTiers: () => ['fast', 'default', 'complex', 'local'],
|
||||||
getAllLabels: () => ({ fast: 'fast', default: 'default', complex: 'complex', local: 'local' }),
|
getAllLabels: () => ({ fast: 'fast', default: 'default', complex: 'complex', local: 'local' }),
|
||||||
getLabel: (tier: string) => tier,
|
getLabel: (tier: string) => tier,
|
||||||
} as MessageRouterDeps['modelRouter'],
|
} as unknown as MessageRouterDeps['modelRouter'],
|
||||||
systemPrompt: 'test prompt',
|
systemPrompt: 'test prompt',
|
||||||
toolRegistry: {
|
toolRegistry: {
|
||||||
clone() { return this; },
|
clone() { return this; },
|
||||||
register: vi.fn(),
|
register: vi.fn(),
|
||||||
} as MessageRouterDeps['toolRegistry'],
|
} as unknown as MessageRouterDeps['toolRegistry'],
|
||||||
toolExecutor: {} as MessageRouterDeps['toolExecutor'],
|
toolExecutor: {} as unknown as MessageRouterDeps['toolExecutor'],
|
||||||
config: {
|
config: {
|
||||||
agents: {
|
agents: {
|
||||||
primary_tier: 'default',
|
primary_tier: 'default',
|
||||||
@@ -190,16 +191,17 @@ describe('daemon command fast-path integration', () => {
|
|||||||
},
|
},
|
||||||
compaction: { enabled: false },
|
compaction: { enabled: false },
|
||||||
models: { default: { provider: 'anthropic', model: 'claude' } },
|
models: { default: { provider: 'anthropic', model: 'claude' } },
|
||||||
} as MessageRouterDeps['config'],
|
} as unknown as MessageRouterDeps['config'],
|
||||||
commandRegistry,
|
commandRegistry,
|
||||||
});
|
});
|
||||||
|
|
||||||
const reply = vi.fn(async () => {});
|
const reply = vi.fn(async (_message: OutboundMessage) => {});
|
||||||
await router.handler({
|
await router.handler({
|
||||||
id: 'm4',
|
id: 'm4',
|
||||||
channel: 'telegram',
|
channel: 'telegram',
|
||||||
senderId: 'user-4',
|
senderId: 'user-4',
|
||||||
text: '/model fast',
|
text: '/model fast',
|
||||||
|
timestamp: Date.now(),
|
||||||
metadata: { isCommand: true, command: 'model', commandArgs: 'fast' },
|
metadata: { isCommand: true, command: 'model', commandArgs: 'fast' },
|
||||||
} as MessageRouterInput, reply);
|
} as MessageRouterInput, reply);
|
||||||
|
|
||||||
@@ -247,18 +249,18 @@ describe('daemon command fast-path integration', () => {
|
|||||||
const router = createMessageRouter({
|
const router = createMessageRouter({
|
||||||
sessionManager: {
|
sessionManager: {
|
||||||
getSession: vi.fn(() => session),
|
getSession: vi.fn(() => session),
|
||||||
} as MessageRouterDeps['sessionManager'],
|
} as unknown as MessageRouterDeps['sessionManager'],
|
||||||
modelRouter: {
|
modelRouter: {
|
||||||
getAvailableTiers: () => ['fast', 'default', 'complex', 'local'],
|
getAvailableTiers: () => ['fast', 'default', 'complex', 'local'],
|
||||||
getAllLabels: () => ({ fast: 'fast', default: 'default', complex: 'complex', local: 'local' }),
|
getAllLabels: () => ({ fast: 'fast', default: 'default', complex: 'complex', local: 'local' }),
|
||||||
getLabel: (tier: string) => tier,
|
getLabel: (tier: string) => tier,
|
||||||
} as MessageRouterDeps['modelRouter'],
|
} as unknown as MessageRouterDeps['modelRouter'],
|
||||||
systemPrompt: 'test prompt',
|
systemPrompt: 'test prompt',
|
||||||
toolRegistry: {
|
toolRegistry: {
|
||||||
clone() { return this; },
|
clone() { return this; },
|
||||||
register: vi.fn(),
|
register: vi.fn(),
|
||||||
} as MessageRouterDeps['toolRegistry'],
|
} as unknown as MessageRouterDeps['toolRegistry'],
|
||||||
toolExecutor: {} as MessageRouterDeps['toolExecutor'],
|
toolExecutor: {} as unknown as MessageRouterDeps['toolExecutor'],
|
||||||
config: {
|
config: {
|
||||||
intents: { enabled: true },
|
intents: { enabled: true },
|
||||||
agents: {
|
agents: {
|
||||||
@@ -275,7 +277,7 @@ describe('daemon command fast-path integration', () => {
|
|||||||
},
|
},
|
||||||
compaction: { enabled: false },
|
compaction: { enabled: false },
|
||||||
models: { default: { provider: 'anthropic', model: 'claude' } },
|
models: { default: { provider: 'anthropic', model: 'claude' } },
|
||||||
} as MessageRouterDeps['config'],
|
} as unknown as MessageRouterDeps['config'],
|
||||||
commandRegistry,
|
commandRegistry,
|
||||||
intentRegistry,
|
intentRegistry,
|
||||||
agentConfigRegistry,
|
agentConfigRegistry,
|
||||||
@@ -287,6 +289,7 @@ describe('daemon command fast-path integration', () => {
|
|||||||
channel: 'telegram',
|
channel: 'telegram',
|
||||||
senderId: 'user-2',
|
senderId: 'user-2',
|
||||||
text: 'deploy backend now',
|
text: 'deploy backend now',
|
||||||
|
timestamp: Date.now(),
|
||||||
metadata: { isCommand: true, command: 'reset' },
|
metadata: { isCommand: true, command: 'reset' },
|
||||||
} as MessageRouterInput, vi.fn(async () => {}));
|
} as MessageRouterInput, vi.fn(async () => {}));
|
||||||
|
|
||||||
@@ -340,18 +343,18 @@ describe('daemon command fast-path integration', () => {
|
|||||||
const router = createMessageRouter({
|
const router = createMessageRouter({
|
||||||
sessionManager: {
|
sessionManager: {
|
||||||
getSession: vi.fn(() => session),
|
getSession: vi.fn(() => session),
|
||||||
} as MessageRouterDeps['sessionManager'],
|
} as unknown as MessageRouterDeps['sessionManager'],
|
||||||
modelRouter: {
|
modelRouter: {
|
||||||
getAvailableTiers: () => ['fast', 'default', 'complex', 'local'],
|
getAvailableTiers: () => ['fast', 'default', 'complex', 'local'],
|
||||||
getAllLabels: () => ({ fast: 'fast', default: 'default', complex: 'complex', local: 'local' }),
|
getAllLabels: () => ({ fast: 'fast', default: 'default', complex: 'complex', local: 'local' }),
|
||||||
getLabel: (tier: string) => tier,
|
getLabel: (tier: string) => tier,
|
||||||
} as MessageRouterDeps['modelRouter'],
|
} as unknown as MessageRouterDeps['modelRouter'],
|
||||||
systemPrompt: 'test prompt',
|
systemPrompt: 'test prompt',
|
||||||
toolRegistry: {
|
toolRegistry: {
|
||||||
clone() { return this; },
|
clone() { return this; },
|
||||||
register: vi.fn(),
|
register: vi.fn(),
|
||||||
} as MessageRouterDeps['toolRegistry'],
|
} as unknown as MessageRouterDeps['toolRegistry'],
|
||||||
toolExecutor: {} as MessageRouterDeps['toolExecutor'],
|
toolExecutor: {} as unknown as MessageRouterDeps['toolExecutor'],
|
||||||
config: {
|
config: {
|
||||||
intents: { enabled: true },
|
intents: { enabled: true },
|
||||||
agents: {
|
agents: {
|
||||||
@@ -368,7 +371,7 @@ describe('daemon command fast-path integration', () => {
|
|||||||
},
|
},
|
||||||
compaction: { enabled: false },
|
compaction: { enabled: false },
|
||||||
models: { default: { provider: 'anthropic', model: 'claude' } },
|
models: { default: { provider: 'anthropic', model: 'claude' } },
|
||||||
} as MessageRouterDeps['config'],
|
} as unknown as MessageRouterDeps['config'],
|
||||||
commandRegistry,
|
commandRegistry,
|
||||||
intentRegistry,
|
intentRegistry,
|
||||||
routingPolicy,
|
routingPolicy,
|
||||||
@@ -381,6 +384,7 @@ describe('daemon command fast-path integration', () => {
|
|||||||
channel: 'telegram',
|
channel: 'telegram',
|
||||||
senderId: 'user-3',
|
senderId: 'user-3',
|
||||||
text: 'deploy backend now',
|
text: 'deploy backend now',
|
||||||
|
timestamp: Date.now(),
|
||||||
metadata: { isCommand: true, command: 'reset' },
|
metadata: { isCommand: true, command: 'reset' },
|
||||||
} as MessageRouterInput, vi.fn(async () => {}));
|
} as MessageRouterInput, vi.fn(async () => {}));
|
||||||
|
|
||||||
@@ -412,15 +416,15 @@ describe('daemon audio routing integration', () => {
|
|||||||
registerBuiltinCommands(commandRegistry);
|
registerBuiltinCommands(commandRegistry);
|
||||||
|
|
||||||
const router = createMessageRouter({
|
const router = createMessageRouter({
|
||||||
sessionManager: { getSession: vi.fn(() => session) } as MessageRouterDeps['sessionManager'],
|
sessionManager: { getSession: vi.fn(() => session) } as unknown as MessageRouterDeps['sessionManager'],
|
||||||
modelRouter: {
|
modelRouter: {
|
||||||
getAvailableTiers: () => ['default'],
|
getAvailableTiers: () => ['default'],
|
||||||
getAllLabels: () => ({ default: 'default' }),
|
getAllLabels: () => ({ default: 'default' }),
|
||||||
getLabel: (tier: string) => tier,
|
getLabel: (tier: string) => tier,
|
||||||
} as MessageRouterDeps['modelRouter'],
|
} as unknown as MessageRouterDeps['modelRouter'],
|
||||||
systemPrompt: 'test prompt',
|
systemPrompt: 'test prompt',
|
||||||
toolRegistry: { clone() { return this; }, register: vi.fn() } as MessageRouterDeps['toolRegistry'],
|
toolRegistry: { clone() { return this; }, register: vi.fn() } as unknown as MessageRouterDeps['toolRegistry'],
|
||||||
toolExecutor: {} as MessageRouterDeps['toolExecutor'],
|
toolExecutor: {} as unknown as MessageRouterDeps['toolExecutor'],
|
||||||
config: {
|
config: {
|
||||||
agents: {
|
agents: {
|
||||||
primary_tier: 'default',
|
primary_tier: 'default',
|
||||||
@@ -438,11 +442,11 @@ describe('daemon audio routing integration', () => {
|
|||||||
// Anthropic doesn't support native audio; ensures routing hits the non-audio path.
|
// Anthropic doesn't support native audio; ensures routing hits the non-audio path.
|
||||||
models: { default: { provider: 'anthropic', model: 'claude' } },
|
models: { default: { provider: 'anthropic', model: 'claude' } },
|
||||||
audio: { enabled: false },
|
audio: { enabled: false },
|
||||||
} as MessageRouterDeps['config'],
|
} as unknown as MessageRouterDeps['config'],
|
||||||
commandRegistry,
|
commandRegistry,
|
||||||
});
|
});
|
||||||
|
|
||||||
const reply = vi.fn(async () => {});
|
const reply = vi.fn(async (_message: OutboundMessage) => {});
|
||||||
await router.handler({
|
await router.handler({
|
||||||
id: 'v1',
|
id: 'v1',
|
||||||
channel: 'telegram',
|
channel: 'telegram',
|
||||||
@@ -485,15 +489,15 @@ describe('daemon audio routing integration', () => {
|
|||||||
registerBuiltinCommands(commandRegistry);
|
registerBuiltinCommands(commandRegistry);
|
||||||
|
|
||||||
const router = createMessageRouter({
|
const router = createMessageRouter({
|
||||||
sessionManager: { getSession: vi.fn(() => session) } as MessageRouterDeps['sessionManager'],
|
sessionManager: { getSession: vi.fn(() => session) } as unknown as MessageRouterDeps['sessionManager'],
|
||||||
modelRouter: {
|
modelRouter: {
|
||||||
getAvailableTiers: () => ['default'],
|
getAvailableTiers: () => ['default'],
|
||||||
getAllLabels: () => ({ default: 'default' }),
|
getAllLabels: () => ({ default: 'default' }),
|
||||||
getLabel: (tier: string) => tier,
|
getLabel: (tier: string) => tier,
|
||||||
} as MessageRouterDeps['modelRouter'],
|
} as unknown as MessageRouterDeps['modelRouter'],
|
||||||
systemPrompt: 'test prompt',
|
systemPrompt: 'test prompt',
|
||||||
toolRegistry: { clone() { return this; }, register: vi.fn() } as MessageRouterDeps['toolRegistry'],
|
toolRegistry: { clone() { return this; }, register: vi.fn() } as unknown as MessageRouterDeps['toolRegistry'],
|
||||||
toolExecutor: {} as MessageRouterDeps['toolExecutor'],
|
toolExecutor: {} as unknown as MessageRouterDeps['toolExecutor'],
|
||||||
config: {
|
config: {
|
||||||
agents: {
|
agents: {
|
||||||
primary_tier: 'default',
|
primary_tier: 'default',
|
||||||
@@ -513,11 +517,11 @@ describe('daemon audio routing integration', () => {
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
provider: { type: 'openai', endpoint: 'https://example.com/v1/audio/transcriptions', api_key: 'sk-test', model: 'whisper-1' },
|
provider: { type: 'openai', endpoint: 'https://example.com/v1/audio/transcriptions', api_key: 'sk-test', model: 'whisper-1' },
|
||||||
},
|
},
|
||||||
} as MessageRouterDeps['config'],
|
} as unknown as MessageRouterDeps['config'],
|
||||||
commandRegistry,
|
commandRegistry,
|
||||||
});
|
});
|
||||||
|
|
||||||
const reply = vi.fn(async () => {});
|
const reply = vi.fn(async (_message: OutboundMessage) => {});
|
||||||
await router.handler({
|
await router.handler({
|
||||||
id: 'v2',
|
id: 'v2',
|
||||||
channel: 'telegram',
|
channel: 'telegram',
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ describe('discoverServices', () => {
|
|||||||
|
|
||||||
it('marks configured channels as disconnected when adapter is not registered', () => {
|
it('marks configured channels as disconnected when adapter is not registered', () => {
|
||||||
const cfg = makeBaseConfig();
|
const cfg = makeBaseConfig();
|
||||||
withMutableConfig(cfg).telegram = { bot_token: 'x', allowed_chat_ids: [123] };
|
withMutableConfig(cfg).telegram = { bot_token: 'x', allowed_chat_ids: [123], require_mention: false };
|
||||||
|
|
||||||
const reg = new ChannelRegistry();
|
const reg = new ChannelRegistry();
|
||||||
const services = discoverServices(cfg, reg);
|
const services = discoverServices(cfg, reg);
|
||||||
@@ -62,7 +62,7 @@ describe('discoverServices', () => {
|
|||||||
|
|
||||||
it('uses adapter status when channel adapter is registered', () => {
|
it('uses adapter status when channel adapter is registered', () => {
|
||||||
const cfg = makeBaseConfig();
|
const cfg = makeBaseConfig();
|
||||||
withMutableConfig(cfg).telegram = { bot_token: 'x', allowed_chat_ids: [123] };
|
withMutableConfig(cfg).telegram = { bot_token: 'x', allowed_chat_ids: [123], require_mention: false };
|
||||||
|
|
||||||
const reg = new ChannelRegistry();
|
const reg = new ChannelRegistry();
|
||||||
reg.register({
|
reg.register({
|
||||||
|
|||||||
@@ -8,12 +8,23 @@ vi.mock('child_process', () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const mockExecFile = vi.mocked(execFile);
|
const mockExecFile = vi.mocked(execFile);
|
||||||
type ExecFileCallback = (error: Error | null, stdout: string, stderr: string) => void;
|
type ExecFileCallback = NonNullable<Parameters<typeof execFile>[3]>;
|
||||||
|
|
||||||
function mockChildProcess(): ChildProcess {
|
function mockChildProcess(): ChildProcess {
|
||||||
return {} as ChildProcess;
|
return {} as ChildProcess;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mockExecFileOnce(
|
||||||
|
impl: (callback: ExecFileCallback) => void,
|
||||||
|
): void {
|
||||||
|
mockExecFile.mockImplementationOnce((_cmd, _args, _opts, callback) => {
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
impl(callback as ExecFileCallback);
|
||||||
|
}
|
||||||
|
return mockChildProcess();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
describe('tailscale', () => {
|
describe('tailscale', () => {
|
||||||
// Import after mocking
|
// Import after mocking
|
||||||
let isTailscaleAvailable: typeof import('./tailscale.js').isTailscaleAvailable;
|
let isTailscaleAvailable: typeof import('./tailscale.js').isTailscaleAvailable;
|
||||||
@@ -33,15 +44,8 @@ describe('tailscale', () => {
|
|||||||
|
|
||||||
describe('isTailscaleAvailable', () => {
|
describe('isTailscaleAvailable', () => {
|
||||||
it('returns available when tailscale CLI works', async () => {
|
it('returns available when tailscale CLI works', async () => {
|
||||||
mockExecFile
|
mockExecFileOnce((callback) => callback(null, '1.62.0', ''));
|
||||||
.mockImplementationOnce((_cmd, _args, _opts, callback: ExecFileCallback) => {
|
mockExecFileOnce((callback) => callback(null, '{}', ''));
|
||||||
callback(null, '1.62.0', '');
|
|
||||||
return mockChildProcess();
|
|
||||||
})
|
|
||||||
.mockImplementationOnce((_cmd, _args, _opts, callback: ExecFileCallback) => {
|
|
||||||
callback(null, '{}', '');
|
|
||||||
return mockChildProcess();
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await isTailscaleAvailable();
|
const result = await isTailscaleAvailable();
|
||||||
expect(result.available).toBe(true);
|
expect(result.available).toBe(true);
|
||||||
@@ -49,10 +53,7 @@ describe('tailscale', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('returns unavailable when tailscale CLI fails', async () => {
|
it('returns unavailable when tailscale CLI fails', async () => {
|
||||||
mockExecFile.mockImplementationOnce((_cmd, _args, _opts, callback: ExecFileCallback) => {
|
mockExecFileOnce((callback) => callback(new Error('command not found'), '', 'command not found'));
|
||||||
callback(new Error('command not found'), '', 'command not found');
|
|
||||||
return mockChildProcess();
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await isTailscaleAvailable();
|
const result = await isTailscaleAvailable();
|
||||||
expect(result.available).toBe(false);
|
expect(result.available).toBe(false);
|
||||||
@@ -62,17 +63,8 @@ describe('tailscale', () => {
|
|||||||
|
|
||||||
describe('startTailscaleServe', () => {
|
describe('startTailscaleServe', () => {
|
||||||
it('calls tailscale serve with correct args', async () => {
|
it('calls tailscale serve with correct args', async () => {
|
||||||
mockExecFile
|
mockExecFileOnce((callback) => callback(null, '', ''));
|
||||||
// serve command
|
mockExecFileOnce((callback) => callback(null, JSON.stringify({ Self: { DNSName: 'myhost.tailnet.ts.net.' } }), ''));
|
||||||
.mockImplementationOnce((_cmd, _args, _opts, callback: ExecFileCallback) => {
|
|
||||||
callback(null, '', '');
|
|
||||||
return mockChildProcess();
|
|
||||||
})
|
|
||||||
// status for hostname
|
|
||||||
.mockImplementationOnce((_cmd, _args, _opts, callback: ExecFileCallback) => {
|
|
||||||
callback(null, JSON.stringify({ Self: { DNSName: 'myhost.tailnet.ts.net.' } }), '');
|
|
||||||
return mockChildProcess();
|
|
||||||
});
|
|
||||||
|
|
||||||
const url = await startTailscaleServe({ localPort: 18800 });
|
const url = await startTailscaleServe({ localPort: 18800 });
|
||||||
expect(url).toBe('https://myhost.tailnet.ts.net');
|
expect(url).toBe('https://myhost.tailnet.ts.net');
|
||||||
@@ -83,15 +75,8 @@ describe('tailscale', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('uses custom serve port', async () => {
|
it('uses custom serve port', async () => {
|
||||||
mockExecFile
|
mockExecFileOnce((callback) => callback(null, '', ''));
|
||||||
.mockImplementationOnce((_cmd, _args, _opts, callback: ExecFileCallback) => {
|
mockExecFileOnce((callback) => callback(null, JSON.stringify({ Self: { DNSName: 'myhost.tailnet.ts.net.' } }), ''));
|
||||||
callback(null, '', '');
|
|
||||||
return mockChildProcess();
|
|
||||||
})
|
|
||||||
.mockImplementationOnce((_cmd, _args, _opts, callback: ExecFileCallback) => {
|
|
||||||
callback(null, JSON.stringify({ Self: { DNSName: 'myhost.tailnet.ts.net.' } }), '');
|
|
||||||
return mockChildProcess();
|
|
||||||
});
|
|
||||||
|
|
||||||
const url = await startTailscaleServe({ localPort: 18800, servePort: 8443 });
|
const url = await startTailscaleServe({ localPort: 18800, servePort: 8443 });
|
||||||
expect(url).toBe('https://myhost.tailnet.ts.net:8443');
|
expect(url).toBe('https://myhost.tailnet.ts.net:8443');
|
||||||
@@ -103,10 +88,7 @@ describe('tailscale', () => {
|
|||||||
|
|
||||||
describe('stopTailscaleServe', () => {
|
describe('stopTailscaleServe', () => {
|
||||||
it('calls tailscale serve off', async () => {
|
it('calls tailscale serve off', async () => {
|
||||||
mockExecFile.mockImplementationOnce((_cmd, _args, _opts, callback: ExecFileCallback) => {
|
mockExecFileOnce((callback) => callback(null, '', ''));
|
||||||
callback(null, '', '');
|
|
||||||
return mockChildProcess();
|
|
||||||
});
|
|
||||||
|
|
||||||
await stopTailscaleServe({ localPort: 18800 });
|
await stopTailscaleServe({ localPort: 18800 });
|
||||||
|
|
||||||
@@ -117,10 +99,7 @@ describe('tailscale', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('does not throw on failure', async () => {
|
it('does not throw on failure', async () => {
|
||||||
mockExecFile.mockImplementationOnce((_cmd, _args, _opts, callback: ExecFileCallback) => {
|
mockExecFileOnce((callback) => callback(new Error('failed'), '', 'failed'));
|
||||||
callback(new Error('failed'), '', 'failed');
|
|
||||||
return mockChildProcess();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Should not throw
|
// Should not throw
|
||||||
await stopTailscaleServe({ localPort: 18800 });
|
await stopTailscaleServe({ localPort: 18800 });
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ function toOpenAIContent(content: string | MessageContentPart[]): string | OpenA
|
|||||||
}
|
}
|
||||||
const url = part.source.type === 'base64'
|
const url = part.source.type === 'base64'
|
||||||
? `data:${part.source.media_type};base64,${part.source.data}`
|
? `data:${part.source.media_type};base64,${part.source.data}`
|
||||||
: part.source.url;
|
: (part.source.url ?? '');
|
||||||
return { type: 'image_url', image_url: { url } };
|
return { type: 'image_url', image_url: { url } };
|
||||||
}
|
}
|
||||||
if (part.type === 'audio') {
|
if (part.type === 'audio') {
|
||||||
|
|||||||
@@ -405,7 +405,7 @@ describe('normalizeMessagesForLlamaCpp', () => {
|
|||||||
content: [
|
content: [
|
||||||
{ type: 'text', text: 'Searching...' },
|
{ type: 'text', text: 'Searching...' },
|
||||||
{ type: 'tool_use', id: 'call_1', name: 'web.search', input: { query: 'news' } },
|
{ type: 'tool_use', id: 'call_1', name: 'web.search', input: { query: 'news' } },
|
||||||
] as Message['content'],
|
] as unknown as Message['content'],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -433,13 +433,13 @@ describe('normalizeMessagesForLlamaCpp', () => {
|
|||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
content: [
|
content: [
|
||||||
{ type: 'tool_use', id: 'call_1', name: 'web.search', input: { query: 'news' } },
|
{ type: 'tool_use', id: 'call_1', name: 'web.search', input: { query: 'news' } },
|
||||||
] as Message['content'],
|
] as unknown as Message['content'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'user',
|
role: 'user',
|
||||||
content: [
|
content: [
|
||||||
{ type: 'tool_result', tool_use_id: 'call_1', content: 'Results here', is_error: false },
|
{ type: 'tool_result', tool_use_id: 'call_1', content: 'Results here', is_error: false },
|
||||||
] as Message['content'],
|
] as unknown as Message['content'],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -460,14 +460,14 @@ describe('normalizeMessagesForLlamaCpp', () => {
|
|||||||
content: [
|
content: [
|
||||||
{ type: 'tool_use', id: 'call_a', name: 'tool.a', input: {} },
|
{ type: 'tool_use', id: 'call_a', name: 'tool.a', input: {} },
|
||||||
{ type: 'tool_use', id: 'call_b', name: 'tool.b', input: { x: 1 } },
|
{ type: 'tool_use', id: 'call_b', name: 'tool.b', input: { x: 1 } },
|
||||||
] as Message['content'],
|
] as unknown as Message['content'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'user',
|
role: 'user',
|
||||||
content: [
|
content: [
|
||||||
{ type: 'tool_result', tool_use_id: 'call_a', content: 'A result' },
|
{ type: 'tool_result', tool_use_id: 'call_a', content: 'A result' },
|
||||||
{ type: 'tool_result', tool_use_id: 'call_b', content: 'B result' },
|
{ type: 'tool_result', tool_use_id: 'call_b', content: 'B result' },
|
||||||
] as Message['content'],
|
] as unknown as Message['content'],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -492,13 +492,13 @@ describe('normalizeMessagesForLlamaCpp', () => {
|
|||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
content: [
|
content: [
|
||||||
{ type: 'tool_use', id: 'call_1', name: 'file.read', input: { path: '/tmp/x' } },
|
{ type: 'tool_use', id: 'call_1', name: 'file.read', input: { path: '/tmp/x' } },
|
||||||
] as Message['content'],
|
] as unknown as Message['content'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'user',
|
role: 'user',
|
||||||
content: [
|
content: [
|
||||||
{ type: 'tool_result', tool_use_id: 'call_1', content: 'File not found', is_error: true },
|
{ type: 'tool_result', tool_use_id: 'call_1', content: 'File not found', is_error: true },
|
||||||
] as Message['content'],
|
] as unknown as Message['content'],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -518,13 +518,13 @@ describe('normalizeMessagesForLlamaCpp', () => {
|
|||||||
content: [
|
content: [
|
||||||
{ type: 'text', text: 'Checking...' },
|
{ type: 'text', text: 'Checking...' },
|
||||||
{ type: 'tool_use', id: 'tc_0', name: 'weather.get', input: { city: 'NYC' } },
|
{ type: 'tool_use', id: 'tc_0', name: 'weather.get', input: { city: 'NYC' } },
|
||||||
] as Message['content'],
|
] as unknown as Message['content'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'user',
|
role: 'user',
|
||||||
content: [
|
content: [
|
||||||
{ type: 'tool_result', tool_use_id: 'tc_0', content: 'Sunny, 72F' },
|
{ type: 'tool_result', tool_use_id: 'tc_0', content: 'Sunny, 72F' },
|
||||||
] as Message['content'],
|
] as unknown as Message['content'],
|
||||||
},
|
},
|
||||||
{ role: 'assistant', content: 'The weather in NYC is sunny, 72F.' },
|
{ role: 'assistant', content: 'The weather in NYC is sunny, 72F.' },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -375,7 +375,7 @@ describe('normalizeMessagesForOllama', () => {
|
|||||||
content: [
|
content: [
|
||||||
{ type: 'text', text: 'Let me search for that.' },
|
{ type: 'text', text: 'Let me search for that.' },
|
||||||
{ type: 'tool_use', id: 'call_1', name: 'web.search', input: { query: 'latest news' } },
|
{ type: 'tool_use', id: 'call_1', name: 'web.search', input: { query: 'latest news' } },
|
||||||
] as Message['content'],
|
] as unknown as Message['content'],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -402,13 +402,13 @@ describe('normalizeMessagesForOllama', () => {
|
|||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
content: [
|
content: [
|
||||||
{ type: 'tool_use', id: 'call_1', name: 'web.search', input: { query: 'news' } },
|
{ type: 'tool_use', id: 'call_1', name: 'web.search', input: { query: 'news' } },
|
||||||
] as Message['content'],
|
] as unknown as Message['content'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'user',
|
role: 'user',
|
||||||
content: [
|
content: [
|
||||||
{ type: 'tool_result', tool_use_id: 'call_1', content: 'Breaking news: ...', is_error: false },
|
{ type: 'tool_result', tool_use_id: 'call_1', content: 'Breaking news: ...', is_error: false },
|
||||||
] as Message['content'],
|
] as unknown as Message['content'],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -430,14 +430,14 @@ describe('normalizeMessagesForOllama', () => {
|
|||||||
content: [
|
content: [
|
||||||
{ type: 'tool_use', id: 'call_1', name: 'web.search', input: { query: 'a' } },
|
{ type: 'tool_use', id: 'call_1', name: 'web.search', input: { query: 'a' } },
|
||||||
{ type: 'tool_use', id: 'call_2', name: 'web.search', input: { query: 'b' } },
|
{ type: 'tool_use', id: 'call_2', name: 'web.search', input: { query: 'b' } },
|
||||||
] as Message['content'],
|
] as unknown as Message['content'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'user',
|
role: 'user',
|
||||||
content: [
|
content: [
|
||||||
{ type: 'tool_result', tool_use_id: 'call_1', content: 'Result A' },
|
{ type: 'tool_result', tool_use_id: 'call_1', content: 'Result A' },
|
||||||
{ type: 'tool_result', tool_use_id: 'call_2', content: 'Result B' },
|
{ type: 'tool_result', tool_use_id: 'call_2', content: 'Result B' },
|
||||||
] as Message['content'],
|
] as unknown as Message['content'],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -457,13 +457,13 @@ describe('normalizeMessagesForOllama', () => {
|
|||||||
content: [
|
content: [
|
||||||
{ type: 'text', text: 'Let me check.' },
|
{ type: 'text', text: 'Let me check.' },
|
||||||
{ type: 'tool_use', id: 'tc_0', name: 'weather.get', input: { city: 'NYC' } },
|
{ type: 'tool_use', id: 'tc_0', name: 'weather.get', input: { city: 'NYC' } },
|
||||||
] as Message['content'],
|
] as unknown as Message['content'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'user',
|
role: 'user',
|
||||||
content: [
|
content: [
|
||||||
{ type: 'tool_result', tool_use_id: 'tc_0', content: 'Sunny, 72F' },
|
{ type: 'tool_result', tool_use_id: 'tc_0', content: 'Sunny, 72F' },
|
||||||
] as Message['content'],
|
] as unknown as Message['content'],
|
||||||
},
|
},
|
||||||
{ role: 'assistant', content: 'The weather in NYC is sunny, 72F.' },
|
{ role: 'assistant', content: 'The weather in NYC is sunny, 72F.' },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ describe('OpenAIClient', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(capturedOptions).toBeDefined();
|
expect(capturedOptions).toBeDefined();
|
||||||
expect(capturedOptions.baseURL).toBe('https://example.com/v1');
|
if (!capturedOptions) {
|
||||||
|
throw new Error('Expected OpenAI options to be captured');
|
||||||
|
}
|
||||||
|
expect((capturedOptions as Record<string, unknown>)['baseURL']).toBe('https://example.com/v1');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ function toOpenAIContent(content: string | MessageContentPart[]): string | OpenA
|
|||||||
// OpenAI accepts data URIs or regular URLs
|
// OpenAI accepts data URIs or regular URLs
|
||||||
const url = part.source.type === 'base64'
|
const url = part.source.type === 'base64'
|
||||||
? `data:${part.source.media_type};base64,${part.source.data}`
|
? `data:${part.source.media_type};base64,${part.source.data}`
|
||||||
: part.source.url;
|
: (part.source.url ?? '');
|
||||||
return { type: 'image_url', image_url: { url } };
|
return { type: 'image_url', image_url: { url } };
|
||||||
}
|
}
|
||||||
if (part.type === 'audio') {
|
if (part.type === 'audio') {
|
||||||
|
|||||||
@@ -26,8 +26,9 @@ export const fileListTool: Tool = {
|
|||||||
const args = rawArgs as FileListArgs;
|
const args = rawArgs as FileListArgs;
|
||||||
try {
|
try {
|
||||||
let entries = readdirSync(args.path, { withFileTypes: true });
|
let entries = readdirSync(args.path, { withFileTypes: true });
|
||||||
if (args.pattern) {
|
const pattern = args.pattern;
|
||||||
entries = entries.filter(e => matchGlob(e.name, args.pattern));
|
if (typeof pattern === 'string' && pattern.length > 0) {
|
||||||
|
entries = entries.filter(e => matchGlob(e.name, pattern));
|
||||||
}
|
}
|
||||||
const output = entries
|
const output = entries
|
||||||
.map(e => e.isDirectory() ? `${e.name}/` : e.name)
|
.map(e => e.isDirectory() ? `${e.name}/` : e.name)
|
||||||
|
|||||||
@@ -400,7 +400,7 @@ describe('ToolExecutor', () => {
|
|||||||
const fakeManager = {
|
const fakeManager = {
|
||||||
getOrCreate: async () => fakeSandbox,
|
getOrCreate: async () => fakeSandbox,
|
||||||
} as { getOrCreate: (sessionId: string) => Promise<typeof fakeSandbox> };
|
} as { getOrCreate: (sessionId: string) => Promise<typeof fakeSandbox> };
|
||||||
executor.setSandboxManager(fakeManager);
|
executor.setSandboxManager(fakeManager as unknown as Parameters<typeof executor.setSandboxManager>[0]);
|
||||||
|
|
||||||
const result = await executor.execute('shell.exec', { command: 'echo hi' }, {
|
const result = await executor.execute('shell.exec', { command: 'echo hi' }, {
|
||||||
executionEnvironment: 'sandbox',
|
executionEnvironment: 'sandbox',
|
||||||
|
|||||||
@@ -115,12 +115,12 @@ describe('ToolRegistry', () => {
|
|||||||
|
|
||||||
it('inherits the policy from original', () => {
|
it('inherits the policy from original', () => {
|
||||||
const reg = new ToolRegistry();
|
const reg = new ToolRegistry();
|
||||||
const mockPolicy: ToolPolicy = {
|
const mockPolicy = {
|
||||||
filterTools: vi.fn((tools) => tools),
|
filterTools: vi.fn((tools) => tools),
|
||||||
isAllowed: vi.fn(() => true),
|
isAllowed: vi.fn(() => true),
|
||||||
resolveAllowedNames: vi.fn(() => new Set()),
|
resolveAllowedNames: vi.fn(() => new Set<string>()),
|
||||||
getEffectiveProfile: vi.fn(() => ({ profile: 'full', source: 'explicit' })),
|
getEffectiveProfile: vi.fn<() => 'full'>(() => 'full'),
|
||||||
};
|
} as unknown as ToolPolicy;
|
||||||
reg.setPolicy(mockPolicy);
|
reg.setPolicy(mockPolicy);
|
||||||
|
|
||||||
const cloned = reg.clone();
|
const cloned = reg.clone();
|
||||||
|
|||||||
Reference in New Issue
Block a user