@minko-ai/minko 0.1.0 → 0.1.1
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 +577 -2
- package/assets/mascot-preview.png +0 -0
- package/assets/mascot-preview.txt +46 -0
- package/assets/mascot-source.png +0 -0
- package/assets/skills/README.md +21 -0
- package/assets/skills/brainstorming/LICENSE +21 -0
- package/assets/skills/brainstorming/SKILL.md +316 -0
- package/assets/skills/buddhist-method/LICENSE +21 -0
- package/assets/skills/buddhist-method/SKILL.md +137 -0
- package/assets/skills/buddhist-method/references/ariyasacca-debug.md +57 -0
- package/assets/skills/buddhist-method/references/extended-principles.md +100 -0
- package/assets/skills/code-humanizer/LICENSE +21 -0
- package/assets/skills/code-humanizer/SKILL.md +174 -0
- package/assets/skills/code-review/LICENSE +21 -0
- package/assets/skills/code-review/SKILL.md +232 -0
- package/assets/skills/code-review/reference/angular.md +768 -0
- package/assets/skills/code-review/reference/architecture-review-guide.md +472 -0
- package/assets/skills/code-review/reference/c.md +890 -0
- package/assets/skills/code-review/reference/code-quality-universal.md +488 -0
- package/assets/skills/code-review/reference/code-review-best-practices.md +136 -0
- package/assets/skills/code-review/reference/common-bugs-checklist.md +302 -0
- package/assets/skills/code-review/reference/cpp.md +893 -0
- package/assets/skills/code-review/reference/cross-cutting/async-concurrency-patterns.md +515 -0
- package/assets/skills/code-review/reference/cross-cutting/error-handling-principles.md +492 -0
- package/assets/skills/code-review/reference/cross-cutting/n-plus-one-queries.md +309 -0
- package/assets/skills/code-review/reference/cross-cutting/sql-injection-prevention.md +308 -0
- package/assets/skills/code-review/reference/cross-cutting/xss-prevention.md +264 -0
- package/assets/skills/code-review/reference/csharp.md +525 -0
- package/assets/skills/code-review/reference/css-less-sass.md +661 -0
- package/assets/skills/code-review/reference/dart.md +670 -0
- package/assets/skills/code-review/reference/django.md +985 -0
- package/assets/skills/code-review/reference/fastapi.md +580 -0
- package/assets/skills/code-review/reference/go.md +993 -0
- package/assets/skills/code-review/reference/java.md +409 -0
- package/assets/skills/code-review/reference/java8.md +586 -0
- package/assets/skills/code-review/reference/kotlin.md +1018 -0
- package/assets/skills/code-review/reference/nestjs.md +593 -0
- package/assets/skills/code-review/reference/performance-review-guide.md +816 -0
- package/assets/skills/code-review/reference/php.md +684 -0
- package/assets/skills/code-review/reference/python.md +1073 -0
- package/assets/skills/code-review/reference/qt.md +757 -0
- package/assets/skills/code-review/reference/react.md +871 -0
- package/assets/skills/code-review/reference/ruby.md +964 -0
- package/assets/skills/code-review/reference/rust.md +846 -0
- package/assets/skills/code-review/reference/security-review-guide.md +494 -0
- package/assets/skills/code-review/reference/svelte.md +1064 -0
- package/assets/skills/code-review/reference/swift.md +936 -0
- package/assets/skills/code-review/reference/typescript.md +1016 -0
- package/assets/skills/code-review/reference/vue.md +924 -0
- package/assets/skills/code-review/reference/zig.md +440 -0
- package/assets/skills/cyclomatic-complexity/LICENSE +201 -0
- package/assets/skills/cyclomatic-complexity/SKILL.md +69 -0
- package/assets/skills/five-whys/LICENSE +21 -0
- package/assets/skills/five-whys/SKILL.md +247 -0
- package/assets/skills/five-whys/examples/performance-regression.md +161 -0
- package/assets/skills/five-whys/examples/production-incident.md +134 -0
- package/assets/skills/five-whys/references/software-patterns.md +284 -0
- package/assets/skills/five-whys/references/toyota-origins.md +257 -0
- package/assets/skills/systematic-debugging/LICENSE +21 -0
- package/assets/skills/systematic-debugging/SKILL.md +283 -0
- package/assets/skills/test-driven-development/LICENSE +21 -0
- package/assets/skills/test-driven-development/SKILL.md +330 -0
- package/assets/skills/verification-before-completion/LICENSE +21 -0
- package/assets/skills/verification-before-completion/SKILL.md +120 -0
- package/assets/skills/vibe-sec/LICENSE +201 -0
- package/assets/skills/vibe-sec/SKILL.md +758 -0
- package/assets/skills/writing-plans/LICENSE +21 -0
- package/assets/skills/writing-plans/SKILL.md +204 -0
- package/bin/minko.js +2 -2
- package/dist/agent/approvalTokens.js +224 -0
- package/dist/agent/branchLock.js +315 -0
- package/dist/agent/compact.js +182 -0
- package/dist/agent/context.js +183 -0
- package/dist/agent/cron.js +326 -0
- package/dist/agent/hooks.js +211 -0
- package/dist/agent/lanes.js +511 -0
- package/dist/agent/loop.js +337 -0
- package/dist/agent/memory.js +159 -0
- package/dist/agent/permissions.js +71 -0
- package/dist/agent/plan.js +118 -0
- package/dist/agent/prompt.js +120 -0
- package/dist/agent/subagent.js +67 -0
- package/dist/agent/tasks.js +449 -0
- package/dist/agent/transcript.js +62 -0
- package/dist/agent/workerStore.js +172 -0
- package/dist/agent/workerTypes.js +1 -0
- package/dist/agent/workers.js +503 -0
- package/dist/cli/app.js +261 -0
- package/dist/cli/argv.js +206 -0
- package/dist/cli/commandsCommand.js +12 -0
- package/dist/cli/configCommand.js +170 -0
- package/dist/cli/doctorCommand.js +112 -0
- package/dist/cli/exportCommand.js +85 -0
- package/dist/cli/forkCommand.js +24 -0
- package/dist/cli/linkCommand.js +272 -0
- package/dist/cli/loginCommand.js +69 -0
- package/dist/cli/mcpLoginCommand.js +68 -0
- package/dist/cli/pluginCommand.js +113 -0
- package/dist/cli/powerCommand.js +174 -0
- package/dist/cli/promptFooter.js +74 -0
- package/dist/cli/relayCommand.js +38 -0
- package/dist/cli/sandboxCommand.js +31 -0
- package/dist/cli/serveCommand.js +104 -0
- package/dist/cli/statusCommand.js +120 -0
- package/dist/cli/tui.js +1104 -0
- package/dist/commands/registry.js +99 -0
- package/dist/config/paths.js +81 -0
- package/dist/config/store.js +605 -0
- package/dist/index.js +199 -0
- package/dist/link/protocol.js +159 -0
- package/dist/link/relay.js +184 -0
- package/dist/link/server.js +566 -0
- package/dist/link/tunnel.js +90 -0
- package/dist/lsp/client.js +340 -0
- package/dist/lsp/tool.js +177 -0
- package/dist/mcp/client.js +469 -0
- package/dist/mcp/httpClient.js +191 -0
- package/dist/mcp/server.js +206 -0
- package/dist/mcp/tools.js +173 -0
- package/dist/oauth/device.js +111 -0
- package/dist/oauth/store.js +56 -0
- package/dist/plugins/loader.js +291 -0
- package/dist/plugins/market.js +98 -0
- package/dist/providers/anthropic.js +279 -0
- package/dist/providers/factory.js +109 -0
- package/dist/providers/openai.js +182 -0
- package/dist/providers/retry.js +95 -0
- package/dist/providers/sse.js +67 -0
- package/dist/providers/types.js +10 -0
- package/dist/rag/embeddings.js +171 -0
- package/dist/rag/index.js +373 -0
- package/dist/rag/server.js +167 -0
- package/dist/rag/tool.js +148 -0
- package/dist/sandbox/index.js +55 -0
- package/dist/sdk/server.js +150 -0
- package/dist/sdk/session.js +104 -0
- package/dist/session/liveness.js +148 -0
- package/dist/session/recorder.js +49 -0
- package/dist/session/store.js +171 -0
- package/dist/skills/index.js +428 -0
- package/dist/theme.js +30 -0
- package/dist/tools/fs.js +195 -0
- package/dist/tools/notebook.js +109 -0
- package/dist/tools/registry.js +32 -0
- package/dist/tools/search.js +130 -0
- package/dist/tools/shell.js +200 -0
- package/dist/tools/structured.js +83 -0
- package/dist/tools/types.js +1 -0
- package/dist/ui/ansi.js +135 -0
- package/dist/ui/banner.js +103 -0
- package/dist/ui/input.js +49 -0
- package/dist/ui/inputModel.js +113 -0
- package/dist/ui/keyboard.js +142 -0
- package/dist/ui/mascot.js +34 -0
- package/dist/ui/mascotData.js +96 -0
- package/dist/ui/md.js +183 -0
- package/dist/ui/screen.js +49 -0
- package/dist/ui/spinner.js +52 -0
- package/dist/ui/transcript.js +287 -0
- package/dist/util/clipboard.js +53 -0
- package/dist/util/errors.js +50 -0
- package/dist/util/version.js +12 -0
- package/dist/web/fetch.js +168 -0
- package/dist/web/search.js +97 -0
- package/package.json +45 -36
- package/src/agent/loop.ts +0 -159
- package/src/agent/prompt.ts +0 -45
- package/src/cli/app.ts +0 -396
- package/src/cli/argv.ts +0 -139
- package/src/cli/configCommand.ts +0 -175
- package/src/config/paths.ts +0 -20
- package/src/config/store.ts +0 -149
- package/src/index.ts +0 -120
- package/src/providers/anthropic.ts +0 -215
- package/src/providers/openai.ts +0 -158
- package/src/providers/sse.ts +0 -57
- package/src/providers/types.ts +0 -59
- package/src/session/store.ts +0 -125
- package/src/theme.ts +0 -41
- package/src/tools/fs.ts +0 -143
- package/src/tools/registry.ts +0 -19
- package/src/tools/shell.ts +0 -120
- package/src/tools/types.ts +0 -26
- package/src/ui/ansi.ts +0 -110
- package/src/ui/banner.ts +0 -39
- package/src/ui/input.ts +0 -54
- package/src/ui/md.ts +0 -160
- package/src/ui/spinner.ts +0 -58
- package/src/util/errors.ts +0 -48
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
import type { ProviderConfig } from '../config/store.ts';
|
|
2
|
-
import { HttpError } from '../util/errors.ts';
|
|
3
|
-
import type { ChatMessage, Provider, StreamHandlers, ToolCall, ToolSpec, TurnResult, Usage } from './types.ts';
|
|
4
|
-
import { sseEvents } from './sse.ts';
|
|
5
|
-
|
|
6
|
-
type Block =
|
|
7
|
-
| { type: 'text'; text: string }
|
|
8
|
-
| { type: 'tool_use'; id: string; name: string; input: unknown }
|
|
9
|
-
| { type: 'tool_result'; tool_use_id: string; content: string };
|
|
10
|
-
|
|
11
|
-
type AnthropicMessage = { role: 'user' | 'assistant'; content: Block[] };
|
|
12
|
-
|
|
13
|
-
export function toAnthropicMessages(messages: ChatMessage[]): {
|
|
14
|
-
system: string;
|
|
15
|
-
messages: AnthropicMessage[];
|
|
16
|
-
} {
|
|
17
|
-
const systemParts: string[] = [];
|
|
18
|
-
const out: AnthropicMessage[] = [];
|
|
19
|
-
const push = (role: 'user' | 'assistant', blocks: Block[]): void => {
|
|
20
|
-
if (blocks.length === 0) { return; }
|
|
21
|
-
const last = out.length > 0 ? out[out.length - 1] : undefined;
|
|
22
|
-
if (last && last.role === role) {
|
|
23
|
-
last.content.push(...blocks);
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
out.push({ role, content: blocks });
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
for (const message of messages) {
|
|
30
|
-
if (message.role === 'system') {
|
|
31
|
-
systemParts.push(message.content);
|
|
32
|
-
continue;
|
|
33
|
-
}
|
|
34
|
-
if (message.role === 'tool') {
|
|
35
|
-
push('user', [{ type: 'tool_result', tool_use_id: message.toolCallId, content: message.content }]);
|
|
36
|
-
continue;
|
|
37
|
-
}
|
|
38
|
-
if (message.role === 'assistant') {
|
|
39
|
-
const blocks: Block[] = [];
|
|
40
|
-
if (message.content.length > 0) { blocks.push({ type: 'text', text: message.content }); }
|
|
41
|
-
for (const call of message.toolCalls) {
|
|
42
|
-
blocks.push({ type: 'tool_use', id: call.id, name: call.name, input: safeParse(call.arguments) });
|
|
43
|
-
}
|
|
44
|
-
push('assistant', blocks);
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
push('user', [{ type: 'text', text: message.content }]);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return { system: systemParts.join('\n\n'), messages: out };
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function safeParse(raw: string): unknown {
|
|
54
|
-
if (raw.trim().length === 0) { return {}; }
|
|
55
|
-
try {
|
|
56
|
-
return JSON.parse(raw);
|
|
57
|
-
} catch {
|
|
58
|
-
return {};
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export class AnthropicProvider implements Provider {
|
|
63
|
-
config: ProviderConfig;
|
|
64
|
-
|
|
65
|
-
constructor(config: ProviderConfig) {
|
|
66
|
-
this.config = config;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async stream(
|
|
70
|
-
messages: ChatMessage[],
|
|
71
|
-
tools: ToolSpec[],
|
|
72
|
-
handlers: StreamHandlers,
|
|
73
|
-
signal: AbortSignal,
|
|
74
|
-
): Promise<TurnResult> {
|
|
75
|
-
const converted = toAnthropicMessages(messages);
|
|
76
|
-
const body: Record<string, unknown> = {
|
|
77
|
-
model: this.config.model,
|
|
78
|
-
max_tokens: this.config.maxTokens && this.config.maxTokens > 0 ? this.config.maxTokens : 8192,
|
|
79
|
-
system: converted.system,
|
|
80
|
-
messages: converted.messages,
|
|
81
|
-
stream: true,
|
|
82
|
-
};
|
|
83
|
-
if (tools.length > 0) {
|
|
84
|
-
body.tools = tools.map((tool) => ({
|
|
85
|
-
name: tool.name,
|
|
86
|
-
description: tool.description,
|
|
87
|
-
input_schema: tool.parameters,
|
|
88
|
-
}));
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const response = await fetch(messagesUrl(this.config.baseUrl), {
|
|
92
|
-
method: 'POST',
|
|
93
|
-
headers: {
|
|
94
|
-
'content-type': 'application/json',
|
|
95
|
-
'x-api-key': this.config.apiKey,
|
|
96
|
-
'anthropic-version': '2023-06-01',
|
|
97
|
-
},
|
|
98
|
-
body: JSON.stringify(body),
|
|
99
|
-
signal,
|
|
100
|
-
});
|
|
101
|
-
if (!response.ok) {
|
|
102
|
-
throw new HttpError(response.status, await response.text());
|
|
103
|
-
}
|
|
104
|
-
if (!response.body) {
|
|
105
|
-
throw new Error('服务端没有返回可读的流');
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const textParts: string[] = [];
|
|
109
|
-
const reasoningParts: string[] = [];
|
|
110
|
-
const toolCalls: ToolCall[] = [];
|
|
111
|
-
let usage: Usage = { input: 0, output: 0 };
|
|
112
|
-
let current: { kind: 'text' | 'thinking' | 'tool'; id: string; name: string; json: string } | null = null;
|
|
113
|
-
|
|
114
|
-
for await (const event of sseEvents(response.body)) {
|
|
115
|
-
let payload: Record<string, unknown>;
|
|
116
|
-
try {
|
|
117
|
-
payload = JSON.parse(event.data) as Record<string, unknown>;
|
|
118
|
-
} catch {
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
switch (event.event) {
|
|
122
|
-
case 'message_start': {
|
|
123
|
-
const message = payload.message as Record<string, unknown> | undefined;
|
|
124
|
-
const rawUsage = message?.usage as Record<string, unknown> | undefined;
|
|
125
|
-
if (rawUsage) {
|
|
126
|
-
usage = { input: numberOrZero(rawUsage.input_tokens), output: numberOrZero(rawUsage.output_tokens) };
|
|
127
|
-
handlers.onUsage?.(usage);
|
|
128
|
-
}
|
|
129
|
-
break;
|
|
130
|
-
}
|
|
131
|
-
case 'content_block_start': {
|
|
132
|
-
const block = payload.content_block as Record<string, unknown> | undefined;
|
|
133
|
-
const index = typeof payload.index === 'number' ? payload.index : -1;
|
|
134
|
-
if (!block) { break; }
|
|
135
|
-
if (block.type === 'tool_use') {
|
|
136
|
-
current = {
|
|
137
|
-
kind: 'tool',
|
|
138
|
-
id: typeof block.id === 'string' ? block.id : 'tool_' + index,
|
|
139
|
-
name: typeof block.name === 'string' ? block.name : '',
|
|
140
|
-
json: '',
|
|
141
|
-
};
|
|
142
|
-
} else if (block.type === 'thinking') {
|
|
143
|
-
current = { kind: 'thinking', id: '', name: '', json: '' };
|
|
144
|
-
} else {
|
|
145
|
-
current = { kind: 'text', id: '', name: '', json: '' };
|
|
146
|
-
const initial = block.text;
|
|
147
|
-
if (typeof initial === 'string' && initial.length > 0) {
|
|
148
|
-
textParts.push(initial);
|
|
149
|
-
handlers.onText?.(initial);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
break;
|
|
153
|
-
}
|
|
154
|
-
case 'content_block_delta': {
|
|
155
|
-
const delta = payload.delta as Record<string, unknown> | undefined;
|
|
156
|
-
if (!delta) { break; }
|
|
157
|
-
if (delta.type === 'text_delta' && typeof delta.text === 'string') {
|
|
158
|
-
textParts.push(delta.text);
|
|
159
|
-
handlers.onText?.(delta.text);
|
|
160
|
-
} else if (delta.type === 'thinking_delta' && typeof delta.thinking === 'string') {
|
|
161
|
-
reasoningParts.push(delta.thinking);
|
|
162
|
-
handlers.onReasoning?.(delta.thinking);
|
|
163
|
-
} else if (delta.type === 'input_json_delta' && typeof delta.partial_json === 'string' && current) {
|
|
164
|
-
current.json += delta.partial_json;
|
|
165
|
-
}
|
|
166
|
-
break;
|
|
167
|
-
}
|
|
168
|
-
case 'content_block_stop': {
|
|
169
|
-
if (current && current.kind === 'tool') {
|
|
170
|
-
const call: ToolCall = {
|
|
171
|
-
id: current.id,
|
|
172
|
-
name: current.name,
|
|
173
|
-
arguments: current.json.trim().length > 0 ? current.json : '{}',
|
|
174
|
-
};
|
|
175
|
-
toolCalls.push(call);
|
|
176
|
-
handlers.onToolCall?.(call);
|
|
177
|
-
}
|
|
178
|
-
current = null;
|
|
179
|
-
break;
|
|
180
|
-
}
|
|
181
|
-
case 'message_delta': {
|
|
182
|
-
const rawUsage = payload.usage as Record<string, unknown> | undefined;
|
|
183
|
-
if (rawUsage && typeof rawUsage.output_tokens === 'number') {
|
|
184
|
-
usage = { input: usage.input, output: rawUsage.output_tokens };
|
|
185
|
-
handlers.onUsage?.(usage);
|
|
186
|
-
}
|
|
187
|
-
break;
|
|
188
|
-
}
|
|
189
|
-
default:
|
|
190
|
-
break;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return {
|
|
195
|
-
text: textParts.join(''),
|
|
196
|
-
reasoning: reasoningParts.join(''),
|
|
197
|
-
toolCalls,
|
|
198
|
-
usage,
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function messagesUrl(baseUrl: string): string {
|
|
204
|
-
const base = baseUrl.replace(/\/+$/, '');
|
|
205
|
-
if (/\/v1$/i.test(base)) { return base + '/messages'; }
|
|
206
|
-
return base + '/v1/messages';
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function numberOrZero(value: unknown): number {
|
|
210
|
-
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
export function createAnthropicProvider(config: ProviderConfig): Provider {
|
|
214
|
-
return new AnthropicProvider(config);
|
|
215
|
-
}
|
package/src/providers/openai.ts
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import type { ProviderConfig } from '../config/store.ts';
|
|
2
|
-
import { HttpError } from '../util/errors.ts';
|
|
3
|
-
import type { ChatMessage, Provider, StreamHandlers, ToolCall, ToolSpec, TurnResult, Usage } from './types.ts';
|
|
4
|
-
import { sseEvents } from './sse.ts';
|
|
5
|
-
|
|
6
|
-
type OpenAiToolCallDelta = {
|
|
7
|
-
index?: number;
|
|
8
|
-
id?: string;
|
|
9
|
-
function?: { name?: string; arguments?: string };
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export function toOpenAiMessages(messages: ChatMessage[]): unknown[] {
|
|
13
|
-
return messages.map((message) => {
|
|
14
|
-
if (message.role === 'tool') {
|
|
15
|
-
return { role: 'tool', tool_call_id: message.toolCallId, content: message.content };
|
|
16
|
-
}
|
|
17
|
-
if (message.role === 'assistant') {
|
|
18
|
-
const out: Record<string, unknown> = {
|
|
19
|
-
role: 'assistant',
|
|
20
|
-
content: message.content.length > 0 ? message.content : null,
|
|
21
|
-
};
|
|
22
|
-
if (message.toolCalls.length > 0) {
|
|
23
|
-
out.tool_calls = message.toolCalls.map((call) => ({
|
|
24
|
-
id: call.id,
|
|
25
|
-
type: 'function',
|
|
26
|
-
function: { name: call.name, arguments: call.arguments },
|
|
27
|
-
}));
|
|
28
|
-
}
|
|
29
|
-
return out;
|
|
30
|
-
}
|
|
31
|
-
return { role: message.role, content: message.content };
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export class OpenAiProvider implements Provider {
|
|
36
|
-
config: ProviderConfig;
|
|
37
|
-
|
|
38
|
-
constructor(config: ProviderConfig) {
|
|
39
|
-
this.config = config;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async stream(
|
|
43
|
-
messages: ChatMessage[],
|
|
44
|
-
tools: ToolSpec[],
|
|
45
|
-
handlers: StreamHandlers,
|
|
46
|
-
signal: AbortSignal,
|
|
47
|
-
): Promise<TurnResult> {
|
|
48
|
-
const body: Record<string, unknown> = {
|
|
49
|
-
model: this.config.model,
|
|
50
|
-
messages: toOpenAiMessages(messages),
|
|
51
|
-
stream: true,
|
|
52
|
-
stream_options: { include_usage: true },
|
|
53
|
-
};
|
|
54
|
-
if (this.config.maxTokens && this.config.maxTokens > 0) {
|
|
55
|
-
body.max_tokens = this.config.maxTokens;
|
|
56
|
-
}
|
|
57
|
-
if (tools.length > 0) {
|
|
58
|
-
body.tools = tools.map((tool) => ({
|
|
59
|
-
type: 'function',
|
|
60
|
-
function: { name: tool.name, description: tool.description, parameters: tool.parameters },
|
|
61
|
-
}));
|
|
62
|
-
body.tool_choice = 'auto';
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const response = await fetch(joinUrl(this.config.baseUrl, 'chat/completions'), {
|
|
66
|
-
method: 'POST',
|
|
67
|
-
headers: {
|
|
68
|
-
'content-type': 'application/json',
|
|
69
|
-
authorization: 'Bearer ' + this.config.apiKey,
|
|
70
|
-
},
|
|
71
|
-
body: JSON.stringify(body),
|
|
72
|
-
signal,
|
|
73
|
-
});
|
|
74
|
-
if (!response.ok) {
|
|
75
|
-
throw new HttpError(response.status, await response.text());
|
|
76
|
-
}
|
|
77
|
-
if (!response.body) {
|
|
78
|
-
throw new Error('服务端没有返回可读的流');
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const textParts: string[] = [];
|
|
82
|
-
const reasoningParts: string[] = [];
|
|
83
|
-
const calls = new Map<number, { id: string; name: string; args: string }>();
|
|
84
|
-
let usage: Usage = { input: 0, output: 0 };
|
|
85
|
-
|
|
86
|
-
for await (const event of sseEvents(response.body)) {
|
|
87
|
-
if (event.data === '[DONE]') { break; }
|
|
88
|
-
let payload: Record<string, unknown>;
|
|
89
|
-
try {
|
|
90
|
-
payload = JSON.parse(event.data) as Record<string, unknown>;
|
|
91
|
-
} catch {
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
const rawUsage = payload.usage as Record<string, unknown> | undefined;
|
|
95
|
-
if (rawUsage) {
|
|
96
|
-
usage = {
|
|
97
|
-
input: numberOrZero(rawUsage.prompt_tokens),
|
|
98
|
-
output: numberOrZero(rawUsage.completion_tokens),
|
|
99
|
-
};
|
|
100
|
-
handlers.onUsage?.(usage);
|
|
101
|
-
}
|
|
102
|
-
const choices = payload.choices as Array<Record<string, unknown>> | undefined;
|
|
103
|
-
const choice = choices && choices.length > 0 ? choices[0] : undefined;
|
|
104
|
-
if (!choice) { continue; }
|
|
105
|
-
const delta = (choice.delta ?? {}) as Record<string, unknown>;
|
|
106
|
-
|
|
107
|
-
const content = delta.content;
|
|
108
|
-
if (typeof content === 'string' && content.length > 0) {
|
|
109
|
-
textParts.push(content);
|
|
110
|
-
handlers.onText?.(content);
|
|
111
|
-
}
|
|
112
|
-
const reasoning = delta.reasoning_content;
|
|
113
|
-
if (typeof reasoning === 'string' && reasoning.length > 0) {
|
|
114
|
-
reasoningParts.push(reasoning);
|
|
115
|
-
handlers.onReasoning?.(reasoning);
|
|
116
|
-
}
|
|
117
|
-
const toolDeltas = delta.tool_calls;
|
|
118
|
-
if (Array.isArray(toolDeltas)) {
|
|
119
|
-
for (const raw of toolDeltas as OpenAiToolCallDelta[]) {
|
|
120
|
-
const index = typeof raw.index === 'number' ? raw.index : 0;
|
|
121
|
-
const entry = calls.get(index) ?? { id: '', name: '', args: '' };
|
|
122
|
-
if (typeof raw.id === 'string' && raw.id.length > 0) { entry.id = raw.id; }
|
|
123
|
-
if (raw.function) {
|
|
124
|
-
if (typeof raw.function.name === 'string') { entry.name += raw.function.name; }
|
|
125
|
-
if (typeof raw.function.arguments === 'string') { entry.args += raw.function.arguments; }
|
|
126
|
-
}
|
|
127
|
-
calls.set(index, entry);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const toolCalls: ToolCall[] = [...calls.entries()]
|
|
133
|
-
.sort((a, b) => a[0] - b[0])
|
|
134
|
-
.flatMap(([index, entry]) => entry.name.length === 0
|
|
135
|
-
? []
|
|
136
|
-
: [{ id: entry.id || 'call_' + index, name: entry.name, arguments: entry.args || '{}' }]);
|
|
137
|
-
|
|
138
|
-
return {
|
|
139
|
-
text: textParts.join(''),
|
|
140
|
-
reasoning: reasoningParts.join(''),
|
|
141
|
-
toolCalls,
|
|
142
|
-
usage,
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function numberOrZero(value: unknown): number {
|
|
148
|
-
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
export function joinUrl(baseUrl: string, suffix: string): string {
|
|
152
|
-
const base = baseUrl.replace(/\/+$/, '');
|
|
153
|
-
return base + '/' + suffix.replace(/^\/+/, '');
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
export function createOpenAiProvider(config: ProviderConfig): Provider {
|
|
157
|
-
return new OpenAiProvider(config);
|
|
158
|
-
}
|
package/src/providers/sse.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
export type SseEvent = { event: string; data: string };
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Parses a Server-Sent Events body. Handles CRLF, split chunks and comments.
|
|
5
|
-
*/
|
|
6
|
-
export async function* sseEvents(body: ReadableStream<Uint8Array>): AsyncGenerator<SseEvent> {
|
|
7
|
-
const reader = body.getReader();
|
|
8
|
-
const decoder = new TextDecoder();
|
|
9
|
-
let buffer = '';
|
|
10
|
-
let event = '';
|
|
11
|
-
let data: string[] = [];
|
|
12
|
-
|
|
13
|
-
const flush = (): SseEvent | null => {
|
|
14
|
-
if (data.length === 0) {
|
|
15
|
-
event = '';
|
|
16
|
-
return null;
|
|
17
|
-
}
|
|
18
|
-
const payload: SseEvent = { event: event || 'message', data: data.join('\n') };
|
|
19
|
-
event = '';
|
|
20
|
-
data = [];
|
|
21
|
-
return payload;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
for (;;) {
|
|
26
|
-
const { done, value } = await reader.read();
|
|
27
|
-
if (done) { break; }
|
|
28
|
-
buffer += decoder.decode(value, { stream: true });
|
|
29
|
-
let index = buffer.indexOf('\n');
|
|
30
|
-
while (index >= 0) {
|
|
31
|
-
let line = buffer.slice(0, index);
|
|
32
|
-
buffer = buffer.slice(index + 1);
|
|
33
|
-
if (line.endsWith('\r')) { line = line.slice(0, -1); }
|
|
34
|
-
if (line.length === 0) {
|
|
35
|
-
const parsed = flush();
|
|
36
|
-
if (parsed) { yield parsed; }
|
|
37
|
-
} else if (line.startsWith(':')) {
|
|
38
|
-
// heartbeat / comment
|
|
39
|
-
} else if (line.startsWith('data:')) {
|
|
40
|
-
data.push(line.slice(5).replace(/^ /, ''));
|
|
41
|
-
} else if (line.startsWith('event:')) {
|
|
42
|
-
event = line.slice(6).trim();
|
|
43
|
-
}
|
|
44
|
-
index = buffer.indexOf('\n');
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
buffer += decoder.decode();
|
|
48
|
-
if (buffer.trim().length > 0) {
|
|
49
|
-
const line = buffer.replace(/\r$/, '');
|
|
50
|
-
if (line.startsWith('data:')) { data.push(line.slice(5).replace(/^ /, '')); }
|
|
51
|
-
}
|
|
52
|
-
const tail = flush();
|
|
53
|
-
if (tail) { yield tail; }
|
|
54
|
-
} finally {
|
|
55
|
-
reader.releaseLock();
|
|
56
|
-
}
|
|
57
|
-
}
|
package/src/providers/types.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
export type ToolCall = {
|
|
2
|
-
id: string;
|
|
3
|
-
name: string;
|
|
4
|
-
/** Raw JSON string exactly as the model produced it. */
|
|
5
|
-
arguments: string;
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
export type ChatMessage =
|
|
9
|
-
| { role: 'system'; content: string }
|
|
10
|
-
| { role: 'user'; content: string }
|
|
11
|
-
| { role: 'assistant'; content: string; toolCalls: ToolCall[] }
|
|
12
|
-
| { role: 'tool'; toolCallId: string; name: string; content: string };
|
|
13
|
-
|
|
14
|
-
export type JsonSchema = {
|
|
15
|
-
type: 'object';
|
|
16
|
-
properties: Record<string, unknown>;
|
|
17
|
-
required?: string[];
|
|
18
|
-
additionalProperties?: boolean;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export type ToolSpec = {
|
|
22
|
-
name: string;
|
|
23
|
-
description: string;
|
|
24
|
-
parameters: JsonSchema;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export type Usage = { input: number; output: number };
|
|
28
|
-
|
|
29
|
-
export type StreamHandlers = {
|
|
30
|
-
onText?: (text: string) => void;
|
|
31
|
-
onReasoning?: (text: string) => void;
|
|
32
|
-
onToolCall?: (call: ToolCall) => void;
|
|
33
|
-
onUsage?: (usage: Usage) => void;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export type TurnResult = {
|
|
37
|
-
text: string;
|
|
38
|
-
reasoning: string;
|
|
39
|
-
toolCalls: ToolCall[];
|
|
40
|
-
usage: Usage;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
export interface Provider {
|
|
44
|
-
stream(
|
|
45
|
-
messages: ChatMessage[],
|
|
46
|
-
tools: ToolSpec[],
|
|
47
|
-
handlers: StreamHandlers,
|
|
48
|
-
signal: AbortSignal,
|
|
49
|
-
): Promise<TurnResult>;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function parseToolArguments(raw: string): Record<string, unknown> {
|
|
53
|
-
if (raw.trim().length === 0) { return {}; }
|
|
54
|
-
const parsed: unknown = JSON.parse(raw);
|
|
55
|
-
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
56
|
-
throw new Error('工具参数必须是 JSON 对象');
|
|
57
|
-
}
|
|
58
|
-
return parsed as Record<string, unknown>;
|
|
59
|
-
}
|
package/src/session/store.ts
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { sessionsDir } from '../config/paths.ts';
|
|
4
|
-
import type { ChatMessage } from '../providers/types.ts';
|
|
5
|
-
|
|
6
|
-
export type SessionMeta = {
|
|
7
|
-
id: string;
|
|
8
|
-
title: string;
|
|
9
|
-
cwd: string;
|
|
10
|
-
model: string;
|
|
11
|
-
createdAt: string;
|
|
12
|
-
updatedAt: string;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export type SessionRecord = {
|
|
16
|
-
meta: SessionMeta;
|
|
17
|
-
messages: ChatMessage[];
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
/** Sessions live in ~/.minko/sessions/<id>.jsonl: one meta line, then messages. */
|
|
21
|
-
export class SessionStore {
|
|
22
|
-
dir: string;
|
|
23
|
-
|
|
24
|
-
constructor(dir: string = sessionsDir()) {
|
|
25
|
-
this.dir = dir;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
fileFor(id: string): string {
|
|
29
|
-
if (!/^[A-Za-z0-9._-]+$/.test(id)) { throw new Error('非法的会话 id'); }
|
|
30
|
-
return path.join(this.dir, id + '.jsonl');
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
create(cwd: string, model: string, id?: string): SessionMeta {
|
|
34
|
-
const now = new Date().toISOString();
|
|
35
|
-
const meta: SessionMeta = {
|
|
36
|
-
id: id ?? newSessionId(),
|
|
37
|
-
title: '新会话',
|
|
38
|
-
cwd,
|
|
39
|
-
model,
|
|
40
|
-
createdAt: now,
|
|
41
|
-
updatedAt: now,
|
|
42
|
-
};
|
|
43
|
-
this.write(meta, []);
|
|
44
|
-
return meta;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
save(meta: SessionMeta, messages: ChatMessage[]): void {
|
|
48
|
-
this.write({ ...meta, updatedAt: new Date().toISOString() }, messages);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
load(id: string): SessionRecord | null {
|
|
52
|
-
const file = this.fileFor(id);
|
|
53
|
-
if (!fs.existsSync(file)) { return null; }
|
|
54
|
-
const lines = fs.readFileSync(file, 'utf8').split('\n');
|
|
55
|
-
let meta: SessionMeta | null = null;
|
|
56
|
-
const messages: ChatMessage[] = [];
|
|
57
|
-
for (const line of lines) {
|
|
58
|
-
if (line.trim().length === 0) { continue; }
|
|
59
|
-
let record: Record<string, unknown>;
|
|
60
|
-
try {
|
|
61
|
-
record = JSON.parse(line) as Record<string, unknown>;
|
|
62
|
-
} catch {
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
if (record.type === 'meta' && record.meta) {
|
|
66
|
-
meta = record.meta as SessionMeta;
|
|
67
|
-
} else if (record.type === 'message' && record.message) {
|
|
68
|
-
messages.push(record.message as ChatMessage);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
if (!meta) { return null; }
|
|
72
|
-
return { meta, messages };
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
list(): SessionMeta[] {
|
|
76
|
-
if (!fs.existsSync(this.dir)) { return []; }
|
|
77
|
-
const metas: SessionMeta[] = [];
|
|
78
|
-
for (const name of fs.readdirSync(this.dir)) {
|
|
79
|
-
if (!name.endsWith('.jsonl')) { continue; }
|
|
80
|
-
const record = this.load(name.slice(0, -6));
|
|
81
|
-
if (record) { metas.push(record.meta); }
|
|
82
|
-
}
|
|
83
|
-
metas.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
84
|
-
return metas;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
latest(cwd?: string): SessionMeta | null {
|
|
88
|
-
const candidates = this.list();
|
|
89
|
-
if (cwd === undefined) { return candidates.length > 0 ? candidates[0] : null; }
|
|
90
|
-
const resolved = path.resolve(cwd);
|
|
91
|
-
return candidates.find((meta) => path.resolve(meta.cwd) === resolved) ?? null;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
delete(id: string): boolean {
|
|
95
|
-
const file = this.fileFor(id);
|
|
96
|
-
if (!fs.existsSync(file)) { return false; }
|
|
97
|
-
fs.unlinkSync(file);
|
|
98
|
-
return true;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
private write(meta: SessionMeta, messages: ChatMessage[]): void {
|
|
102
|
-
fs.mkdirSync(this.dir, { recursive: true });
|
|
103
|
-
const lines = [JSON.stringify({ type: 'meta', meta })];
|
|
104
|
-
for (const message of messages) {
|
|
105
|
-
lines.push(JSON.stringify({ type: 'message', message }));
|
|
106
|
-
}
|
|
107
|
-
fs.writeFileSync(this.fileFor(meta.id), lines.join('\n') + '\n', 'utf8');
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export function newSessionId(now: Date = new Date()): string {
|
|
112
|
-
const pad = (value: number, width: number = 2): string => String(value).padStart(width, '0');
|
|
113
|
-
const stamp = String(now.getFullYear())
|
|
114
|
-
+ pad(now.getMonth() + 1) + pad(now.getDate()) + '-'
|
|
115
|
-
+ pad(now.getHours()) + pad(now.getMinutes()) + pad(now.getSeconds());
|
|
116
|
-
const random = Math.random().toString(36).slice(2, 6);
|
|
117
|
-
return stamp + '-' + random;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export function titleFromMessages(messages: ChatMessage[]): string {
|
|
121
|
-
const first = messages.find((message) => message.role === 'user');
|
|
122
|
-
if (!first || first.role !== 'user') { return '新会话'; }
|
|
123
|
-
const line = first.content.replace(/\s+/g, ' ').trim();
|
|
124
|
-
return line.length > 40 ? line.slice(0, 40) + '…' : (line || '新会话');
|
|
125
|
-
}
|
package/src/theme.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/** Minko keeps one colour family everywhere: violet. */
|
|
2
|
-
export type Palette = {
|
|
3
|
-
primary: string;
|
|
4
|
-
deep: string;
|
|
5
|
-
soft: string;
|
|
6
|
-
accent: string;
|
|
7
|
-
text: string;
|
|
8
|
-
muted: string;
|
|
9
|
-
dim: string;
|
|
10
|
-
ok: string;
|
|
11
|
-
warn: string;
|
|
12
|
-
error: string;
|
|
13
|
-
border: string;
|
|
14
|
-
panel: string;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export const PURPLE: Palette = {
|
|
18
|
-
primary: '#a78bfa',
|
|
19
|
-
deep: '#7c3aed',
|
|
20
|
-
soft: '#c4b5fd',
|
|
21
|
-
accent: '#d8b4fe',
|
|
22
|
-
text: '#ece9f5',
|
|
23
|
-
muted: '#9a94ab',
|
|
24
|
-
dim: '#6b6580',
|
|
25
|
-
ok: '#4ade80',
|
|
26
|
-
warn: '#fbbf24',
|
|
27
|
-
error: '#f87171',
|
|
28
|
-
border: '#4c3a7a',
|
|
29
|
-
panel: '#1b1726',
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
/** Colour is on when writing to a terminal, off for pipes and NO_COLOR. */
|
|
33
|
-
export function colorEnabled(env: Record<string, string | undefined> = process.env,
|
|
34
|
-
isTTY: boolean = Boolean(process.stdout.isTTY)): boolean {
|
|
35
|
-
const noColor = env.NO_COLOR;
|
|
36
|
-
if (noColor !== undefined && noColor !== '') { return false; }
|
|
37
|
-
const force = env.FORCE_COLOR;
|
|
38
|
-
if (force !== undefined && force !== '') { return force !== '0'; }
|
|
39
|
-
if (env.TERM === 'dumb') { return false; }
|
|
40
|
-
return isTTY;
|
|
41
|
-
}
|