openfox 1.6.20 → 1.6.23
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 +46 -13
- package/dist/{auto-compaction-NFLWKJAO.js → auto-compaction-7LK3QGPI.js} +4 -3
- package/dist/{chat-handler-2RVTVNPF.js → chat-handler-ZU74RXED.js} +6 -5
- package/dist/{chunk-KOBGCNUE.js → chunk-4O3C2JMB.js} +58 -14
- package/dist/{chunk-QY7BMXWT.js → chunk-B7E3BICY.js} +6 -2
- package/dist/{chunk-U3KMU3UE.js → chunk-BOEXJCOD.js} +2 -2
- package/dist/{chunk-IN5EP4ZB.js → chunk-CJNQGEYG.js} +2 -2
- package/dist/{chunk-OVLFEBRR.js → chunk-D4ZLSV6P.js} +133 -468
- package/dist/{chunk-LTPZ4GTW.js → chunk-FTJPNCAV.js} +6 -6
- package/dist/chunk-KSASIV4B.js +486 -0
- package/dist/{chunk-ZDNXCVW4.js → chunk-P6GUT2QQ.js} +11 -5
- package/dist/{chunk-KOUMYBYM.js → chunk-PYBB34ZK.js} +54 -3
- package/dist/{chunk-R7UAGXQW.js → chunk-YCQSAFAQ.js} +42 -24
- package/dist/cli/dev.js +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/{config-67AX6CNS.js → config-ACVEHBKG.js} +5 -5
- package/dist/{orchestrator-ZICQ5NIZ.js → orchestrator-OE7WFEW6.js} +5 -4
- package/dist/package.json +3 -3
- package/dist/{processor-MPSYT534.js → processor-GAOK7TPI.js} +2 -2
- package/dist/{protocol-I7rn7Msg.d.ts → protocol-BYM6rZvW.d.ts} +12 -0
- package/dist/{provider-DKGBQHUS.js → provider-RLQMVV2Z.js} +9 -8
- package/dist/{serve-B5A52252.js → serve-IBJ3SN3Q.js} +10 -10
- package/dist/server/index.d.ts +24 -1
- package/dist/server/index.js +8 -8
- package/dist/shared/index.d.ts +2 -2
- package/dist/shared/index.js +1 -1
- package/dist/{tools-ERJ3QRQU.js → tools-CSV7G554.js} +4 -3
- package/dist/{vision-fallback-ADYRFFD4.js → vision-fallback-QJ26DCEF.js} +2 -2
- package/dist/web/assets/index-BrZqZwpA.js +153 -0
- package/dist/web/assets/{index-vBklayFL.css → index-DF26vcqg.css} +1 -1
- package/dist/web/index.html +2 -2
- package/dist/web/sw.js +1 -1
- package/package.json +3 -3
- package/dist/chunk-55N6FAAZ.js +0 -117
- package/dist/web/assets/index-vOba1XKB.js +0 -151
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
-
describeImageFromDataUrl
|
|
3
|
-
|
|
4
|
-
} from "./chunk-IN5EP4ZB.js";
|
|
2
|
+
describeImageFromDataUrl
|
|
3
|
+
} from "./chunk-CJNQGEYG.js";
|
|
5
4
|
import {
|
|
6
5
|
logger
|
|
7
6
|
} from "./chunk-PNBH3RAX.js";
|
|
@@ -167,134 +166,6 @@ function getBackendDisplayName(backend) {
|
|
|
167
166
|
}
|
|
168
167
|
}
|
|
169
168
|
|
|
170
|
-
// src/server/llm/models.ts
|
|
171
|
-
var modelCache = /* @__PURE__ */ new Map();
|
|
172
|
-
var llmStatus = "unknown";
|
|
173
|
-
var lastActiveUrl = null;
|
|
174
|
-
var CACHE_TTL_MS = 3e4;
|
|
175
|
-
function getCacheKey(url) {
|
|
176
|
-
return url.replace(/\/v1\/?$/, "");
|
|
177
|
-
}
|
|
178
|
-
async function detectModel(llmBaseUrl, retries = 3, silent = false) {
|
|
179
|
-
const cacheKey = getCacheKey(llmBaseUrl);
|
|
180
|
-
const now = Date.now();
|
|
181
|
-
const cached = modelCache.get(cacheKey);
|
|
182
|
-
if (cached && now - cached.timestamp < CACHE_TTL_MS) {
|
|
183
|
-
lastActiveUrl = cacheKey;
|
|
184
|
-
llmStatus = "connected";
|
|
185
|
-
return cached.model;
|
|
186
|
-
}
|
|
187
|
-
const url = llmBaseUrl.includes("/v1") ? `${llmBaseUrl}/models` : `${llmBaseUrl}/v1/models`;
|
|
188
|
-
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
189
|
-
try {
|
|
190
|
-
if (silent) {
|
|
191
|
-
logger.debug("Fetching models from LLM server", { url, attempt });
|
|
192
|
-
}
|
|
193
|
-
const response = await fetch(url, {
|
|
194
|
-
signal: AbortSignal.timeout(1e4)
|
|
195
|
-
});
|
|
196
|
-
if (!response.ok) {
|
|
197
|
-
if (silent) {
|
|
198
|
-
logger.debug("Failed to fetch models from LLM server", { status: response.status, attempt });
|
|
199
|
-
} else {
|
|
200
|
-
logger.warn("Failed to fetch models from LLM server", { status: response.status, attempt });
|
|
201
|
-
}
|
|
202
|
-
if (attempt < retries) {
|
|
203
|
-
await new Promise((r) => setTimeout(r, 1e3 * attempt));
|
|
204
|
-
continue;
|
|
205
|
-
}
|
|
206
|
-
llmStatus = "disconnected";
|
|
207
|
-
return cached?.model ?? null;
|
|
208
|
-
}
|
|
209
|
-
const data = await response.json();
|
|
210
|
-
if (data.data && data.data.length > 0) {
|
|
211
|
-
const modelData = data.data[0];
|
|
212
|
-
const modelId = modelData.id;
|
|
213
|
-
modelCache.set(cacheKey, {
|
|
214
|
-
model: modelId,
|
|
215
|
-
modelInfo: modelData,
|
|
216
|
-
timestamp: now
|
|
217
|
-
});
|
|
218
|
-
lastActiveUrl = cacheKey;
|
|
219
|
-
llmStatus = "connected";
|
|
220
|
-
if (silent) {
|
|
221
|
-
logger.debug("Detected LLM model", {
|
|
222
|
-
model: modelId,
|
|
223
|
-
maxLen: modelData.max_model_len,
|
|
224
|
-
root: modelData.root
|
|
225
|
-
});
|
|
226
|
-
} else {
|
|
227
|
-
logger.info("Detected LLM model", {
|
|
228
|
-
model: modelId,
|
|
229
|
-
maxLen: modelData.max_model_len,
|
|
230
|
-
root: modelData.root
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
return modelId;
|
|
234
|
-
}
|
|
235
|
-
if (silent) {
|
|
236
|
-
logger.debug("LLM server returned empty models list");
|
|
237
|
-
} else {
|
|
238
|
-
logger.warn("LLM server returned empty models list");
|
|
239
|
-
}
|
|
240
|
-
llmStatus = "disconnected";
|
|
241
|
-
return null;
|
|
242
|
-
} catch (error) {
|
|
243
|
-
const errMsg = error instanceof Error ? error.message : String(error);
|
|
244
|
-
if (silent) {
|
|
245
|
-
logger.debug("Could not detect model from LLM server", { error: errMsg, attempt });
|
|
246
|
-
} else {
|
|
247
|
-
logger.warn("Could not detect model from LLM server", { error: errMsg, attempt });
|
|
248
|
-
}
|
|
249
|
-
if (attempt < retries) {
|
|
250
|
-
await new Promise((r) => setTimeout(r, 1e3 * attempt));
|
|
251
|
-
continue;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
llmStatus = "disconnected";
|
|
256
|
-
return cached?.model ?? null;
|
|
257
|
-
}
|
|
258
|
-
function getLlmStatus() {
|
|
259
|
-
return llmStatus;
|
|
260
|
-
}
|
|
261
|
-
function setLlmStatus(status) {
|
|
262
|
-
llmStatus = status;
|
|
263
|
-
}
|
|
264
|
-
function clearModelCache(url) {
|
|
265
|
-
if (url) {
|
|
266
|
-
modelCache.delete(getCacheKey(url));
|
|
267
|
-
} else {
|
|
268
|
-
modelCache.clear();
|
|
269
|
-
}
|
|
270
|
-
llmStatus = "unknown";
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// src/server/llm/client.ts
|
|
274
|
-
import OpenAI from "openai";
|
|
275
|
-
|
|
276
|
-
// src/server/utils/errors.ts
|
|
277
|
-
var OpenFoxError = class extends Error {
|
|
278
|
-
constructor(message, code, details) {
|
|
279
|
-
super(message);
|
|
280
|
-
this.code = code;
|
|
281
|
-
this.details = details;
|
|
282
|
-
this.name = "OpenFoxError";
|
|
283
|
-
}
|
|
284
|
-
};
|
|
285
|
-
var SessionNotFoundError = class extends OpenFoxError {
|
|
286
|
-
constructor(sessionId) {
|
|
287
|
-
super(`Session not found: ${sessionId}`, "SESSION_NOT_FOUND", { sessionId });
|
|
288
|
-
this.name = "SessionNotFoundError";
|
|
289
|
-
}
|
|
290
|
-
};
|
|
291
|
-
var LLMError = class extends OpenFoxError {
|
|
292
|
-
constructor(message, details) {
|
|
293
|
-
super(message, "LLM_ERROR", details);
|
|
294
|
-
this.name = "LLMError";
|
|
295
|
-
}
|
|
296
|
-
};
|
|
297
|
-
|
|
298
169
|
// src/server/llm/profiles.ts
|
|
299
170
|
var DEFAULT_PROFILE = {
|
|
300
171
|
name: "default",
|
|
@@ -493,6 +364,14 @@ function getModelProfile(modelName) {
|
|
|
493
364
|
}
|
|
494
365
|
|
|
495
366
|
// src/server/llm/client-pure.ts
|
|
367
|
+
function buildModelParams(params) {
|
|
368
|
+
return {
|
|
369
|
+
...params.temperature !== void 0 && { temperature: params.temperature },
|
|
370
|
+
...params.topP !== void 0 && { topP: params.topP },
|
|
371
|
+
...params.topK !== void 0 && { topK: params.topK },
|
|
372
|
+
...params.maxTokens !== void 0 && { maxTokens: params.maxTokens }
|
|
373
|
+
};
|
|
374
|
+
}
|
|
496
375
|
function buildAttachmentContent(msgContent, attachments, modelSupportsVision) {
|
|
497
376
|
const content = [];
|
|
498
377
|
if (msgContent?.trim()) {
|
|
@@ -705,27 +584,32 @@ async function buildChatCompletionCreateParams(model, request, profile, capabili
|
|
|
705
584
|
onVisionFallbackStart,
|
|
706
585
|
onVisionFallbackDone
|
|
707
586
|
);
|
|
587
|
+
const temperature = request.temperature ?? profile.temperature;
|
|
588
|
+
const maxTokens = request.maxTokens ?? profile.defaultMaxTokens;
|
|
589
|
+
const topP = profile.topP;
|
|
590
|
+
const topK = capabilities.supportsTopK ? profile.topK : void 0;
|
|
708
591
|
const params = {
|
|
709
592
|
model,
|
|
710
593
|
messages: convertedMessages,
|
|
711
594
|
...request.tools ? { tools: convertTools(request.tools) } : {},
|
|
712
595
|
...request.toolChoice ? { tool_choice: request.toolChoice } : {},
|
|
713
|
-
temperature
|
|
714
|
-
max_tokens:
|
|
715
|
-
top_p:
|
|
596
|
+
temperature,
|
|
597
|
+
max_tokens: maxTokens,
|
|
598
|
+
top_p: topP,
|
|
716
599
|
stream: isStreaming,
|
|
717
600
|
...isStreaming ? { stream_options: { include_usage: true } } : {}
|
|
718
601
|
};
|
|
719
|
-
if (
|
|
602
|
+
if (topK !== void 0) {
|
|
720
603
|
;
|
|
721
|
-
params["top_k"] =
|
|
604
|
+
params["top_k"] = topK;
|
|
722
605
|
}
|
|
723
606
|
const shouldDisableThinking = isStreaming ? disableThinking || request.disableThinking : disableThinking;
|
|
724
607
|
if (capabilities.supportsChatTemplateKwargs && profile.supportsReasoning && shouldDisableThinking) {
|
|
725
608
|
;
|
|
726
609
|
params["chat_template_kwargs"] = { enable_thinking: false };
|
|
727
610
|
}
|
|
728
|
-
|
|
611
|
+
const modelParams = buildModelParams({ temperature, topP, topK, maxTokens });
|
|
612
|
+
return { params, modelParams };
|
|
729
613
|
}
|
|
730
614
|
async function buildCreateParamsFromInput(input, isStreaming) {
|
|
731
615
|
const { model, request, profile, capabilities, disableThinking, visionFallbackEnabled = false, onVisionFallbackStart, onVisionFallbackDone } = input;
|
|
@@ -762,348 +646,129 @@ function extractThinking(content) {
|
|
|
762
646
|
};
|
|
763
647
|
}
|
|
764
648
|
|
|
765
|
-
// src/server/llm/
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
let
|
|
774
|
-
let
|
|
775
|
-
let
|
|
776
|
-
const
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
temperature: newProfile.temperature,
|
|
800
|
-
supportsReasoning: newProfile.supportsReasoning
|
|
801
|
-
});
|
|
802
|
-
model = newModel;
|
|
803
|
-
profile = newProfile;
|
|
804
|
-
},
|
|
805
|
-
async complete(request) {
|
|
806
|
-
logger.debug("LLM complete request", {
|
|
807
|
-
messageCount: request.messages.length,
|
|
808
|
-
hasTools: !!request.tools?.length,
|
|
809
|
-
profile: profile.name,
|
|
810
|
-
disableThinking
|
|
811
|
-
});
|
|
812
|
-
try {
|
|
813
|
-
const shouldDisableThinking = disableThinking || request.disableThinking === true;
|
|
814
|
-
await ensureVisionFallbackConfigLoaded();
|
|
815
|
-
const { isVisionFallbackEnabled } = await import("./vision-fallback-ADYRFFD4.js");
|
|
816
|
-
const paramsOptions = {
|
|
817
|
-
model,
|
|
818
|
-
request,
|
|
819
|
-
profile,
|
|
820
|
-
capabilities,
|
|
821
|
-
disableThinking: shouldDisableThinking,
|
|
822
|
-
visionFallbackEnabled: isVisionFallbackEnabled()
|
|
823
|
-
};
|
|
824
|
-
if (request.onVisionFallbackStart) {
|
|
825
|
-
paramsOptions.onVisionFallbackStart = request.onVisionFallbackStart;
|
|
826
|
-
}
|
|
827
|
-
if (request.onVisionFallbackDone) {
|
|
828
|
-
paramsOptions.onVisionFallbackDone = request.onVisionFallbackDone;
|
|
829
|
-
}
|
|
830
|
-
const createParams = await buildNonStreamingCreateParams(paramsOptions);
|
|
831
|
-
const response = await openai.chat.completions.create(createParams, {
|
|
832
|
-
signal: request.signal
|
|
833
|
-
});
|
|
834
|
-
const choice = response.choices[0];
|
|
835
|
-
if (!choice) {
|
|
836
|
-
throw new LLMError("No completion choice returned");
|
|
837
|
-
}
|
|
838
|
-
const message = choice.message;
|
|
839
|
-
let content = message.content ?? "";
|
|
840
|
-
let thinkingContent = "";
|
|
841
|
-
if (profile.supportsReasoning) {
|
|
842
|
-
if (capabilities.supportsReasoningField) {
|
|
843
|
-
thinkingContent = message.reasoning_content ?? message.reasoning ?? "";
|
|
844
|
-
} else {
|
|
845
|
-
const extracted = extractThinking(content);
|
|
846
|
-
content = extracted.content;
|
|
847
|
-
thinkingContent = extracted.thinkingContent ?? "";
|
|
848
|
-
}
|
|
849
|
-
if (profile.reasoningAsContent && thinkingContent.trim()) {
|
|
850
|
-
content = thinkingContent;
|
|
851
|
-
thinkingContent = "";
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
if (!content.trim() && thinkingContent.trim()) {
|
|
855
|
-
content = thinkingContent;
|
|
856
|
-
thinkingContent = "";
|
|
857
|
-
}
|
|
858
|
-
const toolCalls = message.tool_calls?.map((tc) => ({
|
|
859
|
-
id: tc.id,
|
|
860
|
-
name: tc.function.name,
|
|
861
|
-
arguments: JSON.parse(tc.function.arguments)
|
|
862
|
-
}));
|
|
863
|
-
return {
|
|
864
|
-
id: response.id,
|
|
865
|
-
content,
|
|
866
|
-
...thinkingContent ? { thinkingContent } : {},
|
|
867
|
-
...toolCalls && toolCalls.length > 0 ? { toolCalls } : {},
|
|
868
|
-
finishReason: mapFinishReason(choice.finish_reason),
|
|
869
|
-
usage: {
|
|
870
|
-
promptTokens: response.usage?.prompt_tokens ?? 0,
|
|
871
|
-
completionTokens: response.usage?.completion_tokens ?? 0,
|
|
872
|
-
totalTokens: response.usage?.total_tokens ?? 0
|
|
873
|
-
}
|
|
874
|
-
};
|
|
875
|
-
} catch (error) {
|
|
876
|
-
logger.error("LLM complete error", { error: error.toString() });
|
|
877
|
-
throw new LLMError(
|
|
878
|
-
error instanceof Error ? error.message : "Unknown LLM error",
|
|
879
|
-
{ originalError: error }
|
|
880
|
-
);
|
|
881
|
-
}
|
|
882
|
-
},
|
|
883
|
-
async *stream(request) {
|
|
884
|
-
logger.debug("LLM stream request", {
|
|
885
|
-
messageCount: request.messages.length,
|
|
886
|
-
hasTools: !!request.tools?.length,
|
|
887
|
-
profile: profile.name,
|
|
888
|
-
disableThinking,
|
|
889
|
-
idleTimeout
|
|
890
|
-
});
|
|
891
|
-
try {
|
|
892
|
-
await ensureVisionFallbackConfigLoaded();
|
|
893
|
-
const { isVisionFallbackEnabled } = await import("./vision-fallback-ADYRFFD4.js");
|
|
894
|
-
const shouldDisableThinking = disableThinking || request.disableThinking === true;
|
|
895
|
-
const createParams = await buildStreamingCreateParams({
|
|
896
|
-
model,
|
|
897
|
-
request,
|
|
898
|
-
profile,
|
|
899
|
-
capabilities,
|
|
900
|
-
disableThinking: shouldDisableThinking,
|
|
901
|
-
visionFallbackEnabled: isVisionFallbackEnabled(),
|
|
902
|
-
onVisionFallbackStart: request.onVisionFallbackStart,
|
|
903
|
-
onVisionFallbackDone: request.onVisionFallbackDone
|
|
904
|
-
});
|
|
905
|
-
const stream = await openai.chat.completions.create(createParams, {
|
|
906
|
-
signal: request.signal
|
|
907
|
-
});
|
|
908
|
-
let fullContent = "";
|
|
909
|
-
let fullThinking = "";
|
|
910
|
-
let inThinking = false;
|
|
911
|
-
let tagBuffer = "";
|
|
912
|
-
const toolCalls = /* @__PURE__ */ new Map();
|
|
913
|
-
let finishReason = "stop";
|
|
914
|
-
let usage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
|
|
915
|
-
let responseId = "";
|
|
916
|
-
let lastChunkTime = Date.now();
|
|
917
|
-
const idleTimeoutController = new AbortController();
|
|
918
|
-
const idleTimer = setInterval(() => {
|
|
919
|
-
const idleDuration = Date.now() - lastChunkTime;
|
|
920
|
-
if (idleDuration > idleTimeout) {
|
|
921
|
-
logger.warn("LLM stream idle timeout triggered", { idleDuration, idleTimeout });
|
|
922
|
-
idleTimeoutController.abort();
|
|
923
|
-
}
|
|
924
|
-
}, 100);
|
|
925
|
-
try {
|
|
926
|
-
for await (const chunk of stream) {
|
|
927
|
-
if (idleTimeoutController.signal.aborted) {
|
|
928
|
-
throw new Error(`LLM stream idle timeout: no chunks received for ${idleTimeout}ms`);
|
|
929
|
-
}
|
|
930
|
-
lastChunkTime = Date.now();
|
|
931
|
-
responseId = chunk.id;
|
|
932
|
-
if (chunk.usage) {
|
|
933
|
-
usage = {
|
|
934
|
-
promptTokens: chunk.usage.prompt_tokens,
|
|
935
|
-
completionTokens: chunk.usage.completion_tokens,
|
|
936
|
-
totalTokens: chunk.usage.total_tokens
|
|
937
|
-
};
|
|
938
|
-
}
|
|
939
|
-
const choice = chunk.choices[0];
|
|
940
|
-
if (!choice) continue;
|
|
941
|
-
if (choice.finish_reason) {
|
|
942
|
-
finishReason = mapFinishReason(choice.finish_reason);
|
|
943
|
-
}
|
|
944
|
-
const delta = choice.delta;
|
|
945
|
-
if (capabilities.supportsReasoningField) {
|
|
946
|
-
const reasoning = delta.reasoning_content ?? delta.reasoning;
|
|
947
|
-
if (reasoning) {
|
|
948
|
-
if (profile.supportsReasoning && !profile.reasoningAsContent) {
|
|
949
|
-
fullThinking += reasoning;
|
|
950
|
-
yield { type: "thinking_delta", content: reasoning };
|
|
951
|
-
} else {
|
|
952
|
-
fullContent += reasoning;
|
|
953
|
-
yield { type: "text_delta", content: reasoning };
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
if (delta.content) {
|
|
958
|
-
fullContent += delta.content;
|
|
959
|
-
if (!capabilities.supportsReasoningField && profile.supportsReasoning && !profile.reasoningAsContent) {
|
|
960
|
-
tagBuffer += delta.content;
|
|
961
|
-
while (tagBuffer.length > 0) {
|
|
962
|
-
if (inThinking) {
|
|
963
|
-
const closeIdx = tagBuffer.indexOf("</think>");
|
|
964
|
-
if (closeIdx !== -1) {
|
|
965
|
-
const thinkText = tagBuffer.slice(0, closeIdx);
|
|
966
|
-
if (thinkText) {
|
|
967
|
-
fullThinking += thinkText;
|
|
968
|
-
yield { type: "thinking_delta", content: thinkText };
|
|
969
|
-
}
|
|
970
|
-
tagBuffer = tagBuffer.slice(closeIdx + "</think>".length);
|
|
971
|
-
inThinking = false;
|
|
972
|
-
} else if (tagBuffer.includes("</")) {
|
|
973
|
-
const partialIdx = tagBuffer.lastIndexOf("</");
|
|
974
|
-
const before = tagBuffer.slice(0, partialIdx);
|
|
975
|
-
if (before) {
|
|
976
|
-
fullThinking += before;
|
|
977
|
-
yield { type: "thinking_delta", content: before };
|
|
978
|
-
}
|
|
979
|
-
tagBuffer = tagBuffer.slice(partialIdx);
|
|
980
|
-
break;
|
|
981
|
-
} else {
|
|
982
|
-
fullThinking += tagBuffer;
|
|
983
|
-
yield { type: "thinking_delta", content: tagBuffer };
|
|
984
|
-
tagBuffer = "";
|
|
985
|
-
}
|
|
986
|
-
} else {
|
|
987
|
-
const openIdx = tagBuffer.indexOf("<think>");
|
|
988
|
-
if (openIdx !== -1) {
|
|
989
|
-
const textBefore = tagBuffer.slice(0, openIdx);
|
|
990
|
-
if (textBefore) {
|
|
991
|
-
yield { type: "text_delta", content: textBefore };
|
|
992
|
-
}
|
|
993
|
-
tagBuffer = tagBuffer.slice(openIdx + "<think>".length);
|
|
994
|
-
inThinking = true;
|
|
995
|
-
} else if (tagBuffer.includes("<")) {
|
|
996
|
-
const partialIdx = tagBuffer.lastIndexOf("<");
|
|
997
|
-
const before = tagBuffer.slice(0, partialIdx);
|
|
998
|
-
if (before) {
|
|
999
|
-
yield { type: "text_delta", content: before };
|
|
1000
|
-
}
|
|
1001
|
-
tagBuffer = tagBuffer.slice(partialIdx);
|
|
1002
|
-
break;
|
|
1003
|
-
} else {
|
|
1004
|
-
yield { type: "text_delta", content: tagBuffer };
|
|
1005
|
-
tagBuffer = "";
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
} else {
|
|
1010
|
-
yield { type: "text_delta", content: delta.content };
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
if (delta.tool_calls) {
|
|
1014
|
-
for (const tc of delta.tool_calls) {
|
|
1015
|
-
const existing = toolCalls.get(tc.index);
|
|
1016
|
-
if (!existing) {
|
|
1017
|
-
toolCalls.set(tc.index, {
|
|
1018
|
-
id: tc.id ?? "",
|
|
1019
|
-
name: tc.function?.name ?? "",
|
|
1020
|
-
arguments: tc.function?.arguments ?? ""
|
|
1021
|
-
});
|
|
1022
|
-
} else {
|
|
1023
|
-
if (tc.id) existing.id = tc.id;
|
|
1024
|
-
if (tc.function?.name) existing.name += tc.function.name;
|
|
1025
|
-
if (tc.function?.arguments) existing.arguments += tc.function.arguments;
|
|
1026
|
-
}
|
|
1027
|
-
yield {
|
|
1028
|
-
type: "tool_call_delta",
|
|
1029
|
-
index: tc.index,
|
|
1030
|
-
...tc.id ? { id: tc.id } : {},
|
|
1031
|
-
...tc.function?.name ? { name: tc.function.name } : {},
|
|
1032
|
-
...tc.function?.arguments ? { arguments: tc.function.arguments } : {}
|
|
1033
|
-
};
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
649
|
+
// src/server/llm/streaming.ts
|
|
650
|
+
var XML_TOOL_PATTERNS = ["<tool_call>", "<function=", "</tool_call>", "<parameter="];
|
|
651
|
+
function hasXmlToolPattern(text) {
|
|
652
|
+
return XML_TOOL_PATTERNS.some((p) => text.includes(p));
|
|
653
|
+
}
|
|
654
|
+
async function* streamWithSegments(client, request) {
|
|
655
|
+
const xmlAbortController = new AbortController();
|
|
656
|
+
const combinedSignal = request.signal ? AbortSignal.any([request.signal, xmlAbortController.signal]) : xmlAbortController.signal;
|
|
657
|
+
let content = "";
|
|
658
|
+
let thinkingContent = "";
|
|
659
|
+
let response = null;
|
|
660
|
+
const segments = [];
|
|
661
|
+
let currentTextSegment = "";
|
|
662
|
+
let currentThinkingSegment = "";
|
|
663
|
+
const startTime = performance.now();
|
|
664
|
+
let firstTokenTime = null;
|
|
665
|
+
const flushText = () => {
|
|
666
|
+
if (currentTextSegment.trim()) {
|
|
667
|
+
segments.push({ type: "text", content: currentTextSegment });
|
|
668
|
+
}
|
|
669
|
+
currentTextSegment = "";
|
|
670
|
+
};
|
|
671
|
+
const flushThinking = () => {
|
|
672
|
+
if (currentThinkingSegment.trim()) {
|
|
673
|
+
segments.push({ type: "thinking", content: currentThinkingSegment });
|
|
674
|
+
}
|
|
675
|
+
currentThinkingSegment = "";
|
|
676
|
+
};
|
|
677
|
+
try {
|
|
678
|
+
for await (const event of client.stream({ ...request, signal: combinedSignal })) {
|
|
679
|
+
switch (event.type) {
|
|
680
|
+
case "text_delta":
|
|
681
|
+
if (firstTokenTime === null) {
|
|
682
|
+
firstTokenTime = performance.now();
|
|
1036
683
|
}
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
684
|
+
flushThinking();
|
|
685
|
+
content += event.content;
|
|
686
|
+
currentTextSegment += event.content;
|
|
687
|
+
if (hasXmlToolPattern(content)) {
|
|
688
|
+
xmlAbortController.abort();
|
|
689
|
+
yield { type: "xml_tool_abort" };
|
|
690
|
+
return null;
|
|
1043
691
|
}
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
const extracted = extractThinking(finalContent);
|
|
1050
|
-
finalContent = extracted.content;
|
|
1051
|
-
finalThinking = extracted.thinkingContent ?? "";
|
|
1052
|
-
}
|
|
1053
|
-
if (!finalContent && finalThinking) {
|
|
1054
|
-
finalContent = finalThinking;
|
|
1055
|
-
finalThinking = "";
|
|
1056
|
-
}
|
|
1057
|
-
const parsedToolCalls = [];
|
|
1058
|
-
for (const [, tc] of toolCalls) {
|
|
1059
|
-
try {
|
|
1060
|
-
parsedToolCalls.push({
|
|
1061
|
-
id: tc.id,
|
|
1062
|
-
name: tc.name,
|
|
1063
|
-
arguments: JSON.parse(tc.arguments)
|
|
1064
|
-
});
|
|
1065
|
-
} catch (error) {
|
|
1066
|
-
logger.warn("Failed to parse tool call arguments", { name: tc.name, arguments: tc.arguments });
|
|
1067
|
-
parsedToolCalls.push({
|
|
1068
|
-
id: tc.id,
|
|
1069
|
-
name: tc.name,
|
|
1070
|
-
arguments: {},
|
|
1071
|
-
parseError: error instanceof Error ? error.message : "Unknown JSON parse error",
|
|
1072
|
-
rawArguments: tc.arguments
|
|
1073
|
-
});
|
|
692
|
+
yield { type: "text_delta", content: event.content };
|
|
693
|
+
break;
|
|
694
|
+
case "thinking_delta":
|
|
695
|
+
if (firstTokenTime === null) {
|
|
696
|
+
firstTokenTime = performance.now();
|
|
1074
697
|
}
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
...parsedToolCalls.length > 0 ? { toolCalls: parsedToolCalls } : {},
|
|
1083
|
-
finishReason,
|
|
1084
|
-
usage
|
|
698
|
+
flushText();
|
|
699
|
+
thinkingContent += event.content;
|
|
700
|
+
currentThinkingSegment += event.content;
|
|
701
|
+
if (hasXmlToolPattern(thinkingContent)) {
|
|
702
|
+
xmlAbortController.abort();
|
|
703
|
+
yield { type: "xml_tool_abort" };
|
|
704
|
+
return null;
|
|
1085
705
|
}
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
706
|
+
yield { type: "thinking_delta", content: event.content };
|
|
707
|
+
break;
|
|
708
|
+
case "tool_call_delta":
|
|
709
|
+
yield {
|
|
710
|
+
type: "tool_call_delta",
|
|
711
|
+
index: event.index,
|
|
712
|
+
...event.id !== void 0 ? { id: event.id } : {},
|
|
713
|
+
...event.name !== void 0 ? { name: event.name } : {},
|
|
714
|
+
...event.arguments !== void 0 ? { arguments: event.arguments } : {}
|
|
715
|
+
};
|
|
716
|
+
break;
|
|
717
|
+
case "done":
|
|
718
|
+
flushThinking();
|
|
719
|
+
flushText();
|
|
720
|
+
response = event.response;
|
|
721
|
+
yield { type: "done", response: event.response };
|
|
722
|
+
break;
|
|
723
|
+
case "error":
|
|
724
|
+
yield { type: "error", error: event.error };
|
|
725
|
+
return null;
|
|
1093
726
|
}
|
|
1094
727
|
}
|
|
728
|
+
} catch (error) {
|
|
729
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
730
|
+
return null;
|
|
731
|
+
}
|
|
732
|
+
yield { type: "error", error: error instanceof Error ? error.message : "Unknown error" };
|
|
733
|
+
return null;
|
|
734
|
+
}
|
|
735
|
+
if (!response) {
|
|
736
|
+
return null;
|
|
737
|
+
}
|
|
738
|
+
const toolCalls = response.toolCalls ?? [];
|
|
739
|
+
for (const tc of toolCalls) {
|
|
740
|
+
segments.push({ type: "tool_call", toolCallId: tc.id });
|
|
741
|
+
}
|
|
742
|
+
const endTime = performance.now();
|
|
743
|
+
const ttft = ((firstTokenTime ?? endTime) - startTime) / 1e3;
|
|
744
|
+
const completionTime = (endTime - (firstTokenTime ?? startTime)) / 1e3;
|
|
745
|
+
const { promptTokens, completionTokens } = response.usage;
|
|
746
|
+
const effectiveThinkingContent = thinkingContent || content === "" && response.reasoning_content || "";
|
|
747
|
+
return {
|
|
748
|
+
content,
|
|
749
|
+
thinkingContent: effectiveThinkingContent,
|
|
750
|
+
toolCalls,
|
|
751
|
+
response,
|
|
752
|
+
segments,
|
|
753
|
+
timing: {
|
|
754
|
+
ttft,
|
|
755
|
+
completionTime,
|
|
756
|
+
tps: completionTime > 0 ? completionTokens / completionTime : 0,
|
|
757
|
+
prefillTps: ttft > 0 ? promptTokens / ttft : 0
|
|
758
|
+
}
|
|
1095
759
|
};
|
|
1096
760
|
}
|
|
1097
761
|
|
|
1098
762
|
export {
|
|
1099
|
-
SessionNotFoundError,
|
|
1100
763
|
getModelProfile,
|
|
764
|
+
getBackendCapabilities,
|
|
1101
765
|
detectBackend,
|
|
1102
766
|
getBackendDisplayName,
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
767
|
+
buildModelParams,
|
|
768
|
+
buildNonStreamingCreateParams,
|
|
769
|
+
buildStreamingCreateParams,
|
|
770
|
+
mapFinishReason,
|
|
771
|
+
extractThinking,
|
|
772
|
+
streamWithSegments
|
|
1108
773
|
};
|
|
1109
|
-
//# sourceMappingURL=chunk-
|
|
774
|
+
//# sourceMappingURL=chunk-D4ZLSV6P.js.map
|