nolo-cli 0.1.18 → 0.1.20
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 +9 -1
- package/agent-runtime/agentConfigOptions.ts +12 -0
- package/agent-runtime/agentRecordConfig.ts +99 -0
- package/agent-runtime/agentRecordKeys.ts +14 -0
- package/agent-runtime/dialogMessageRecord.ts +16 -0
- package/agent-runtime/dialogWritePlan.ts +130 -0
- package/agent-runtime/hostAdapter.ts +14 -0
- package/agent-runtime/hybridRecordStore.ts +147 -0
- package/agent-runtime/index.ts +69 -0
- package/agent-runtime/localLoop.ts +78 -6
- package/agent-runtime/localToolPolicy.ts +130 -0
- package/agent-runtime/localWorkspaceTools.ts +1532 -0
- package/agent-runtime/openAiCompatibleProvider.ts +70 -0
- package/agent-runtime/openAiCompatibleProviderConfig.ts +38 -0
- package/agent-runtime/platformChatProvider.ts +241 -0
- package/agent-runtime/taskWorkspace.ts +193 -0
- package/agent-runtime/types.ts +2 -0
- package/agent-runtime/workspaceSession.ts +76 -0
- package/agentAliases.ts +37 -0
- package/agentPullCommand.ts +1 -1
- package/agentRunCommand.ts +289 -54
- package/agentRuntimeCommands.ts +354 -164
- package/agentRuntimeLocal.ts +38 -0
- package/ai/agent/agentSlice.ts +10 -0
- package/ai/agent/buildEditingContext.ts +5 -0
- package/ai/agent/buildSystemPrompt.ts +41 -18
- package/ai/agent/canvasEditingContext.ts +49 -0
- package/ai/agent/cliExecutor.ts +15 -4
- package/ai/agent/createAgentSchema.ts +2 -0
- package/ai/agent/executeToolCall.ts +3 -2
- package/ai/agent/hooks/usePublicAgents.ts +6 -0
- package/ai/agent/pageBuilderHandoffRules.ts +75 -0
- package/ai/agent/runAgentClientLoop.ts +4 -1
- package/ai/agent/runtimeGuidance.ts +19 -0
- package/ai/agent/server/fetchPublicAgents.ts +51 -1
- package/ai/agent/streamAgentChatTurn.ts +20 -2
- package/ai/agent/streamAgentChatTurnUtils.ts +60 -16
- package/ai/chat/accumulateToolCallChunks.ts +40 -9
- package/ai/chat/parseApiError.ts +3 -0
- package/ai/chat/sendOpenAICompletionsRequest.native.ts +23 -10
- package/ai/chat/sendOpenAICompletionsRequest.ts +13 -1
- package/ai/chat/updateTotalUsage.ts +26 -9
- package/ai/llm/deepinfra.ts +51 -0
- package/ai/llm/getPricing.ts +6 -0
- package/ai/llm/kimi.ts +2 -0
- package/ai/llm/openrouterModels.ts +0 -135
- package/ai/llm/providers.ts +1 -0
- package/ai/llm/types.ts +8 -0
- package/ai/taskRun/taskRunProtocol.ts +823 -0
- package/ai/token/calculatePrice.ts +30 -0
- package/ai/token/externalToolCost.ts +49 -29
- package/ai/token/prepareTokenUsageData.ts +6 -1
- package/ai/token/serverTokenWriter.ts +4 -2
- package/ai/tools/agent/agentTools.ts +21 -0
- package/ai/tools/agent/presets/appBuilderPreset.ts +7 -0
- package/ai/tools/agent/streamParallelAgentsTool.ts +2 -1
- package/ai/tools/agent/taskRunTool.ts +112 -0
- package/ai/tools/applyEditTool.ts +6 -3
- package/ai/tools/applyLineEditsTool.ts +6 -3
- package/ai/tools/checkEnvTool.ts +14 -9
- package/ai/tools/codeSearchTool.ts +17 -5
- package/ai/tools/execBashTool.ts +33 -29
- package/ai/tools/fetchWebpageSupport.ts +24 -0
- package/ai/tools/fetchWebpageTool.ts +18 -5
- package/ai/tools/index.ts +158 -0
- package/ai/tools/jdProductScraperTool.ts +821 -0
- package/ai/tools/listFilesTool.ts +6 -3
- package/ai/tools/localFilesTool.ts +200 -0
- package/ai/tools/readFileTool.ts +6 -3
- package/ai/tools/searchRepoTool.ts +6 -3
- package/ai/tools/table/rowTools.ts +6 -1
- package/ai/tools/taobaoTmallProductScraperTool.ts +49 -0
- package/ai/tools/toolApiClient.ts +20 -6
- package/ai/tools/wereadGatewayTool.ts +152 -0
- package/ai/tools/writeFileTool.ts +6 -3
- package/client/agentConfigResolver.test.ts +70 -0
- package/client/agentConfigResolver.ts +1 -0
- package/client/agentRun.test.ts +361 -7
- package/client/agentRun.ts +449 -63
- package/client/hybridRecordStore.test.ts +115 -0
- package/client/hybridRecordStore.ts +41 -0
- package/client/localAgentRecords.test.ts +27 -0
- package/client/localAgentRecords.ts +7 -0
- package/client/localDialogRecords.test.ts +124 -0
- package/client/localDialogRecords.ts +30 -0
- package/client/localProviderResolver.test.ts +78 -0
- package/client/localProviderResolver.ts +1 -0
- package/client/localRuntimeAdapter.test.ts +813 -20
- package/client/localRuntimeAdapter.ts +279 -232
- package/client/localRuntimeDryRun.test.ts +116 -0
- package/client/localToolPolicy.ts +8 -81
- package/client/taskRunPrompt.ts +26 -0
- package/client/taskWorktree.ts +8 -0
- package/client/workspaceSession.test.ts +57 -0
- package/client/workspaceSession.ts +11 -0
- package/commandRegistry.ts +23 -6
- package/connectorRunArtifact.ts +121 -0
- package/database/actions/write.ts +16 -2
- package/database/hooks/useUserData.ts +9 -3
- package/database/server/dataHandlers.ts +18 -20
- package/database/server/emailRepository.ts +3 -3
- package/database/server/patch.ts +18 -10
- package/database/server/query.ts +43 -4
- package/database/server/read.ts +24 -38
- package/database/server/recordIdentity.ts +100 -0
- package/database/server/write.ts +21 -25
- package/index.ts +70 -33
- package/machineCommands.ts +318 -144
- package/package.json +4 -1
- package/tableCommands.ts +181 -0
- package/taskRunCommand.ts +237 -0
|
@@ -180,6 +180,17 @@ const extractAgentRunUserText = (userInput: string | any[]) => {
|
|
|
180
180
|
.trim();
|
|
181
181
|
};
|
|
182
182
|
|
|
183
|
+
const requiresServerResolvedProviderConfig = (agentConfig: Agent): boolean => {
|
|
184
|
+
if (agentConfig.useServerProxy !== true) return false;
|
|
185
|
+
|
|
186
|
+
const provider = String(agentConfig.provider ?? "").toLowerCase();
|
|
187
|
+
const apiSource = String((agentConfig as any).apiSource ?? "").toLowerCase();
|
|
188
|
+
const isCustomProvider = provider === "custom" || apiSource === "custom";
|
|
189
|
+
if (!isCustomProvider) return false;
|
|
190
|
+
|
|
191
|
+
return !agentConfig.customProviderUrl?.trim() || !agentConfig.apiKey?.trim();
|
|
192
|
+
};
|
|
193
|
+
|
|
183
194
|
const hasAgentRunUserInputContent = (userInput: string | any[]) => {
|
|
184
195
|
if (typeof userInput === "string") {
|
|
185
196
|
return userInput.trim().length > 0;
|
|
@@ -612,12 +623,18 @@ export const streamAgentChatTurnHandler = async (
|
|
|
612
623
|
typeof args.serverBase === "string" && args.serverBase.trim()
|
|
613
624
|
? args.serverBase.trim()
|
|
614
625
|
: null;
|
|
626
|
+
const currentServer = selectCurrentServer(state);
|
|
615
627
|
const declaredRuntimeServerBase = extractAgentRuntimeServerBase(agentConfig);
|
|
616
|
-
const
|
|
628
|
+
const needsServerResolvedProviderConfig =
|
|
629
|
+
requiresServerResolvedProviderConfig(agentConfig);
|
|
630
|
+
const requestedServerBase =
|
|
631
|
+
explicitServerBase ??
|
|
632
|
+
declaredRuntimeServerBase ??
|
|
633
|
+
(needsServerResolvedProviderConfig ? currentServer : null);
|
|
617
634
|
const normalizedRequestedServerBase =
|
|
618
635
|
requestedServerBase && normalizeServerOrigin(requestedServerBase);
|
|
619
636
|
const normalizedCurrentServer = normalizeServerOrigin(
|
|
620
|
-
|
|
637
|
+
currentServer,
|
|
621
638
|
);
|
|
622
639
|
const canAutoRouteRemotely =
|
|
623
640
|
!Array.isArray(userInput) &&
|
|
@@ -626,6 +643,7 @@ export const streamAgentChatTurnHandler = async (
|
|
|
626
643
|
!runtimeOptions?.imageConfigOverride;
|
|
627
644
|
if (requestedServerBase && canAutoRouteRemotely) {
|
|
628
645
|
if (
|
|
646
|
+
!needsServerResolvedProviderConfig &&
|
|
629
647
|
normalizedRequestedServerBase &&
|
|
630
648
|
normalizedCurrentServer &&
|
|
631
649
|
normalizedRequestedServerBase === normalizedCurrentServer
|
|
@@ -28,7 +28,12 @@ import {
|
|
|
28
28
|
selectCurrentUserBalance,
|
|
29
29
|
selectUserId,
|
|
30
30
|
} from "auth/authSlice";
|
|
31
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
getModelPricing,
|
|
33
|
+
getPrices,
|
|
34
|
+
getFinalPrice,
|
|
35
|
+
hasExplicitAgentPricing,
|
|
36
|
+
} from "ai/llm/getPricing";
|
|
32
37
|
import {
|
|
33
38
|
buildStaticUserPolicyContext,
|
|
34
39
|
resolveSpaceContextPreloadPlan,
|
|
@@ -52,6 +57,35 @@ import {
|
|
|
52
57
|
import { buildRecentAppToolMemory } from "./appWorkingMemory";
|
|
53
58
|
import type { AgentRuntimeOptions } from "./types";
|
|
54
59
|
|
|
60
|
+
const BROWSER_UNAVAILABLE_CORE_TOOLS = new Set([
|
|
61
|
+
"queryModelUsage",
|
|
62
|
+
"createDialogGoal",
|
|
63
|
+
"getDialogGoal",
|
|
64
|
+
"completeDialogGoal",
|
|
65
|
+
"createScheduledTask",
|
|
66
|
+
"notifyUser",
|
|
67
|
+
]);
|
|
68
|
+
|
|
69
|
+
const getRuntimeCoreTools = (): string[] => {
|
|
70
|
+
if (typeof window === "undefined") {
|
|
71
|
+
return TOOL_PACKS.CORE;
|
|
72
|
+
}
|
|
73
|
+
return TOOL_PACKS.CORE.filter(
|
|
74
|
+
(toolName) => !BROWSER_UNAVAILABLE_CORE_TOOLS.has(toolName),
|
|
75
|
+
);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const isInlineVisualArtifactAgent = (agentConfig: Agent): boolean => {
|
|
79
|
+
const tags = Array.isArray((agentConfig as any).tags)
|
|
80
|
+
? ((agentConfig as any).tags as unknown[])
|
|
81
|
+
: [];
|
|
82
|
+
return tags.some(
|
|
83
|
+
(tag) =>
|
|
84
|
+
typeof tag === "string" &&
|
|
85
|
+
["inline-artifact", "streaming-ui"].includes(tag)
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
|
|
55
89
|
/**
|
|
56
90
|
* 估算单条 OpenAI 消息的 token 数(包括 tool_calls)。
|
|
57
91
|
*/
|
|
@@ -353,11 +387,11 @@ export const validateAccessAndBalance = (
|
|
|
353
387
|
|
|
354
388
|
const serverPrices = getModelPricing(agentConfig.provider || "", agentConfig.model);
|
|
355
389
|
|
|
356
|
-
if (!serverPrices) {
|
|
390
|
+
if (!serverPrices && !hasExplicitAgentPricing(agentConfig)) {
|
|
357
391
|
return "无法获取模型定价信息,请稍后重试。";
|
|
358
392
|
}
|
|
359
393
|
|
|
360
|
-
const prices = getPrices(agentConfig, serverPrices);
|
|
394
|
+
const prices = getPrices(agentConfig, serverPrices ?? null);
|
|
361
395
|
const maxPrice = getFinalPrice(prices);
|
|
362
396
|
|
|
363
397
|
if (userBalance < maxPrice) {
|
|
@@ -677,11 +711,17 @@ export const mergeAgentToolsWithRuntime = (
|
|
|
677
711
|
);
|
|
678
712
|
const enhancedTools = new Set<string>([
|
|
679
713
|
...baseTools,
|
|
680
|
-
...
|
|
681
|
-
...(baseTools.length > 0
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
...
|
|
714
|
+
...(isInlineVisualArtifactAgent(agentConfig) ? [] : getRuntimeCoreTools()),
|
|
715
|
+
...(baseTools.length > 0 && !isInlineVisualArtifactAgent(agentConfig)
|
|
716
|
+
? TOOL_PACKS.LIGHT_WEB
|
|
717
|
+
: []),
|
|
718
|
+
...(isInlineVisualArtifactAgent(agentConfig) ? [] : requiredSkillTools),
|
|
719
|
+
...(isInlineVisualArtifactAgent(agentConfig)
|
|
720
|
+
? []
|
|
721
|
+
: canonicalizeToolNames(referencedTools)),
|
|
722
|
+
...(isInlineVisualArtifactAgent(agentConfig)
|
|
723
|
+
? []
|
|
724
|
+
: canonicalizeToolNames(mentionedTools)),
|
|
685
725
|
]);
|
|
686
726
|
|
|
687
727
|
// Intelligence: If user explicitly added ANY browser tool, auto-inject the FULL browser pack
|
|
@@ -690,18 +730,22 @@ export const mergeAgentToolsWithRuntime = (
|
|
|
690
730
|
TOOL_PACKS.FULL_BROWSER.forEach((t) => enhancedTools.add(t));
|
|
691
731
|
}
|
|
692
732
|
|
|
693
|
-
const extraTools =
|
|
733
|
+
const extraTools = isInlineVisualArtifactAgent(agentConfig)
|
|
734
|
+
? []
|
|
735
|
+
: canonicalizeToolNames(runtimeOptions?.extraTools ?? []);
|
|
694
736
|
for (const t of extraTools) {
|
|
695
737
|
enhancedTools.add(t);
|
|
696
738
|
}
|
|
697
739
|
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
740
|
+
if (!isInlineVisualArtifactAgent(agentConfig)) {
|
|
741
|
+
const viewMode = state ? selectViewMode(state) : "categories";
|
|
742
|
+
if (viewMode === "all") {
|
|
743
|
+
enhancedTools.delete("search_workspace");
|
|
744
|
+
enhancedTools.add("search_all_spaces");
|
|
745
|
+
} else {
|
|
746
|
+
enhancedTools.delete("search_all_spaces");
|
|
747
|
+
enhancedTools.add("search_workspace");
|
|
748
|
+
}
|
|
705
749
|
}
|
|
706
750
|
|
|
707
751
|
return {
|
|
@@ -6,13 +6,14 @@
|
|
|
6
6
|
* - 不再过滤特殊标记,保持原样透传
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
|
|
9
10
|
export interface ToolCallChunk {
|
|
10
11
|
index?: number;
|
|
11
12
|
id?: string;
|
|
12
13
|
type?: "function";
|
|
13
14
|
function?: {
|
|
14
15
|
name?: string;
|
|
15
|
-
arguments?: string;
|
|
16
|
+
arguments?: string | object;
|
|
16
17
|
};
|
|
17
18
|
}
|
|
18
19
|
|
|
@@ -22,7 +23,7 @@ export interface AccumulatedToolCall {
|
|
|
22
23
|
type: "function";
|
|
23
24
|
function: {
|
|
24
25
|
name: string;
|
|
25
|
-
arguments: string;
|
|
26
|
+
arguments: string | object;
|
|
26
27
|
};
|
|
27
28
|
}
|
|
28
29
|
|
|
@@ -39,9 +40,12 @@ export function accumulateToolCallChunks(
|
|
|
39
40
|
if (index !== undefined) {
|
|
40
41
|
// 确保数组长度足够覆盖 index
|
|
41
42
|
while (out.length <= index) {
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
// 先占位,后续填充。初始化所有必需字段以防空指针。
|
|
44
|
+
out.push({
|
|
45
|
+
id: "",
|
|
46
|
+
type: "function",
|
|
47
|
+
function: { name: "", arguments: "" },
|
|
48
|
+
});
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
const cur = out[index];
|
|
@@ -53,7 +57,20 @@ export function accumulateToolCallChunks(
|
|
|
53
57
|
|
|
54
58
|
if (fn) {
|
|
55
59
|
if (fn.name) cur.function.name += fn.name;
|
|
56
|
-
|
|
60
|
+
|
|
61
|
+
if (fn.arguments) {
|
|
62
|
+
if (typeof fn.arguments === "string") {
|
|
63
|
+
// 字符串增量:追加
|
|
64
|
+
const currentArgs =
|
|
65
|
+
typeof cur.function.arguments === "string"
|
|
66
|
+
? cur.function.arguments
|
|
67
|
+
: "";
|
|
68
|
+
cur.function.arguments = currentArgs + fn.arguments;
|
|
69
|
+
} else {
|
|
70
|
+
// 对象全量:覆盖(非标流直接给 final object)
|
|
71
|
+
cur.function.arguments = fn.arguments;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
57
74
|
}
|
|
58
75
|
continue;
|
|
59
76
|
}
|
|
@@ -68,14 +85,21 @@ export function accumulateToolCallChunks(
|
|
|
68
85
|
targetIndex = out.findIndex((c) => c.id === id);
|
|
69
86
|
} else if (out.length > 0) {
|
|
70
87
|
// 如果没有 ID,默认追加到最后一个(假设顺序性)
|
|
71
|
-
// 注意:这是兜底逻辑,OpenAI 规范通常会带 index 或 id
|
|
72
88
|
targetIndex = out.length - 1;
|
|
73
89
|
}
|
|
74
90
|
|
|
75
91
|
if (targetIndex >= 0) {
|
|
76
92
|
const target = out[targetIndex];
|
|
77
93
|
if (fn.name) target.function.name += fn.name; // 追加
|
|
78
|
-
|
|
94
|
+
|
|
95
|
+
if (fn.arguments) {
|
|
96
|
+
if (typeof fn.arguments === "string") {
|
|
97
|
+
const currentArgs = typeof target.function.arguments === "string" ? target.function.arguments : "";
|
|
98
|
+
target.function.arguments = currentArgs + fn.arguments;
|
|
99
|
+
} else {
|
|
100
|
+
target.function.arguments = fn.arguments;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
79
103
|
} else if (id) {
|
|
80
104
|
// 是新的调用
|
|
81
105
|
const newCall: AccumulatedToolCall = {
|
|
@@ -83,9 +107,16 @@ export function accumulateToolCallChunks(
|
|
|
83
107
|
type: type || "function",
|
|
84
108
|
function: {
|
|
85
109
|
name: fn.name || "",
|
|
86
|
-
arguments: fn.arguments || ""
|
|
110
|
+
arguments: fn.arguments || (!fn.arguments && typeof fn.arguments === 'object' ? {} : "") // Initial empty value based on type? Or just default string
|
|
87
111
|
},
|
|
88
112
|
};
|
|
113
|
+
// For arguments, if it's object, use it. If undefined, use "".
|
|
114
|
+
if (fn.arguments) {
|
|
115
|
+
newCall.function.arguments = fn.arguments;
|
|
116
|
+
} else {
|
|
117
|
+
newCall.function.arguments = "";
|
|
118
|
+
}
|
|
119
|
+
|
|
89
120
|
out.push(newCall);
|
|
90
121
|
}
|
|
91
122
|
}
|
package/ai/chat/parseApiError.ts
CHANGED
|
@@ -28,6 +28,9 @@ export async function parseApiError(response: Response): Promise<string> {
|
|
|
28
28
|
if (isContextOverflow(errorMessage) || isContextOverflow(errorBody) || errorCode === "UPSTREAM_400") {
|
|
29
29
|
return "上下文过长:本轮消息或工具结果太大。请缩小范围,或先读取更小片段后再继续。";
|
|
30
30
|
}
|
|
31
|
+
if (errorCode === "MISSING_PROVIDER_API_KEY") {
|
|
32
|
+
return truncateErrorMessage(errorMessage);
|
|
33
|
+
}
|
|
31
34
|
if (errorMessage && errorMessage !== defaultMessage) {
|
|
32
35
|
return `请求参数错误: ${truncateErrorMessage(errorMessage)}`;
|
|
33
36
|
}
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
messageStreaming,
|
|
13
13
|
} from "chat/messages/messageSlice";
|
|
14
14
|
import { handleToolCalls } from "chat/messages/toolThunks";
|
|
15
|
-
import { MessageContentPart, OpenAITextContent } from "chat/messages/types";
|
|
15
|
+
import { CompletionFinishReason, CompletionUsage, MessageContentPart, OpenAITextContent } from "chat/messages/types";
|
|
16
16
|
import { selectCurrentServer } from "app/settings/settingSlice";
|
|
17
17
|
import { getApiEndpoint } from "ai/llm/providers";
|
|
18
18
|
import { createDialogMessageKeyAndId } from "database/keys";
|
|
@@ -23,7 +23,7 @@ import { performSSEFetchRequest } from "./fetchUtils";
|
|
|
23
23
|
import { createSSEParser } from "./parseMultilineSSE";
|
|
24
24
|
import { parseApiError } from "./parseApiError";
|
|
25
25
|
import { updateTotalUsage } from "./updateTotalUsage";
|
|
26
|
-
import { accumulateToolCallChunks, AccumulatedToolCall
|
|
26
|
+
import { accumulateToolCallChunks, AccumulatedToolCall } from "./accumulateToolCallChunks";
|
|
27
27
|
import { prepareTools } from "../tools/prepareTools";
|
|
28
28
|
|
|
29
29
|
import { getModelInfo } from "ai/llm/getModelContextWindow";
|
|
@@ -65,14 +65,14 @@ type AssistantToolCall = {
|
|
|
65
65
|
/** 单次流式请求过程中的全部中间状态(显式 state) */
|
|
66
66
|
type StreamState = {
|
|
67
67
|
contentBuffer: MessageContentPart[];
|
|
68
|
-
totalUsage:
|
|
68
|
+
totalUsage: CompletionUsage | null;
|
|
69
69
|
accumulatedToolCalls: AccumulatedToolCall[];
|
|
70
70
|
reasoningBuffer: string;
|
|
71
71
|
assistantToolCalls?: AssistantToolCall[];
|
|
72
72
|
hasHandedOff: boolean;
|
|
73
73
|
hasProcessedToolCalls: boolean;
|
|
74
74
|
alreadyFinalized: boolean;
|
|
75
|
-
finishReason:
|
|
75
|
+
finishReason: CompletionFinishReason | null;
|
|
76
76
|
};
|
|
77
77
|
|
|
78
78
|
type FinalizeContext = {
|
|
@@ -100,12 +100,13 @@ type StreamCompletionContext = {
|
|
|
100
100
|
agentConfig: any;
|
|
101
101
|
};
|
|
102
102
|
|
|
103
|
+
|
|
103
104
|
/** 单轮调用后返回给 Agent Loop 的元信息 */
|
|
104
105
|
export type CompletionMeta = {
|
|
105
106
|
hasToolCalls: boolean;
|
|
106
107
|
hasPendingInteraction: boolean;
|
|
107
108
|
hasHandedOff: boolean;
|
|
108
|
-
finishReason:
|
|
109
|
+
finishReason: CompletionFinishReason | null;
|
|
109
110
|
usage?: any;
|
|
110
111
|
};
|
|
111
112
|
|
|
@@ -222,11 +223,11 @@ function applyDelta(
|
|
|
222
223
|
next = {
|
|
223
224
|
...next,
|
|
224
225
|
accumulatedToolCalls: accumulated,
|
|
225
|
-
assistantToolCalls: accumulated.map((call) => ({
|
|
226
|
+
assistantToolCalls: accumulated.map((call: AccumulatedToolCall) => ({
|
|
226
227
|
id: call.id,
|
|
227
228
|
type: "function",
|
|
228
229
|
function: {
|
|
229
|
-
name: call.function?.name,
|
|
230
|
+
name: call.function?.name || '', // Ensure name is string
|
|
230
231
|
arguments:
|
|
231
232
|
typeof call.function?.arguments === "string"
|
|
232
233
|
? call.function.arguments
|
|
@@ -417,6 +418,18 @@ export const sendOpenAICompletionsRequest = async ({
|
|
|
417
418
|
return "Unknown error";
|
|
418
419
|
};
|
|
419
420
|
|
|
421
|
+
const formatStreamErrorMessage = (data: any): string => {
|
|
422
|
+
const rawMessage = getStreamErrorMessage(data);
|
|
423
|
+
if (
|
|
424
|
+
/prohibited|violation|terms\s+of\s+service|content\s+policy|safety/i.test(
|
|
425
|
+
rawMessage
|
|
426
|
+
)
|
|
427
|
+
) {
|
|
428
|
+
return "当前模型服务商拒绝了这次请求。你可以稍后重试,或切换到其他模型继续。";
|
|
429
|
+
}
|
|
430
|
+
return rawMessage;
|
|
431
|
+
};
|
|
432
|
+
|
|
420
433
|
const { dispatch, getState, signal: thunkSignal } = thunkApi;
|
|
421
434
|
|
|
422
435
|
const dialogId = extractCustomId(dialogKey);
|
|
@@ -460,7 +473,7 @@ export const sendOpenAICompletionsRequest = async ({
|
|
|
460
473
|
|
|
461
474
|
let hasHandedOffOverall = false;
|
|
462
475
|
let hasPendingInteractionOverall = false;
|
|
463
|
-
let lastFinishReason:
|
|
476
|
+
let lastFinishReason: CompletionFinishReason = null;
|
|
464
477
|
|
|
465
478
|
const buildMeta = (): CompletionMeta => ({
|
|
466
479
|
hasToolCalls:
|
|
@@ -551,7 +564,7 @@ export const sendOpenAICompletionsRequest = async ({
|
|
|
551
564
|
}
|
|
552
565
|
|
|
553
566
|
if (data.error) {
|
|
554
|
-
const errorMsg = `Error: ${
|
|
567
|
+
const errorMsg = `Error: ${formatStreamErrorMessage(data)}`;
|
|
555
568
|
streamState = {
|
|
556
569
|
...streamState,
|
|
557
570
|
contentBuffer: appendTextChunk(
|
|
@@ -585,7 +598,7 @@ export const sendOpenAICompletionsRequest = async ({
|
|
|
585
598
|
dialogId,
|
|
586
599
|
});
|
|
587
600
|
|
|
588
|
-
const finishReason = choice.finish_reason;
|
|
601
|
+
const finishReason = choice.finish_reason as CompletionFinishReason;
|
|
589
602
|
if (finishReason) {
|
|
590
603
|
lastFinishReason = finishReason;
|
|
591
604
|
streamState.finishReason = finishReason;
|
|
@@ -97,6 +97,18 @@ function getStreamErrorMessage(data: any): string {
|
|
|
97
97
|
return "Unknown error";
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
function formatStreamErrorMessage(data: any): string {
|
|
101
|
+
const rawMessage = getStreamErrorMessage(data);
|
|
102
|
+
if (
|
|
103
|
+
/prohibited|violation|terms\s+of\s+service|content\s+policy|safety/i.test(
|
|
104
|
+
rawMessage
|
|
105
|
+
)
|
|
106
|
+
) {
|
|
107
|
+
return "当前模型服务商拒绝了这次请求。你可以稍后重试,或切换到其他模型继续。";
|
|
108
|
+
}
|
|
109
|
+
return rawMessage;
|
|
110
|
+
}
|
|
111
|
+
|
|
100
112
|
/** 单次流式请求过程中的全部中间状态(显式 state) */
|
|
101
113
|
type StreamState = {
|
|
102
114
|
contentBuffer: MessageContentPart[];
|
|
@@ -607,7 +619,7 @@ export const sendOpenAICompletionsRequest = async ({
|
|
|
607
619
|
}
|
|
608
620
|
|
|
609
621
|
if (data.error) {
|
|
610
|
-
const errorMsg = `Error: ${
|
|
622
|
+
const errorMsg = `Error: ${formatStreamErrorMessage(data)}`;
|
|
611
623
|
streamState = {
|
|
612
624
|
...streamState,
|
|
613
625
|
contentBuffer: appendTextChunk(
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// 文件路径: ai/chat/updateTotalUsage.ts
|
|
2
2
|
|
|
3
|
+
import { CompletionUsage } from "chat/messages/types";
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* ✨ 新增辅助函数 ✨
|
|
5
7
|
* 根据新的数据块更新累积的 token 使用量。
|
|
@@ -7,26 +9,41 @@
|
|
|
7
9
|
* @param newUsageChunk - 从流中收到的新 usage 数据块。
|
|
8
10
|
* @returns 更新后的 usage 对象。
|
|
9
11
|
*/
|
|
10
|
-
export function updateTotalUsage(
|
|
12
|
+
export function updateTotalUsage(
|
|
13
|
+
currentUsage: CompletionUsage | null,
|
|
14
|
+
newUsageChunk: Partial<CompletionUsage>
|
|
15
|
+
): CompletionUsage | null {
|
|
11
16
|
if (!newUsageChunk) {
|
|
12
17
|
return currentUsage;
|
|
13
18
|
}
|
|
14
19
|
|
|
15
20
|
// 如果是第一次接收,直接克隆新数据块
|
|
16
21
|
if (!currentUsage) {
|
|
17
|
-
return {
|
|
22
|
+
return {
|
|
23
|
+
completion_tokens: 0,
|
|
24
|
+
prompt_tokens: 0,
|
|
25
|
+
total_tokens: 0,
|
|
26
|
+
...newUsageChunk,
|
|
27
|
+
} as CompletionUsage;
|
|
18
28
|
}
|
|
19
29
|
|
|
20
30
|
// 否则,在现有基础上进行累加或更新
|
|
21
|
-
const updatedUsage = { ...currentUsage };
|
|
31
|
+
const updatedUsage: CompletionUsage = { ...currentUsage };
|
|
22
32
|
|
|
23
33
|
// === token 相关 ===
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
34
|
+
// 这里假设 usage 是累积的还是单次的?
|
|
35
|
+
// 如果是 OpenAI 流式 output,usage 通常只在最后一次 chunk 发送完整的统计值 (除了 Azure 等可能变体)。
|
|
36
|
+
// 如果是这种情况,我们直接覆盖即可。
|
|
37
|
+
// 但如果服务端分片发送增量(比较少见但有可能),则需要累加。
|
|
38
|
+
// 原代码逻辑是:
|
|
39
|
+
// updatedUsage.completion_tokens = newUsageChunk.completion_tokens ?? updatedUsage.completion_tokens;
|
|
40
|
+
// 这意味着如果有新值就覆盖,没新值保持原样。这适合 "最后一次发送完整值" 的场景。
|
|
41
|
+
|
|
42
|
+
// 保持原逻辑:覆盖
|
|
43
|
+
if (newUsageChunk.completion_tokens !== undefined) updatedUsage.completion_tokens = newUsageChunk.completion_tokens;
|
|
44
|
+
if (newUsageChunk.prompt_tokens !== undefined) updatedUsage.prompt_tokens = newUsageChunk.prompt_tokens;
|
|
45
|
+
if (newUsageChunk.total_tokens !== undefined) updatedUsage.total_tokens = newUsageChunk.total_tokens;
|
|
46
|
+
|
|
30
47
|
|
|
31
48
|
if (newUsageChunk.prompt_tokens_details) {
|
|
32
49
|
updatedUsage.prompt_tokens_details = {
|
package/ai/llm/deepinfra.ts
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
// ai/llm/deepinfra.ts
|
|
2
|
+
import { DEEPINFRA_KIMI_FALLBACK_MODEL } from "./kimi";
|
|
3
|
+
|
|
2
4
|
export const deepinfraModels = [
|
|
5
|
+
{
|
|
6
|
+
name: DEEPINFRA_KIMI_FALLBACK_MODEL,
|
|
7
|
+
displayName: "MoonshotAI: Kimi K2.6 (DeepInfra)",
|
|
8
|
+
hasVision: true,
|
|
9
|
+
price: {
|
|
10
|
+
input: 0.6 * 8,
|
|
11
|
+
output: 2.5 * 8,
|
|
12
|
+
inputCacheHit: 0.06 * 8,
|
|
13
|
+
},
|
|
14
|
+
maxOutputTokens: 262144,
|
|
15
|
+
contextWindow: 262144,
|
|
16
|
+
supportsTool: true,
|
|
17
|
+
},
|
|
3
18
|
{
|
|
4
19
|
name: "moonshotai/Kimi-K2.5",
|
|
5
20
|
displayName: "MoonshotAI: Kimi K2.5 (DeepInfra)",
|
|
@@ -25,4 +40,40 @@ export const deepinfraModels = [
|
|
|
25
40
|
contextWindow: 202752,
|
|
26
41
|
supportsTool: true,
|
|
27
42
|
},
|
|
43
|
+
{
|
|
44
|
+
name: "anthropic/claude-haiku-4-5",
|
|
45
|
+
displayName: "Anthropic: Claude Haiku 4.5 (DeepInfra)",
|
|
46
|
+
hasVision: true,
|
|
47
|
+
price: {
|
|
48
|
+
input: 1 * 9,
|
|
49
|
+
output: 5 * 9,
|
|
50
|
+
},
|
|
51
|
+
contextWindow: 195000,
|
|
52
|
+
maxOutputTokens: 4092,
|
|
53
|
+
supportsTool: false,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: "anthropic/claude-sonnet-4-6",
|
|
57
|
+
displayName: "Anthropic: Claude Sonnet 4.6 (DeepInfra)",
|
|
58
|
+
hasVision: true,
|
|
59
|
+
price: {
|
|
60
|
+
input: 3 * 9,
|
|
61
|
+
output: 15 * 9,
|
|
62
|
+
},
|
|
63
|
+
contextWindow: 976000,
|
|
64
|
+
maxOutputTokens: 4092,
|
|
65
|
+
supportsTool: false,
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "anthropic/claude-opus-4-7",
|
|
69
|
+
displayName: "Anthropic: Claude Opus 4.7 (DeepInfra)",
|
|
70
|
+
hasVision: true,
|
|
71
|
+
price: {
|
|
72
|
+
input: 5 * 9,
|
|
73
|
+
output: 25 * 9,
|
|
74
|
+
},
|
|
75
|
+
contextWindow: 976000,
|
|
76
|
+
maxOutputTokens: 4092,
|
|
77
|
+
supportsTool: false,
|
|
78
|
+
},
|
|
28
79
|
];
|
package/ai/llm/getPricing.ts
CHANGED
|
@@ -70,6 +70,12 @@ export const getModelPricingForModel = (
|
|
|
70
70
|
};
|
|
71
71
|
};
|
|
72
72
|
|
|
73
|
+
export const hasExplicitAgentPricing = (config: any): boolean =>
|
|
74
|
+
[config?.inputPrice, config?.outputPrice].some(
|
|
75
|
+
(value) =>
|
|
76
|
+
typeof value === "number" && Number.isFinite(value) && value > 0
|
|
77
|
+
);
|
|
78
|
+
|
|
73
79
|
export const getPrices = (config: any, serverPrices: any): Prices => ({
|
|
74
80
|
cybotInput: Number(config?.inputPrice ?? 0),
|
|
75
81
|
cybotOutput: Number(config?.outputPrice ?? 0),
|
package/ai/llm/kimi.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export const FIREWORKS_KIMI_LATEST_MODEL = "accounts/fireworks/models/kimi-latest";
|
|
2
2
|
export const FIREWORKS_KIMI_CURRENT_MODEL = "accounts/fireworks/models/kimi-k2p6";
|
|
3
|
+
export const DEEPINFRA_KIMI_FALLBACK_MODEL = "moonshotai/Kimi-K2.6";
|
|
3
4
|
export const OPENROUTER_KIMI_FALLBACK_MODEL = "moonshotai/kimi-k2.6";
|
|
5
|
+
export const KIMI_PLATFORM_FALLBACK_STATUSES = [402, 429, 500, 502, 503, 504];
|
|
4
6
|
|
|
5
7
|
export const isFireworksKimiModel = (model?: string | null): boolean =>
|
|
6
8
|
model === FIREWORKS_KIMI_LATEST_MODEL || model === FIREWORKS_KIMI_CURRENT_MODEL;
|