kiku-agent 1.3.2 → 1.3.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 +69 -12
- package/package.json +2 -1
- package/server/agent/agentCore.js +319 -46
- package/server/agent/appPaths.js +31 -0
- package/server/agent/audioPlayer.js +339 -12
- package/server/agent/autoContext.js +193 -0
- package/server/agent/catalogEntityResolver.js +170 -28
- package/server/agent/clarifyingQuestions.js +137 -35
- package/server/agent/cli.js +177 -48
- package/server/agent/cloudAdapters.js +116 -0
- package/server/agent/commandRegistry.js +409 -80
- package/server/agent/commands.js +1056 -125
- package/server/agent/config.js +1088 -690
- package/server/agent/contextCompactor.js +213 -21
- package/server/agent/cpuMetrics.js +199 -0
- package/server/agent/engineService.js +591 -0
- package/server/agent/eval/modules/evaluation/runner.js +4 -14
- package/server/agent/eval/modules/evaluation/scoring.js +15 -3
- package/server/agent/eval/modules/server/evalApi.js +5 -5
- package/server/agent/eval/modules/ui/evalUi.js +7 -4
- package/server/agent/eventBus.js +1 -0
- package/server/agent/ink/main.jsx +196 -191
- package/server/agent/jobManager.js +11 -1
- package/server/agent/lanDiscovery.js +721 -23
- package/server/agent/llmClient.js +317 -71
- package/server/agent/lmStudioWakeup.js +517 -65
- package/server/agent/locationService.js +797 -0
- package/server/agent/mcpQuizData.js +509 -0
- package/server/agent/memory/contextAllocator.js +19 -7
- package/server/agent/memory.js +3 -1
- package/server/agent/modelCompatibility.js +352 -0
- package/server/agent/modelDefaults.js +1 -0
- package/server/agent/modelManager.js +46 -2
- package/server/agent/newsWatch.js +9 -3
- package/server/agent/processManager.js +229 -0
- package/server/agent/prompts.js +174 -8
- package/server/agent/routingInterceptor.js +9 -1
- package/server/agent/sanitizer.js +41 -6
- package/server/agent/searchQueryPlanner.js +202 -0
- package/server/agent/session.js +59 -4
- package/server/agent/tests/agentFollowUps.test.js +128 -2
- package/server/agent/tests/agentMusicControl.test.js +245 -0
- package/server/agent/tests/apiRouter.test.js +1 -1
- package/server/agent/tests/autoContext.test.js +176 -0
- package/server/agent/tests/bilibiliEmbed.test.js +45 -0
- package/server/agent/tests/chatHardening.test.js +121 -0
- package/server/agent/tests/chineseWebSearchEscalation.test.js +62 -0
- package/server/agent/tests/clarifyingQuestions.test.js +33 -0
- package/server/agent/tests/cliAutomation.test.js +38 -1
- package/server/agent/tests/cloudProviders.test.js +91 -3
- package/server/agent/tests/commands.test.js +92 -3
- package/server/agent/tests/contextIntelligence.test.js +50 -3
- package/server/agent/tests/cpuMetrics.test.js +101 -0
- package/server/agent/tests/decisionAndSearchEnhancements.test.js +32 -0
- package/server/agent/tests/edgeCasesScenarios.test.js +82 -0
- package/server/agent/tests/engineService.test.js +436 -0
- package/server/agent/tests/engineUnload.test.js +94 -0
- package/server/agent/tests/envCommand.test.js +69 -0
- package/server/agent/tests/evalMetrics.test.js +8 -0
- package/server/agent/tests/fetchApi.test.js +99 -0
- package/server/agent/tests/hybridWebSearch.test.js +2 -0
- package/server/agent/tests/lanDiscovery.test.js +5 -0
- package/server/agent/tests/lanInterfaces.test.js +510 -0
- package/server/agent/tests/lmStudioWakeup.test.js +278 -0
- package/server/agent/tests/locationTool.test.js +278 -0
- package/server/agent/tests/memoryRoute.test.js +173 -0
- package/server/agent/tests/memoryUiNormalization.test.js +75 -0
- package/server/agent/tests/modelCompatibility.test.js +148 -0
- package/server/agent/tests/modelManager.test.js +32 -0
- package/server/agent/tests/musicControlUiBridge.test.js +113 -0
- package/server/agent/tests/nextGenArchitecture.test.js +4 -2
- package/server/agent/tests/openDomainPrompt.test.js +197 -0
- package/server/agent/tests/openDomainSearch.test.js +121 -0
- package/server/agent/tests/processManager.test.js +144 -0
- package/server/agent/tests/providerPersistence.test.js +10 -5
- package/server/agent/tests/quickActionsAndModals.test.js +180 -0
- package/server/agent/tests/searchQueryPlanner.test.js +108 -0
- package/server/agent/tests/securityAuditRemediation.test.js +16 -9
- package/server/agent/tests/securityAuditRemediations2.test.js +154 -0
- package/server/agent/tests/securityHardening.test.js +4 -4
- package/server/agent/tests/sessionAndCopy.test.js +39 -4
- package/server/agent/tests/sidebarControls.test.js +34 -0
- package/server/agent/tests/statefulMemory.test.js +8 -1
- package/server/agent/tests/textProtocolLoop.test.js +71 -0
- package/server/agent/tests/textToolParsing.test.js +48 -0
- package/server/agent/tests/toolRoutingRegressions.test.js +41 -0
- package/server/agent/tests/unifiedEndpoints.test.js +13 -6
- package/server/agent/tests/v110Features.test.js +1 -1
- package/server/agent/tests/vramEstimator.test.js +75 -0
- package/server/agent/tests/webChatDocs.test.js +199 -0
- package/server/agent/tests/webChatInferenceControl.test.js +298 -0
- package/server/agent/tests/webChatInspection.test.js +256 -5
- package/server/agent/tests/webChatMcp.test.js +94 -0
- package/server/agent/tests/webExtract.test.js +23 -0
- package/server/agent/tests/webSources.test.js +46 -12
- package/server/agent/textToolProtocol.js +173 -46
- package/server/agent/toolRouter.js +318 -44
- package/server/agent/tools.js +877 -96
- package/server/agent/ui.js +39 -34
- package/server/agent/vramService.js +35 -54
- package/server/agent/webSearch.js +441 -162
- package/server/agent/websearch/orchestrator.js +15 -5
- package/server/agent/websearch/resilience/cache.js +9 -2
- package/server/agent/websearch/security/ssrf.js +41 -10
- package/server/agent/youtubeAuth.js +5 -20
- package/server/apiRouter.js +5558 -4422
- package/server/mcp/data/discography.js +2 -2
- package/server/mcp/examples/antigravity.example.json +17 -0
- package/server/mcp/handler.js +8 -4
- package/server/mcp/security.js +220 -21
- package/server/mcp/server.js +2 -1
- package/server/mcp/tests/connection.test.js +14 -8
- package/server/mcp/tests/mcpKeyAuth.test.js +156 -0
- package/server/mcp/tools.js +7 -1
- package/server/supabase_migration_prefix.sql +102 -0
- package/server/supabase_schema.sql +18 -18
- package/server/web-chat/core/connection.js +179 -218
- package/server/web-chat/core/conversationContext.js +9 -2
- package/server/web-chat/core/directCatalogEngine.js +52 -28
- package/server/web-chat/core/dramaKnowledge.js +33 -22
- package/server/web-chat/core/followUpEngine.js +735 -81
- package/server/web-chat/core/futureEventGuard.js +5 -1
- package/server/web-chat/core/musicControlAction.js +70 -0
- package/server/web-chat/core/security.js +32 -21
- package/server/web-chat/core/toolInspector.js +6 -6
- package/server/web-chat/core/urlGuard.js +45 -0
- package/server/web-chat/router.js +204 -157
- package/server/web-chat/routes/chatRoute.js +885 -488
- package/server/web-chat/routes/engineRoute.js +270 -0
- package/server/web-chat/routes/exportRoute.js +1 -1
- package/server/web-chat/routes/lanDiscoveryRoute.js +17 -2
- package/server/web-chat/routes/lmStudioLoadRoute.js +172 -0
- package/server/web-chat/routes/mcpRoute.js +386 -0
- package/server/web-chat/routes/memoryRoute.js +253 -0
- package/server/web-chat/routes/providerRoute.js +49 -16
- package/server/web-chat/routes/staticRoute.js +306 -17
- package/server/web-chat/routes/statusRoute.js +194 -73
- package/server/web-chat/routes/suggestRoute.js +11 -3
- package/server/web-chat/routes/verifyRoute.js +26 -4
- package/server/web-chat/server.js +7 -6
- package/server/web-chat/ui/docsBuilder.js +3558 -0
- package/server/web-chat/ui/htmlBuilder.js +12 -0
- package/server/web-chat/ui/scripts/client.js +3401 -1302
- package/server/web-chat/ui/scripts/webchatI18n.js +155 -153
- package/server/web-chat/ui/settingsBuilder.js +7870 -0
- package/server/web-chat/ui/styles/lan-card.css +165 -0
- package/server/web-chat/ui/styles/main.css +4289 -447
- package/server/web-chat/ui/sw.js +64 -0
- package/server/web-chat/ui/templates/body.html +407 -281
- package/src/data/awardsData.js +146 -146
- package/src/data/musicVideoData.js +6 -4
- package/src/lib/vram/estimator.js +195 -19
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { config, CLOUD_PROVIDERS, normalizeProviderModel, matchModelIdentifier, extractModelQuantization, savePersistedProviderKey, savePersistedActiveProvider, savePersistedProviderModel, getPersistedActiveProvider, getProviderBaseUrl, savePersistedProviderBaseUrl, setActiveProvider } from './config.js';
|
|
1
|
+
import { config, CLOUD_PROVIDERS, normalizeProviderModel, matchModelIdentifier, extractModelQuantization, savePersistedProviderKey, savePersistedActiveProvider, savePersistedProviderModel, getPersistedActiveProvider, getProviderBaseUrl, savePersistedProviderBaseUrl, setActiveProvider, getPersistedInferenceSettings } from './config.js';
|
|
2
2
|
import { parseApiErrorText } from './errors.js';
|
|
3
3
|
import { estimateTokens } from './telemetry.js';
|
|
4
|
-
import { estimateTokenCount } from './contextCompactor.js';
|
|
4
|
+
import { estimateTokenCount, prepareChatMessagesForDispatch } from './contextCompactor.js';
|
|
5
|
+
import { isIpAddress, isPrivateIPv4, isPrivateIPv6 } from './sanitizer.js';
|
|
6
|
+
import { applyModelRequestOptions, normalizeAssistantMessage, StreamContentNormalizer } from './modelCompatibility.js';
|
|
7
|
+
import { toAnthropicMessages, toAnthropicTools, normalizeAnthropicResponse } from './cloudAdapters.js';
|
|
5
8
|
|
|
6
9
|
function computeRetryDelay(attempt, headers) {
|
|
7
10
|
const retryAfter = Number(headers?.get?.('retry-after'));
|
|
@@ -11,6 +14,22 @@ function computeRetryDelay(attempt, headers) {
|
|
|
11
14
|
return Math.min(8000, 1200 * 2 ** attempt) + Math.floor(Math.random() * 400);
|
|
12
15
|
}
|
|
13
16
|
|
|
17
|
+
function isLocalInferenceEndpoint(baseUrl) {
|
|
18
|
+
let hostname = '';
|
|
19
|
+
try {
|
|
20
|
+
hostname = new URL(baseUrl).hostname.replace(/^\[|\]$/g, '').replace(/\.$/, '').toLowerCase();
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
if (hostname === 'localhost' || hostname.endsWith('.localhost') || hostname.endsWith('.local')) return true;
|
|
25
|
+
if (!isIpAddress(hostname)) return false;
|
|
26
|
+
return isPrivateIPv4(hostname) || isPrivateIPv6(hostname);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function parseAnthropicArgs(value) {
|
|
30
|
+
try { return JSON.parse(value || '{}'); } catch { return {}; }
|
|
31
|
+
}
|
|
32
|
+
|
|
14
33
|
export class LLMClient {
|
|
15
34
|
constructor(options = {}) {
|
|
16
35
|
const providerId = (options.provider || getPersistedActiveProvider() || config.activeProvider || 'lmstudio').toLowerCase();
|
|
@@ -25,6 +44,7 @@ export class LLMClient {
|
|
|
25
44
|
this.apiKey = options.apiKey !== undefined
|
|
26
45
|
? options.apiKey
|
|
27
46
|
: (providerDef.apiKey !== undefined ? providerDef.apiKey : (providerDef.isLocal ? (config.lmStudio.apiKey || 'lm-studio') : ''));
|
|
47
|
+
this.modelExplicitlySet = Boolean(options.model);
|
|
28
48
|
this.model = normalizeProviderModel(
|
|
29
49
|
providerId,
|
|
30
50
|
options.model || providerDef.defaultModel || config.lmStudio.model || 'google/gemma-4-e4b'
|
|
@@ -73,6 +93,7 @@ export class LLMClient {
|
|
|
73
93
|
setModel(model) {
|
|
74
94
|
const trimmed = String(model || '').trim();
|
|
75
95
|
if (!trimmed) throw new Error('A model ID is required.');
|
|
96
|
+
this.modelExplicitlySet = true;
|
|
76
97
|
this.model = normalizeProviderModel(this.provider, trimmed);
|
|
77
98
|
savePersistedProviderModel(this.provider, this.model);
|
|
78
99
|
return this.model;
|
|
@@ -102,7 +123,9 @@ export class LLMClient {
|
|
|
102
123
|
...this.extraHeaders,
|
|
103
124
|
};
|
|
104
125
|
|
|
105
|
-
if (this.
|
|
126
|
+
if (this.provider === 'anthropic') {
|
|
127
|
+
if (this.apiKey) headers['x-api-key'] = this.apiKey;
|
|
128
|
+
} else if (this.apiKey) {
|
|
106
129
|
headers['Authorization'] = `Bearer ${this.apiKey}`;
|
|
107
130
|
if (this.provider === 'gemini') {
|
|
108
131
|
headers['x-goog-api-key'] = this.apiKey;
|
|
@@ -111,6 +134,8 @@ export class LLMClient {
|
|
|
111
134
|
headers['Authorization'] = 'Bearer lm-studio';
|
|
112
135
|
} else if (this.provider === 'vllm') {
|
|
113
136
|
headers['Authorization'] = 'Bearer token-kiku';
|
|
137
|
+
} else if (this.provider === 'ollama') {
|
|
138
|
+
headers['Authorization'] = this.apiKey ? `Bearer ${this.apiKey}` : 'Bearer ollama';
|
|
114
139
|
}
|
|
115
140
|
|
|
116
141
|
return headers;
|
|
@@ -120,7 +145,20 @@ export class LLMClient {
|
|
|
120
145
|
const def = CLOUD_PROVIDERS[this.provider] || {};
|
|
121
146
|
const startTime = Date.now();
|
|
122
147
|
|
|
123
|
-
if (
|
|
148
|
+
if (this.provider === 'custom' && !this.baseUrl) {
|
|
149
|
+
return {
|
|
150
|
+
connected: false,
|
|
151
|
+
provider: this.provider,
|
|
152
|
+
providerName: this.providerName,
|
|
153
|
+
error: `No base URL configured for ${this.providerName}. Set LLM_BASE_URL in .env or apply a discovered server in /settings`,
|
|
154
|
+
models: [],
|
|
155
|
+
latencyMs: 0
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const keylessLocalEndpoint = this.provider === 'custom' && isLocalInferenceEndpoint(this.baseUrl);
|
|
160
|
+
|
|
161
|
+
if (!def.isLocal && !this.apiKey && !keylessLocalEndpoint) {
|
|
124
162
|
return {
|
|
125
163
|
connected: false,
|
|
126
164
|
provider: this.provider,
|
|
@@ -130,6 +168,21 @@ export class LLMClient {
|
|
|
130
168
|
latencyMs: 0
|
|
131
169
|
};
|
|
132
170
|
}
|
|
171
|
+
if (this.provider === 'anthropic') {
|
|
172
|
+
return {
|
|
173
|
+
connected: true,
|
|
174
|
+
provider: this.provider,
|
|
175
|
+
providerName: this.providerName,
|
|
176
|
+
baseUrl: this.baseUrl,
|
|
177
|
+
models: [this.model],
|
|
178
|
+
loadedModels: [this.model],
|
|
179
|
+
activeModel: this.model,
|
|
180
|
+
activeLoadedModel: this.model,
|
|
181
|
+
isModelAvailable: true,
|
|
182
|
+
isModelLoaded: true,
|
|
183
|
+
latencyMs: 0,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
133
186
|
try {
|
|
134
187
|
const controller = new AbortController();
|
|
135
188
|
const timeoutMs = process.env.NODE_ENV === 'test' || process.execArgv.includes('--test') ? 800 : 8000;
|
|
@@ -185,13 +238,38 @@ export class LLMClient {
|
|
|
185
238
|
.map(m => m.id || m.name)
|
|
186
239
|
.filter(Boolean);
|
|
187
240
|
|
|
188
|
-
|
|
241
|
+
let activeLoaded = loadedModels.length > 0 ? loadedModels[0] : null;
|
|
242
|
+
|
|
243
|
+
if (this.provider === 'lmstudio') {
|
|
244
|
+
try {
|
|
245
|
+
const rootUrl = String(this.baseUrl || '').replace(/\/v1\/?$/, '').replace(/\/+$/, '');
|
|
246
|
+
const v0Res = await fetch(`${rootUrl}/api/v0/models`, {
|
|
247
|
+
method: 'GET',
|
|
248
|
+
headers: this._buildHeaders(),
|
|
249
|
+
signal: controller.signal
|
|
250
|
+
});
|
|
251
|
+
if (v0Res.ok) {
|
|
252
|
+
const v0Data = await v0Res.json();
|
|
253
|
+
const v0List = Array.isArray(v0Data?.data) ? v0Data.data : [];
|
|
254
|
+
const v0Loaded = v0List
|
|
255
|
+
.filter(m => m && typeof m === 'object' && m.state === 'loaded')
|
|
256
|
+
.map(m => m.id)
|
|
257
|
+
.filter(Boolean);
|
|
258
|
+
if (v0Loaded.length > 0) {
|
|
259
|
+
activeLoaded = v0Loaded[0];
|
|
260
|
+
for (const id of v0Loaded) {
|
|
261
|
+
if (!loadedModels.includes(id)) loadedModels.push(id);
|
|
262
|
+
}
|
|
263
|
+
} else if (v0List.length > 0 && loadedModels.length === 0) {
|
|
264
|
+
activeLoaded = null;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
} catch {}
|
|
268
|
+
}
|
|
189
269
|
|
|
190
|
-
|
|
191
270
|
const exactMatch = models.find(m => m === this.model);
|
|
192
271
|
const fuzzyMatch = models.find(m => matchModelIdentifier(this.model, m));
|
|
193
272
|
|
|
194
|
-
|
|
195
273
|
const modelIsGemmaOrUnset = !this.model || /gemma/i.test(this.model);
|
|
196
274
|
const matchedGemma = modelIsGemmaOrUnset
|
|
197
275
|
? ((activeLoaded && /gemma/i.test(activeLoaded))
|
|
@@ -205,37 +283,53 @@ export class LLMClient {
|
|
|
205
283
|
)))
|
|
206
284
|
: null;
|
|
207
285
|
|
|
208
|
-
const
|
|
286
|
+
const isDefaultGemma = !this.model || this.model === 'google/gemma-4-e4b' || this.model === 'gemma-4-e4b';
|
|
287
|
+
const activeLoadedMatches = Boolean(activeLoaded && (
|
|
288
|
+
matchModelIdentifier(this.model, activeLoaded) ||
|
|
289
|
+
(isDefaultGemma && /gemma/i.test(activeLoaded))
|
|
290
|
+
));
|
|
291
|
+
|
|
292
|
+
const resolvedActive = exactMatch || (activeLoadedMatches ? activeLoaded : (activeLoaded || fuzzyMatch || matchedGemma || this.model));
|
|
209
293
|
|
|
210
|
-
|
|
211
294
|
if (this.provider === 'lmstudio' && models.length > 0) {
|
|
212
295
|
if (exactMatch) {
|
|
213
296
|
this.model = exactMatch;
|
|
214
|
-
} else if (
|
|
297
|
+
} else if (activeLoadedMatches) {
|
|
215
298
|
this.model = activeLoaded;
|
|
216
|
-
} else if (fuzzyMatch) {
|
|
299
|
+
} else if (fuzzyMatch && matchModelIdentifier(this.model, fuzzyMatch)) {
|
|
217
300
|
this.model = fuzzyMatch;
|
|
218
|
-
} else if (matchedGemma &&
|
|
301
|
+
} else if (matchedGemma && isDefaultGemma) {
|
|
219
302
|
this.model = matchedGemma;
|
|
220
303
|
}
|
|
221
304
|
}
|
|
222
305
|
|
|
223
306
|
const isAvailable = models.length === 0 ? true : (
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
307
|
+
exactMatch ? true : (
|
|
308
|
+
activeLoadedMatches ? true : (
|
|
309
|
+
(fuzzyMatch && matchModelIdentifier(this.model, fuzzyMatch)) ? true : (
|
|
310
|
+
this.modelExplicitlySet ? false : Boolean(activeLoaded && models.includes(activeLoaded))
|
|
311
|
+
)
|
|
312
|
+
)
|
|
313
|
+
)
|
|
227
314
|
);
|
|
228
315
|
|
|
316
|
+
const isLocal = this.provider === 'lmstudio' || this.provider === 'vllm' || this.provider === 'ollama';
|
|
317
|
+
const effectiveLoadedModels = loadedModels.length > 0
|
|
318
|
+
? loadedModels
|
|
319
|
+
: (isLocal ? [] : (models.length > 0 ? [models[0]] : []));
|
|
320
|
+
const isModelLoaded = isLocal ? Boolean(activeLoaded) : (models.length > 0);
|
|
321
|
+
|
|
229
322
|
return {
|
|
230
323
|
connected: true,
|
|
231
324
|
provider: this.provider,
|
|
232
325
|
providerName: this.providerName,
|
|
233
326
|
baseUrl: this.baseUrl,
|
|
234
327
|
models,
|
|
235
|
-
loadedModels:
|
|
328
|
+
loadedModels: effectiveLoadedModels,
|
|
236
329
|
activeLoadedModel: activeLoaded,
|
|
237
330
|
activeModel: resolvedActive,
|
|
238
331
|
isModelAvailable: isAvailable,
|
|
332
|
+
isModelLoaded,
|
|
239
333
|
latencyMs
|
|
240
334
|
};
|
|
241
335
|
} catch (err) {
|
|
@@ -262,7 +356,7 @@ export class LLMClient {
|
|
|
262
356
|
}
|
|
263
357
|
|
|
264
358
|
_isLegacyReasoningModel() {
|
|
265
|
-
return /^(o1|o3|deepseek-reasoner)/i.test(this.model) || this.model.includes('reasoner');
|
|
359
|
+
return /^(o1|o3|deepseek-reasoner)/i.test(this.model) || this.model.includes('reasoner') || /deepseek.*r1/i.test(this.model);
|
|
266
360
|
}
|
|
267
361
|
|
|
268
362
|
|
|
@@ -270,47 +364,174 @@ export class LLMClient {
|
|
|
270
364
|
return this._isLegacyReasoningModel();
|
|
271
365
|
}
|
|
272
366
|
|
|
367
|
+
async _chatAnthropic({ messages, tools, temperature, maxTokens, stream, signal, onToken, onThought }) {
|
|
368
|
+
const converted = toAnthropicMessages(messages);
|
|
369
|
+
const payload = {
|
|
370
|
+
model: this.model,
|
|
371
|
+
messages: converted.messages,
|
|
372
|
+
max_tokens: maxTokens,
|
|
373
|
+
stream,
|
|
374
|
+
};
|
|
375
|
+
if (converted.system) payload.system = converted.system;
|
|
376
|
+
if (temperature !== undefined && temperature !== null) payload.temperature = temperature;
|
|
377
|
+
const anthropicTools = toAnthropicTools(tools);
|
|
378
|
+
if (anthropicTools.length > 0) {
|
|
379
|
+
payload.tools = anthropicTools;
|
|
380
|
+
payload.tool_choice = { type: 'auto' };
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const response = await fetch(`${this.baseUrl}/v1/messages`, {
|
|
384
|
+
method: 'POST',
|
|
385
|
+
headers: this._buildHeaders(),
|
|
386
|
+
body: JSON.stringify(payload),
|
|
387
|
+
signal,
|
|
388
|
+
});
|
|
389
|
+
if (!response.ok) {
|
|
390
|
+
const errorText = await response.text();
|
|
391
|
+
const error = new Error(`${this.providerName} error (${response.status}): ${parseApiErrorText(errorText)}`);
|
|
392
|
+
error.status = response.status;
|
|
393
|
+
throw error;
|
|
394
|
+
}
|
|
395
|
+
if (!stream) return normalizeAnthropicResponse(await response.json());
|
|
396
|
+
|
|
397
|
+
const reader = response.body.getReader();
|
|
398
|
+
const decoder = new TextDecoder();
|
|
399
|
+
let buffer = '';
|
|
400
|
+
let content = '';
|
|
401
|
+
let reasoning = '';
|
|
402
|
+
const toolCalls = [];
|
|
403
|
+
const processEvent = (eventText) => {
|
|
404
|
+
const dataLine = eventText.split('\n').find(line => line.startsWith('data:'));
|
|
405
|
+
if (!dataLine) return;
|
|
406
|
+
let event;
|
|
407
|
+
try { event = JSON.parse(dataLine.replace(/^data:\s*/, '')); } catch { return; }
|
|
408
|
+
const block = event.content_block;
|
|
409
|
+
if (event.type === 'content_block_start' && block?.type === 'tool_use') {
|
|
410
|
+
toolCalls[event.index || toolCalls.length] = {
|
|
411
|
+
id: block.id || `call_${Date.now()}`,
|
|
412
|
+
type: 'function',
|
|
413
|
+
function: { name: block.name || '', arguments: '' },
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
const delta = event.delta;
|
|
417
|
+
if (!delta) return;
|
|
418
|
+
if (delta.type === 'text_delta') {
|
|
419
|
+
content += delta.text || '';
|
|
420
|
+
if (onToken) onToken(delta.text || '');
|
|
421
|
+
} else if (delta.type === 'thinking_delta') {
|
|
422
|
+
reasoning += delta.thinking || '';
|
|
423
|
+
if (onThought) onThought(delta.thinking || '');
|
|
424
|
+
} else if (delta.type === 'input_json_delta') {
|
|
425
|
+
const call = toolCalls[event.index || 0];
|
|
426
|
+
if (call) call.function.arguments += delta.partial_json || '';
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
while (true) {
|
|
430
|
+
const { done, value } = await reader.read();
|
|
431
|
+
if (done) break;
|
|
432
|
+
buffer += decoder.decode(value, { stream: true });
|
|
433
|
+
const events = buffer.split('\n\n');
|
|
434
|
+
buffer = events.pop() || '';
|
|
435
|
+
for (const event of events) processEvent(event);
|
|
436
|
+
}
|
|
437
|
+
if (buffer.trim()) processEvent(buffer);
|
|
438
|
+
return normalizeAnthropicResponse({ content: [
|
|
439
|
+
...(content ? [{ type: 'text', text: content }] : []),
|
|
440
|
+
...(reasoning ? [{ type: 'thinking', thinking: reasoning }] : []),
|
|
441
|
+
...toolCalls.filter(Boolean).map(call => ({ type: 'tool_use', id: call.id, name: call.function.name, input: parseAnthropicArgs(call.function.arguments) })),
|
|
442
|
+
] });
|
|
443
|
+
}
|
|
444
|
+
|
|
273
445
|
async chat(firstArg, secondArg = {}) {
|
|
274
|
-
let messages, tools, temperature, maxTokens, stream, signal, onToken, onThought;
|
|
446
|
+
let messages, tools, temperature, topP, minP, repeatPenalty, maxTokens, stream, signal, onToken, onThought, contextLimit, thinkingMode;
|
|
447
|
+
const inferenceSettings = typeof getPersistedInferenceSettings === 'function' ? getPersistedInferenceSettings() : {};
|
|
275
448
|
if (Array.isArray(firstArg)) {
|
|
276
449
|
messages = firstArg;
|
|
277
450
|
({
|
|
278
451
|
tools = null,
|
|
279
452
|
temperature = 0.3,
|
|
453
|
+
topP = null,
|
|
454
|
+
minP = inferenceSettings.minP ?? 0.08,
|
|
455
|
+
repeatPenalty = inferenceSettings.repeatPenalty ?? 1.10,
|
|
280
456
|
maxTokens = 8192,
|
|
281
457
|
stream = true,
|
|
282
458
|
signal = null,
|
|
283
459
|
onToken = null,
|
|
284
|
-
onThought = null
|
|
460
|
+
onThought = null,
|
|
461
|
+
contextLimit = undefined,
|
|
462
|
+
thinkingMode = null
|
|
285
463
|
} = secondArg || {});
|
|
286
464
|
} else {
|
|
287
465
|
({
|
|
288
466
|
messages,
|
|
289
467
|
tools = null,
|
|
290
468
|
temperature = 0.3,
|
|
469
|
+
topP = null,
|
|
470
|
+
minP = inferenceSettings.minP ?? 0.08,
|
|
471
|
+
repeatPenalty = inferenceSettings.repeatPenalty ?? 1.10,
|
|
291
472
|
maxTokens = 8192,
|
|
292
473
|
stream = true,
|
|
293
474
|
signal = null,
|
|
294
475
|
onToken = null,
|
|
295
|
-
onThought = null
|
|
476
|
+
onThought = null,
|
|
477
|
+
contextLimit = undefined,
|
|
478
|
+
thinkingMode = null
|
|
296
479
|
} = firstArg || {});
|
|
297
480
|
}
|
|
481
|
+
const dispatch = prepareChatMessagesForDispatch(messages, {
|
|
482
|
+
tools,
|
|
483
|
+
maxContextTokens: Number(contextLimit) || Number(process.env.KIKU_CONTEXT_LIMIT) || 32768,
|
|
484
|
+
generationReserve: maxTokens,
|
|
485
|
+
pruneAt: 0.85,
|
|
486
|
+
});
|
|
487
|
+
messages = dispatch.messages;
|
|
488
|
+
tools = dispatch.tools;
|
|
489
|
+
this.lastContextBudget = dispatch.budget;
|
|
490
|
+
if (this.provider === 'anthropic') {
|
|
491
|
+
return this._chatAnthropic({ messages, tools, temperature, maxTokens, stream, signal, onToken, onThought });
|
|
492
|
+
}
|
|
298
493
|
const isLegacyReasoningModel = this._isLegacyReasoningModel();
|
|
299
494
|
const usesCompletionTokenLimit = /^(o1|o3|gpt-5(?:\.|-|$))/i.test(this.model);
|
|
300
495
|
const rejectsTemperature = /^(o1|o3|gpt-5(?:\.|-|$))/i.test(this.model);
|
|
301
496
|
|
|
302
497
|
this.toolsDisabledReason = null;
|
|
303
498
|
|
|
499
|
+
|
|
500
|
+
const sanitizedMessages = messages.map(m => {
|
|
501
|
+
if (m && m.role === 'assistant' && m.reasoning_content) {
|
|
502
|
+
const { reasoning_content, ...rest } = m;
|
|
503
|
+
return rest;
|
|
504
|
+
}
|
|
505
|
+
return m;
|
|
506
|
+
});
|
|
507
|
+
|
|
304
508
|
const buildPayload = (includeTools = true, includeTemp = true) => {
|
|
305
509
|
const p = {
|
|
306
510
|
model: this.model,
|
|
307
|
-
messages,
|
|
511
|
+
messages: sanitizedMessages,
|
|
308
512
|
stream
|
|
309
513
|
};
|
|
310
514
|
|
|
311
515
|
if (!rejectsTemperature && includeTemp && temperature !== undefined && temperature !== null) {
|
|
312
516
|
p.temperature = temperature;
|
|
313
517
|
}
|
|
518
|
+
const customTemp = temperature !== undefined && temperature !== null && Number(temperature) !== 0.3;
|
|
519
|
+
if (!rejectsTemperature && topP !== undefined && topP !== null && !customTemp) {
|
|
520
|
+
const tp = Number(topP);
|
|
521
|
+
if (Number.isFinite(tp)) p.top_p = Math.min(1, Math.max(0.01, tp));
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const isLocalOrCompatible = this.provider === 'lmstudio' || this.provider === 'vllm' || this.provider === 'custom' || isLocalInferenceEndpoint(this.baseUrl);
|
|
525
|
+
if (!rejectsTemperature && !isLegacyReasoningModel && isLocalOrCompatible) {
|
|
526
|
+
if (minP !== undefined && minP !== null) {
|
|
527
|
+
const mp = Number(minP);
|
|
528
|
+
if (Number.isFinite(mp) && mp > 0 && mp <= 1) p.min_p = mp;
|
|
529
|
+
}
|
|
530
|
+
if (repeatPenalty !== undefined && repeatPenalty !== null) {
|
|
531
|
+
const rp = Number(repeatPenalty);
|
|
532
|
+
if (Number.isFinite(rp) && rp >= 1 && rp <= 2) p.repeat_penalty = rp;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
314
535
|
|
|
315
536
|
if (usesCompletionTokenLimit) {
|
|
316
537
|
p.max_completion_tokens = maxTokens;
|
|
@@ -331,7 +552,11 @@ export class LLMClient {
|
|
|
331
552
|
p.stream_options = { include_usage: true };
|
|
332
553
|
}
|
|
333
554
|
|
|
334
|
-
return p
|
|
555
|
+
return applyModelRequestOptions(p, {
|
|
556
|
+
provider: this.provider,
|
|
557
|
+
model: this.model,
|
|
558
|
+
thinkingMode,
|
|
559
|
+
});
|
|
335
560
|
};
|
|
336
561
|
|
|
337
562
|
let payload = buildPayload(true, true);
|
|
@@ -381,6 +606,10 @@ export class LLMClient {
|
|
|
381
606
|
} else if (lowerErr.includes('temperature') && payload.temperature !== undefined) {
|
|
382
607
|
payload = buildPayload(Boolean(payload.tools), false);
|
|
383
608
|
retryNeeded = true;
|
|
609
|
+
} else if ((lowerErr.includes('min_p') || lowerErr.includes('repeat_penalty') || lowerErr.includes('extra') || lowerErr.includes('additional properties')) && (payload.min_p !== undefined || payload.repeat_penalty !== undefined)) {
|
|
610
|
+
delete payload.min_p;
|
|
611
|
+
delete payload.repeat_penalty;
|
|
612
|
+
retryNeeded = true;
|
|
384
613
|
} else if (lowerErr.includes('max_tokens') && payload.max_tokens !== undefined) {
|
|
385
614
|
delete payload.max_tokens;
|
|
386
615
|
payload.max_completion_tokens = maxTokens;
|
|
@@ -442,11 +671,8 @@ export class LLMClient {
|
|
|
442
671
|
const data = await res.json();
|
|
443
672
|
clearTimeout(timeoutId);
|
|
444
673
|
if (signal) signal.removeEventListener('abort', abortHandler);
|
|
445
|
-
const msg = data.choices && data.choices[0] ? data.choices[0].message : null;
|
|
674
|
+
const msg = data.choices && data.choices[0] ? normalizeAssistantMessage(data.choices[0].message || {}) : null;
|
|
446
675
|
if (msg) {
|
|
447
|
-
if (!msg.reasoning_content && (msg.reasoning || msg.thinking)) {
|
|
448
|
-
msg.reasoning_content = msg.reasoning || msg.thinking;
|
|
449
|
-
}
|
|
450
676
|
if (data.usage) {
|
|
451
677
|
msg.usage = {
|
|
452
678
|
prompt_tokens: data.usage.prompt_tokens || 0,
|
|
@@ -476,6 +702,56 @@ export class LLMClient {
|
|
|
476
702
|
let buffer = '';
|
|
477
703
|
let reportedUsage = null;
|
|
478
704
|
let malformedChunks = 0;
|
|
705
|
+
const contentNormalizer = new StreamContentNormalizer();
|
|
706
|
+
const processSseLine = line => {
|
|
707
|
+
const trimmed = line.trim();
|
|
708
|
+
if (!trimmed || !trimmed.startsWith('data:')) return;
|
|
709
|
+
const jsonStr = trimmed.replace(/^data:\s*/, '');
|
|
710
|
+
if (jsonStr === '[DONE]') return;
|
|
711
|
+
|
|
712
|
+
try {
|
|
713
|
+
const parsed = JSON.parse(jsonStr);
|
|
714
|
+
if (parsed.usage) reportedUsage = parsed.usage;
|
|
715
|
+
const delta = parsed.choices?.[0]?.delta;
|
|
716
|
+
if (!delta) return;
|
|
717
|
+
|
|
718
|
+
const reasoning = delta.reasoning_content || delta.reasoning || delta.thinking;
|
|
719
|
+
if (reasoning) {
|
|
720
|
+
fullReasoning += reasoning;
|
|
721
|
+
if (onThought) onThought(reasoning);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
if (delta.content) {
|
|
725
|
+
const contentParts = contentNormalizer.push(delta.content);
|
|
726
|
+
if (contentParts.reasoning) {
|
|
727
|
+
fullReasoning += contentParts.reasoning;
|
|
728
|
+
if (onThought) onThought(contentParts.reasoning);
|
|
729
|
+
}
|
|
730
|
+
if (contentParts.answer) {
|
|
731
|
+
fullContent += contentParts.answer;
|
|
732
|
+
if (onToken) onToken(contentParts.answer);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
const streamedToolCalls = delta.tool_calls || (delta.function_call ? [{ index: 0, function: delta.function_call }] : null);
|
|
737
|
+
if (streamedToolCalls) {
|
|
738
|
+
for (const tc of streamedToolCalls) {
|
|
739
|
+
const idx = tc.index ?? 0;
|
|
740
|
+
if (!toolCalls[idx]) {
|
|
741
|
+
toolCalls[idx] = {
|
|
742
|
+
id: tc.id || `call_${Date.now()}_${idx}`,
|
|
743
|
+
type: 'function',
|
|
744
|
+
function: { name: '', arguments: '' }
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
if (tc.function?.name) toolCalls[idx].function.name += tc.function.name;
|
|
748
|
+
if (tc.function?.arguments) toolCalls[idx].function.arguments += tc.function.arguments;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
} catch {
|
|
752
|
+
malformedChunks++;
|
|
753
|
+
}
|
|
754
|
+
};
|
|
479
755
|
|
|
480
756
|
while (true) {
|
|
481
757
|
const { done, value } = await reader.read();
|
|
@@ -486,50 +762,19 @@ export class LLMClient {
|
|
|
486
762
|
buffer += decoder.decode(value, { stream: true });
|
|
487
763
|
const lines = buffer.split('\n');
|
|
488
764
|
buffer = lines.pop() || '';
|
|
765
|
+
for (const line of lines) processSseLine(line);
|
|
766
|
+
}
|
|
489
767
|
|
|
490
|
-
|
|
491
|
-
const trimmed = line.trim();
|
|
492
|
-
if (!trimmed || !trimmed.startsWith('data:')) continue;
|
|
493
|
-
const jsonStr = trimmed.replace(/^data:\s*/, '');
|
|
494
|
-
if (jsonStr === '[DONE]') continue;
|
|
495
|
-
|
|
496
|
-
try {
|
|
497
|
-
const parsed = JSON.parse(jsonStr);
|
|
498
|
-
if (parsed.usage) {
|
|
499
|
-
reportedUsage = parsed.usage;
|
|
500
|
-
}
|
|
501
|
-
const delta = parsed.choices?.[0]?.delta;
|
|
502
|
-
if (!delta) continue;
|
|
503
|
-
|
|
504
|
-
const reasoning = delta.reasoning_content || delta.reasoning || delta.thinking;
|
|
505
|
-
if (reasoning) {
|
|
506
|
-
fullReasoning += reasoning;
|
|
507
|
-
if (onThought) onThought(reasoning);
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
if (delta.content) {
|
|
511
|
-
fullContent += delta.content;
|
|
512
|
-
if (onToken) onToken(delta.content);
|
|
513
|
-
}
|
|
768
|
+
if (buffer.trim()) processSseLine(buffer);
|
|
514
769
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
};
|
|
524
|
-
}
|
|
525
|
-
if (tc.function?.name) toolCalls[idx].function.name += tc.function.name;
|
|
526
|
-
if (tc.function?.arguments) toolCalls[idx].function.arguments += tc.function.arguments;
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
} catch {
|
|
530
|
-
malformedChunks++;
|
|
531
|
-
}
|
|
532
|
-
}
|
|
770
|
+
const finalContentParts = contentNormalizer.flush();
|
|
771
|
+
if (finalContentParts.reasoning) {
|
|
772
|
+
fullReasoning += finalContentParts.reasoning;
|
|
773
|
+
if (onThought) onThought(finalContentParts.reasoning);
|
|
774
|
+
}
|
|
775
|
+
if (finalContentParts.answer) {
|
|
776
|
+
fullContent += finalContentParts.answer;
|
|
777
|
+
if (onToken) onToken(finalContentParts.answer);
|
|
533
778
|
}
|
|
534
779
|
|
|
535
780
|
clearTimeout(timeoutId);
|
|
@@ -555,9 +800,10 @@ export class LLMClient {
|
|
|
555
800
|
|
|
556
801
|
const totalTokens = reportedUsage?.total_tokens ?? (promptTokens + completionTokens);
|
|
557
802
|
|
|
558
|
-
const
|
|
803
|
+
const rawToolContent = finalContentParts.toolCallBuffer || '';
|
|
804
|
+
const resultMessage = normalizeAssistantMessage({
|
|
559
805
|
role: 'assistant',
|
|
560
|
-
content: fullContent,
|
|
806
|
+
content: fullContent + (rawToolContent ? `\n${rawToolContent}` : ''),
|
|
561
807
|
reasoning_content: fullReasoning,
|
|
562
808
|
usage: {
|
|
563
809
|
prompt_tokens: promptTokens,
|
|
@@ -565,7 +811,7 @@ export class LLMClient {
|
|
|
565
811
|
reasoning_tokens: reasoningTokens,
|
|
566
812
|
total_tokens: totalTokens
|
|
567
813
|
}
|
|
568
|
-
};
|
|
814
|
+
});
|
|
569
815
|
|
|
570
816
|
if (toolCalls.length > 0) {
|
|
571
817
|
resultMessage.tool_calls = toolCalls.filter(Boolean);
|