nolo-cli 0.1.22 → 0.1.26
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 +25 -3
- package/agent-runtime/agentRecordConfig.ts +4 -0
- package/agent-runtime/hostAdapter.ts +2 -0
- package/agent-runtime/hybridRecordStore.ts +30 -9
- package/agent-runtime/index.ts +7 -0
- package/agent-runtime/localLoop.ts +14 -4
- package/agent-runtime/localToolPolicy.ts +22 -10
- package/agent-runtime/localWorkspaceTools.ts +140 -374
- package/agent-runtime/platformChatProvider.ts +3 -0
- package/agent-runtime/runtimeToolPolicy.ts +133 -0
- package/agent-runtime/types.ts +47 -0
- package/agentAliases.ts +1 -1
- package/agentCommandSupport.ts +77 -0
- package/agentInternalCommandEntries.ts +34 -0
- package/agentListCommands.ts +123 -0
- package/agentListHelpers.ts +187 -0
- package/agentMachineCommands.ts +402 -0
- package/agentRecordCommands.ts +141 -0
- package/agentRecordHelpers.ts +395 -0
- package/agentRunCommand.ts +75 -3
- package/agentRuntimeCommands.ts +7 -773
- package/ai/agent/_executeModel.ts +29 -3
- package/ai/agent/buildSystemPrompt.ts +17 -0
- package/ai/agent/cliChatClient.ts +38 -4
- package/ai/agent/cliExecutor.ts +304 -25
- package/ai/agent/createAgentSchema.ts +2 -2
- package/ai/agent/guidedCreation/assistant.ts +82 -0
- package/ai/agent/guidedCreation/capabilities.ts +84 -0
- package/ai/agent/guidedCreation/draft.ts +62 -0
- package/ai/agent/guidedCreation/types.ts +38 -0
- package/ai/agent/machineRunPermissions.ts +21 -3
- package/ai/agent/runtimeGuidance.ts +1 -1
- package/ai/agent/server/fetchPublicAgents.ts +6 -1
- package/ai/agent/streamAgentChatTurn.ts +593 -36
- package/ai/agent/web/useGuidedAgentCreation.ts +217 -0
- package/ai/ai.locale.ts +14 -2
- package/ai/chat/fetchUtils.native.ts +2 -0
- package/ai/chat/fetchUtils.ts +2 -0
- package/ai/chat/sendOpenAICompletionsRequest.ts +82 -1
- package/ai/chat/sendOpenAIResponseRequest.ts +65 -0
- package/ai/llm/getPricing.ts +14 -0
- package/ai/llm/kimi.ts +4 -1
- package/ai/llm/modelAvatar.ts +1 -0
- package/ai/llm/providers.ts +4 -1
- package/ai/llm/reasoningModels.ts +1 -0
- package/ai/taskRun/taskRunProtocol.ts +25 -10
- package/ai/token/modelUsageQuery.ts +17 -0
- package/ai/tools/agent/agentTools.ts +38 -0
- package/ai/tools/agent/prepareAgentDraftTool.ts +159 -0
- package/ai/tools/agent/startAgentDialogTool.ts +53 -0
- package/ai/tools/checkEnvTool.ts +52 -20
- package/ai/tools/convertMarxistsBookTool.ts +103 -0
- package/ai/tools/{execBashTool.ts → execShellTool.ts} +44 -161
- package/ai/tools/index.ts +24 -14
- package/ai/tools/marxistsOfflineBook.ts +253 -0
- package/ai/tools/modelUsageTools.ts +9 -2
- package/ai/tools/readTool.ts +31 -0
- package/ai/tools/toolApiClient.ts +35 -1
- package/ai/tools/toolNameAliases.ts +13 -0
- package/cliCommandAdapters.ts +28 -0
- package/cliCommandDispatch.ts +66 -0
- package/cliCommandFactories.ts +34 -0
- package/cliCommandTypes.ts +49 -0
- package/cliEnvHelpers.ts +44 -0
- package/client/agentRun.test.ts +330 -24
- package/client/agentRun.ts +117 -35
- package/client/hybridRecordStore.test.ts +42 -0
- package/client/hybridRecordStore.ts +11 -0
- package/client/localRuntimeAdapter.test.ts +191 -21
- package/client/localRuntimeAdapter.ts +99 -10
- package/client/localRuntimeDryRun.test.ts +5 -2
- package/client/taskRunPrompt.ts +1 -0
- package/commandRegistry.ts +11 -61
- package/connector-experimental/capabilities.ts +1 -0
- package/database/actions/common.ts +4 -3
- package/database/config.ts +19 -0
- package/database/server/coreDataProxy.ts +43 -26
- package/index.ts +54 -191
- package/internalCommandEntries.ts +12 -0
- package/machineCommands.ts +688 -64
- package/offlineMarxistsAgentCommand.ts +314 -0
- package/package.json +22 -2
- package/render/canvas/canvasEditContext.ts +127 -0
- package/render/canvas/canvasRuntime.ts +57 -0
- package/render/canvas/canvasSnapshotParser.ts +76 -0
- package/render/canvas/canvasTree.ts +308 -0
- package/render/canvas/types.ts +46 -0
- package/render/layout/deleteBehavior.ts +52 -0
- package/render/layout/mainLayoutSidebar.ts +17 -0
- package/render/layout/mainLayoutViewMode.ts +56 -0
- package/render/layout/topbarUtils.ts +87 -0
- package/render/layout/useDevReloadPending.ts +30 -0
- package/render/page/createPageAction.ts +183 -0
- package/render/page/docSlice.ts +468 -0
- package/render/page/server/createPage.ts +174 -0
- package/render/page/server/handleCreatePage.ts +91 -0
- package/render/page/server/index.ts +4 -0
- package/render/page/types.ts +17 -0
- package/render/page/useKeyboardSave.ts +48 -0
- package/render/styles/zIndex.ts +12 -0
- package/render/surf/WeatherIconStyles.ts +17 -0
- package/render/surf/color.ts +9 -0
- package/render/surf/config.ts +46 -0
- package/render/surf/screens/style.ts +1 -0
- package/render/surf/styles/ToggleButtonStyles.ts +8 -0
- package/render/surf/utils/groupedWeatherData.ts +32 -0
- package/render/surf/weatherUtils.ts +50 -0
- package/render/table/activityColumns.ts +6 -0
- package/render/table/createTableAction.ts +270 -0
- package/render/table/deleteTableAction.ts +129 -0
- package/render/table/fetchAndCacheTableRows.ts +174 -0
- package/render/table/tableSlice.ts +1106 -0
- package/render/table/tableView.ts +289 -0
- package/render/table/toolValueUtils.ts +363 -0
- package/render/table/types.ts +252 -0
- package/render/table/useCreateTable.ts +72 -0
- package/render/table/useTable.ts +61 -0
- package/render/table/utils/tableSerialization.ts +50 -0
- package/render/web/elements/artifactPreviewCode.ts +43 -0
- package/render/web/elements/artifactRuntimePreload.ts +52 -0
- package/render/web/elements/codeBlockAutoPreview.ts +10 -0
- package/render/web/elements/mermaidPreview.ts +21 -0
- package/render/web/ui/useInlineEdit.ts +135 -0
- package/runtimeDoctorCommands.ts +118 -0
- package/scriptCommandEntries.ts +71 -0
- package/systemInternalCommandEntries.ts +71 -0
- package/tableCommands.ts +42 -5
- package/workflowInternalCommandEntries.ts +11 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
buildAgentFormDataFromGuidedDraft,
|
|
4
|
+
mergeGuidedAgentDraft,
|
|
5
|
+
validateGuidedAgentDraft,
|
|
6
|
+
} from "ai/agent/guidedCreation/draft";
|
|
7
|
+
import { DEFAULT_MODEL } from "ai/llm/providers";
|
|
8
|
+
import type {
|
|
9
|
+
GuidedAgentAssistantMessage,
|
|
10
|
+
GuidedAgentCapabilityId,
|
|
11
|
+
GuidedAgentDraft,
|
|
12
|
+
} from "ai/agent/guidedCreation/types";
|
|
13
|
+
import { mapCapabilityIdsToToolIds } from "ai/agent/guidedCreation/capabilities";
|
|
14
|
+
|
|
15
|
+
const EMPTY_DRAFT: GuidedAgentDraft = {
|
|
16
|
+
name: "",
|
|
17
|
+
introduction: "",
|
|
18
|
+
prompt: "",
|
|
19
|
+
promptSummary: "",
|
|
20
|
+
provider: DEFAULT_MODEL.provider,
|
|
21
|
+
model: DEFAULT_MODEL.name,
|
|
22
|
+
isPublic: false,
|
|
23
|
+
capabilityIds: [],
|
|
24
|
+
toolIds: [],
|
|
25
|
+
references: [],
|
|
26
|
+
tags: [],
|
|
27
|
+
unresolved: ["用途", "回答风格", "能力"],
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const QUESTIONS = [
|
|
31
|
+
"你想让这个 AI 主要帮你完成什么?",
|
|
32
|
+
"它主要服务谁?希望用什么语气和风格回答?",
|
|
33
|
+
"它需要哪些能力:联网搜索、读写文档、处理表格、调用其他 AI、创建应用或代码?",
|
|
34
|
+
"有哪些资料、边界或禁区需要它遵守?",
|
|
35
|
+
"这个 AI 是只给自己用,还是公开给别人使用?",
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
const textIncludes = (text: string, words: string[]) =>
|
|
39
|
+
words.some((word) => text.includes(word));
|
|
40
|
+
|
|
41
|
+
const inferCapabilityIds = (text: string): GuidedAgentCapabilityId[] => {
|
|
42
|
+
const lower = text.toLowerCase();
|
|
43
|
+
const ids: GuidedAgentCapabilityId[] = [];
|
|
44
|
+
if (textIncludes(lower, ["搜索", "联网", "网页", "资料", "最新", "research", "search"])) {
|
|
45
|
+
ids.push("webSearch");
|
|
46
|
+
}
|
|
47
|
+
if (textIncludes(lower, ["文档", "知识库", "页面", "资料", "doc", "docs", "knowledge"])) {
|
|
48
|
+
ids.push("docs");
|
|
49
|
+
}
|
|
50
|
+
if (textIncludes(lower, ["表格", "数据", "记录", "table", "sheet", "csv"])) {
|
|
51
|
+
ids.push("tables");
|
|
52
|
+
}
|
|
53
|
+
if (textIncludes(lower, ["多 agent", "多agent", "评审", "协作", "agent"])) {
|
|
54
|
+
ids.push("agents");
|
|
55
|
+
}
|
|
56
|
+
if (textIncludes(lower, ["应用", "代码", "app", "code", "开发", "生成页面"])) {
|
|
57
|
+
ids.push("apps");
|
|
58
|
+
}
|
|
59
|
+
return Array.from(new Set(ids));
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const inferName = (text: string) => {
|
|
63
|
+
const cleaned = text
|
|
64
|
+
.replace(/[。!?!?,,;;::]/g, " ")
|
|
65
|
+
.replace(/\bAI\b/gi, "")
|
|
66
|
+
.replace(/帮我创建|创建一个|创建|面向/g, "")
|
|
67
|
+
.replace(/\s+/g, " ")
|
|
68
|
+
.trim();
|
|
69
|
+
if (!cleaned) return "";
|
|
70
|
+
const purpose = cleaned.slice(0, 18);
|
|
71
|
+
return `${purpose.replace(/\s+/g, "")}助手`;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const buildPrompt = (messages: GuidedAgentAssistantMessage[], draft: GuidedAgentDraft) => {
|
|
75
|
+
const userNeeds = messages
|
|
76
|
+
.filter((message) => message.role === "user")
|
|
77
|
+
.map((message) => `- ${message.content}`)
|
|
78
|
+
.join("\n");
|
|
79
|
+
return [
|
|
80
|
+
`你是${draft.name || "用户的专属 AI"}。`,
|
|
81
|
+
"你需要先理解用户目标,再给出可执行、清晰、有边界的帮助。",
|
|
82
|
+
"根据以下需求工作:",
|
|
83
|
+
userNeeds || "- 尚未补充具体需求",
|
|
84
|
+
"如果信息不足,先提出必要澄清问题;不要编造用户没有确认的资料来源。",
|
|
85
|
+
].join("\n");
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const buildNextQuestion = (draft: GuidedAgentDraft) => {
|
|
89
|
+
if (!draft.name || !draft.prompt) return QUESTIONS[1];
|
|
90
|
+
if (draft.capabilityIds.length === 0) return QUESTIONS[2];
|
|
91
|
+
if (!draft.introduction) return QUESTIONS[3];
|
|
92
|
+
if (draft.unresolved.includes("公开状态")) return QUESTIONS[4];
|
|
93
|
+
return "配置草稿已经可创建。你还想补充哪些资料、语气或使用边界?";
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const createAssistantDraft = (
|
|
97
|
+
messages: GuidedAgentAssistantMessage[],
|
|
98
|
+
current: GuidedAgentDraft
|
|
99
|
+
) => {
|
|
100
|
+
const userText = messages
|
|
101
|
+
.filter((message) => message.role === "user")
|
|
102
|
+
.map((message) => message.content)
|
|
103
|
+
.join("\n");
|
|
104
|
+
const capabilityIds = Array.from(
|
|
105
|
+
new Set([...current.capabilityIds, ...inferCapabilityIds(userText)])
|
|
106
|
+
);
|
|
107
|
+
const name = current.name || inferName(userText);
|
|
108
|
+
const isPublic =
|
|
109
|
+
current.isPublic ||
|
|
110
|
+
textIncludes(userText.toLowerCase(), ["公开", "分享", "public", "给别人"]);
|
|
111
|
+
const unresolved = [
|
|
112
|
+
!name ? "name" : "",
|
|
113
|
+
!userText.trim() ? "prompt" : "",
|
|
114
|
+
capabilityIds.length === 0 ? "能力" : "",
|
|
115
|
+
current.introduction || userText.length > 24 ? "" : "introduction",
|
|
116
|
+
textIncludes(userText.toLowerCase(), ["公开", "私有", "private", "public"]) ? "" : "公开状态",
|
|
117
|
+
].filter(Boolean);
|
|
118
|
+
const next: GuidedAgentDraft = mergeGuidedAgentDraft(current, {
|
|
119
|
+
name,
|
|
120
|
+
introduction:
|
|
121
|
+
current.introduction ||
|
|
122
|
+
(userText.trim()
|
|
123
|
+
? `面向你的需求,帮助整理、判断并执行:${userText.trim().slice(0, 70)}`
|
|
124
|
+
: ""),
|
|
125
|
+
promptSummary: userText.trim()
|
|
126
|
+
? `围绕“${userText.trim().slice(0, 46)}”提供结构化帮助,必要时先追问。`
|
|
127
|
+
: "",
|
|
128
|
+
capabilityIds,
|
|
129
|
+
toolIds: mapCapabilityIdsToToolIds(capabilityIds),
|
|
130
|
+
isPublic,
|
|
131
|
+
tags: Array.from(new Set([...current.tags, ...capabilityIds])),
|
|
132
|
+
unresolved,
|
|
133
|
+
});
|
|
134
|
+
return mergeGuidedAgentDraft(next, {
|
|
135
|
+
prompt: buildPrompt(messages, next),
|
|
136
|
+
});
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const createId = () => `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
140
|
+
|
|
141
|
+
export const useGuidedAgentCreation = (initialDraft?: Partial<GuidedAgentDraft> | null) => {
|
|
142
|
+
const [draft, setDraft] = React.useState<GuidedAgentDraft>(() =>
|
|
143
|
+
initialDraft ? mergeGuidedAgentDraft(EMPTY_DRAFT, initialDraft) : EMPTY_DRAFT
|
|
144
|
+
);
|
|
145
|
+
const [messages, setMessages] = React.useState<GuidedAgentAssistantMessage[]>([
|
|
146
|
+
{
|
|
147
|
+
id: "assistant-initial",
|
|
148
|
+
role: "assistant",
|
|
149
|
+
content: initialDraft?.name
|
|
150
|
+
? "我已经带入了当前对话整理出的草稿。你可以继续补充要求,或直接预览并创建。"
|
|
151
|
+
: QUESTIONS[0],
|
|
152
|
+
},
|
|
153
|
+
]);
|
|
154
|
+
const [input, setInput] = React.useState("");
|
|
155
|
+
const [isThinking, setIsThinking] = React.useState(false);
|
|
156
|
+
const [error, setError] = React.useState<string | null>(null);
|
|
157
|
+
|
|
158
|
+
const submit = React.useCallback(async () => {
|
|
159
|
+
const content = input.trim();
|
|
160
|
+
if (!content || isThinking) return;
|
|
161
|
+
|
|
162
|
+
setInput("");
|
|
163
|
+
setError(null);
|
|
164
|
+
setIsThinking(true);
|
|
165
|
+
|
|
166
|
+
const userMessage: GuidedAgentAssistantMessage = {
|
|
167
|
+
id: createId(),
|
|
168
|
+
role: "user",
|
|
169
|
+
content,
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const nextMessages = [...messages, userMessage];
|
|
174
|
+
const nextDraft = createAssistantDraft(nextMessages, draft);
|
|
175
|
+
const assistantMessage: GuidedAgentAssistantMessage = {
|
|
176
|
+
id: createId(),
|
|
177
|
+
role: "assistant",
|
|
178
|
+
content: buildNextQuestion(nextDraft),
|
|
179
|
+
};
|
|
180
|
+
setMessages([...nextMessages, assistantMessage]);
|
|
181
|
+
setDraft(nextDraft);
|
|
182
|
+
} catch {
|
|
183
|
+
setError("暂时没能整理这段需求,请换一种说法再试。");
|
|
184
|
+
} finally {
|
|
185
|
+
setIsThinking(false);
|
|
186
|
+
}
|
|
187
|
+
}, [draft, input, isThinking, messages]);
|
|
188
|
+
|
|
189
|
+
const toggleReference = React.useCallback((dbKey: string, selected: boolean) => {
|
|
190
|
+
setDraft((current) =>
|
|
191
|
+
mergeGuidedAgentDraft(current, {
|
|
192
|
+
references: current.references.map((reference) =>
|
|
193
|
+
reference.dbKey === dbKey ? { ...reference, selected } : reference
|
|
194
|
+
),
|
|
195
|
+
})
|
|
196
|
+
);
|
|
197
|
+
}, []);
|
|
198
|
+
|
|
199
|
+
const validation = validateGuidedAgentDraft(draft);
|
|
200
|
+
const formInitialValues = React.useMemo(
|
|
201
|
+
() => buildAgentFormDataFromGuidedDraft(draft),
|
|
202
|
+
[draft]
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
draft,
|
|
207
|
+
messages,
|
|
208
|
+
input,
|
|
209
|
+
setInput,
|
|
210
|
+
submit,
|
|
211
|
+
isThinking,
|
|
212
|
+
error,
|
|
213
|
+
validation,
|
|
214
|
+
formInitialValues,
|
|
215
|
+
toggleReference,
|
|
216
|
+
};
|
|
217
|
+
};
|
package/ai/ai.locale.ts
CHANGED
|
@@ -27,6 +27,9 @@ export default {
|
|
|
27
27
|
textOnly: "Text-only",
|
|
28
28
|
details: "Details",
|
|
29
29
|
price: "Price",
|
|
30
|
+
modelCost: "Model cost",
|
|
31
|
+
inputCostPerMillionTokens: "Input",
|
|
32
|
+
outputCostPerMillionTokens: "Output",
|
|
30
33
|
referencePricePerTurn: "Per-turn reference price",
|
|
31
34
|
defaultImageProfileEstimate: "Default profile estimate",
|
|
32
35
|
perMillionTokens: "per 1M tokens",
|
|
@@ -251,7 +254,7 @@ export default {
|
|
|
251
254
|
description: "Create a new Agent (intelligent assistant/app) based on the given configuration and return the complete Agent information.",
|
|
252
255
|
params: {
|
|
253
256
|
name: "The name of the Agent, e.g., 'Meeting Minutes Assistant'.",
|
|
254
|
-
model: "The model name to use. Logic: Default 'xiaomi/mimo-v2.5-pro'. Complex tasks: 'openai/gpt-5.2'. Cost-saving: 'openai/gpt-5.1'. Aesthetic/Creative: 'gemini-3-pro-preview'.
|
|
257
|
+
model: "The model name to use. Logic: Default 'xiaomi/mimo-v2.5-pro'. Complex tasks: 'openai/gpt-5.2'. Cost-saving: 'openai/gpt-5.1'. Aesthetic/Creative: 'gemini-3-pro-preview'. Reliable low-cost summary/reasoning: 'deepseek-v4-pro'. Image Generation: 'gemini-3-pro-image-preview' (Best/Expensive) or 'gemini-2.5-flash-image' (Ordinary).",
|
|
255
258
|
provider: "The model provider identifier, must match the selected model, e.g., 'openrouter', 'deepseek', 'fireworks', 'mistral', etc.",
|
|
256
259
|
prompt: "System prompt/instruction describing the Agent's role, tone, and usage.",
|
|
257
260
|
introduction: "Introduction displayed in the app market or list, explanatory text for end users.",
|
|
@@ -318,6 +321,9 @@ export default {
|
|
|
318
321
|
textOnly: "纯文本",
|
|
319
322
|
details: "详细信息",
|
|
320
323
|
price: "价格",
|
|
324
|
+
modelCost: "模型成本",
|
|
325
|
+
inputCostPerMillionTokens: "输入",
|
|
326
|
+
outputCostPerMillionTokens: "输出",
|
|
321
327
|
referencePricePerTurn: "按次参考价",
|
|
322
328
|
defaultImageProfileEstimate: "默认档参考价",
|
|
323
329
|
perMillionTokens: "每百万Token",
|
|
@@ -539,7 +545,7 @@ export default {
|
|
|
539
545
|
description: "根据给定配置创建一个新的 Agent(智能体 / 应用),并返回完整的 Agent 信息。",
|
|
540
546
|
params: {
|
|
541
547
|
name: "Agent 的名称,例如:'会议纪要助手'。",
|
|
542
|
-
model: "默认使用 'xiaomi/mimo-v2.5-pro'。复杂任务: 'openai/gpt-5.2'。省成本但复杂: 'openai/gpt-5.1'。追求美观/创意: 'gemini-3-pro-preview'
|
|
548
|
+
model: "默认使用 'xiaomi/mimo-v2.5-pro'。复杂任务: 'openai/gpt-5.2'。省成本但复杂: 'openai/gpt-5.1'。追求美观/创意: 'gemini-3-pro-preview'。低成本摘要/推理: 'deepseek-v4-pro'。生成图片: 贵/高质量选 'gemini-3-pro-image-preview',普通选 'gemini-2.5-flash-image'。",
|
|
543
549
|
provider: "模型提供方标识,必须与所选模型相匹配,例如 'openrouter'、'deepseek'、'fireworks'、'mistral' 等。",
|
|
544
550
|
prompt: "系统提示词 / 指令,描述这个 Agent 的角色、语气和使用方式。",
|
|
545
551
|
introduction: "在应用市场或列表中展示的简介,面向终端用户的说明文案。",
|
|
@@ -606,6 +612,9 @@ export default {
|
|
|
606
612
|
textOnly: "純文字",
|
|
607
613
|
details: "詳細資訊",
|
|
608
614
|
price: "價格",
|
|
615
|
+
modelCost: "模型成本",
|
|
616
|
+
inputCostPerMillionTokens: "輸入",
|
|
617
|
+
outputCostPerMillionTokens: "輸出",
|
|
609
618
|
referencePricePerTurn: "按次參考價",
|
|
610
619
|
defaultImageProfileEstimate: "預設檔參考價",
|
|
611
620
|
perMillionTokens: "每百萬Token",
|
|
@@ -852,6 +861,9 @@ export default {
|
|
|
852
861
|
textOnly: "テキストのみ",
|
|
853
862
|
details: "詳細",
|
|
854
863
|
price: "価格",
|
|
864
|
+
modelCost: "モデルコスト",
|
|
865
|
+
inputCostPerMillionTokens: "入力",
|
|
866
|
+
outputCostPerMillionTokens: "出力",
|
|
855
867
|
referencePricePerTurn: "回ごとの参考価格",
|
|
856
868
|
defaultImageProfileEstimate: "デフォルト構成の参考価格",
|
|
857
869
|
perMillionTokens: "100万トークンあたり",
|
|
@@ -42,7 +42,9 @@ const buildProxyPayload = (
|
|
|
42
42
|
...bodyData,
|
|
43
43
|
url: api,
|
|
44
44
|
provider,
|
|
45
|
+
agentKey: agentConfig.dbKey,
|
|
45
46
|
...(apiSource ? { apiSource } : {}),
|
|
47
|
+
...((agentConfig as any).apiKeyHeader ? { apiKeyHeader: (agentConfig as any).apiKeyHeader } : {}),
|
|
46
48
|
KEY: apiKey,
|
|
47
49
|
};
|
|
48
50
|
};
|
package/ai/chat/fetchUtils.ts
CHANGED
|
@@ -40,7 +40,9 @@ const buildProxyPayload = (
|
|
|
40
40
|
...bodyData,
|
|
41
41
|
url: api,
|
|
42
42
|
provider,
|
|
43
|
+
agentKey: agentConfig.dbKey,
|
|
43
44
|
...(apiSource ? { apiSource } : {}),
|
|
45
|
+
...((agentConfig as any).apiKeyHeader ? { apiKeyHeader: (agentConfig as any).apiKeyHeader } : {}),
|
|
44
46
|
KEY: apiKey,
|
|
45
47
|
};
|
|
46
48
|
};
|
|
@@ -160,6 +160,23 @@ export type CompletionMeta = {
|
|
|
160
160
|
usage?: any;
|
|
161
161
|
};
|
|
162
162
|
|
|
163
|
+
const STREAM_READ_TIMEOUT_MS = 45_000;
|
|
164
|
+
|
|
165
|
+
const logQuickChatPerfStage = (
|
|
166
|
+
startedAt: number | undefined,
|
|
167
|
+
stage: string,
|
|
168
|
+
details?: Record<string, unknown>
|
|
169
|
+
) => {
|
|
170
|
+
if (!startedAt) return;
|
|
171
|
+
const now = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
172
|
+
console.info("[QuickChatPerf]", {
|
|
173
|
+
stage,
|
|
174
|
+
elapsedMs: now - startedAt,
|
|
175
|
+
...(typeof performance !== "undefined" ? { atMs: now } : {}),
|
|
176
|
+
...(details ?? {}),
|
|
177
|
+
});
|
|
178
|
+
};
|
|
179
|
+
|
|
163
180
|
/**
|
|
164
181
|
* 初始化流式状态
|
|
165
182
|
*/
|
|
@@ -398,6 +415,28 @@ async function processAccumulatedToolCalls(
|
|
|
398
415
|
};
|
|
399
416
|
}
|
|
400
417
|
|
|
418
|
+
async function readStreamChunkWithTimeout(
|
|
419
|
+
reader: ReadableStreamDefaultReader<Uint8Array>
|
|
420
|
+
) {
|
|
421
|
+
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
422
|
+
try {
|
|
423
|
+
return await Promise.race([
|
|
424
|
+
reader.read(),
|
|
425
|
+
new Promise<ReadableStreamReadResult<Uint8Array>>((_, reject) => {
|
|
426
|
+
timeoutId = setTimeout(() => {
|
|
427
|
+
reject(
|
|
428
|
+
new Error(
|
|
429
|
+
`模型响应流 ${Math.round(STREAM_READ_TIMEOUT_MS / 1000)} 秒内没有返回新内容`
|
|
430
|
+
)
|
|
431
|
+
);
|
|
432
|
+
}, STREAM_READ_TIMEOUT_MS);
|
|
433
|
+
}),
|
|
434
|
+
]);
|
|
435
|
+
} finally {
|
|
436
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
401
440
|
/**
|
|
402
441
|
* 处理「流结束」场景(done === true)
|
|
403
442
|
*/
|
|
@@ -455,6 +494,7 @@ export const sendOpenAICompletionsRequest = async ({
|
|
|
455
494
|
parentMessageId,
|
|
456
495
|
messageMetadata,
|
|
457
496
|
disableToolsForThisRequest = false,
|
|
497
|
+
quickChatPerfStartedAt,
|
|
458
498
|
}: {
|
|
459
499
|
bodyData: any;
|
|
460
500
|
agentConfig: any;
|
|
@@ -463,6 +503,7 @@ export const sendOpenAICompletionsRequest = async ({
|
|
|
463
503
|
parentMessageId?: string;
|
|
464
504
|
messageMetadata?: Partial<Message>;
|
|
465
505
|
disableToolsForThisRequest?: boolean;
|
|
506
|
+
quickChatPerfStartedAt?: number;
|
|
466
507
|
}): Promise<CompletionMeta> => {
|
|
467
508
|
const { dispatch, getState, signal: thunkSignal } = thunkApi;
|
|
468
509
|
|
|
@@ -549,6 +590,10 @@ export const sendOpenAICompletionsRequest = async ({
|
|
|
549
590
|
|
|
550
591
|
const api = getApiEndpoint(agentConfig);
|
|
551
592
|
const token = selectCurrentToken(getState() as RootState);
|
|
593
|
+
logQuickChatPerfStage(quickChatPerfStartedAt, "openai-completions-fetch-starting", {
|
|
594
|
+
api,
|
|
595
|
+
dialogKey,
|
|
596
|
+
});
|
|
552
597
|
const response = await performFetchRequest({
|
|
553
598
|
agentConfig,
|
|
554
599
|
api,
|
|
@@ -557,6 +602,11 @@ export const sendOpenAICompletionsRequest = async ({
|
|
|
557
602
|
signal,
|
|
558
603
|
token,
|
|
559
604
|
});
|
|
605
|
+
logQuickChatPerfStage(quickChatPerfStartedAt, "openai-completions-fetch-response", {
|
|
606
|
+
ok: response.ok,
|
|
607
|
+
status: response.status,
|
|
608
|
+
dialogKey,
|
|
609
|
+
});
|
|
560
610
|
|
|
561
611
|
if (!response.ok) {
|
|
562
612
|
const errorMessage = await parseApiError(response);
|
|
@@ -578,9 +628,12 @@ export const sendOpenAICompletionsRequest = async ({
|
|
|
578
628
|
}
|
|
579
629
|
|
|
580
630
|
const decoder = new TextDecoder();
|
|
631
|
+
let loggedFirstStreamChunk = false;
|
|
632
|
+
let loggedFirstParsedEvent = false;
|
|
633
|
+
let loggedFirstVisibleDelta = false;
|
|
581
634
|
|
|
582
635
|
while (true) {
|
|
583
|
-
const { done, value } = await reader
|
|
636
|
+
const { done, value } = await readStreamChunkWithTimeout(reader);
|
|
584
637
|
|
|
585
638
|
if (done) {
|
|
586
639
|
const completion = await handleStreamCompletion(
|
|
@@ -602,8 +655,25 @@ export const sendOpenAICompletionsRequest = async ({
|
|
|
602
655
|
break;
|
|
603
656
|
}
|
|
604
657
|
|
|
658
|
+
if (!loggedFirstStreamChunk) {
|
|
659
|
+
loggedFirstStreamChunk = true;
|
|
660
|
+
logQuickChatPerfStage(
|
|
661
|
+
quickChatPerfStartedAt,
|
|
662
|
+
"openai-completions-first-stream-chunk",
|
|
663
|
+
{ dialogKey, byteLength: value.byteLength }
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
|
|
605
667
|
const chunk = decoder.decode(value, { stream: true });
|
|
606
668
|
const parsedResults = parseSSE(chunk);
|
|
669
|
+
if (parsedResults.length > 0 && !loggedFirstParsedEvent) {
|
|
670
|
+
loggedFirstParsedEvent = true;
|
|
671
|
+
logQuickChatPerfStage(
|
|
672
|
+
quickChatPerfStartedAt,
|
|
673
|
+
"openai-completions-first-sse-event",
|
|
674
|
+
{ dialogKey, eventCount: parsedResults.length }
|
|
675
|
+
);
|
|
676
|
+
}
|
|
607
677
|
|
|
608
678
|
for (const parsedData of parsedResults) {
|
|
609
679
|
const dataList = Array.isArray(parsedData) ? parsedData : [parsedData];
|
|
@@ -641,6 +711,14 @@ export const sendOpenAICompletionsRequest = async ({
|
|
|
641
711
|
delta
|
|
642
712
|
);
|
|
643
713
|
streamState = updatedState;
|
|
714
|
+
if (hasNewVisibleContent && !loggedFirstVisibleDelta) {
|
|
715
|
+
loggedFirstVisibleDelta = true;
|
|
716
|
+
logQuickChatPerfStage(
|
|
717
|
+
quickChatPerfStartedAt,
|
|
718
|
+
"openai-completions-first-visible-delta",
|
|
719
|
+
{ dialogKey }
|
|
720
|
+
);
|
|
721
|
+
}
|
|
644
722
|
|
|
645
723
|
emitStreamingUpdate(hasNewVisibleContent, streamState, {
|
|
646
724
|
dispatch,
|
|
@@ -712,6 +790,9 @@ export const sendOpenAICompletionsRequest = async ({
|
|
|
712
790
|
};
|
|
713
791
|
streamState = await finalizeStream(streamState, finalizeCtx);
|
|
714
792
|
} finally {
|
|
793
|
+
logQuickChatPerfStage(quickChatPerfStartedAt, "openai-completions-stream-finished", {
|
|
794
|
+
dialogKey,
|
|
795
|
+
});
|
|
715
796
|
dispatch(removeActiveController({ messageId, dialogKey }));
|
|
716
797
|
try {
|
|
717
798
|
await reader?.cancel();
|
|
@@ -42,6 +42,21 @@ const shouldEnableBuiltInImageGeneration = (agentConfig: any): boolean =>
|
|
|
42
42
|
!getModelInfo(String(agentConfig?.model || ""))?.hasImageOutput &&
|
|
43
43
|
!!agentConfig?.imageConfig?.enabled;
|
|
44
44
|
|
|
45
|
+
const logQuickChatPerfStage = (
|
|
46
|
+
startedAt: number | undefined,
|
|
47
|
+
stage: string,
|
|
48
|
+
details?: Record<string, unknown>
|
|
49
|
+
) => {
|
|
50
|
+
if (!startedAt) return;
|
|
51
|
+
const now = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
52
|
+
console.info("[QuickChatPerf]", {
|
|
53
|
+
stage,
|
|
54
|
+
elapsedMs: now - startedAt,
|
|
55
|
+
...(typeof performance !== "undefined" ? { atMs: now } : {}),
|
|
56
|
+
...(details ?? {}),
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
|
|
45
60
|
type StreamState = {
|
|
46
61
|
content: string;
|
|
47
62
|
contentBuffer: Array<
|
|
@@ -140,6 +155,7 @@ export const sendOpenAIResponseRequest = async ({
|
|
|
140
155
|
dialogKey,
|
|
141
156
|
parentMessageId,
|
|
142
157
|
messageMetadata,
|
|
158
|
+
quickChatPerfStartedAt,
|
|
143
159
|
}: {
|
|
144
160
|
bodyData: any;
|
|
145
161
|
agentConfig: any;
|
|
@@ -147,6 +163,7 @@ export const sendOpenAIResponseRequest = async ({
|
|
|
147
163
|
dialogKey: string;
|
|
148
164
|
parentMessageId?: string;
|
|
149
165
|
messageMetadata?: Partial<Message>;
|
|
166
|
+
quickChatPerfStartedAt?: number;
|
|
150
167
|
}): Promise<CompletionMeta> => {
|
|
151
168
|
const { dispatch, getState, signal: thunkSignal } = thunkApi;
|
|
152
169
|
const dialogId = extractCustomId(dialogKey);
|
|
@@ -354,6 +371,10 @@ export const sendOpenAIResponseRequest = async ({
|
|
|
354
371
|
|
|
355
372
|
const api = getApiEndpoint(agentConfig);
|
|
356
373
|
const token = selectCurrentToken(getState() as RootState);
|
|
374
|
+
logQuickChatPerfStage(quickChatPerfStartedAt, "openai-response-fetch-starting", {
|
|
375
|
+
api,
|
|
376
|
+
dialogKey,
|
|
377
|
+
});
|
|
357
378
|
const response = await performFetchRequest({
|
|
358
379
|
agentConfig,
|
|
359
380
|
api,
|
|
@@ -362,6 +383,11 @@ export const sendOpenAIResponseRequest = async ({
|
|
|
362
383
|
signal,
|
|
363
384
|
token,
|
|
364
385
|
});
|
|
386
|
+
logQuickChatPerfStage(quickChatPerfStartedAt, "openai-response-fetch-response", {
|
|
387
|
+
ok: response.ok,
|
|
388
|
+
status: response.status,
|
|
389
|
+
dialogKey,
|
|
390
|
+
});
|
|
365
391
|
|
|
366
392
|
if (!response.ok) {
|
|
367
393
|
const errorMessage = await parseApiError(response);
|
|
@@ -379,13 +405,33 @@ export const sendOpenAIResponseRequest = async ({
|
|
|
379
405
|
const parseSSE = createSSEParser();
|
|
380
406
|
const decoder = new TextDecoder();
|
|
381
407
|
let finishReason: string | null = null;
|
|
408
|
+
let loggedFirstStreamChunk = false;
|
|
409
|
+
let loggedFirstParsedEvent = false;
|
|
410
|
+
let loggedFirstVisibleDelta = false;
|
|
382
411
|
|
|
383
412
|
while (true) {
|
|
384
413
|
const { done, value } = await reader.read();
|
|
385
414
|
if (done) break;
|
|
386
415
|
|
|
416
|
+
if (!loggedFirstStreamChunk) {
|
|
417
|
+
loggedFirstStreamChunk = true;
|
|
418
|
+
logQuickChatPerfStage(
|
|
419
|
+
quickChatPerfStartedAt,
|
|
420
|
+
"openai-response-first-stream-chunk",
|
|
421
|
+
{ dialogKey, byteLength: value.byteLength }
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
|
|
387
425
|
const chunk = decoder.decode(value, { stream: true });
|
|
388
426
|
const events = parseSSE(chunk);
|
|
427
|
+
if (events.length > 0 && !loggedFirstParsedEvent) {
|
|
428
|
+
loggedFirstParsedEvent = true;
|
|
429
|
+
logQuickChatPerfStage(
|
|
430
|
+
quickChatPerfStartedAt,
|
|
431
|
+
"openai-response-first-sse-event",
|
|
432
|
+
{ dialogKey, eventCount: events.length }
|
|
433
|
+
);
|
|
434
|
+
}
|
|
389
435
|
const eventList = Array.isArray(events) ? events : [events];
|
|
390
436
|
|
|
391
437
|
for (const event of eventList) {
|
|
@@ -404,6 +450,14 @@ export const sendOpenAIResponseRequest = async ({
|
|
|
404
450
|
if (event.delta) {
|
|
405
451
|
state.content += event.delta;
|
|
406
452
|
state.contentBuffer = seg(state.content);
|
|
453
|
+
if (!loggedFirstVisibleDelta) {
|
|
454
|
+
loggedFirstVisibleDelta = true;
|
|
455
|
+
logQuickChatPerfStage(
|
|
456
|
+
quickChatPerfStartedAt,
|
|
457
|
+
"openai-response-first-visible-delta",
|
|
458
|
+
{ dialogKey }
|
|
459
|
+
);
|
|
460
|
+
}
|
|
407
461
|
flush();
|
|
408
462
|
}
|
|
409
463
|
break;
|
|
@@ -427,6 +481,14 @@ export const sendOpenAIResponseRequest = async ({
|
|
|
427
481
|
if (itemText) {
|
|
428
482
|
state.content = itemText;
|
|
429
483
|
state.contentBuffer = seg(state.content);
|
|
484
|
+
if (!loggedFirstVisibleDelta) {
|
|
485
|
+
loggedFirstVisibleDelta = true;
|
|
486
|
+
logQuickChatPerfStage(
|
|
487
|
+
quickChatPerfStartedAt,
|
|
488
|
+
"openai-response-first-visible-delta",
|
|
489
|
+
{ dialogKey }
|
|
490
|
+
);
|
|
491
|
+
}
|
|
430
492
|
flush();
|
|
431
493
|
}
|
|
432
494
|
}
|
|
@@ -506,6 +568,9 @@ export const sendOpenAIResponseRequest = async ({
|
|
|
506
568
|
await finalize();
|
|
507
569
|
return buildMeta(false, false, "error");
|
|
508
570
|
} finally {
|
|
571
|
+
logQuickChatPerfStage(quickChatPerfStartedAt, "openai-response-stream-finished", {
|
|
572
|
+
dialogKey,
|
|
573
|
+
});
|
|
509
574
|
dispatch(removeActiveController({ messageId, dialogKey }));
|
|
510
575
|
await safeCancel(reader);
|
|
511
576
|
}
|
package/ai/llm/getPricing.ts
CHANGED
|
@@ -209,6 +209,20 @@ export const formatPriceAmount = (amount: number): string => {
|
|
|
209
209
|
return amount.toFixed(6);
|
|
210
210
|
};
|
|
211
211
|
|
|
212
|
+
export const formatModelCostPerMillion = (
|
|
213
|
+
value?: number,
|
|
214
|
+
creditsUnit = "积分"
|
|
215
|
+
): string => {
|
|
216
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return "未知";
|
|
217
|
+
if (value === 0) return "免费";
|
|
218
|
+
const decimals = Math.abs(value) < 1 ? 3 : 2;
|
|
219
|
+
const amount = value
|
|
220
|
+
.toFixed(decimals)
|
|
221
|
+
.replace(/\.0+$/, "")
|
|
222
|
+
.replace(/(\.\d*?[1-9])0+$/, "$1");
|
|
223
|
+
return `${amount} ${creditsUnit}`;
|
|
224
|
+
};
|
|
225
|
+
|
|
212
226
|
export const formatCompactTurnPrice = (
|
|
213
227
|
amount: number
|
|
214
228
|
): CompactTurnPriceDisplay => {
|
package/ai/llm/kimi.ts
CHANGED
|
@@ -2,11 +2,14 @@ export const FIREWORKS_KIMI_LATEST_MODEL = "accounts/fireworks/models/kimi-lates
|
|
|
2
2
|
export const FIREWORKS_KIMI_CURRENT_MODEL = "accounts/fireworks/models/kimi-k2p6";
|
|
3
3
|
export const DEEPINFRA_KIMI_FALLBACK_MODEL = "moonshotai/Kimi-K2.6";
|
|
4
4
|
export const OPENROUTER_KIMI_FALLBACK_MODEL = "moonshotai/kimi-k2.6";
|
|
5
|
-
export const KIMI_PLATFORM_FALLBACK_STATUSES = [402, 429, 500, 502, 503, 504];
|
|
5
|
+
export const KIMI_PLATFORM_FALLBACK_STATUSES = [401, 402, 429, 500, 502, 503, 504];
|
|
6
6
|
|
|
7
7
|
export const isFireworksKimiModel = (model?: string | null): boolean =>
|
|
8
8
|
model === FIREWORKS_KIMI_LATEST_MODEL || model === FIREWORKS_KIMI_CURRENT_MODEL;
|
|
9
9
|
|
|
10
|
+
export const isDeepInfraKimiModel = (model?: string | null): boolean =>
|
|
11
|
+
model === DEEPINFRA_KIMI_FALLBACK_MODEL;
|
|
12
|
+
|
|
10
13
|
export const resolveFireworksKimiModel = (model?: string | null): string => {
|
|
11
14
|
if (model === FIREWORKS_KIMI_LATEST_MODEL) {
|
|
12
15
|
return FIREWORKS_KIMI_CURRENT_MODEL;
|
package/ai/llm/modelAvatar.ts
CHANGED
|
@@ -393,6 +393,7 @@ export async function getModelAvatarComponent(
|
|
|
393
393
|
if (cliProviderKey === "copilot") return get("copilot");
|
|
394
394
|
if (cliProviderKey === "codex") return get("openai");
|
|
395
395
|
if (cliProviderKey === "claude") return get("claude");
|
|
396
|
+
if (cliProviderKey === "agy") return get("gemini");
|
|
396
397
|
|
|
397
398
|
// ── 按 model 名称匹配(优先,处理 openrouter/deepinfra 等聚合场景) ──
|
|
398
399
|
if (m.includes("claude")) return get("claude");
|
package/ai/llm/providers.ts
CHANGED
|
@@ -267,7 +267,7 @@ export function getApiEndpoint(agent: Agent): string {
|
|
|
267
267
|
}
|
|
268
268
|
|
|
269
269
|
/* custom provider / apiSource=custom 但未给 URL 的兜底
|
|
270
|
-
* 注意:apiSource="custom" 的 agent
|
|
270
|
+
* 注意:apiSource="custom" 的 agent 不需要 provider 字段,
|
|
271
271
|
* 使用 customProviderUrl + 自己的 apiKey,上面已经 return 了。
|
|
272
272
|
* 只有 customProviderUrl 为空时才会走到这里报错。
|
|
273
273
|
*/
|
|
@@ -276,6 +276,9 @@ export function getApiEndpoint(agent: Agent): string {
|
|
|
276
276
|
effectiveProvider.toLowerCase() === "custom" ||
|
|
277
277
|
(agent as any).apiSource === "custom"
|
|
278
278
|
) {
|
|
279
|
+
if (agent.useServerProxy) {
|
|
280
|
+
return "";
|
|
281
|
+
}
|
|
279
282
|
throw new Error(
|
|
280
283
|
"Custom provider URL is required when apiSource is 'custom'."
|
|
281
284
|
);
|