kimi-vercel-ai-sdk-provider 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +426 -31
- package/dist/index.d.mts +1608 -2
- package/dist/index.d.ts +1608 -2
- package/dist/index.js +1949 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1924 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/auto-detect.test.ts +140 -0
- package/src/__tests__/code-validation.test.ts +267 -0
- package/src/__tests__/ensemble.test.ts +242 -0
- package/src/__tests__/multi-agent.test.ts +201 -0
- package/src/__tests__/project-tools.test.ts +181 -0
- package/src/__tests__/tools.test.ts +1 -1
- package/src/chat/kimi-chat-settings.ts +15 -1
- package/src/code-validation/detector.ts +319 -0
- package/src/code-validation/index.ts +31 -0
- package/src/code-validation/types.ts +291 -0
- package/src/code-validation/validator.ts +547 -0
- package/src/core/errors.ts +91 -0
- package/src/core/index.ts +5 -0
- package/src/ensemble/index.ts +17 -0
- package/src/ensemble/multi-sampler.ts +433 -0
- package/src/ensemble/types.ts +279 -0
- package/src/index.ts +102 -3
- package/src/kimi-provider.ts +354 -1
- package/src/multi-agent/index.ts +21 -0
- package/src/multi-agent/types.ts +312 -0
- package/src/multi-agent/workflows.ts +539 -0
- package/src/project-tools/index.ts +16 -0
- package/src/project-tools/scaffolder.ts +494 -0
- package/src/project-tools/types.ts +244 -0
- package/src/tools/auto-detect.ts +276 -0
- package/src/tools/index.ts +6 -2
- package/src/tools/prepare-tools.ts +91 -2
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { DEFAULT_SYSTEM_PROMPTS, type WorkflowType } from '../multi-agent/types';
|
|
3
|
+
import { WorkflowRunner, createEmptyMultiAgentResult } from '../multi-agent/workflows';
|
|
4
|
+
|
|
5
|
+
describe('Multi-Agent Collaboration', () => {
|
|
6
|
+
const createMockGenerator = (responses: Record<string, string>) =>
|
|
7
|
+
vi.fn().mockImplementation(async (modelId: string) => {
|
|
8
|
+
return {
|
|
9
|
+
text: responses[modelId] || `Response from ${modelId}`,
|
|
10
|
+
usage: { promptTokens: 10, completionTokens: 20, totalTokens: 30 }
|
|
11
|
+
};
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe('WorkflowRunner', () => {
|
|
15
|
+
describe('planner-executor workflow', () => {
|
|
16
|
+
it('runs planner-executor workflow', async () => {
|
|
17
|
+
const generator = createMockGenerator({
|
|
18
|
+
'kimi-k2.5-thinking': '1. Analyze requirements\n2. Design solution\n3. Implement',
|
|
19
|
+
'kimi-k2.5': 'function solve() { return 42; }'
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const runner = new WorkflowRunner(generator);
|
|
23
|
+
const result = await runner.run('Create a function', {
|
|
24
|
+
workflow: 'planner-executor',
|
|
25
|
+
modelA: 'kimi-k2.5-thinking',
|
|
26
|
+
modelB: 'kimi-k2.5'
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
expect(result.text).toContain('function solve');
|
|
30
|
+
expect(result.reasoning).toContain('Analyze requirements');
|
|
31
|
+
expect(result.intermediateSteps).toHaveLength(2);
|
|
32
|
+
expect(result.metadata.workflow).toBe('planner-executor');
|
|
33
|
+
expect(result.metadata.models).toContain('kimi-k2.5-thinking');
|
|
34
|
+
expect(result.metadata.models).toContain('kimi-k2.5');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('includes validation step when enabled', async () => {
|
|
38
|
+
const generator = createMockGenerator({
|
|
39
|
+
'kimi-k2.5-thinking': 'Plan step',
|
|
40
|
+
'kimi-k2.5': '```javascript\nconst x = 5;\n```'
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const validateCode = vi.fn().mockResolvedValue({
|
|
44
|
+
valid: true,
|
|
45
|
+
errors: []
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const runner = new WorkflowRunner(generator, validateCode);
|
|
49
|
+
const result = await runner.run('Create code', {
|
|
50
|
+
workflow: 'planner-executor',
|
|
51
|
+
validateCode: true
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
expect(result.intermediateSteps.length).toBeGreaterThanOrEqual(2);
|
|
55
|
+
expect(result.validation).toBeDefined();
|
|
56
|
+
expect(result.validation?.valid).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('proposer-critic workflow', () => {
|
|
61
|
+
it('runs proposer-critic workflow', async () => {
|
|
62
|
+
const generator = createMockGenerator({
|
|
63
|
+
'kimi-k2.5': 'First attempt at solution',
|
|
64
|
+
'kimi-k2.5-thinking': 'Consider these improvements...'
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const runner = new WorkflowRunner(generator);
|
|
68
|
+
const result = await runner.run('Solve this problem', {
|
|
69
|
+
workflow: 'proposer-critic',
|
|
70
|
+
iterations: 2,
|
|
71
|
+
modelA: 'kimi-k2.5',
|
|
72
|
+
modelB: 'kimi-k2.5-thinking'
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
expect(result.metadata.workflow).toBe('proposer-critic');
|
|
76
|
+
expect(result.intermediateSteps.length).toBeGreaterThanOrEqual(2);
|
|
77
|
+
|
|
78
|
+
const roles = result.intermediateSteps.map((s) => s.role);
|
|
79
|
+
expect(roles).toContain('proposer');
|
|
80
|
+
expect(roles).toContain('critic');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('iterates specified number of times', async () => {
|
|
84
|
+
const generator = vi.fn().mockResolvedValue({
|
|
85
|
+
text: 'Response',
|
|
86
|
+
usage: { promptTokens: 10, completionTokens: 20, totalTokens: 30 }
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const runner = new WorkflowRunner(generator);
|
|
90
|
+
await runner.run('Task', {
|
|
91
|
+
workflow: 'proposer-critic',
|
|
92
|
+
iterations: 3
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// 3 proposer iterations + 2 critic iterations (critic doesn't run on last)
|
|
96
|
+
expect(generator).toHaveBeenCalledTimes(5);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('debate workflow', () => {
|
|
101
|
+
it('runs debate workflow', async () => {
|
|
102
|
+
const generator = createMockGenerator({
|
|
103
|
+
'kimi-k2.5': 'Position A'
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const runner = new WorkflowRunner(generator);
|
|
107
|
+
const result = await runner.run('Debate this topic', {
|
|
108
|
+
workflow: 'debate',
|
|
109
|
+
iterations: 2
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
expect(result.metadata.workflow).toBe('debate');
|
|
113
|
+
expect(result.intermediateSteps.length).toBeGreaterThan(0);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('custom workflow', () => {
|
|
118
|
+
it('runs custom workflow', async () => {
|
|
119
|
+
const generator = createMockGenerator({});
|
|
120
|
+
const runner = new WorkflowRunner(generator);
|
|
121
|
+
|
|
122
|
+
const customWorkflow = vi.fn().mockResolvedValue({
|
|
123
|
+
text: 'Custom result',
|
|
124
|
+
intermediateSteps: [
|
|
125
|
+
{
|
|
126
|
+
agent: 'Custom',
|
|
127
|
+
role: 'custom',
|
|
128
|
+
action: 'Custom action',
|
|
129
|
+
output: 'Custom output',
|
|
130
|
+
timestamp: Date.now(),
|
|
131
|
+
durationMs: 100
|
|
132
|
+
}
|
|
133
|
+
],
|
|
134
|
+
usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
|
135
|
+
metadata: {
|
|
136
|
+
workflow: 'custom',
|
|
137
|
+
iterations: 1,
|
|
138
|
+
durationMs: 0,
|
|
139
|
+
models: [],
|
|
140
|
+
validationEnabled: false,
|
|
141
|
+
success: true
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const result = await runner.run('Task', {
|
|
146
|
+
workflow: 'custom',
|
|
147
|
+
customWorkflow
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
expect(customWorkflow).toHaveBeenCalled();
|
|
151
|
+
expect(result.text).toBe('Custom result');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('throws without customWorkflow function', async () => {
|
|
155
|
+
const generator = createMockGenerator({});
|
|
156
|
+
const runner = new WorkflowRunner(generator);
|
|
157
|
+
|
|
158
|
+
await expect(runner.run('Task', { workflow: 'custom' })).rejects.toThrow(
|
|
159
|
+
'Custom workflow requires customWorkflow function'
|
|
160
|
+
);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe('error handling', () => {
|
|
165
|
+
it('throws for unknown workflow', async () => {
|
|
166
|
+
const generator = createMockGenerator({});
|
|
167
|
+
const runner = new WorkflowRunner(generator);
|
|
168
|
+
|
|
169
|
+
await expect(runner.run('Task', { workflow: 'invalid' as WorkflowType })).rejects.toThrow(
|
|
170
|
+
'Unknown workflow type'
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
describe('createEmptyMultiAgentResult', () => {
|
|
177
|
+
it('creates empty result with error', () => {
|
|
178
|
+
const result = createEmptyMultiAgentResult('planner-executor', 'Something failed');
|
|
179
|
+
|
|
180
|
+
expect(result.text).toBe('');
|
|
181
|
+
expect(result.intermediateSteps).toHaveLength(0);
|
|
182
|
+
expect(result.metadata.success).toBe(false);
|
|
183
|
+
expect(result.metadata.error).toBe('Something failed');
|
|
184
|
+
expect(result.metadata.workflow).toBe('planner-executor');
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
describe('DEFAULT_SYSTEM_PROMPTS', () => {
|
|
189
|
+
it('has all required prompts', () => {
|
|
190
|
+
expect(DEFAULT_SYSTEM_PROMPTS.planner).toBeDefined();
|
|
191
|
+
expect(DEFAULT_SYSTEM_PROMPTS.executor).toBeDefined();
|
|
192
|
+
expect(DEFAULT_SYSTEM_PROMPTS.proposer).toBeDefined();
|
|
193
|
+
expect(DEFAULT_SYSTEM_PROMPTS.critic).toBeDefined();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('prompts are non-empty strings', () => {
|
|
197
|
+
expect(typeof DEFAULT_SYSTEM_PROMPTS.planner).toBe('string');
|
|
198
|
+
expect(DEFAULT_SYSTEM_PROMPTS.planner.length).toBeGreaterThan(50);
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
});
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { ProjectScaffolder, createEmptyScaffoldResult } from '../project-tools/scaffolder';
|
|
3
|
+
|
|
4
|
+
describe('Project Tools', () => {
|
|
5
|
+
describe('ProjectScaffolder', () => {
|
|
6
|
+
const createMockGenerator = (response: string) => vi.fn().mockResolvedValue({ text: response });
|
|
7
|
+
|
|
8
|
+
it('scaffolds a project from JSON response', async () => {
|
|
9
|
+
const jsonResponse = `
|
|
10
|
+
Here's your project:
|
|
11
|
+
|
|
12
|
+
\`\`\`json
|
|
13
|
+
{
|
|
14
|
+
"projectName": "my-api",
|
|
15
|
+
"projectType": "express",
|
|
16
|
+
"technologies": ["Express.js", "TypeScript"],
|
|
17
|
+
"files": [
|
|
18
|
+
{
|
|
19
|
+
"path": "package.json",
|
|
20
|
+
"content": "{\\"name\\": \\"my-api\\"}",
|
|
21
|
+
"description": "Package manifest"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"path": "src/index.ts",
|
|
25
|
+
"content": "import express from 'express';",
|
|
26
|
+
"description": "Entry point"
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"setupCommands": ["npm install", "npm run dev"],
|
|
30
|
+
"estimatedSetupTime": "5 minutes"
|
|
31
|
+
}
|
|
32
|
+
\`\`\`
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
const scaffolder = new ProjectScaffolder({
|
|
36
|
+
generateText: createMockGenerator(jsonResponse)
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const result = await scaffolder.scaffold('A REST API for todos', {
|
|
40
|
+
type: 'express'
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
expect(result.files).toHaveLength(2);
|
|
44
|
+
expect(result.files[0].path).toBe('package.json');
|
|
45
|
+
expect(result.metadata.projectName).toBe('my-api');
|
|
46
|
+
expect(result.metadata.projectType).toBe('express');
|
|
47
|
+
expect(result.setupCommands).toContain('npm install');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('extracts files from code blocks when JSON parsing fails', async () => {
|
|
51
|
+
const codeBlockResponse = `
|
|
52
|
+
Here are your files:
|
|
53
|
+
|
|
54
|
+
\`\`\`package.json
|
|
55
|
+
{
|
|
56
|
+
"name": "test-project"
|
|
57
|
+
}
|
|
58
|
+
\`\`\`
|
|
59
|
+
|
|
60
|
+
\`\`\`src/index.ts
|
|
61
|
+
console.log("Hello");
|
|
62
|
+
\`\`\`
|
|
63
|
+
`;
|
|
64
|
+
|
|
65
|
+
const scaffolder = new ProjectScaffolder({
|
|
66
|
+
generateText: createMockGenerator(codeBlockResponse)
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const result = await scaffolder.scaffold('A simple project', {
|
|
70
|
+
type: 'node'
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
expect(result.files.length).toBeGreaterThan(0);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('includes configuration options in prompt', async () => {
|
|
77
|
+
let capturedPrompt = '';
|
|
78
|
+
const scaffolder = new ProjectScaffolder({
|
|
79
|
+
generateText: vi.fn().mockImplementation(async (prompt: string) => {
|
|
80
|
+
capturedPrompt = prompt;
|
|
81
|
+
return { text: '{"files": [], "projectName": "test"}' };
|
|
82
|
+
})
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
await scaffolder.scaffold('A project', {
|
|
86
|
+
type: 'nextjs',
|
|
87
|
+
includeTests: true,
|
|
88
|
+
includeCI: true,
|
|
89
|
+
includeDocker: true,
|
|
90
|
+
includeDocs: true,
|
|
91
|
+
includeLinting: true,
|
|
92
|
+
useTypeScript: true
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
expect(capturedPrompt).toContain('nextjs');
|
|
96
|
+
expect(capturedPrompt).toContain('test files');
|
|
97
|
+
expect(capturedPrompt).toContain('GitHub Actions');
|
|
98
|
+
expect(capturedPrompt).toContain('Dockerfile');
|
|
99
|
+
expect(capturedPrompt).toContain('README');
|
|
100
|
+
expect(capturedPrompt).toContain('ESLint');
|
|
101
|
+
expect(capturedPrompt).toContain('TypeScript');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('generates instructions', async () => {
|
|
105
|
+
const scaffolder = new ProjectScaffolder({
|
|
106
|
+
generateText: createMockGenerator(`{
|
|
107
|
+
"projectName": "my-project",
|
|
108
|
+
"projectType": "node",
|
|
109
|
+
"files": [{"path": "index.js", "content": "// code"}],
|
|
110
|
+
"setupCommands": ["npm install"],
|
|
111
|
+
"estimatedSetupTime": "2 minutes"
|
|
112
|
+
}`)
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const result = await scaffolder.scaffold('A project', {});
|
|
116
|
+
|
|
117
|
+
expect(result.instructions).toContain('Quick Start');
|
|
118
|
+
expect(result.instructions).toContain('my-project');
|
|
119
|
+
expect(result.instructions).toContain('npm install');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('handles custom template', async () => {
|
|
123
|
+
let capturedPrompt = '';
|
|
124
|
+
const scaffolder = new ProjectScaffolder({
|
|
125
|
+
generateText: vi.fn().mockImplementation(async (prompt: string) => {
|
|
126
|
+
capturedPrompt = prompt;
|
|
127
|
+
return { text: '{"files": []}' };
|
|
128
|
+
})
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
await scaffolder.scaffold('A project', {
|
|
132
|
+
customTemplate: 'Must use Redux and React Query'
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
expect(capturedPrompt).toContain('Redux');
|
|
136
|
+
expect(capturedPrompt).toContain('React Query');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('detects project type from files', async () => {
|
|
140
|
+
const scaffolder = new ProjectScaffolder({
|
|
141
|
+
generateText: createMockGenerator(`{
|
|
142
|
+
"files": [
|
|
143
|
+
{"path": "package.json", "content": "{\\"dependencies\\": {\\"react\\": \\"^18.0.0\\"}}"},
|
|
144
|
+
{"path": "src/App.tsx", "content": "export const App = () => <div>Hello</div>"}
|
|
145
|
+
]
|
|
146
|
+
}`)
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const result = await scaffolder.scaffold('A project', { type: 'auto' });
|
|
150
|
+
|
|
151
|
+
expect(result.metadata.projectType).toBe('react');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('returns empty files for instructions-only format', async () => {
|
|
155
|
+
const scaffolder = new ProjectScaffolder({
|
|
156
|
+
generateText: createMockGenerator(`{
|
|
157
|
+
"projectName": "test",
|
|
158
|
+
"files": [{"path": "index.js", "content": "code"}]
|
|
159
|
+
}`)
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const result = await scaffolder.scaffold('A project', {
|
|
163
|
+
outputFormat: 'instructions'
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
expect(result.files).toHaveLength(0);
|
|
167
|
+
expect(result.instructions).toBeTruthy();
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe('createEmptyScaffoldResult', () => {
|
|
172
|
+
it('creates empty result with error message', () => {
|
|
173
|
+
const result = createEmptyScaffoldResult('Failed to generate project');
|
|
174
|
+
|
|
175
|
+
expect(result.files).toHaveLength(0);
|
|
176
|
+
expect(result.instructions).toContain('Failed to generate project');
|
|
177
|
+
expect(result.setupCommands).toHaveLength(0);
|
|
178
|
+
expect(result.metadata.fileCount).toBe(0);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
});
|
|
@@ -462,7 +462,7 @@ describe('prepareKimiTools', () => {
|
|
|
462
462
|
details: 'Using tool choice polyfill with system message injection.'
|
|
463
463
|
});
|
|
464
464
|
expect(result.toolChoiceSystemMessage).toBeDefined();
|
|
465
|
-
expect(result.toolChoiceSystemMessage).toContain('MUST use one of the available tools');
|
|
465
|
+
expect(result.toolChoiceSystemMessage).toContain('MUST use at least one of the available tools');
|
|
466
466
|
});
|
|
467
467
|
|
|
468
468
|
it('should warn and fallback to auto for required tool choice without polyfill', () => {
|
|
@@ -116,6 +116,15 @@ export interface KimiChatSettings {
|
|
|
116
116
|
* Reduces costs by up to 90% for repeated long prompts.
|
|
117
117
|
*/
|
|
118
118
|
caching?: boolean | KimiCachingConfig;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Auto-enable tools based on prompt content analysis.
|
|
122
|
+
* When true, the provider will analyze the prompt and automatically
|
|
123
|
+
* enable webSearch or codeInterpreter if patterns match.
|
|
124
|
+
*
|
|
125
|
+
* @default false
|
|
126
|
+
*/
|
|
127
|
+
autoEnableTools?: boolean;
|
|
119
128
|
}
|
|
120
129
|
|
|
121
130
|
// ============================================================================
|
|
@@ -207,7 +216,12 @@ export const kimiProviderOptionsSchema = z.object({
|
|
|
207
216
|
/**
|
|
208
217
|
* Enable tool choice polyfill for this request.
|
|
209
218
|
*/
|
|
210
|
-
toolChoicePolyfill: z.boolean().optional()
|
|
219
|
+
toolChoicePolyfill: z.boolean().optional(),
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Auto-enable tools based on prompt content analysis.
|
|
223
|
+
*/
|
|
224
|
+
autoEnableTools: z.boolean().optional()
|
|
211
225
|
});
|
|
212
226
|
|
|
213
227
|
/**
|