nolo-cli 0.1.19 → 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.
Files changed (111) hide show
  1. package/README.md +9 -1
  2. package/agent-runtime/agentConfigOptions.ts +12 -0
  3. package/agent-runtime/agentRecordConfig.ts +99 -0
  4. package/agent-runtime/agentRecordKeys.ts +14 -0
  5. package/agent-runtime/dialogMessageRecord.ts +16 -0
  6. package/agent-runtime/dialogWritePlan.ts +130 -0
  7. package/agent-runtime/hostAdapter.ts +13 -0
  8. package/agent-runtime/hybridRecordStore.ts +147 -0
  9. package/agent-runtime/index.ts +69 -0
  10. package/agent-runtime/localLoop.ts +69 -5
  11. package/agent-runtime/localToolPolicy.ts +130 -0
  12. package/agent-runtime/localWorkspaceTools.ts +1532 -0
  13. package/agent-runtime/openAiCompatibleProvider.ts +70 -0
  14. package/agent-runtime/openAiCompatibleProviderConfig.ts +38 -0
  15. package/agent-runtime/platformChatProvider.ts +241 -0
  16. package/agent-runtime/taskWorkspace.ts +193 -0
  17. package/agent-runtime/types.ts +1 -0
  18. package/agent-runtime/workspaceSession.ts +76 -0
  19. package/agentAliases.ts +37 -0
  20. package/agentPullCommand.ts +1 -1
  21. package/agentRunCommand.ts +278 -52
  22. package/agentRuntimeCommands.ts +354 -164
  23. package/agentRuntimeLocal.ts +38 -0
  24. package/ai/agent/agentSlice.ts +10 -0
  25. package/ai/agent/buildEditingContext.ts +5 -0
  26. package/ai/agent/buildSystemPrompt.ts +41 -18
  27. package/ai/agent/canvasEditingContext.ts +49 -0
  28. package/ai/agent/cliExecutor.ts +15 -4
  29. package/ai/agent/createAgentSchema.ts +2 -0
  30. package/ai/agent/executeToolCall.ts +3 -2
  31. package/ai/agent/hooks/usePublicAgents.ts +6 -0
  32. package/ai/agent/pageBuilderHandoffRules.ts +75 -0
  33. package/ai/agent/runAgentClientLoop.ts +4 -1
  34. package/ai/agent/runtimeGuidance.ts +19 -0
  35. package/ai/agent/server/fetchPublicAgents.ts +51 -1
  36. package/ai/agent/streamAgentChatTurn.ts +20 -2
  37. package/ai/agent/streamAgentChatTurnUtils.ts +60 -16
  38. package/ai/chat/accumulateToolCallChunks.ts +40 -9
  39. package/ai/chat/parseApiError.ts +3 -0
  40. package/ai/chat/sendOpenAICompletionsRequest.native.ts +23 -10
  41. package/ai/chat/sendOpenAICompletionsRequest.ts +13 -1
  42. package/ai/chat/updateTotalUsage.ts +26 -9
  43. package/ai/llm/deepinfra.ts +51 -0
  44. package/ai/llm/getPricing.ts +6 -0
  45. package/ai/llm/kimi.ts +2 -0
  46. package/ai/llm/openrouterModels.ts +0 -135
  47. package/ai/llm/providers.ts +1 -0
  48. package/ai/llm/types.ts +8 -0
  49. package/ai/taskRun/taskRunProtocol.ts +823 -0
  50. package/ai/token/calculatePrice.ts +30 -0
  51. package/ai/token/externalToolCost.ts +49 -29
  52. package/ai/token/prepareTokenUsageData.ts +6 -1
  53. package/ai/token/serverTokenWriter.ts +4 -2
  54. package/ai/tools/agent/agentTools.ts +21 -0
  55. package/ai/tools/agent/presets/appBuilderPreset.ts +7 -0
  56. package/ai/tools/agent/streamParallelAgentsTool.ts +2 -1
  57. package/ai/tools/agent/taskRunTool.ts +112 -0
  58. package/ai/tools/applyEditTool.ts +6 -3
  59. package/ai/tools/applyLineEditsTool.ts +6 -3
  60. package/ai/tools/checkEnvTool.ts +14 -9
  61. package/ai/tools/codeSearchTool.ts +17 -5
  62. package/ai/tools/execBashTool.ts +33 -29
  63. package/ai/tools/fetchWebpageSupport.ts +24 -0
  64. package/ai/tools/fetchWebpageTool.ts +18 -5
  65. package/ai/tools/index.ts +158 -0
  66. package/ai/tools/jdProductScraperTool.ts +821 -0
  67. package/ai/tools/listFilesTool.ts +6 -3
  68. package/ai/tools/localFilesTool.ts +200 -0
  69. package/ai/tools/readFileTool.ts +6 -3
  70. package/ai/tools/searchRepoTool.ts +6 -3
  71. package/ai/tools/table/rowTools.ts +6 -1
  72. package/ai/tools/taobaoTmallProductScraperTool.ts +49 -0
  73. package/ai/tools/toolApiClient.ts +20 -6
  74. package/ai/tools/wereadGatewayTool.ts +152 -0
  75. package/ai/tools/writeFileTool.ts +6 -3
  76. package/client/agentConfigResolver.test.ts +70 -0
  77. package/client/agentConfigResolver.ts +1 -0
  78. package/client/agentRun.test.ts +361 -7
  79. package/client/agentRun.ts +449 -63
  80. package/client/hybridRecordStore.test.ts +115 -0
  81. package/client/hybridRecordStore.ts +41 -0
  82. package/client/localAgentRecords.test.ts +27 -0
  83. package/client/localAgentRecords.ts +7 -0
  84. package/client/localDialogRecords.test.ts +124 -0
  85. package/client/localDialogRecords.ts +30 -0
  86. package/client/localProviderResolver.test.ts +78 -0
  87. package/client/localProviderResolver.ts +1 -0
  88. package/client/localRuntimeAdapter.test.ts +621 -9
  89. package/client/localRuntimeAdapter.ts +275 -250
  90. package/client/localRuntimeDryRun.test.ts +116 -0
  91. package/client/localToolPolicy.ts +8 -81
  92. package/client/taskRunPrompt.ts +26 -0
  93. package/client/taskWorktree.ts +8 -0
  94. package/client/workspaceSession.test.ts +57 -0
  95. package/client/workspaceSession.ts +11 -0
  96. package/commandRegistry.ts +23 -6
  97. package/connectorRunArtifact.ts +121 -0
  98. package/database/actions/write.ts +16 -2
  99. package/database/hooks/useUserData.ts +9 -3
  100. package/database/server/dataHandlers.ts +18 -20
  101. package/database/server/emailRepository.ts +3 -3
  102. package/database/server/patch.ts +18 -10
  103. package/database/server/query.ts +43 -4
  104. package/database/server/read.ts +24 -38
  105. package/database/server/recordIdentity.ts +100 -0
  106. package/database/server/write.ts +21 -25
  107. package/index.ts +70 -33
  108. package/machineCommands.ts +318 -144
  109. package/package.json +4 -1
  110. package/tableCommands.ts +181 -0
  111. 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 requestedServerBase = explicitServerBase ?? declaredRuntimeServerBase;
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
- selectCurrentServer(state),
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 { getModelPricing, getPrices, getFinalPrice } from "ai/llm/getPricing";
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
- ...TOOL_PACKS.CORE,
681
- ...(baseTools.length > 0 ? TOOL_PACKS.LIGHT_WEB : []),
682
- ...requiredSkillTools,
683
- ...canonicalizeToolNames(referencedTools),
684
- ...canonicalizeToolNames(mentionedTools),
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 = canonicalizeToolNames(runtimeOptions?.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
- const viewMode = state ? selectViewMode(state) : "categories";
699
- if (viewMode === "all") {
700
- enhancedTools.delete("search_workspace");
701
- enhancedTools.add("search_all_spaces");
702
- } else {
703
- enhancedTools.delete("search_all_spaces");
704
- enhancedTools.add("search_workspace");
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
- // 先占位,后续必须填充 id/type/function 才能成为有效的 AccumulatedToolCall
43
- // 这里暂时断言为空对象,等待后续逻辑填充完整
44
- out.push({} as AccumulatedToolCall);
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
- if (fn.arguments) cur.function.arguments += fn.arguments;
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
- if (fn.arguments) target.function.arguments += fn.arguments; // 追加
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
  }
@@ -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, ToolCallChunk } from "./accumulateToolCallChunks";
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: any | null;
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: string | null;
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: string | null;
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: string | null = null;
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: ${getStreamErrorMessage(data)}`;
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: ${getStreamErrorMessage(data)}`;
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(currentUsage: any, newUsageChunk: any): any {
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 { ...newUsageChunk };
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
- updatedUsage.completion_tokens =
25
- newUsageChunk.completion_tokens ?? updatedUsage.completion_tokens;
26
- updatedUsage.prompt_tokens =
27
- newUsageChunk.prompt_tokens ?? updatedUsage.prompt_tokens;
28
- updatedUsage.total_tokens =
29
- newUsageChunk.total_tokens ?? updatedUsage.total_tokens;
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 = {
@@ -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
  ];
@@ -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;