kiku-agent 1.0.0 → 1.0.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/README.md +293 -122
- package/package.json +60 -61
- package/server/agent/agentCore.js +892 -782
- package/server/agent/appPaths.js +21 -0
- package/server/agent/audioPlayer.js +739 -673
- package/server/agent/cli.js +1414 -1111
- package/server/agent/commandRegistry.js +74 -58
- package/server/agent/commands.js +1694 -788
- package/server/agent/config.js +264 -90
- package/server/agent/contextCompactor.js +79 -0
- package/server/agent/doctor.js +69 -0
- package/server/agent/eventBus.js +107 -103
- package/server/agent/gpuMetrics.js +189 -0
- package/server/agent/llmClient.js +369 -200
- package/server/agent/mcpClient.js +364 -0
- package/server/agent/memory.js +670 -566
- package/server/agent/modelManager.js +77 -0
- package/server/agent/research.js +202 -0
- package/server/agent/session.js +244 -239
- package/server/agent/subagent.js +116 -0
- package/server/agent/theme.js +125 -124
- package/server/agent/toolRouter.js +201 -0
- package/server/agent/tools.js +1679 -1528
- package/server/agent/ui.js +760 -469
- package/server/agent/youtubeAuth.js +144 -65
- package/server/mcp/claude-desktop-config.example.json +1 -1
- package/server/mcp/data/discography.js +36 -36
- package/server/mcp/dataAdapter.js +102 -0
- package/server/mcp/examples/claude-code.mcp.example.json +19 -0
- package/server/mcp/examples/claude-desktop.example.json +11 -0
- package/server/mcp/examples/cline.example.json +13 -0
- package/server/mcp/examples/cursor.example.json +14 -0
- package/server/mcp/examples/gemini-cli.example.json +17 -0
- package/server/mcp/examples/opencode.example.json +21 -0
- package/server/mcp/examples/vscode.example.json +19 -0
- package/server/mcp/examples/windsurf.example.json +14 -0
- package/server/mcp/handler.js +85 -159
- package/server/mcp/security.js +140 -138
- package/server/mcp/server.js +129 -100
- package/server/mcp/stdio.js +4 -2
|
@@ -1,200 +1,369 @@
|
|
|
1
|
-
import { config } from './config.js';
|
|
2
|
-
|
|
3
|
-
export class
|
|
4
|
-
constructor(options = {}) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
signal: controller.signal
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
1
|
+
import { config, CLOUD_PROVIDERS } from './config.js';
|
|
2
|
+
|
|
3
|
+
export class LLMClient {
|
|
4
|
+
constructor(options = {}) {
|
|
5
|
+
const providerId = (options.provider || config.activeProvider || 'lmstudio').toLowerCase();
|
|
6
|
+
const providerDef = CLOUD_PROVIDERS[providerId] || CLOUD_PROVIDERS.lmstudio;
|
|
7
|
+
|
|
8
|
+
this.provider = providerId;
|
|
9
|
+
this.providerName = providerDef.name || providerId;
|
|
10
|
+
this.baseUrl = (options.baseUrl || providerDef.baseUrl || config.lmStudio.baseUrl).replace(/\/+$/, '');
|
|
11
|
+
this.apiKey = options.apiKey !== undefined
|
|
12
|
+
? options.apiKey
|
|
13
|
+
: (providerDef.apiKey !== undefined ? providerDef.apiKey : (providerDef.isLocal ? (config.lmStudio.apiKey || 'lm-studio') : ''));
|
|
14
|
+
this.model = options.model || providerDef.defaultModel || config.lmStudio.model || 'google/gemma-4-e4b';
|
|
15
|
+
this.timeout = options.timeout || config.lmStudio.requestTimeoutMs || 180000;
|
|
16
|
+
this.extraHeaders = { ...(providerDef.extraHeaders || {}), ...(options.extraHeaders || {}) };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
switchProvider(providerId, options = {}) {
|
|
20
|
+
const pId = String(providerId || '').toLowerCase().trim();
|
|
21
|
+
const providerDef = CLOUD_PROVIDERS[pId];
|
|
22
|
+
if (!providerDef) {
|
|
23
|
+
throw new Error(`Unknown provider "${providerId}". Supported: ${Object.keys(CLOUD_PROVIDERS).join(', ')}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
this.provider = pId;
|
|
27
|
+
this.providerName = providerDef.name || pId;
|
|
28
|
+
this.baseUrl = (options.baseUrl || providerDef.baseUrl).replace(/\/+$/, '');
|
|
29
|
+
this.apiKey = options.apiKey !== undefined ? options.apiKey : (providerDef.apiKey || '');
|
|
30
|
+
this.model = options.model || providerDef.defaultModel;
|
|
31
|
+
this.extraHeaders = { ...(providerDef.extraHeaders || {}), ...(options.extraHeaders || {}) };
|
|
32
|
+
return this.getProviderInfo();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
setApiKey(apiKey) {
|
|
36
|
+
this.apiKey = String(apiKey || '').trim();
|
|
37
|
+
return this.getProviderInfo();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
setModel(model) {
|
|
41
|
+
const trimmed = String(model || '').trim();
|
|
42
|
+
if (!trimmed) throw new Error('A model ID is required.');
|
|
43
|
+
this.model = trimmed;
|
|
44
|
+
return this.model;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
getProviderInfo() {
|
|
48
|
+
const def = CLOUD_PROVIDERS[this.provider] || {};
|
|
49
|
+
return {
|
|
50
|
+
provider: this.provider,
|
|
51
|
+
providerName: this.providerName,
|
|
52
|
+
baseUrl: this.baseUrl,
|
|
53
|
+
model: this.model,
|
|
54
|
+
hasApiKey: Boolean(this.apiKey && this.apiKey !== 'lm-studio'),
|
|
55
|
+
isLocal: def.isLocal ?? false,
|
|
56
|
+
envKey: def.envKey || null,
|
|
57
|
+
docUrl: def.docUrl || null,
|
|
58
|
+
apiRefUrl: def.apiRefUrl || null,
|
|
59
|
+
pricingSummary: def.pricingSummary || null,
|
|
60
|
+
features: def.features || [],
|
|
61
|
+
recommendedModels: def.recommendedModels || [],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
_buildHeaders() {
|
|
66
|
+
const headers = {
|
|
67
|
+
'Content-Type': 'application/json',
|
|
68
|
+
...this.extraHeaders,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
if (this.apiKey) {
|
|
72
|
+
headers['Authorization'] = `Bearer ${this.apiKey}`;
|
|
73
|
+
if (this.provider === 'gemini') {
|
|
74
|
+
headers['x-goog-api-key'] = this.apiKey;
|
|
75
|
+
}
|
|
76
|
+
} else if (this.provider === 'lmstudio') {
|
|
77
|
+
headers['Authorization'] = 'Bearer lm-studio';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return headers;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async checkConnection() {
|
|
84
|
+
const def = CLOUD_PROVIDERS[this.provider] || {};
|
|
85
|
+
const startTime = Date.now();
|
|
86
|
+
|
|
87
|
+
if (!def.isLocal && !this.apiKey) {
|
|
88
|
+
return {
|
|
89
|
+
connected: false,
|
|
90
|
+
provider: this.provider,
|
|
91
|
+
providerName: this.providerName,
|
|
92
|
+
error: `No API key configured for ${this.providerName}. Set ${def.envKey || 'API_KEY'} in .env or run /provider setkey <key>`,
|
|
93
|
+
models: [],
|
|
94
|
+
latencyMs: 0
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const controller = new AbortController();
|
|
100
|
+
const timeoutId = setTimeout(() => controller.abort(), 8000);
|
|
101
|
+
|
|
102
|
+
const res = await fetch(`${this.baseUrl}/models`, {
|
|
103
|
+
method: 'GET',
|
|
104
|
+
headers: this._buildHeaders(),
|
|
105
|
+
signal: controller.signal
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
clearTimeout(timeoutId);
|
|
109
|
+
const latencyMs = Date.now() - startTime;
|
|
110
|
+
|
|
111
|
+
if (!res.ok) {
|
|
112
|
+
let errSnippet = res.statusText;
|
|
113
|
+
try {
|
|
114
|
+
const errBody = await res.text();
|
|
115
|
+
if (errBody) {
|
|
116
|
+
try {
|
|
117
|
+
const parsed = JSON.parse(errBody);
|
|
118
|
+
errSnippet = parsed.error?.message || parsed.message || errBody.slice(0, 150);
|
|
119
|
+
} catch {
|
|
120
|
+
errSnippet = errBody.slice(0, 150);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
} catch {}
|
|
124
|
+
|
|
125
|
+
let formattedError = `${this.providerName} returned status ${res.status}: ${errSnippet}`;
|
|
126
|
+
if (res.status === 401) {
|
|
127
|
+
formattedError = `401 Unauthorized: Invalid API key for ${this.providerName}. Check ${def.envKey || 'API key'}.`;
|
|
128
|
+
} else if (res.status === 429) {
|
|
129
|
+
formattedError = `429 Rate Limit / Quota Exceeded: ${errSnippet}`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
connected: false,
|
|
134
|
+
provider: this.provider,
|
|
135
|
+
providerName: this.providerName,
|
|
136
|
+
error: formattedError,
|
|
137
|
+
models: [],
|
|
138
|
+
latencyMs
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const data = await res.json();
|
|
143
|
+
const rawList = Array.isArray(data) ? data : (data.data || []);
|
|
144
|
+
const models = rawList.map(m => typeof m === 'string' ? m : (m.id || m.name)).filter(Boolean);
|
|
145
|
+
|
|
146
|
+
const matchedGemma = models.find(m =>
|
|
147
|
+
m === 'google/gemma-4-e4b' ||
|
|
148
|
+
m === 'gemma-4-e4b' ||
|
|
149
|
+
m.toLowerCase().includes('gemma-4-e4b') ||
|
|
150
|
+
(m.toLowerCase().includes('gemma') && m.toLowerCase().includes('e4b'))
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const isAvailable = models.length === 0 ? true : models.includes(this.model);
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
connected: true,
|
|
157
|
+
provider: this.provider,
|
|
158
|
+
providerName: this.providerName,
|
|
159
|
+
baseUrl: this.baseUrl,
|
|
160
|
+
models,
|
|
161
|
+
activeModel: models.includes(this.model) ? this.model : (matchedGemma || this.model),
|
|
162
|
+
isModelAvailable: isAvailable,
|
|
163
|
+
latencyMs
|
|
164
|
+
};
|
|
165
|
+
} catch (err) {
|
|
166
|
+
const latencyMs = Date.now() - startTime;
|
|
167
|
+
return {
|
|
168
|
+
connected: false,
|
|
169
|
+
provider: this.provider,
|
|
170
|
+
providerName: this.providerName,
|
|
171
|
+
error: err.name === 'AbortError'
|
|
172
|
+
? `Connection timed out at ${this.baseUrl} (>8s)`
|
|
173
|
+
: `Cannot reach ${this.providerName} at ${this.baseUrl} (${err.message})`,
|
|
174
|
+
models: [],
|
|
175
|
+
latencyMs
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async chat({
|
|
181
|
+
messages,
|
|
182
|
+
tools = null,
|
|
183
|
+
temperature = 0.3,
|
|
184
|
+
maxTokens = 1536,
|
|
185
|
+
stream = true,
|
|
186
|
+
signal = null,
|
|
187
|
+
onToken = null,
|
|
188
|
+
onThought = null
|
|
189
|
+
}) {
|
|
190
|
+
const isReasoningModel = /^(o1|o3|deepseek-reasoner)/i.test(this.model) || this.model.includes('reasoner');
|
|
191
|
+
const isO1OrO3 = /^(o1|o3)/i.test(this.model);
|
|
192
|
+
|
|
193
|
+
const buildPayload = (includeTools = true, includeTemp = true) => {
|
|
194
|
+
const p = {
|
|
195
|
+
model: this.model,
|
|
196
|
+
messages,
|
|
197
|
+
stream
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
if (!isO1OrO3 && includeTemp && temperature !== undefined && temperature !== null) {
|
|
201
|
+
p.temperature = temperature;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (isO1OrO3) {
|
|
205
|
+
p.max_completion_tokens = maxTokens;
|
|
206
|
+
} else {
|
|
207
|
+
p.max_tokens = maxTokens;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (includeTools && tools && Array.isArray(tools) && tools.length > 0 && !isReasoningModel) {
|
|
211
|
+
p.tools = tools;
|
|
212
|
+
p.tool_choice = "auto";
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return p;
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
let payload = buildPayload(true, true);
|
|
219
|
+
|
|
220
|
+
const controller = new AbortController();
|
|
221
|
+
let timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
222
|
+
|
|
223
|
+
const abortHandler = () => controller.abort();
|
|
224
|
+
if (signal) signal.addEventListener('abort', abortHandler, { once: true });
|
|
225
|
+
|
|
226
|
+
const resetTimeout = () => {
|
|
227
|
+
clearTimeout(timeoutId);
|
|
228
|
+
timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
let res = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
233
|
+
method: 'POST',
|
|
234
|
+
headers: this._buildHeaders(),
|
|
235
|
+
body: JSON.stringify(payload),
|
|
236
|
+
signal: controller.signal
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
resetTimeout();
|
|
240
|
+
|
|
241
|
+
// Graceful auto-retry if provider rejects tools/temperature/max_tokens with 400
|
|
242
|
+
if (!res.ok && res.status === 400) {
|
|
243
|
+
const errText = await res.text();
|
|
244
|
+
const lowerErr = errText.toLowerCase();
|
|
245
|
+
let retryNeeded = false;
|
|
246
|
+
|
|
247
|
+
if ((lowerErr.includes('tool') || lowerErr.includes('function') || lowerErr.includes('schema')) && payload.tools) {
|
|
248
|
+
payload = buildPayload(false, true);
|
|
249
|
+
retryNeeded = true;
|
|
250
|
+
} else if (lowerErr.includes('temperature') && payload.temperature !== undefined) {
|
|
251
|
+
payload = buildPayload(Boolean(payload.tools), false);
|
|
252
|
+
retryNeeded = true;
|
|
253
|
+
} else if (lowerErr.includes('max_tokens') && payload.max_tokens !== undefined) {
|
|
254
|
+
delete payload.max_tokens;
|
|
255
|
+
payload.max_completion_tokens = maxTokens;
|
|
256
|
+
retryNeeded = true;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (retryNeeded) {
|
|
260
|
+
res = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
261
|
+
method: 'POST',
|
|
262
|
+
headers: this._buildHeaders(),
|
|
263
|
+
body: JSON.stringify(payload),
|
|
264
|
+
signal: controller.signal
|
|
265
|
+
});
|
|
266
|
+
resetTimeout();
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (!res.ok) {
|
|
270
|
+
const finalErr = retryNeeded ? await res.text() : errText;
|
|
271
|
+
throw new Error(`${this.providerName} error (${res.status}): ${finalErr}`);
|
|
272
|
+
}
|
|
273
|
+
} else if (!res.ok) {
|
|
274
|
+
const errorText = await res.text();
|
|
275
|
+
throw new Error(`${this.providerName} error (${res.status}): ${errorText}`);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (!stream) {
|
|
279
|
+
const data = await res.json();
|
|
280
|
+
clearTimeout(timeoutId);
|
|
281
|
+
if (signal) signal.removeEventListener('abort', abortHandler);
|
|
282
|
+
const msg = data.choices && data.choices[0] ? data.choices[0].message : null;
|
|
283
|
+
if (msg && !msg.reasoning_content && (msg.reasoning || msg.thinking)) {
|
|
284
|
+
msg.reasoning_content = msg.reasoning || msg.thinking;
|
|
285
|
+
}
|
|
286
|
+
return msg;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const reader = res.body.getReader();
|
|
290
|
+
const decoder = new TextDecoder('utf-8');
|
|
291
|
+
let fullContent = '';
|
|
292
|
+
let fullReasoning = '';
|
|
293
|
+
let toolCalls = [];
|
|
294
|
+
let buffer = '';
|
|
295
|
+
|
|
296
|
+
while (true) {
|
|
297
|
+
const { done, value } = await reader.read();
|
|
298
|
+
if (done) break;
|
|
299
|
+
|
|
300
|
+
resetTimeout();
|
|
301
|
+
|
|
302
|
+
buffer += decoder.decode(value, { stream: true });
|
|
303
|
+
const lines = buffer.split('\n');
|
|
304
|
+
buffer = lines.pop() || '';
|
|
305
|
+
|
|
306
|
+
for (const line of lines) {
|
|
307
|
+
const trimmed = line.trim();
|
|
308
|
+
if (!trimmed || !trimmed.startsWith('data:')) continue;
|
|
309
|
+
const jsonStr = trimmed.replace(/^data:\s*/, '');
|
|
310
|
+
if (jsonStr === '[DONE]') continue;
|
|
311
|
+
|
|
312
|
+
try {
|
|
313
|
+
const parsed = JSON.parse(jsonStr);
|
|
314
|
+
const delta = parsed.choices?.[0]?.delta;
|
|
315
|
+
if (!delta) continue;
|
|
316
|
+
|
|
317
|
+
const reasoning = delta.reasoning_content || delta.reasoning || delta.thinking;
|
|
318
|
+
if (reasoning) {
|
|
319
|
+
fullReasoning += reasoning;
|
|
320
|
+
if (onThought) onThought(reasoning);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (delta.content) {
|
|
324
|
+
fullContent += delta.content;
|
|
325
|
+
if (onToken) onToken(delta.content);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (delta.tool_calls) {
|
|
329
|
+
for (const tc of delta.tool_calls) {
|
|
330
|
+
const idx = tc.index ?? 0;
|
|
331
|
+
if (!toolCalls[idx]) {
|
|
332
|
+
toolCalls[idx] = {
|
|
333
|
+
id: tc.id || `call_${Date.now()}_${idx}`,
|
|
334
|
+
type: 'function',
|
|
335
|
+
function: { name: '', arguments: '' }
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
if (tc.function?.name) toolCalls[idx].function.name += tc.function.name;
|
|
339
|
+
if (tc.function?.arguments) toolCalls[idx].function.arguments += tc.function.arguments;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
} catch {
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
clearTimeout(timeoutId);
|
|
348
|
+
|
|
349
|
+
const resultMessage = {
|
|
350
|
+
role: 'assistant',
|
|
351
|
+
content: fullContent,
|
|
352
|
+
reasoning_content: fullReasoning
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
if (toolCalls.length > 0) {
|
|
356
|
+
resultMessage.tool_calls = toolCalls.filter(Boolean);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (signal) signal.removeEventListener('abort', abortHandler);
|
|
360
|
+
return resultMessage;
|
|
361
|
+
} catch (err) {
|
|
362
|
+
clearTimeout(timeoutId);
|
|
363
|
+
if (signal) signal.removeEventListener('abort', abortHandler);
|
|
364
|
+
throw err;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export { LLMClient as LMStudioClient };
|