mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-07-27 10:04:58 +00:00
108 lines
3.5 KiB
TypeScript
108 lines
3.5 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
|
|
import type { Agent } from '../../src/agent';
|
|
import type { SkillActivationOrigin } from '../../src/agent/context';
|
|
import { SkillRegistry, type SkillDefinition } from '../../src/skill';
|
|
import {
|
|
MAX_SKILL_QUERY_DEPTH,
|
|
NestedSkillTooDeepError,
|
|
SkillTool,
|
|
} from '../../src/tools/builtin/collaboration/skill-tool';
|
|
import { executeTool } from './fixtures/execute-tool';
|
|
|
|
const signal = new AbortController().signal;
|
|
|
|
function skill(name: string, metadata: SkillDefinition['metadata'] = {}): SkillDefinition {
|
|
return {
|
|
name,
|
|
description: `desc for ${name}`,
|
|
path: `/skills/${name}/SKILL.md`,
|
|
dir: `/skills/${name}`,
|
|
content: `body of ${name}`,
|
|
metadata,
|
|
source: 'user',
|
|
};
|
|
}
|
|
|
|
function registry(skills: readonly SkillDefinition[] = []): SkillRegistry {
|
|
const registry = new SkillRegistry();
|
|
for (const item of skills) {
|
|
registry.register(item);
|
|
}
|
|
return registry;
|
|
}
|
|
|
|
interface SkillToolMethods {
|
|
readonly recordSkillActivation: (origin: SkillActivationOrigin) => void;
|
|
readonly recordSystemReminder: (content: string, origin: SkillActivationOrigin) => void;
|
|
}
|
|
|
|
function skillToolMethods() {
|
|
return {
|
|
recordSkillActivation: vi.fn<SkillToolMethods['recordSkillActivation']>(),
|
|
recordSystemReminder: vi.fn<SkillToolMethods['recordSystemReminder']>(),
|
|
} satisfies SkillToolMethods;
|
|
}
|
|
|
|
function skillToolAgent(skills: SkillRegistry, methods: SkillToolMethods): Agent {
|
|
return {
|
|
skills: {
|
|
registry: skills,
|
|
recordActivation: methods.recordSkillActivation,
|
|
},
|
|
context: {
|
|
appendSystemReminder: methods.recordSystemReminder,
|
|
},
|
|
} as unknown as Agent;
|
|
}
|
|
|
|
function skillTool(
|
|
skills: SkillRegistry,
|
|
methods = skillToolMethods(),
|
|
options?: ConstructorParameters<typeof SkillTool>[1],
|
|
): SkillTool {
|
|
return new SkillTool(skillToolAgent(skills, methods), options);
|
|
}
|
|
|
|
function execute(tool: SkillTool, args: { skill: string; args?: string }) {
|
|
return executeTool(tool, {
|
|
turnId: '0',
|
|
toolCallId: 'call_skill',
|
|
args,
|
|
signal,
|
|
});
|
|
}
|
|
|
|
describe('SkillTool dispatch edges', () => {
|
|
it('treats prompt skills as inline skills', async () => {
|
|
const methods = skillToolMethods();
|
|
const tool = skillTool(registry([skill('prompt-skill', { type: 'prompt' })]), methods);
|
|
|
|
const result = await execute(tool, { skill: 'prompt-skill' });
|
|
|
|
expect(result.output).toContain('loaded inline');
|
|
expect(result.output).not.toContain('body of prompt-skill');
|
|
expect(methods.recordSystemReminder.mock.calls[0]?.[0]).toContain('body of prompt-skill');
|
|
expect(methods.recordSkillActivation).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('treats omitted skill type as inline for backwards-compatible skill files', async () => {
|
|
const methods = skillToolMethods();
|
|
const tool = skillTool(registry([skill('legacy')]), methods);
|
|
|
|
const result = await execute(tool, { skill: 'legacy' });
|
|
|
|
expect(result.output).toContain('loaded inline');
|
|
expect(result.output).not.toContain('body of legacy');
|
|
expect(methods.recordSystemReminder.mock.calls[0]?.[0]).toContain('body of legacy');
|
|
expect(methods.recordSkillActivation).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('honors initialQueryDepth as an alias for queryDepth', async () => {
|
|
const tool = skillTool(registry([skill('loop')]), skillToolMethods(), {
|
|
initialQueryDepth: MAX_SKILL_QUERY_DEPTH,
|
|
});
|
|
|
|
await expect(execute(tool, { skill: 'loop' })).rejects.toBeInstanceOf(NestedSkillTooDeepError);
|
|
});
|
|
});
|