@miphamai/cli 0.6.2 → 0.6.4
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/package.json +1 -1
- package/src/core/engine.ts +59 -8
- package/src/index.tsx +27 -12
- package/src/mcp/registry.ts +142 -0
- package/src/providers/openai-compat.ts +26 -9
- package/src/shared/types.ts +4 -0
package/package.json
CHANGED
package/src/core/engine.ts
CHANGED
|
@@ -140,6 +140,7 @@ export class QueryEngine {
|
|
|
140
140
|
const toolDefs = this.getToolDefinitions()
|
|
141
141
|
|
|
142
142
|
let assistantContent = ''
|
|
143
|
+
let reasoningContent = ''
|
|
143
144
|
const toolUses: Array<{ id: string; name: string; input: Record<string, unknown> }> = []
|
|
144
145
|
|
|
145
146
|
// Stream model response
|
|
@@ -162,6 +163,10 @@ export class QueryEngine {
|
|
|
162
163
|
assistantContent += chunk.content
|
|
163
164
|
}
|
|
164
165
|
|
|
166
|
+
if (chunk.reasoning_content) {
|
|
167
|
+
reasoningContent += chunk.reasoning_content
|
|
168
|
+
}
|
|
169
|
+
|
|
165
170
|
if (chunk.type === 'tool_use' && chunk.toolUse) {
|
|
166
171
|
toolUses.push({
|
|
167
172
|
id: chunk.toolUse.id,
|
|
@@ -172,16 +177,26 @@ export class QueryEngine {
|
|
|
172
177
|
|
|
173
178
|
if (chunk.type === 'stop') {
|
|
174
179
|
// Add assistant response to context
|
|
175
|
-
if (assistantContent) {
|
|
176
|
-
|
|
180
|
+
if (assistantContent || reasoningContent) {
|
|
181
|
+
const msg: import('../shared/types').Message = {
|
|
182
|
+
role: 'assistant',
|
|
183
|
+
content: assistantContent || '',
|
|
184
|
+
}
|
|
185
|
+
if (reasoningContent) msg.reasoning_content = reasoningContent
|
|
186
|
+
this.context.addMessage(msg)
|
|
177
187
|
}
|
|
178
188
|
}
|
|
179
189
|
}
|
|
180
190
|
} catch (err) {
|
|
181
191
|
if (isAbortError(err)) {
|
|
182
192
|
// User interrupted — keep partial content, stop gracefully
|
|
183
|
-
if (assistantContent) {
|
|
184
|
-
|
|
193
|
+
if (assistantContent || reasoningContent) {
|
|
194
|
+
const msg: import('../shared/types').Message = {
|
|
195
|
+
role: 'assistant',
|
|
196
|
+
content: assistantContent || '',
|
|
197
|
+
}
|
|
198
|
+
if (reasoningContent) msg.reasoning_content = reasoningContent
|
|
199
|
+
this.context.addMessage(msg)
|
|
185
200
|
}
|
|
186
201
|
yield { type: 'stop' }
|
|
187
202
|
return
|
|
@@ -259,6 +274,7 @@ export class QueryEngine {
|
|
|
259
274
|
const messages = this.context.getMessages()
|
|
260
275
|
|
|
261
276
|
let assistantContent = ''
|
|
277
|
+
let reasoningContent = ''
|
|
262
278
|
const toolUses: Array<{ id: string; name: string; input: Record<string, unknown> }> = []
|
|
263
279
|
|
|
264
280
|
try {
|
|
@@ -274,6 +290,10 @@ export class QueryEngine {
|
|
|
274
290
|
if (chunk.type === 'error') return
|
|
275
291
|
if (chunk.type === 'text' && chunk.content) assistantContent += chunk.content
|
|
276
292
|
|
|
293
|
+
if (chunk.reasoning_content) {
|
|
294
|
+
reasoningContent += chunk.reasoning_content
|
|
295
|
+
}
|
|
296
|
+
|
|
277
297
|
if (chunk.type === 'tool_use' && chunk.toolUse) {
|
|
278
298
|
toolUses.push({
|
|
279
299
|
id: chunk.toolUse.id,
|
|
@@ -284,8 +304,13 @@ export class QueryEngine {
|
|
|
284
304
|
}
|
|
285
305
|
} catch (err) {
|
|
286
306
|
if (isAbortError(err)) {
|
|
287
|
-
if (assistantContent) {
|
|
288
|
-
|
|
307
|
+
if (assistantContent || reasoningContent) {
|
|
308
|
+
const msg: import('../shared/types').Message = {
|
|
309
|
+
role: 'assistant',
|
|
310
|
+
content: assistantContent || '',
|
|
311
|
+
}
|
|
312
|
+
if (reasoningContent) msg.reasoning_content = reasoningContent
|
|
313
|
+
this.context.addMessage(msg)
|
|
289
314
|
}
|
|
290
315
|
yield { type: 'stop' }
|
|
291
316
|
return
|
|
@@ -294,8 +319,13 @@ export class QueryEngine {
|
|
|
294
319
|
return
|
|
295
320
|
}
|
|
296
321
|
|
|
297
|
-
if (assistantContent) {
|
|
298
|
-
|
|
322
|
+
if (assistantContent || reasoningContent) {
|
|
323
|
+
const msg: import('../shared/types').Message = {
|
|
324
|
+
role: 'assistant',
|
|
325
|
+
content: assistantContent || '',
|
|
326
|
+
}
|
|
327
|
+
if (reasoningContent) msg.reasoning_content = reasoningContent
|
|
328
|
+
this.context.addMessage(msg)
|
|
299
329
|
}
|
|
300
330
|
|
|
301
331
|
// No more tool calls — fire Stop hook and potentially continue
|
|
@@ -401,6 +431,27 @@ export class QueryEngine {
|
|
|
401
431
|
return this.tools
|
|
402
432
|
}
|
|
403
433
|
|
|
434
|
+
/** Register a tool dynamically (used by MCP auto-registration). */
|
|
435
|
+
registerTool(tool: ToolDefinition): void {
|
|
436
|
+
if (this.tools.has(tool.name)) {
|
|
437
|
+
process.stderr.write(`[mcp] Tool collision: "${tool.name}" already registered. Skipping.\n`)
|
|
438
|
+
return
|
|
439
|
+
}
|
|
440
|
+
this.tools.set(tool.name, tool)
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/** Remove a dynamically registered tool by name. */
|
|
444
|
+
unregisterTool(name: string): void {
|
|
445
|
+
this.tools.delete(name)
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/** Register multiple tools at once. */
|
|
449
|
+
registerTools(tools: ToolDefinition[]): void {
|
|
450
|
+
for (const tool of tools) {
|
|
451
|
+
this.tools.set(tool.name, tool)
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
404
455
|
switchProvider(providerId: string, modelId?: string): void {
|
|
405
456
|
this.registry.switchProvider(providerId, modelId)
|
|
406
457
|
}
|
package/src/index.tsx
CHANGED
|
@@ -11,6 +11,7 @@ import { SkillsLoader } from './skills/loader'
|
|
|
11
11
|
import { PluginManager } from './plugin/plugin-manager'
|
|
12
12
|
import { createToolRegistry } from './tools'
|
|
13
13
|
import { McpClient } from './mcp/client'
|
|
14
|
+
import { registerMcpServerTools } from './mcp/registry'
|
|
14
15
|
import { HookEngine } from './core/hooks'
|
|
15
16
|
import { ArtifactServer } from './artifacts/server'
|
|
16
17
|
import { ARTIFACTS_DIR, ARTIFACT_PORT, MIPHAM_DIR } from './shared/constants'
|
|
@@ -50,17 +51,6 @@ export async function runApp(options: RunOptions): Promise<void> {
|
|
|
50
51
|
// Load configuration
|
|
51
52
|
const config = loadConfig()
|
|
52
53
|
|
|
53
|
-
// Connect MCP servers (fire-and-forget — do not block startup)
|
|
54
|
-
const mcpServers = config.skills?.mcpServers ?? []
|
|
55
|
-
if (mcpServers.length > 0) {
|
|
56
|
-
const mcp = McpClient.getInstance()
|
|
57
|
-
for (const server of mcpServers) {
|
|
58
|
-
mcp.connect(server).catch((err) => {
|
|
59
|
-
process.stderr.write(`[mcp] Failed to connect "${server.name}": ${String(err)}\n`)
|
|
60
|
-
})
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
54
|
// Bootstrap providers
|
|
65
55
|
const defaultProvider = options.provider || config.defaultProvider
|
|
66
56
|
const defaultModel = options.model || config.defaultModel
|
|
@@ -97,9 +87,34 @@ export async function runApp(options: RunOptions): Promise<void> {
|
|
|
97
87
|
context.setSystemPrompt(instructions.buildSystemPrompt())
|
|
98
88
|
}
|
|
99
89
|
|
|
100
|
-
// Create tool registry with all
|
|
90
|
+
// Create tool registry with all built-in tools
|
|
101
91
|
const tools = createToolRegistry()
|
|
102
92
|
|
|
93
|
+
// Connect MCP servers and register their tools into the tool registry.
|
|
94
|
+
// Uses Promise.allSettled for parallel connection — failures are non-fatal.
|
|
95
|
+
const mcpServers = config.skills?.mcpServers ?? []
|
|
96
|
+
if (mcpServers.length > 0) {
|
|
97
|
+
const mcp = McpClient.getInstance()
|
|
98
|
+
const results = await Promise.allSettled(
|
|
99
|
+
mcpServers.map(async (server) => {
|
|
100
|
+
await mcp.connect(server)
|
|
101
|
+
const count = registerMcpServerTools(server.name, tools)
|
|
102
|
+
if (count > 0) {
|
|
103
|
+
process.stderr.write(`[mcp] "${server.name}": registered ${count} tools\n`)
|
|
104
|
+
}
|
|
105
|
+
}),
|
|
106
|
+
)
|
|
107
|
+
// Log failures (non-fatal — app starts without that server's tools)
|
|
108
|
+
for (let i = 0; i < results.length; i++) {
|
|
109
|
+
const result = results[i]!
|
|
110
|
+
if (result.status === 'rejected') {
|
|
111
|
+
process.stderr.write(
|
|
112
|
+
`[mcp] Failed to connect "${mcpServers[i]!.name}": ${String(result.reason)}\n`,
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
103
118
|
// Initialize hook engine — register skill-defined hooks
|
|
104
119
|
const hookEngine = new HookEngine()
|
|
105
120
|
for (const skill of skillsLoader.list()) {
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool Registry — converts MCP server tools into main ToolDefinitions
|
|
3
|
+
* and registers them into the central tool registry.
|
|
4
|
+
*
|
|
5
|
+
* Naming convention: mcp__<sanitizedServerName>__<sanitizedToolName>
|
|
6
|
+
* Example: server "chrome-devtools-mcp" tool "click" → "mcp__chrome_devtools_mcp__click"
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ToolDefinition as McpToolDefinition, ToolCallResult } from './types'
|
|
10
|
+
import type { ToolDefinition, ToolResult, ToolContext } from '../shared/types'
|
|
11
|
+
import { McpClient } from './client'
|
|
12
|
+
|
|
13
|
+
const MCP_TOOL_PREFIX = 'mcp__'
|
|
14
|
+
const MAX_NAME_LENGTH = 128
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Sanitize a name for use in a tool identifier.
|
|
18
|
+
* Lowercases and replaces non-alphanumeric characters (except hyphens) with underscores.
|
|
19
|
+
*/
|
|
20
|
+
function sanitizeName(name: string): string {
|
|
21
|
+
return name
|
|
22
|
+
.toLowerCase()
|
|
23
|
+
.replace(/[^a-z0-9-]/g, '_')
|
|
24
|
+
.replace(/_+/g, '_')
|
|
25
|
+
.replace(/^_|_$/g, '')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Build a namespaced MCP tool name.
|
|
30
|
+
* Format: mcp__<sanitizedServerName>__<sanitizedToolName>
|
|
31
|
+
*/
|
|
32
|
+
function buildToolName(serverName: string, toolName: string): string {
|
|
33
|
+
const base = `${MCP_TOOL_PREFIX}${sanitizeName(serverName)}__${sanitizeName(toolName)}`
|
|
34
|
+
if (base.length <= MAX_NAME_LENGTH) return base
|
|
35
|
+
const prefix = `${MCP_TOOL_PREFIX}${sanitizeName(serverName)}__`
|
|
36
|
+
const maxToolLen = MAX_NAME_LENGTH - prefix.length
|
|
37
|
+
return prefix + sanitizeName(toolName).slice(0, maxToolLen)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Convert an MCP server ToolDefinition into the main ToolDefinition format
|
|
42
|
+
* that can be registered in the central tool registry.
|
|
43
|
+
*/
|
|
44
|
+
export function convertMcpTool(serverName: string, mcpTool: McpToolDefinition): ToolDefinition {
|
|
45
|
+
const toolName = buildToolName(serverName, mcpTool.name)
|
|
46
|
+
|
|
47
|
+
// Build parameters schema — pass through MCP inputSchema as-is
|
|
48
|
+
const parameters: Record<string, unknown> = {
|
|
49
|
+
type: 'object',
|
|
50
|
+
properties: (mcpTool.inputSchema.properties as Record<string, unknown>) ?? {},
|
|
51
|
+
}
|
|
52
|
+
if (mcpTool.inputSchema.required) {
|
|
53
|
+
parameters.required = mcpTool.inputSchema.required
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
name: toolName,
|
|
58
|
+
description: `[MCP:${serverName}] ${mcpTool.description || mcpTool.name}`,
|
|
59
|
+
category: 'system',
|
|
60
|
+
permission: 'ask',
|
|
61
|
+
parameters,
|
|
62
|
+
async execute(params: Record<string, unknown>, _ctx: ToolContext): Promise<ToolResult> {
|
|
63
|
+
const client = McpClient.getInstance()
|
|
64
|
+
|
|
65
|
+
const conn = client.getConnection(serverName)
|
|
66
|
+
if (!conn || conn.status !== 'connected') {
|
|
67
|
+
return {
|
|
68
|
+
success: false,
|
|
69
|
+
content: '',
|
|
70
|
+
error: `MCP server "${serverName}" is not connected (status: ${conn?.status || 'unknown'}). The server may have crashed or been disconnected.`,
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const result: ToolCallResult = await client.callTool(serverName, mcpTool.name, params)
|
|
75
|
+
|
|
76
|
+
const text = result.content
|
|
77
|
+
.map((c) => {
|
|
78
|
+
if (c.type === 'text' && c.text) return c.text
|
|
79
|
+
if (c.type === 'image') return `[Image: ${c.mimeType || 'unknown'}]`
|
|
80
|
+
return JSON.stringify(c)
|
|
81
|
+
})
|
|
82
|
+
.join('\n')
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
success: !result.isError,
|
|
86
|
+
content: text || '(empty result)',
|
|
87
|
+
error: result.isError ? text : undefined,
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Register all tools from a connected MCP server into the tool registry.
|
|
95
|
+
* Returns the number of tools registered.
|
|
96
|
+
*/
|
|
97
|
+
export function registerMcpServerTools(
|
|
98
|
+
serverName: string,
|
|
99
|
+
toolsMap: Map<string, ToolDefinition>,
|
|
100
|
+
): number {
|
|
101
|
+
const client = McpClient.getInstance()
|
|
102
|
+
const mcpTools = client.getTools(serverName)
|
|
103
|
+
let registered = 0
|
|
104
|
+
|
|
105
|
+
for (const mcpTool of mcpTools) {
|
|
106
|
+
try {
|
|
107
|
+
const tool = convertMcpTool(serverName, mcpTool)
|
|
108
|
+
|
|
109
|
+
if (toolsMap.has(tool.name)) {
|
|
110
|
+
process.stderr.write(
|
|
111
|
+
`[mcp] Tool collision: "${tool.name}" (from server "${serverName}") conflicts with existing tool. Skipping.\n`,
|
|
112
|
+
)
|
|
113
|
+
continue
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
toolsMap.set(tool.name, tool)
|
|
117
|
+
registered++
|
|
118
|
+
} catch (err) {
|
|
119
|
+
process.stderr.write(
|
|
120
|
+
`[mcp] Failed to register tool "${mcpTool.name}" from server "${serverName}": ${String(err)}\n`,
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return registered
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Unregister all tools previously registered from a specific MCP server.
|
|
130
|
+
* Tools are identified by the mcp__<serverName>__ prefix.
|
|
131
|
+
*/
|
|
132
|
+
export function unregisterMcpServerTools(
|
|
133
|
+
serverName: string,
|
|
134
|
+
toolsMap: Map<string, ToolDefinition>,
|
|
135
|
+
): void {
|
|
136
|
+
const prefix = `${MCP_TOOL_PREFIX}${sanitizeName(serverName)}__`
|
|
137
|
+
for (const name of toolsMap.keys()) {
|
|
138
|
+
if (name.startsWith(prefix)) {
|
|
139
|
+
toolsMap.delete(name)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -44,8 +44,9 @@ export class OpenAICompatProvider implements ProviderInstance {
|
|
|
44
44
|
const decoder = new TextDecoder()
|
|
45
45
|
let buffer = ''
|
|
46
46
|
|
|
47
|
-
// Track incremental tool calls across streaming deltas
|
|
47
|
+
// Track incremental tool calls and reasoning content across streaming deltas
|
|
48
48
|
const pendingToolCalls = new Map<number, { id: string; name: string; arguments: string }>()
|
|
49
|
+
let reasoningContent = ''
|
|
49
50
|
|
|
50
51
|
while (true) {
|
|
51
52
|
const { done, value } = await reader.read()
|
|
@@ -72,7 +73,7 @@ export class OpenAICompatProvider implements ProviderInstance {
|
|
|
72
73
|
},
|
|
73
74
|
}
|
|
74
75
|
}
|
|
75
|
-
yield { type: 'stop' }
|
|
76
|
+
yield { type: 'stop', reasoning_content: reasoningContent }
|
|
76
77
|
return
|
|
77
78
|
}
|
|
78
79
|
|
|
@@ -104,6 +105,10 @@ export class OpenAICompatProvider implements ProviderInstance {
|
|
|
104
105
|
yield { type: 'text', content: delta.content }
|
|
105
106
|
}
|
|
106
107
|
|
|
108
|
+
if (delta?.reasoning_content) {
|
|
109
|
+
reasoningContent += delta.reasoning_content
|
|
110
|
+
}
|
|
111
|
+
|
|
107
112
|
if (choice.finish_reason === 'tool_calls') {
|
|
108
113
|
// Emit fully accumulated tool calls
|
|
109
114
|
for (const [, tc] of pendingToolCalls) {
|
|
@@ -121,7 +126,7 @@ export class OpenAICompatProvider implements ProviderInstance {
|
|
|
121
126
|
}
|
|
122
127
|
|
|
123
128
|
if (choice.finish_reason === 'stop') {
|
|
124
|
-
yield { type: 'stop' }
|
|
129
|
+
yield { type: 'stop', reasoning_content: reasoningContent }
|
|
125
130
|
}
|
|
126
131
|
} catch {
|
|
127
132
|
// skip unparseable chunks
|
|
@@ -129,7 +134,7 @@ export class OpenAICompatProvider implements ProviderInstance {
|
|
|
129
134
|
}
|
|
130
135
|
}
|
|
131
136
|
|
|
132
|
-
yield { type: 'stop' }
|
|
137
|
+
yield { type: 'stop', reasoning_content: reasoningContent }
|
|
133
138
|
}
|
|
134
139
|
|
|
135
140
|
async listModels(): Promise<ModelInfo[]> {
|
|
@@ -174,7 +179,7 @@ export class OpenAICompatProvider implements ProviderInstance {
|
|
|
174
179
|
next.content.some((b) => b.type === 'tool_use')
|
|
175
180
|
) {
|
|
176
181
|
const toolUses = next.content.filter((b) => b.type === 'tool_use')
|
|
177
|
-
|
|
182
|
+
const combinedMsg: Record<string, unknown> = {
|
|
178
183
|
role: 'assistant',
|
|
179
184
|
content: msg.content,
|
|
180
185
|
tool_calls: toolUses.map((tu) => ({
|
|
@@ -185,12 +190,20 @@ export class OpenAICompatProvider implements ProviderInstance {
|
|
|
185
190
|
arguments: JSON.stringify(tu.input),
|
|
186
191
|
},
|
|
187
192
|
})),
|
|
188
|
-
}
|
|
193
|
+
}
|
|
194
|
+
if (msg.reasoning_content) {
|
|
195
|
+
combinedMsg.reasoning_content = msg.reasoning_content
|
|
196
|
+
}
|
|
197
|
+
result.push(combinedMsg)
|
|
189
198
|
i++ // skip the next message (consumed)
|
|
190
199
|
continue
|
|
191
200
|
}
|
|
192
201
|
|
|
193
|
-
|
|
202
|
+
const standaloneMsg: Record<string, unknown> = { role: msg.role, content: msg.content }
|
|
203
|
+
if (msg.role === 'assistant' && msg.reasoning_content) {
|
|
204
|
+
standaloneMsg.reasoning_content = msg.reasoning_content
|
|
205
|
+
}
|
|
206
|
+
result.push(standaloneMsg)
|
|
194
207
|
continue
|
|
195
208
|
}
|
|
196
209
|
|
|
@@ -207,7 +220,7 @@ export class OpenAICompatProvider implements ProviderInstance {
|
|
|
207
220
|
.map((b) => (b.type === 'text' ? b.text : ''))
|
|
208
221
|
.join('') || null
|
|
209
222
|
|
|
210
|
-
|
|
223
|
+
const toolUseMsg: Record<string, unknown> = {
|
|
211
224
|
role: 'assistant',
|
|
212
225
|
content: textContent,
|
|
213
226
|
tool_calls: toolUses.map((tu) => ({
|
|
@@ -218,7 +231,11 @@ export class OpenAICompatProvider implements ProviderInstance {
|
|
|
218
231
|
arguments: JSON.stringify(tu.input),
|
|
219
232
|
},
|
|
220
233
|
})),
|
|
221
|
-
}
|
|
234
|
+
}
|
|
235
|
+
if (msg.reasoning_content) {
|
|
236
|
+
toolUseMsg.reasoning_content = msg.reasoning_content
|
|
237
|
+
}
|
|
238
|
+
result.push(toolUseMsg)
|
|
222
239
|
continue
|
|
223
240
|
}
|
|
224
241
|
|
package/src/shared/types.ts
CHANGED
|
@@ -46,6 +46,8 @@ export type ContentBlock = TextContent | ImageContent | ToolUseContent | ToolRes
|
|
|
46
46
|
export interface Message {
|
|
47
47
|
role: 'system' | 'user' | 'assistant'
|
|
48
48
|
content: string | ContentBlock[]
|
|
49
|
+
/** DeepSeek reasoning tokens — must be passed back to the API in multi-turn conversations. */
|
|
50
|
+
reasoning_content?: string
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
// ── Tool Types ──
|
|
@@ -105,6 +107,8 @@ export interface StreamChunk {
|
|
|
105
107
|
toolUse?: ToolUseContent
|
|
106
108
|
tool_use_id?: string
|
|
107
109
|
error?: string
|
|
110
|
+
/** DeepSeek reasoning tokens accumulated during this stream. */
|
|
111
|
+
reasoning_content?: string
|
|
108
112
|
}
|
|
109
113
|
|
|
110
114
|
// ── Config Types ──
|