nolo-cli 0.1.29 → 0.1.31

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 (329) hide show
  1. package/agent-runtime/agentRecordConfig.ts +0 -2
  2. package/agent-runtime/hostAdapter.ts +0 -1
  3. package/agent-runtime/hostRuntimeCapabilities.ts +7 -0
  4. package/agent-runtime/hostRuntimeConfig.ts +86 -0
  5. package/agent-runtime/index.ts +13 -0
  6. package/agent-runtime/localToolPolicy.ts +13 -6
  7. package/agent-runtime/localWorkspaceTools.ts +333 -30
  8. package/agent-runtime/platformChatProvider.ts +4 -1
  9. package/agent-runtime/runtimeToolPolicy.ts +32 -47
  10. package/agent-runtime/shellCommandPolicy.ts +117 -0
  11. package/agent-runtime/toolGate.ts +38 -0
  12. package/agentCreateCustomCommands.ts +9 -4
  13. package/agentDialogsCommands.ts +4 -3
  14. package/agentDoctorCommands.ts +3 -3
  15. package/agentListCommands.ts +3 -3
  16. package/agentMachineCommands.ts +6 -6
  17. package/agentPullCommand.ts +4 -12
  18. package/agentRecordCommands.ts +5 -3
  19. package/agentRecordHelpers.ts +42 -23
  20. package/agentRunCommand.ts +2 -6
  21. package/agentRunParsing.ts +18 -5
  22. package/agentRuntimeLocal.ts +10 -0
  23. package/agentUnpublishCommands.ts +3 -2
  24. package/ai/agent/agentRunDialogLineage.ts +56 -0
  25. package/ai/agent/agentSlice.ts +45 -10
  26. package/ai/agent/avatarUtils.ts +1 -1
  27. package/ai/agent/buildSystemPrompt.ts +1 -2
  28. package/ai/agent/cliExecutor.ts +330 -19
  29. package/ai/agent/createAgentSchema.ts +22 -0
  30. package/ai/agent/hooks/fetchPublicAgents.ts +14 -39
  31. package/ai/agent/hooks/usePublicAgents.ts +90 -192
  32. package/ai/agent/publicAgentCatalog.ts +359 -0
  33. package/ai/agent/publicAgentIdentity.ts +76 -0
  34. package/ai/agent/server/fetchPublicAgents.ts +21 -46
  35. package/ai/agent/serverOrigin.ts +7 -0
  36. package/ai/agent/streamAgentChatTurn.ts +23 -21
  37. package/ai/agent/streamAgentChatTurnUtils.ts +292 -6
  38. package/ai/agent/types.ts +2 -0
  39. package/ai/agent/web/agentDisplayUtils.ts +171 -0
  40. package/ai/agent/web/useAgentModelAvatarComponent.ts +32 -0
  41. package/ai/ai.locale.ts +8 -0
  42. package/ai/chat/sendOpenAICompletionsRequest.native.ts +15 -0
  43. package/ai/chat/sendOpenAICompletionsRequest.ts +93 -1
  44. package/ai/chat/sendOpenAIResponseRequest.ts +86 -9
  45. package/ai/chat/updateTotalUsage.ts +7 -0
  46. package/ai/llm/deepinfra.ts +30 -15
  47. package/ai/llm/mistral.ts +3 -4
  48. package/ai/llm/providers.ts +2 -0
  49. package/ai/llm/types.ts +15 -0
  50. package/ai/memory/capture.ts +3 -3
  51. package/ai/memory/consolidate.ts +2 -2
  52. package/ai/memory/delete.ts +4 -3
  53. package/ai/memory/query.ts +3 -3
  54. package/ai/memory/remember.ts +2 -2
  55. package/ai/memory/store.ts +3 -3
  56. package/ai/memory/storeShared.ts +3 -2
  57. package/ai/policy/commercialMultiplier.ts +16 -0
  58. package/ai/token/billingAdminAuditEvent.ts +68 -0
  59. package/ai/token/billingAdminAuditEventWriter.ts +41 -0
  60. package/ai/token/billingAnomaly.ts +90 -0
  61. package/ai/token/billingAnomalyLifecycle.ts +54 -0
  62. package/ai/token/billingAnomalyLifecycleWriter.ts +44 -0
  63. package/ai/token/billingAnomalyWriter.ts +41 -0
  64. package/ai/token/calculatePrice.ts +3 -2
  65. package/ai/token/deepinfraReconciliation.ts +153 -0
  66. package/ai/token/deepinfraRequestIdQuery.ts +92 -0
  67. package/ai/token/externalToolCost.ts +54 -41
  68. package/ai/token/missingUsageEstimate.ts +12 -3
  69. package/ai/token/normalizeUsage.ts +45 -5
  70. package/ai/token/openaiReconciliation.ts +188 -0
  71. package/ai/token/providerBillableEvent.ts +84 -0
  72. package/ai/token/providerBillableEventWriter.ts +36 -0
  73. package/ai/token/providerBillingAnomalyDrilldown.ts +308 -0
  74. package/ai/token/providerBillingDrilldown.ts +361 -0
  75. package/ai/token/providerBillingHealthReport.ts +366 -0
  76. package/ai/token/providerCall.ts +144 -0
  77. package/ai/token/providerCallSweeper.ts +168 -0
  78. package/ai/token/providerCallWriter.ts +33 -0
  79. package/ai/token/providerCredential.ts +89 -0
  80. package/ai/token/providerCredentialAnomaly.ts +154 -0
  81. package/ai/token/providerCredentialLifecycle.ts +59 -0
  82. package/ai/token/providerCredentialLifecycleWriter.ts +44 -0
  83. package/ai/token/providerCredentialRegistry.ts +83 -0
  84. package/ai/token/providerCredentialRegistryCommand.ts +97 -0
  85. package/ai/token/providerCredentialRegistryList.ts +124 -0
  86. package/ai/token/providerCredentialRegistryResolver.ts +150 -0
  87. package/ai/token/providerCredentialRegistryWriter.ts +41 -0
  88. package/ai/token/providerCredentialRotationCommand.ts +102 -0
  89. package/ai/token/providerCredentialRuntime.ts +56 -0
  90. package/ai/token/providerDispatchIntent.ts +67 -0
  91. package/ai/token/providerDispatchIntentWriter.ts +36 -0
  92. package/ai/token/providerGateway.ts +260 -0
  93. package/ai/token/providerReconciliation.ts +148 -0
  94. package/ai/token/providerReconciliationLocal.ts +97 -0
  95. package/ai/token/providerReconciliationRunner.ts +165 -0
  96. package/ai/token/providerReconciliationWriter.ts +41 -0
  97. package/ai/token/ratingResult.ts +202 -0
  98. package/ai/token/ratingResultWriter.ts +33 -0
  99. package/ai/token/serverDialogProjection.ts +5 -3
  100. package/ai/token/serverTokenWriter.ts +11 -3
  101. package/ai/token/types.ts +10 -0
  102. package/ai/tools/agent/callAgentTool.ts +3 -4
  103. package/ai/tools/agent/presets/appBuilderPreset.ts +5 -7
  104. package/ai/tools/agent/runStreamingAgentTool.ts +1 -2
  105. package/ai/tools/agent/streamParallelAgentsTool.ts +2 -3
  106. package/ai/tools/readXPostTool.ts +1 -5
  107. package/app/admin/adminPages.ts +9 -0
  108. package/app/i18n/i18n.config.ts +5 -0
  109. package/app/i18n/translations/pricing.locale.ts +397 -0
  110. package/app/utils/desktopAgentRuntimeStatusClient.ts +5 -4
  111. package/app/utils/desktopAgentRuntimeTurnClient.ts +4 -1
  112. package/app/utils/imageUtils.ts +64 -34
  113. package/auth/adminPermissions.ts +18 -0
  114. package/auth/authSlice.ts +26 -12
  115. package/auth/hooks/useFetchUsers.ts +3 -0
  116. package/auth/routes.ts +46 -0
  117. package/auth/server/accessActivity.ts +4 -3
  118. package/auth/server/adminPermissions.ts +43 -0
  119. package/auth/server/adminUsageReport.ts +375 -0
  120. package/auth/server/billingAnomalies.ts +235 -0
  121. package/auth/server/billingUsageReport.ts +41 -2
  122. package/auth/server/creatorEarningCreditConversion.ts +10 -2
  123. package/auth/server/deduct.ts +14 -7
  124. package/auth/server/delete.ts +10 -2
  125. package/auth/server/disable.ts +4 -3
  126. package/auth/server/emailAutomation.ts +7 -5
  127. package/auth/server/emailDbConfig.ts +4 -2
  128. package/auth/server/emailDelivery.ts +24 -20
  129. package/auth/server/emailDeliveryGuards.ts +10 -7
  130. package/auth/server/emailPreferences.ts +6 -4
  131. package/auth/server/emailReport.ts +6 -4
  132. package/auth/server/emailRuntimeConfig.ts +3 -3
  133. package/auth/server/emailScenarioNotifications.ts +9 -8
  134. package/auth/server/enable.ts +4 -3
  135. package/auth/server/getRechargeHistory.ts +3 -2
  136. package/auth/server/getUser.ts +3 -2
  137. package/auth/server/gptProAccess.ts +44 -0
  138. package/auth/server/inviteReward.ts +10 -7
  139. package/auth/server/listusers.ts +6 -4
  140. package/auth/server/login.ts +2 -2
  141. package/auth/server/productEntitlements.ts +7 -4
  142. package/auth/server/productPurchase.ts +10 -2
  143. package/auth/server/providerBillingDrilldown.ts +47 -0
  144. package/auth/server/providerBillingHealth.ts +71 -0
  145. package/auth/server/providerCredentials.ts +136 -0
  146. package/auth/server/recharge.ts +14 -8
  147. package/auth/server/revokeSessions.ts +4 -3
  148. package/auth/server/route.ts +77 -0
  149. package/auth/server/sendEmail.ts +5 -4
  150. package/auth/server/signup.ts +10 -3
  151. package/auth/server/token.ts +53 -55
  152. package/auth/server/tokenUsageBilling.ts +18 -7
  153. package/auth/server/usageManagementAccess.ts +43 -0
  154. package/auth/server/usageReport.ts +59 -0
  155. package/auth/types.ts +12 -0
  156. package/auth/utils.ts +24 -22
  157. package/authCommands.ts +36 -2
  158. package/buildPublish.ts +3 -1
  159. package/buildPublishArtifactAssembly.ts +62 -1
  160. package/buildPublishImportAnalysis.ts +21 -16
  161. package/chat/dialog/dialogLaunchScope.ts +32 -0
  162. package/chat/dialog/objectAssistantRegistry.ts +2 -2
  163. package/chat/dialog/useCreateDialog.ts +1 -1
  164. package/chat/hooks/useChatInput.ts +41 -10
  165. package/chat/messages/messageSlice.ts +5 -1
  166. package/chat/messages/pendingAttachmentParts.ts +28 -2
  167. package/chat/messages/sendFirstMessage.ts +76 -79
  168. package/chat/messages/types.ts +9 -0
  169. package/chat/messages/web/streamingMarkdownModel.ts +41 -0
  170. package/chat/messages/web/useStreamingReveal.ts +91 -0
  171. package/chat/web/messageInputAgentUi.ts +94 -6
  172. package/cliEnvHelpers.ts +68 -13
  173. package/cliRuntimeDoctorReport.ts +193 -0
  174. package/client/agentConfigResolver.test.ts +0 -2
  175. package/client/agentRun.test.ts +171 -10
  176. package/client/agentRun.ts +44 -637
  177. package/client/agentRunInput.ts +10 -0
  178. package/client/agentRunLocalExecutor.ts +179 -0
  179. package/client/agentRunServerTransport.ts +394 -0
  180. package/client/agentRunTaskRunRecorder.ts +176 -0
  181. package/client/localRuntimeAdapter.test.ts +236 -9
  182. package/client/localRuntimeAdapter.ts +221 -23
  183. package/client/localToolPolicy.test.ts +28 -4
  184. package/connector-experimental/daemonLauncher.ts +38 -4
  185. package/connectorRunArtifact.ts +34 -10
  186. package/connectorWebSocketTarget.ts +40 -15
  187. package/core/builtinAgents.ts +155 -11
  188. package/create/space/contentLabels.ts +1 -1
  189. package/create/space/contentTypeMeta.ts +3 -2
  190. package/create/space/sidebarVisibleTypes.ts +27 -0
  191. package/create/space/space.locale.ts +76 -4
  192. package/database/actions/agentReadResolution.ts +2 -8
  193. package/database/actions/read.ts +11 -9
  194. package/database/actions/readAndWait.ts +10 -5
  195. package/database/server/agentDelegation.ts +3 -2
  196. package/database/server/authorityStoreConformance.ts +209 -0
  197. package/database/server/authorityStoreTypes.ts +31 -0
  198. package/database/server/coreDataProxy.ts +1 -1
  199. package/database/server/dataHandlers.ts +13 -5
  200. package/database/server/db.ts +15 -34
  201. package/database/server/delete.ts +88 -71
  202. package/database/server/emailRepository.ts +11 -9
  203. package/database/server/fileService.ts +2 -1
  204. package/database/server/legacyServerDb.ts +58 -0
  205. package/database/server/levelAuthorityStore.ts +75 -0
  206. package/database/server/memoryAuthorityStore.ts +133 -0
  207. package/database/server/patch.ts +13 -12
  208. package/database/server/query.ts +3 -2
  209. package/database/server/read.ts +33 -30
  210. package/database/server/resourceAccess.ts +7 -5
  211. package/database/server/serverStoreFactory.ts +124 -0
  212. package/database/server/spaceMemberAuthority.ts +3 -2
  213. package/database/server/storeBatchCompat.ts +37 -0
  214. package/database/server/testAuthorityStore.ts +76 -0
  215. package/database/server/write.ts +29 -27
  216. package/dialogReadCommands.ts +2 -1
  217. package/dialogStatusCommands.ts +2 -1
  218. package/integrations/anthropic/anthropicModels.ts +12 -12
  219. package/integrations/deepseek/models.ts +67 -67
  220. package/integrations/google/models.ts +111 -53
  221. package/integrations/openai/models.ts +22 -22
  222. package/integrations/xai/models.ts +1 -1
  223. package/localRuntimeDb.ts +39 -2
  224. package/machineCommandPrompts.ts +2 -0
  225. package/machineCommands.ts +193 -62
  226. package/machineStatusCommands.ts +4 -12
  227. package/machineWsRunDispatch.ts +80 -15
  228. package/machineWsSession.ts +46 -2
  229. package/noloReadBridgePrompt.ts +16 -0
  230. package/package.json +114 -21
  231. package/pageCreateCommands.ts +2 -2
  232. package/pageDeleteCommands.ts +3 -3
  233. package/pageReadCommands.ts +3 -3
  234. package/pageUpdateCommands.ts +3 -3
  235. package/render/layout/mainLayoutViewMode.ts +25 -0
  236. package/render/page/server/createPage.ts +3 -1
  237. package/render/page/server/handleCreatePage.ts +2 -11
  238. package/runtimeDoctorCommands.ts +18 -69
  239. package/server/api/methods.ts +34 -12
  240. package/server/api/tableHandlers.ts +6 -3
  241. package/server/authenticateToken.ts +47 -2
  242. package/server/billingLedgerRoutes.ts +15 -0
  243. package/server/buildManifest.ts +54 -0
  244. package/server/coreReadiness.ts +34 -25
  245. package/server/coreRuntime.ts +15 -10
  246. package/server/handlers/activityStatsHandler.ts +5 -2
  247. package/server/handlers/agentEmailRegistrationE2EHandler.ts +25 -23
  248. package/server/handlers/agentRun/agentLookup.ts +1 -7
  249. package/server/handlers/agentRun/autonomousRuntime.ts +11 -10
  250. package/server/handlers/agentRun/builtinAgents.ts +12 -88
  251. package/server/handlers/agentRun/continueDialogContext.ts +15 -3
  252. package/server/handlers/agentRun/{inheritedDialogPrompt.ts → conversationReference.ts} +3 -3
  253. package/server/handlers/agentRun/dialogLookup.ts +4 -3
  254. package/server/handlers/agentRun/index.ts +864 -404
  255. package/server/handlers/agentRun/localRuntimeBootstrap.ts +17 -1
  256. package/server/handlers/agentRun/loop.ts +920 -508
  257. package/server/handlers/agentRun/machineConnector.ts +56 -8
  258. package/server/handlers/agentRun/machineRunAudit.ts +3 -2
  259. package/server/handlers/agentRun/modelUsageToolHandlers.ts +13 -10
  260. package/server/handlers/agentRun/requestContext.ts +12 -8
  261. package/server/handlers/agentRun/runtimeExecutionContext.ts +15 -3
  262. package/server/handlers/agentRun/runtimePreparation.ts +26 -24
  263. package/server/handlers/agentRun/runtimeRouting.ts +25 -40
  264. package/server/handlers/agentRun/runtimeSystemMessages.ts +65 -0
  265. package/server/handlers/agentRun/saveDialog.ts +97 -87
  266. package/server/handlers/agentRun/taskRunToolHandler.ts +9 -7
  267. package/server/handlers/agentRun/toolExecutor.ts +42 -78
  268. package/server/handlers/agentRun/types.ts +2 -1
  269. package/server/handlers/appDeployStatusStore.ts +17 -10
  270. package/server/handlers/appDomainHandler.ts +21 -15
  271. package/server/handlers/appLegacyInfraCleanup.ts +5 -2
  272. package/server/handlers/appSandboxHandler.ts +29 -8
  273. package/server/handlers/appSpaceAccess.ts +15 -8
  274. package/server/handlers/chatHandler.ts +22 -2
  275. package/server/handlers/chatUpstreamRetry.ts +225 -2
  276. package/server/handlers/connector/connectorConnectionRegistry.ts +55 -7
  277. package/server/handlers/connector/connectorWebSocket.ts +14 -10
  278. package/server/handlers/desktopAgentRuntimeAdapter.ts +4 -4
  279. package/server/handlers/desktopAgentRuntimeHostCapabilities.ts +25 -0
  280. package/server/handlers/desktopAgentRuntimeReadiness.ts +62 -0
  281. package/server/handlers/desktopAgentRuntimeStatusHandler.ts +38 -30
  282. package/server/handlers/desktopAgentRuntimeTurnHandler.ts +7 -3
  283. package/server/handlers/desktopAgentRuntimeTurnService.ts +39 -45
  284. package/server/handlers/desktopLlamaRuntimeHandler.ts +85 -51
  285. package/server/handlers/desktopUpdaterHandler.ts +132 -24
  286. package/server/handlers/favoriteHandlers.ts +8 -2
  287. package/server/handlers/getDialogBillingHandler.ts +4 -3
  288. package/server/handlers/getTransactionsHandler.ts +5 -2
  289. package/server/handlers/machines/machineBinding.ts +1 -0
  290. package/server/handlers/machines/machineHandlers.ts +0 -14
  291. package/server/handlers/machines/machineInstallAssets.ts +4 -2
  292. package/server/handlers/notificationEmitter.ts +5 -3
  293. package/server/handlers/notificationsHandler.ts +5 -2
  294. package/server/handlers/providerCallsRecentHandler.ts +234 -0
  295. package/server/handlers/providerReconciliationRunHandler.ts +180 -0
  296. package/server/handlers/providerRequestIdsHandler.ts +105 -0
  297. package/server/handlers/tableAccess.ts +6 -2
  298. package/server/handlers/tableToolHandlers.ts +28 -14
  299. package/server/handlers/userSecretsHandler.ts +17 -6
  300. package/server/handlers/versionHandler.ts +9 -4
  301. package/server/handlers/versionStore.ts +18 -10
  302. package/server/html/ssrPreload.ts +5 -3
  303. package/server/payments/crypto/cryptoUsdcBasePaymentProvider.ts +11 -8
  304. package/server/payments/crypto/cryptoUsdcBaseScanner.ts +12 -7
  305. package/server/payments/crypto/cryptoUsdtTronPaymentProvider.ts +11 -8
  306. package/server/payments/crypto/cryptoUsdtTronScanner.ts +9 -7
  307. package/server/payments/waffo/waffoPaymentProvider.ts +12 -9
  308. package/server/runtimeRole.ts +15 -0
  309. package/server/scheduler/scheduledTaskRuntime.ts +4 -2
  310. package/server/scheduler/schedulerService.ts +2 -2
  311. package/server/taskRunBridgeRoutes.ts +8 -5
  312. package/share/server/fetchCommunityShares.ts +1 -1
  313. package/share/server/helpers.ts +19 -17
  314. package/share/server/importShare.ts +9 -13
  315. package/share/server/listCommunityShares.ts +4 -4
  316. package/share/server/purchaseDoc.ts +8 -16
  317. package/share/server/readCommunityShareCover.ts +3 -2
  318. package/share/server/readSharedTable.ts +14 -13
  319. package/share/server/tableReplication.ts +42 -39
  320. package/spaceAttachCommands.ts +135 -0
  321. package/spaceCategoryCommands.ts +6 -6
  322. package/spaceCommands.ts +3 -3
  323. package/spaceDeleteCommands.ts +2 -2
  324. package/spaceInternalCommandEntries.ts +2 -0
  325. package/spaceReadCommands.ts +3 -3
  326. package/tableMetaCommands.ts +2 -2
  327. package/taskRunCommand.ts +11 -0
  328. package/ai/agent/runtimeServerBase.ts +0 -37
  329. package/server/handlers/desktopAgentRuntimeHostFacts.ts +0 -40
@@ -33,8 +33,11 @@ import {
33
33
  toResponsesTools,
34
34
  } from "../../../integrations/openai/responsesHelpers";
35
35
  import { normalizeChatCompletionsBodyForProvider } from "../../../integrations/openai/providerBodyCompatibility";
36
- import { fetchUpstreamWithGentleRetry } from "../chatUpstreamRetry";
37
- import { executeToolOnServer } from "./toolExecutor";
36
+ import { fetchUpstreamWithGentleRetry } from "../chatUpstreamRetry";
37
+ import type { ProviderCallStore } from "../../../ai/token/providerCallWriter";
38
+ import type { ProviderApiKeySource } from "../../../ai/token/providerCredential";
39
+ import { resolveRuntimeProviderCredential } from "../../../ai/token/providerCredentialRuntime";
40
+ import { executeToolOnServer } from "./toolExecutor";
38
41
  import type {
39
42
  AgentRunRuntimeContext,
40
43
  ChatMessage,
@@ -161,21 +164,41 @@ async function hydrateApiKeyReferenceIfNeeded(
161
164
  return {
162
165
  ...agentConfig,
163
166
  apiKey,
167
+ apiKeySource: "agent_reference",
164
168
  };
165
169
  }
166
170
 
171
+ function resolveApiKeySource(agentConfig: any): ProviderApiKeySource {
172
+ if (agentConfig?.apiKeySource === "agent_reference") return "agent_reference";
173
+ if (agentConfig?.apiKey?.trim()) return "agent_secret";
174
+ return "platform_env";
175
+ }
176
+
167
177
  async function fetchLoopUpstream(
168
- endpoint: string,
169
- agentConfig: any,
170
- body: Record<string, any>,
171
- ): Promise<{
172
- response: Response;
173
- provider?: string;
174
- model?: string;
175
- inputPrice?: number;
176
- outputPrice?: number;
177
- }> {
178
- const apiKey = getApiKey(agentConfig);
178
+ endpoint: string,
179
+ agentConfig: any,
180
+ body: Record<string, any>,
181
+ providerCallContext?: {
182
+ userId?: string | null;
183
+ dialogId?: string | null;
184
+ agentId?: string | null;
185
+ },
186
+ providerCallStore?: ProviderCallStore,
187
+ ): Promise<{
188
+ response: Response;
189
+ provider?: string;
190
+ model?: string;
191
+ inputPrice?: number;
192
+ outputPrice?: number;
193
+ providerCallIds?: string[];
194
+ }> {
195
+ const apiKey = getApiKey(agentConfig);
196
+ const credential = await resolveRuntimeProviderCredential({
197
+ provider: String(agentConfig?.provider || "unknown"),
198
+ apiKey,
199
+ apiKeySource: resolveApiKeySource(agentConfig),
200
+ registryStore: providerCallStore,
201
+ });
179
202
  const llmRequestTimeoutMs = resolveLlmRequestTimeoutMs(agentConfig, endpoint);
180
203
  const init: RequestInit = {
181
204
  method: "POST",
@@ -243,10 +266,16 @@ async function fetchLoopUpstream(
243
266
  const googleFallbackRequest =
244
267
  shouldUseGoogleOpenRouterFallback && openRouterApiKey
245
268
  ? {
246
- url: OPENROUTER_CHAT_COMPLETIONS_URL,
247
- provider: "openrouter",
248
- model: toOpenRouterGoogleModelName(agentConfig.model),
249
- init: {
269
+ url: OPENROUTER_CHAT_COMPLETIONS_URL,
270
+ provider: "openrouter",
271
+ model: toOpenRouterGoogleModelName(agentConfig.model),
272
+ credential: await resolveRuntimeProviderCredential({
273
+ provider: "openrouter",
274
+ apiKey: openRouterApiKey,
275
+ apiKeySource: "platform_env",
276
+ registryStore: providerCallStore,
277
+ }),
278
+ init: {
250
279
  method: "POST",
251
280
  headers: buildUpstreamHeaders(
252
281
  OPENROUTER_CHAT_COMPLETIONS_URL,
@@ -270,6 +299,12 @@ async function fetchLoopUpstream(
270
299
  url: fireworksKimiFallbackUrl,
271
300
  provider: "fireworks",
272
301
  model: FIREWORKS_KIMI_CURRENT_MODEL,
302
+ credential: await resolveRuntimeProviderCredential({
303
+ provider: "fireworks",
304
+ apiKey: fireworksKimiFallbackApiKey,
305
+ apiKeySource: "platform_env",
306
+ registryStore: providerCallStore,
307
+ }),
273
308
  init: {
274
309
  method: "POST",
275
310
  headers: buildUpstreamHeaders(
@@ -292,6 +327,12 @@ async function fetchLoopUpstream(
292
327
  url: deepInfraKimiFallbackUrl,
293
328
  provider: "deepinfra",
294
329
  model: DEEPINFRA_KIMI_FALLBACK_MODEL,
330
+ credential: await resolveRuntimeProviderCredential({
331
+ provider: "deepinfra",
332
+ apiKey: deepInfraKimiFallbackApiKey,
333
+ apiKeySource: "platform_env",
334
+ registryStore: providerCallStore,
335
+ }),
295
336
  init: {
296
337
  method: "POST",
297
338
  headers: buildUpstreamHeaders(
@@ -314,6 +355,12 @@ async function fetchLoopUpstream(
314
355
  url: OPENROUTER_CHAT_COMPLETIONS_URL,
315
356
  provider: "openrouter",
316
357
  model: OPENROUTER_KIMI_FALLBACK_MODEL,
358
+ credential: await resolveRuntimeProviderCredential({
359
+ provider: "openrouter",
360
+ apiKey: openRouterKimiFallbackApiKey,
361
+ apiKeySource: "platform_env",
362
+ registryStore: providerCallStore,
363
+ }),
317
364
  init: {
318
365
  method: "POST",
319
366
  headers: buildUpstreamHeaders(
@@ -337,9 +384,15 @@ async function fetchLoopUpstream(
337
384
  shouldUseConfiguredFallback && configuredFallbackApiKey
338
385
  ? {
339
386
  url: OPENROUTER_CHAT_COMPLETIONS_URL,
340
- provider: "openrouter",
341
- model: agentConfig.fallbackModel,
342
- init: {
387
+ provider: "openrouter",
388
+ model: agentConfig.fallbackModel,
389
+ credential: await resolveRuntimeProviderCredential({
390
+ provider: "openrouter",
391
+ apiKey: configuredFallbackApiKey,
392
+ apiKeySource: "platform_env",
393
+ registryStore: providerCallStore,
394
+ }),
395
+ init: {
343
396
  method: "POST",
344
397
  headers: buildUpstreamHeaders(
345
398
  OPENROUTER_CHAT_COMPLETIONS_URL,
@@ -408,9 +461,10 @@ async function fetchLoopUpstream(
408
461
  ? {
409
462
  url: endpoint,
410
463
  init,
411
- provider: agentConfig.provider,
412
- model: agentConfig.model,
413
- log: console,
464
+ provider: agentConfig.provider,
465
+ model: agentConfig.model,
466
+ credential,
467
+ log: console,
414
468
  fallbackRequest: googleFallbackRequest,
415
469
  fallbackOnStatuses:
416
470
  String(agentConfig?.provider || "").toLowerCase() === "google"
@@ -423,6 +477,7 @@ async function fetchLoopUpstream(
423
477
  init,
424
478
  provider: agentConfig.provider,
425
479
  model: agentConfig.model,
480
+ credential,
426
481
  log: console,
427
482
  fallbackRequests: kimiPlatformFallbackRequests,
428
483
  fallbackOnStatuses: KIMI_PLATFORM_FALLBACK_STATUSES,
@@ -430,23 +485,36 @@ async function fetchLoopUpstream(
430
485
  : configuredFallbackRequest
431
486
  ? {
432
487
  url: endpoint,
433
- init,
434
- provider: agentConfig.provider,
435
- model: agentConfig.model,
436
- log: console,
437
- fallbackRequest: configuredFallbackRequest,
488
+ init,
489
+ provider: agentConfig.provider,
490
+ model: agentConfig.model,
491
+ credential,
492
+ log: console,
493
+ fallbackRequest: configuredFallbackRequest,
438
494
  fallbackOnStatuses: FIREWORKS_OPENROUTER_FALLBACK_STATUSES,
439
495
  }
440
496
  : {
441
497
  url: endpoint,
442
- init,
443
- provider: agentConfig.provider,
444
- model: agentConfig.model,
445
- log: console,
446
- };
447
- const upstreamResponse = await fetchUpstreamWithGentleRetry({
448
- ...requestArgs,
449
- });
498
+ init,
499
+ provider: agentConfig.provider,
500
+ model: agentConfig.model,
501
+ credential,
502
+ log: console,
503
+ };
504
+ const upstreamResponse = await fetchUpstreamWithGentleRetry({
505
+ ...requestArgs,
506
+ ...(providerCallStore && providerCallContext?.userId
507
+ ? {
508
+ providerCallStore,
509
+ providerCallContext: {
510
+ userId: providerCallContext.userId,
511
+ dialogId: providerCallContext.dialogId ?? undefined,
512
+ agentId: providerCallContext.agentId ?? undefined,
513
+ endpoint: "agentRun",
514
+ },
515
+ }
516
+ : {}),
517
+ });
450
518
  const actualProvider = upstreamResponse.provider;
451
519
  const actualModel = upstreamResponse.model;
452
520
  const actualModelConfig =
@@ -456,10 +524,11 @@ async function fetchLoopUpstream(
456
524
  return {
457
525
  response: upstreamResponse.response,
458
526
  provider: actualProvider,
459
- model: actualModel,
460
- inputPrice: actualModelConfig?.price.input ?? agentConfig.inputPrice,
461
- outputPrice: actualModelConfig?.price.output ?? agentConfig.outputPrice,
462
- };
527
+ model: actualModel,
528
+ inputPrice: actualModelConfig?.price.input ?? agentConfig.inputPrice,
529
+ outputPrice: actualModelConfig?.price.output ?? agentConfig.outputPrice,
530
+ providerCallIds: upstreamResponse.providerCallIds,
531
+ };
463
532
  } catch (error: any) {
464
533
  if (error?.name === "TimeoutError" || error?.name === "AbortError") {
465
534
  throw new Error(`LLM API timeout after ${llmRequestTimeoutMs}ms: ${endpoint}`);
@@ -1223,22 +1292,22 @@ async function runWereadGatewayPreflightIfNeeded(args: {
1223
1292
  args.send?.({ type: "tool_start", calls: ["wereadGateway"] });
1224
1293
 
1225
1294
  const startedAt = Date.now();
1226
- const toolResult = await executeToolOnServer(toolCall, args.serverBase, args.authToken, {
1227
- policy: args.runtimePolicy,
1228
- runtimeState: args.runtimeState,
1229
- userId: args.userId,
1230
- agentKey:
1231
- typeof args.agentConfig?.dbKey === "string" && args.agentConfig.dbKey.trim()
1232
- ? args.agentConfig.dbKey.trim()
1233
- : typeof args.agentConfig?.id === "string" && args.agentConfig.id.trim()
1234
- ? args.agentConfig.id.trim()
1235
- : null,
1236
- dialogId: args.dialogId,
1237
- currentSpaceId: args.currentSpaceId,
1238
- userInput: args.userInput,
1239
- runtimeContext: args.runtimeContext,
1240
- imageUrls: collectLatestImageUrls(args.messages),
1241
- });
1295
+ const toolResult = await executeToolOnServer(
1296
+ toolCall,
1297
+ args.serverBase,
1298
+ args.authToken,
1299
+ buildLoopToolExecutionOptions({
1300
+ agentConfig: args.agentConfig,
1301
+ messages: args.messages,
1302
+ runtimePolicy: args.runtimePolicy,
1303
+ runtimeState: args.runtimeState,
1304
+ userId: args.userId,
1305
+ dialogId: args.dialogId,
1306
+ currentSpaceId: args.currentSpaceId,
1307
+ userInput: args.userInput,
1308
+ runtimeContext: args.runtimeContext,
1309
+ }),
1310
+ );
1242
1311
  const completedAt = Date.now();
1243
1312
  args.messages.push({
1244
1313
  role: "tool",
@@ -1323,8 +1392,116 @@ export function applyLoopStreamMode(body: Record<string, any>, stream: boolean)
1323
1392
  }
1324
1393
  return body;
1325
1394
  }
1326
-
1327
- function buildAssistantMessageFromResponse(data: any): ChatMessage | null {
1395
+
1396
+ function addNumericUsageField(
1397
+ target: Record<string, any>,
1398
+ field: string,
1399
+ next: Record<string, any>,
1400
+ ) {
1401
+ if (typeof next[field] !== "number" || !Number.isFinite(next[field])) return;
1402
+ target[field] = (typeof target[field] === "number" && Number.isFinite(target[field]) ? target[field] : 0) + next[field];
1403
+ }
1404
+
1405
+ function uniqueStringList(...values: unknown[]): string[] {
1406
+ return Array.from(
1407
+ new Set(
1408
+ values
1409
+ .flatMap((value) => Array.isArray(value) ? value : [value])
1410
+ .filter((item): item is string => typeof item === "string")
1411
+ .map((item) => item.trim())
1412
+ .filter(Boolean)
1413
+ )
1414
+ );
1415
+ }
1416
+
1417
+ function addStringListUsageField(
1418
+ target: Record<string, any>,
1419
+ field: string,
1420
+ next: Record<string, any>,
1421
+ ) {
1422
+ const merged = uniqueStringList(target[field], next[field]);
1423
+ if (merged.length > 0) target[field] = merged;
1424
+ }
1425
+
1426
+ function buildProviderAuditUsageMetadata(
1427
+ response?: Response | null,
1428
+ payload?: any,
1429
+ ): Record<string, any> | undefined {
1430
+ const providerRequestIds = uniqueStringList(
1431
+ response?.headers.get("x-request-id"),
1432
+ response?.headers.get("x-openai-request-id"),
1433
+ response?.headers.get("openai-request-id"),
1434
+ response?.headers.get("cf-ray"),
1435
+ );
1436
+ const providerResponseIds = uniqueStringList(
1437
+ typeof payload?.id === "string" ? payload.id : undefined,
1438
+ typeof payload?.response?.id === "string" ? payload.response.id : undefined,
1439
+ );
1440
+ if (providerRequestIds.length === 0 && providerResponseIds.length === 0) {
1441
+ return undefined;
1442
+ }
1443
+ return {
1444
+ ...(providerRequestIds.length > 0 ? { provider_request_ids: providerRequestIds } : {}),
1445
+ ...(providerResponseIds.length > 0 ? { provider_response_ids: providerResponseIds } : {}),
1446
+ };
1447
+ }
1448
+
1449
+ function addUsageTotal(
1450
+ current: Record<string, any> | undefined | null,
1451
+ next: Record<string, any> | undefined | null,
1452
+ ): Record<string, any> | undefined {
1453
+ if (!next || typeof next !== "object") return current ?? undefined;
1454
+ if (!current || typeof current !== "object") return { ...next };
1455
+
1456
+ const merged: Record<string, any> = { ...current };
1457
+ for (const field of [
1458
+ "prompt_tokens",
1459
+ "completion_tokens",
1460
+ "total_tokens",
1461
+ "input_tokens",
1462
+ "output_tokens",
1463
+ "cache_creation_input_tokens",
1464
+ "cache_read_input_tokens",
1465
+ "prompt_cache_hit_tokens",
1466
+ "prompt_cache_miss_tokens",
1467
+ "cost",
1468
+ ]) {
1469
+ addNumericUsageField(merged, field, next);
1470
+ }
1471
+ for (const field of ["provider_response_ids", "provider_request_ids"]) {
1472
+ addStringListUsageField(merged, field, next);
1473
+ }
1474
+
1475
+ for (const [key, value] of Object.entries(next)) {
1476
+ if (
1477
+ !(key in merged) &&
1478
+ typeof value !== "number" &&
1479
+ !["provider_response_ids", "provider_request_ids"].includes(key)
1480
+ ) {
1481
+ merged[key] = value;
1482
+ }
1483
+ }
1484
+ return merged;
1485
+ }
1486
+
1487
+ function mergeUsageSnapshot(
1488
+ current: Record<string, any> | undefined | null,
1489
+ next: Record<string, any> | undefined | null,
1490
+ ): Record<string, any> | undefined {
1491
+ if (!next || typeof next !== "object") return current ?? undefined;
1492
+ const merged: Record<string, any> =
1493
+ current && typeof current === "object" ? { ...current, ...next } : { ...next };
1494
+ for (const field of ["provider_response_ids", "provider_request_ids"]) {
1495
+ const values = uniqueStringList(
1496
+ current && typeof current === "object" ? current[field] : undefined,
1497
+ next[field],
1498
+ );
1499
+ if (values.length > 0) merged[field] = values;
1500
+ }
1501
+ return merged;
1502
+ }
1503
+
1504
+ function buildAssistantMessageFromResponse(data: any): ChatMessage | null {
1328
1505
  const toolCalls = extractToolCallsFromResponseOutput(data);
1329
1506
  const contentText = extractTextFromResponseOutput(data);
1330
1507
  const contentImages = extractImagePartsFromResponseOutput(data);
@@ -1352,10 +1529,10 @@ function extractTextFromResponseOutputItem(item: any): string {
1352
1529
  .join("");
1353
1530
  }
1354
1531
 
1355
- function buildLatencyProfile(args: {
1356
- startedAt: number;
1357
- llmRequestCount: number;
1358
- llmWaitMs: number;
1532
+ function buildLatencyProfile(args: {
1533
+ startedAt: number;
1534
+ llmRequestCount: number;
1535
+ llmWaitMs: number;
1359
1536
  llmJsonParseMs: number;
1360
1537
  toolExecutionMs: number;
1361
1538
  firstAssistantAt?: number;
@@ -1374,11 +1551,210 @@ function buildLatencyProfile(args: {
1374
1551
  ...(typeof args.firstToolResultAt === "number"
1375
1552
  ? { timeToFirstToolResultMs: Math.max(0, args.firstToolResultAt - args.startedAt) }
1376
1553
  : {}),
1377
- endedAt,
1378
- };
1379
- }
1380
-
1381
- // ── 非流式 ────────────────────────────────────────────────────────────────────
1554
+ endedAt,
1555
+ };
1556
+ }
1557
+
1558
+ function buildLoopCompletion(args: {
1559
+ content: string;
1560
+ model: string;
1561
+ provider?: string;
1562
+ inputPrice?: number;
1563
+ outputPrice?: number;
1564
+ usage?: Record<string, any>;
1565
+ trace: ChatMessage[];
1566
+ runtimeToolNames: string[];
1567
+ toolCallCount: number;
1568
+ runtimePolicy?: ResolvedRuntimePolicy;
1569
+ runtimeState: ToolPolicyRuntimeState;
1570
+ startedAt: number;
1571
+ llmRequestCount: number;
1572
+ llmWaitMs: number;
1573
+ llmJsonParseMs: number;
1574
+ toolExecutionMs: number;
1575
+ firstAssistantAt?: number;
1576
+ firstToolResultAt?: number;
1577
+ providerCallIds?: string[];
1578
+ }): LoopResult {
1579
+ return {
1580
+ content: args.content,
1581
+ model: args.model,
1582
+ provider: args.provider,
1583
+ inputPrice: args.inputPrice,
1584
+ outputPrice: args.outputPrice,
1585
+ usage: args.usage,
1586
+ ...(args.providerCallIds && args.providerCallIds.length > 0
1587
+ ? { providerCallIds: [...args.providerCallIds] }
1588
+ : {}),
1589
+ trace: args.trace,
1590
+ runtimeToolNames: args.runtimeToolNames,
1591
+ toolCallCount: args.toolCallCount,
1592
+ policyState: {
1593
+ autoKnowledgeCaptureCount:
1594
+ (args.runtimePolicy?.knowledgeCapture.currentAutoCreates ?? 0) +
1595
+ args.runtimeState.autoKnowledgeCaptureCountThisRun,
1596
+ autoSpaceReadCount:
1597
+ (args.runtimePolicy?.spaceContext.currentAutoReads ?? 0) +
1598
+ args.runtimeState.autoSpaceReadCountThisRun,
1599
+ updatedAt: Date.now(),
1600
+ },
1601
+ latencyProfile: buildLatencyProfile({
1602
+ startedAt: args.startedAt,
1603
+ llmRequestCount: args.llmRequestCount,
1604
+ llmWaitMs: args.llmWaitMs,
1605
+ llmJsonParseMs: args.llmJsonParseMs,
1606
+ toolExecutionMs: args.toolExecutionMs,
1607
+ firstAssistantAt: args.firstAssistantAt,
1608
+ firstToolResultAt: args.firstToolResultAt,
1609
+ }),
1610
+ };
1611
+ }
1612
+
1613
+ function buildStreamDoneEvent(args: {
1614
+ usage?: Record<string, any>;
1615
+ provider?: string;
1616
+ model?: string;
1617
+ inputPrice?: number;
1618
+ outputPrice?: number;
1619
+ elapsed?: number;
1620
+ providerCallIds?: string[];
1621
+ }) {
1622
+ return {
1623
+ type: "done" as const,
1624
+ ...(args.usage ? { usage: args.usage } : {}),
1625
+ ...(args.provider ? { provider: args.provider } : {}),
1626
+ ...(args.model ? { model: args.model } : {}),
1627
+ ...(typeof args.inputPrice === "number" ? { inputPrice: args.inputPrice } : {}),
1628
+ ...(typeof args.outputPrice === "number" ? { outputPrice: args.outputPrice } : {}),
1629
+ ...(typeof args.elapsed === "number" ? { elapsed: args.elapsed } : {}),
1630
+ ...(args.providerCallIds?.length ? { providerCallIds: [...args.providerCallIds] } : {}),
1631
+ };
1632
+ }
1633
+
1634
+ function buildRepairRequestMessages(args: {
1635
+ messages: ChatMessage[];
1636
+ finalParallelSummaryRepairPending: boolean;
1637
+ emptyAssistantRepairPending: boolean;
1638
+ }): ChatMessage[] {
1639
+ if (args.finalParallelSummaryRepairPending) {
1640
+ return [
1641
+ ...args.messages,
1642
+ { role: "system", content: FINAL_PARALLEL_SUMMARY_REPAIR_PROMPT } satisfies ChatMessage,
1643
+ ];
1644
+ }
1645
+ if (args.emptyAssistantRepairPending) {
1646
+ return [
1647
+ ...args.messages,
1648
+ { role: "system", content: EMPTY_ASSISTANT_REPAIR_PROMPT } satisfies ChatMessage,
1649
+ ];
1650
+ }
1651
+ return args.messages;
1652
+ }
1653
+
1654
+ function resolveAssistantRepairDecision(args: {
1655
+ messages: ChatMessage[];
1656
+ assistantMsg: ChatMessage;
1657
+ finalParallelSummaryRepairUsed: boolean;
1658
+ emptyAssistantRepairUsed: boolean;
1659
+ }): "parallel-summary" | "empty-assistant" | null {
1660
+ const assistantText = extractAssistantText(args.assistantMsg).trim();
1661
+ if (
1662
+ !args.assistantMsg.tool_calls?.length &&
1663
+ !assistantText &&
1664
+ !args.finalParallelSummaryRepairUsed &&
1665
+ hasParallelBranchMessages(args.messages)
1666
+ ) {
1667
+ return "parallel-summary";
1668
+ }
1669
+ if (
1670
+ !args.assistantMsg.tool_calls?.length &&
1671
+ !assistantText &&
1672
+ !args.emptyAssistantRepairUsed
1673
+ ) {
1674
+ return "empty-assistant";
1675
+ }
1676
+ return null;
1677
+ }
1678
+
1679
+ async function requestLoopUpstreamTurn(args: {
1680
+ endpoint: string;
1681
+ agentConfig: any;
1682
+ messages: ChatMessage[];
1683
+ tools: ReturnType<typeof buildTools>;
1684
+ stream: boolean;
1685
+ finalParallelSummaryRepairPending: boolean;
1686
+ emptyAssistantRepairPending: boolean;
1687
+ resolvedProvider?: string;
1688
+ resolvedModel: string;
1689
+ resolvedInputPrice?: number;
1690
+ resolvedOutputPrice?: number;
1691
+ providerCallContext?: {
1692
+ userId?: string | null;
1693
+ dialogId?: string | null;
1694
+ agentId?: string | null;
1695
+ };
1696
+ providerCallStore?: ProviderCallStore;
1697
+ }): Promise<{
1698
+ response: Response;
1699
+ provider?: string;
1700
+ model: string;
1701
+ inputPrice?: number;
1702
+ outputPrice?: number;
1703
+ providerCallIds?: string[];
1704
+ }> {
1705
+ const requestMessages = buildRepairRequestMessages({
1706
+ messages: args.messages,
1707
+ finalParallelSummaryRepairPending: args.finalParallelSummaryRepairPending,
1708
+ emptyAssistantRepairPending: args.emptyAssistantRepairPending,
1709
+ });
1710
+ const body = buildLoopRequestBody(args.agentConfig, requestMessages, args.tools, args.stream);
1711
+ const upstreamResult = await fetchLoopUpstream(
1712
+ args.endpoint,
1713
+ args.agentConfig,
1714
+ body,
1715
+ args.providerCallContext,
1716
+ args.providerCallStore,
1717
+ );
1718
+ return {
1719
+ response: upstreamResult.response,
1720
+ provider: upstreamResult.provider ?? args.resolvedProvider,
1721
+ model: upstreamResult.model ?? args.resolvedModel,
1722
+ inputPrice: upstreamResult.inputPrice ?? args.resolvedInputPrice,
1723
+ outputPrice: upstreamResult.outputPrice ?? args.resolvedOutputPrice,
1724
+ providerCallIds: upstreamResult.providerCallIds,
1725
+ };
1726
+ }
1727
+
1728
+ function buildLoopToolExecutionOptions(args: {
1729
+ agentConfig: any;
1730
+ messages: ChatMessage[];
1731
+ runtimePolicy?: ResolvedRuntimePolicy;
1732
+ runtimeState: ToolPolicyRuntimeState;
1733
+ userId?: string | null;
1734
+ dialogId?: string | null;
1735
+ currentSpaceId?: string | null;
1736
+ userInput?: string | null;
1737
+ runtimeContext?: AgentRunRuntimeContext | null;
1738
+ }) {
1739
+ return {
1740
+ policy: args.runtimePolicy,
1741
+ runtimeState: args.runtimeState,
1742
+ userId: args.userId,
1743
+ agentKey:
1744
+ typeof args.agentConfig?.dbKey === "string" && args.agentConfig.dbKey.trim()
1745
+ ? args.agentConfig.dbKey.trim()
1746
+ : typeof args.agentConfig?.id === "string" && args.agentConfig.id.trim()
1747
+ ? args.agentConfig.id.trim()
1748
+ : null,
1749
+ dialogId: args.dialogId,
1750
+ currentSpaceId: args.currentSpaceId,
1751
+ userInput: args.userInput,
1752
+ runtimeContext: args.runtimeContext,
1753
+ imageUrls: collectLatestImageUrls(args.messages),
1754
+ };
1755
+ }
1756
+
1757
+ // ── 非流式 ────────────────────────────────────────────────────────────────────
1382
1758
 
1383
1759
  export async function runAgentLoop(
1384
1760
  agentConfig: any,
@@ -1392,130 +1768,144 @@ export async function runAgentLoop(
1392
1768
  currentSpaceId?: string | null,
1393
1769
  userInput?: string | null,
1394
1770
  runtimeContext?: AgentRunRuntimeContext | null,
1771
+ providerCallStore?: ProviderCallStore,
1395
1772
  ): Promise<LoopResult> {
1396
1773
  const startTime = Date.now();
1397
1774
  agentConfig = await hydrateApiKeyReferenceIfNeeded(agentConfig, serverBase, authToken);
1775
+ const dispatchCliLoop = async () => {
1776
+ // CLI 与普通 model 共享 prompt/model/最近历史,但这里有意保持“单次文本委托”的边界。
1777
+ const provider: CliProvider = (agentConfig.cliProvider ?? "copilot") as CliProvider;
1778
+ const taskPrompt = buildCliTaskPrompt(initialMessages, userInput);
1779
+ const fullPrompt = buildCliPrompt(agentConfig.prompt, taskPrompt);
1780
+ const result = await executeCli(provider, fullPrompt, {
1781
+ model: agentConfig.model || undefined,
1782
+ yolo: true,
1783
+ });
1784
+
1785
+ return buildLoopCompletion({
1786
+ content: result.text,
1787
+ model: agentConfig.model ?? "unknown",
1788
+ provider: agentConfig.provider ?? undefined,
1789
+ inputPrice: agentConfig.inputPrice,
1790
+ outputPrice: agentConfig.outputPrice,
1791
+ usage: undefined,
1792
+ trace: [
1793
+ {
1794
+ role: "assistant",
1795
+ content: result.text,
1796
+ },
1797
+ ],
1798
+ runtimeToolNames: [],
1799
+ toolCallCount: 0,
1800
+ runtimePolicy,
1801
+ runtimeState: {
1802
+ autoKnowledgeCaptureCountThisRun: 0,
1803
+ autoSpaceReadCountThisRun: 0,
1804
+ },
1805
+ startedAt: startTime,
1806
+ llmRequestCount: 0,
1807
+ llmWaitMs: 0,
1808
+ llmJsonParseMs: 0,
1809
+ toolExecutionMs: 0,
1810
+ firstAssistantAt: startTime + Math.max(0, result.elapsed ?? 0),
1811
+ });
1812
+ };
1398
1813
  if (agentConfig.apiSource === "cli") {
1399
- // CLI 与普通 model 共享 prompt/model/最近历史,但这里有意保持“单次文本委托”的边界。
1400
- const provider: CliProvider = (agentConfig.cliProvider ?? "copilot") as CliProvider;
1401
- const taskPrompt = buildCliTaskPrompt(initialMessages, userInput);
1402
- const fullPrompt = buildCliPrompt(agentConfig.prompt, taskPrompt);
1403
- const result = await executeCli(provider, fullPrompt, {
1404
- model: agentConfig.model || undefined,
1405
- yolo: true,
1406
- });
1407
-
1408
- return {
1409
- content: result.text,
1410
- model: agentConfig.model ?? "unknown",
1411
- provider: agentConfig.provider ?? undefined,
1412
- inputPrice: agentConfig.inputPrice,
1413
- outputPrice: agentConfig.outputPrice,
1414
- usage: undefined,
1415
- trace: [
1416
- {
1417
- role: "assistant",
1418
- content: result.text,
1419
- },
1420
- ],
1421
- runtimeToolNames: [],
1422
- toolCallCount: 0,
1423
- policyState: {
1424
- autoKnowledgeCaptureCount:
1425
- (runtimePolicy?.knowledgeCapture.currentAutoCreates ?? 0),
1426
- autoSpaceReadCount:
1427
- (runtimePolicy?.spaceContext.currentAutoReads ?? 0),
1428
- updatedAt: Date.now(),
1429
- },
1430
- latencyProfile: buildLatencyProfile({
1431
- startedAt: startTime,
1432
- llmRequestCount: 0,
1433
- llmWaitMs: 0,
1434
- llmJsonParseMs: 0,
1435
- toolExecutionMs: 0,
1436
- firstAssistantAt: startTime + Math.max(0, result.elapsed ?? 0),
1437
- }),
1438
- };
1439
- }
1440
-
1441
- const endpoint = getApiEndpoint(agentConfig);
1442
- const tools = buildTools(agentConfig);
1443
- const runtimeToolNames = getPreparedToolNames(tools);
1444
- // maxRounds 仅为兼容旧请求字段保留;工具轮次不再做硬限制。
1445
- // 后续如需控制,应改为基于成本/预算的策略,而不是轮次数量。
1446
- void _maxRounds;
1447
-
1448
- const messages: ChatMessage[] = [...initialMessages];
1449
- const traceStartIndex = messages.length;
1450
- let finalContent = "";
1451
- let lastUsage: Record<string, any> | undefined;
1452
- let lastResolvedProvider = agentConfig.provider ?? undefined;
1453
- let lastResolvedModel = agentConfig.model ?? "unknown";
1454
- let lastResolvedInputPrice = agentConfig.inputPrice;
1455
- let lastResolvedOutputPrice = agentConfig.outputPrice;
1456
- let toolCallCount = 0;
1457
- let llmRequestCount = 0;
1458
- let llmWaitMs = 0;
1459
- let llmJsonParseMs = 0;
1460
- let toolExecutionMs = 0;
1461
- let firstAssistantAt: number | undefined;
1462
- let firstToolResultAt: number | undefined;
1463
- const runtimeState: ToolPolicyRuntimeState = {
1464
- autoKnowledgeCaptureCountThisRun: 0,
1465
- autoSpaceReadCountThisRun: 0,
1466
- };
1467
- let finalParallelSummaryRepairPending = false;
1468
- let finalParallelSummaryRepairUsed = false;
1469
- let emptyAssistantRepairPending = false;
1470
- let emptyAssistantRepairUsed = false;
1471
-
1472
- const wereadPreflight = await runWereadGatewayPreflightIfNeeded({
1473
- agentConfig,
1474
- tools,
1475
- messages,
1476
- serverBase,
1477
- authToken,
1478
- runtimePolicy,
1479
- runtimeState,
1480
- userId,
1481
- dialogId,
1482
- currentSpaceId,
1483
- userInput,
1484
- runtimeContext,
1485
- });
1486
- if (wereadPreflight.ran) {
1487
- toolCallCount += 1;
1488
- toolExecutionMs += wereadPreflight.elapsedMs;
1489
- firstToolResultAt = wereadPreflight.completedAt;
1814
+ return await dispatchCliLoop();
1490
1815
  }
1816
+ const dispatchModelLoop = async () => {
1817
+
1818
+ const endpoint = getApiEndpoint(agentConfig);
1819
+ const tools = buildTools(agentConfig);
1820
+ const runtimeToolNames = getPreparedToolNames(tools);
1821
+ // maxRounds 仅为兼容旧请求字段保留;工具轮次不再做硬限制。
1822
+ // 后续如需控制,应改为基于成本/预算的策略,而不是轮次数量。
1823
+ void _maxRounds;
1824
+
1825
+ const messages: ChatMessage[] = [...initialMessages];
1826
+ const traceStartIndex = messages.length;
1827
+ let finalContent = "";
1828
+ let lastUsage: Record<string, any> | undefined;
1829
+ let lastResolvedProvider = agentConfig.provider ?? undefined;
1830
+ let lastResolvedModel = agentConfig.model ?? "unknown";
1831
+ let lastResolvedInputPrice = agentConfig.inputPrice;
1832
+ let lastResolvedOutputPrice = agentConfig.outputPrice;
1833
+ const providerCallIds: string[] = [];
1834
+ let toolCallCount = 0;
1835
+ let llmRequestCount = 0;
1836
+ let llmWaitMs = 0;
1837
+ let llmJsonParseMs = 0;
1838
+ let toolExecutionMs = 0;
1839
+ let firstAssistantAt: number | undefined;
1840
+ let firstToolResultAt: number | undefined;
1841
+ const runtimeState: ToolPolicyRuntimeState = {
1842
+ autoKnowledgeCaptureCountThisRun: 0,
1843
+ autoSpaceReadCountThisRun: 0,
1844
+ };
1845
+ let finalParallelSummaryRepairPending = false;
1846
+ let finalParallelSummaryRepairUsed = false;
1847
+ let emptyAssistantRepairPending = false;
1848
+ let emptyAssistantRepairUsed = false;
1849
+
1850
+ const wereadPreflight = await runWereadGatewayPreflightIfNeeded({
1851
+ agentConfig,
1852
+ tools,
1853
+ messages,
1854
+ serverBase,
1855
+ authToken,
1856
+ runtimePolicy,
1857
+ runtimeState,
1858
+ userId,
1859
+ dialogId,
1860
+ currentSpaceId,
1861
+ userInput,
1862
+ runtimeContext,
1863
+ });
1864
+ if (wereadPreflight.ran) {
1865
+ toolCallCount += 1;
1866
+ toolExecutionMs += wereadPreflight.elapsedMs;
1867
+ firstToolResultAt = wereadPreflight.completedAt;
1868
+ }
1491
1869
 
1492
- for (;;) {
1493
- if (Date.now() - startTime > MAX_TIME_MS) break;
1494
-
1495
- const requestMessages = finalParallelSummaryRepairPending
1496
- ? [
1497
- ...messages,
1498
- { role: "system", content: FINAL_PARALLEL_SUMMARY_REPAIR_PROMPT } satisfies ChatMessage,
1499
- ]
1500
- : emptyAssistantRepairPending
1501
- ? [
1502
- ...messages,
1503
- { role: "system", content: EMPTY_ASSISTANT_REPAIR_PROMPT } satisfies ChatMessage,
1504
- ]
1505
- : messages;
1506
- const body = buildLoopRequestBody(agentConfig, requestMessages, tools, false);
1507
- finalParallelSummaryRepairPending = false;
1508
- emptyAssistantRepairPending = false;
1509
-
1510
- const upstreamStartedAt = Date.now();
1511
- const upstreamResult = await fetchLoopUpstream(endpoint, agentConfig, body);
1512
- const res = upstreamResult.response;
1513
- llmRequestCount += 1;
1514
- llmWaitMs += Math.max(0, Date.now() - upstreamStartedAt);
1515
- lastResolvedProvider = upstreamResult.provider ?? lastResolvedProvider;
1516
- lastResolvedModel = upstreamResult.model ?? lastResolvedModel;
1517
- lastResolvedInputPrice = upstreamResult.inputPrice ?? lastResolvedInputPrice;
1518
- lastResolvedOutputPrice = upstreamResult.outputPrice ?? lastResolvedOutputPrice;
1870
+ for (;;) {
1871
+ if (Date.now() - startTime > MAX_TIME_MS) break;
1872
+
1873
+ const repairPendingState = {
1874
+ finalParallelSummaryRepairPending,
1875
+ emptyAssistantRepairPending,
1876
+ };
1877
+ const upstreamStartedAt = Date.now();
1878
+ const upstreamTurn = await requestLoopUpstreamTurn({
1879
+ endpoint,
1880
+ agentConfig,
1881
+ messages,
1882
+ tools,
1883
+ stream: false,
1884
+ finalParallelSummaryRepairPending: repairPendingState.finalParallelSummaryRepairPending,
1885
+ emptyAssistantRepairPending: repairPendingState.emptyAssistantRepairPending,
1886
+ resolvedProvider: lastResolvedProvider,
1887
+ resolvedModel: lastResolvedModel,
1888
+ resolvedInputPrice: lastResolvedInputPrice,
1889
+ resolvedOutputPrice: lastResolvedOutputPrice,
1890
+ providerCallContext: {
1891
+ userId,
1892
+ dialogId,
1893
+ agentId: agentConfig.dbKey ?? agentConfig.id ?? null,
1894
+ },
1895
+ providerCallStore,
1896
+ });
1897
+ finalParallelSummaryRepairPending = false;
1898
+ emptyAssistantRepairPending = false;
1899
+ const res = upstreamTurn.response;
1900
+ llmRequestCount += 1;
1901
+ llmWaitMs += Math.max(0, Date.now() - upstreamStartedAt);
1902
+ lastResolvedProvider = upstreamTurn.provider;
1903
+ lastResolvedModel = upstreamTurn.model;
1904
+ lastResolvedInputPrice = upstreamTurn.inputPrice;
1905
+ lastResolvedOutputPrice = upstreamTurn.outputPrice;
1906
+ if (upstreamTurn.providerCallIds?.length) {
1907
+ providerCallIds.push(...upstreamTurn.providerCallIds);
1908
+ }
1519
1909
 
1520
1910
  if (!res.ok) throw new Error(`LLM API error (${res.status}): ${await res.text()}`);
1521
1911
 
@@ -1531,23 +1921,23 @@ export async function runAgentLoop(
1531
1921
  firstAssistantAt = Date.now();
1532
1922
  }
1533
1923
 
1534
- lastUsage = data.usage ?? data.response?.usage ?? lastUsage;
1535
- const assistantText = extractAssistantText(assistantMsg).trim();
1536
- if (
1537
- !assistantMsg.tool_calls?.length &&
1538
- !assistantText &&
1539
- !finalParallelSummaryRepairUsed &&
1540
- hasParallelBranchMessages(messages)
1541
- ) {
1542
- finalParallelSummaryRepairPending = true;
1924
+ lastUsage = addUsageTotal(
1925
+ lastUsage,
1926
+ addUsageTotal(data.usage ?? data.response?.usage, buildProviderAuditUsageMetadata(res, data)),
1927
+ );
1928
+ const assistantText = extractAssistantText(assistantMsg).trim();
1929
+ const repairDecision = resolveAssistantRepairDecision({
1930
+ messages,
1931
+ assistantMsg,
1932
+ finalParallelSummaryRepairUsed,
1933
+ emptyAssistantRepairUsed,
1934
+ });
1935
+ if (repairDecision === "parallel-summary") {
1936
+ finalParallelSummaryRepairPending = true;
1543
1937
  finalParallelSummaryRepairUsed = true;
1544
1938
  continue;
1545
1939
  }
1546
- if (
1547
- !assistantMsg.tool_calls?.length &&
1548
- !assistantText &&
1549
- !emptyAssistantRepairUsed
1550
- ) {
1940
+ if (repairDecision === "empty-assistant") {
1551
1941
  emptyAssistantRepairPending = true;
1552
1942
  emptyAssistantRepairUsed = true;
1553
1943
  continue;
@@ -1562,22 +1952,22 @@ export async function runAgentLoop(
1562
1952
  for (const tc of assistantMsg.tool_calls!) {
1563
1953
  toolCallCount++;
1564
1954
  const toolStartedAt = Date.now();
1565
- const toolResult = await executeToolOnServer(tc, serverBase, authToken, {
1566
- policy: runtimePolicy,
1567
- runtimeState,
1568
- userId,
1569
- agentKey:
1570
- typeof agentConfig?.dbKey === "string" && agentConfig.dbKey.trim()
1571
- ? agentConfig.dbKey.trim()
1572
- : typeof agentConfig?.id === "string" && agentConfig.id.trim()
1573
- ? agentConfig.id.trim()
1574
- : null,
1575
- dialogId,
1576
- currentSpaceId,
1577
- userInput,
1578
- runtimeContext,
1579
- imageUrls: collectLatestImageUrls(messages),
1580
- });
1955
+ const toolResult = await executeToolOnServer(
1956
+ tc,
1957
+ serverBase,
1958
+ authToken,
1959
+ buildLoopToolExecutionOptions({
1960
+ agentConfig,
1961
+ messages,
1962
+ runtimePolicy,
1963
+ runtimeState,
1964
+ userId,
1965
+ dialogId,
1966
+ currentSpaceId,
1967
+ userInput,
1968
+ runtimeContext,
1969
+ }),
1970
+ );
1581
1971
  toolExecutionMs += Math.max(0, Date.now() - toolStartedAt);
1582
1972
  if (firstToolResultAt === undefined) {
1583
1973
  firstToolResultAt = Date.now();
@@ -1588,125 +1978,103 @@ export async function runAgentLoop(
1588
1978
  content: toolResult,
1589
1979
  });
1590
1980
  const directCompletion = buildDirectCompletionFromToolResult(tc.function.name, toolResult);
1591
- if (directCompletion) {
1592
- finalContent = directCompletion;
1593
- messages.push({
1594
- role: "assistant",
1595
- content: directCompletion,
1596
- });
1597
- return {
1598
- content: finalContent,
1599
- model: lastResolvedModel,
1600
- provider: lastResolvedProvider,
1601
- inputPrice: lastResolvedInputPrice,
1602
- outputPrice: lastResolvedOutputPrice,
1603
- usage: lastUsage,
1604
- trace: messages.slice(traceStartIndex),
1605
- runtimeToolNames,
1606
- toolCallCount,
1607
- policyState: {
1608
- autoKnowledgeCaptureCount:
1609
- (runtimePolicy?.knowledgeCapture.currentAutoCreates ?? 0) +
1610
- runtimeState.autoKnowledgeCaptureCountThisRun,
1611
- autoSpaceReadCount:
1612
- (runtimePolicy?.spaceContext.currentAutoReads ?? 0) +
1613
- runtimeState.autoSpaceReadCountThisRun,
1614
- updatedAt: Date.now(),
1615
- },
1616
- latencyProfile: buildLatencyProfile({
1617
- startedAt: startTime,
1618
- llmRequestCount,
1619
- llmWaitMs,
1620
- llmJsonParseMs,
1621
- toolExecutionMs,
1622
- firstAssistantAt,
1623
- firstToolResultAt,
1624
- }),
1625
- };
1626
- }
1981
+ if (directCompletion) {
1982
+ finalContent = directCompletion;
1983
+ messages.push({
1984
+ role: "assistant",
1985
+ content: directCompletion,
1986
+ });
1987
+ return buildLoopCompletion({
1988
+ content: finalContent,
1989
+ model: lastResolvedModel,
1990
+ provider: lastResolvedProvider,
1991
+ inputPrice: lastResolvedInputPrice,
1992
+ outputPrice: lastResolvedOutputPrice,
1993
+ usage: lastUsage,
1994
+ trace: messages.slice(traceStartIndex),
1995
+ runtimeToolNames,
1996
+ toolCallCount,
1997
+ runtimePolicy,
1998
+ runtimeState,
1999
+ startedAt: startTime,
2000
+ llmRequestCount,
2001
+ llmWaitMs,
2002
+ llmJsonParseMs,
2003
+ toolExecutionMs,
2004
+ firstAssistantAt,
2005
+ firstToolResultAt,
2006
+ providerCallIds,
2007
+ });
2008
+ }
1627
2009
  if (["runStreamingAgent", "streamParallelAgents"].includes(canonicalizeToolName(tc.function.name))) {
1628
2010
  const parsed = parseToolResult(toolResult);
1629
2011
  if (canonicalizeToolName(tc.function.name) === "streamParallelAgents") {
1630
2012
  appendParallelBranchMessages(messages, parsed);
1631
2013
  }
1632
- if (parsed?.handoff && typeof parsed.content === "string" && parsed.content.trim()) {
1633
- finalContent = parsed.content;
1634
- if (canonicalizeToolName(tc.function.name) !== "streamParallelAgents") {
1635
- messages.push({
1636
- role: "assistant",
1637
- content: parsed.content,
1638
- });
1639
- }
1640
- return {
1641
- content: finalContent,
1642
- model: parsed.model || lastResolvedModel,
1643
- provider: parsed.provider || lastResolvedProvider,
1644
- inputPrice:
1645
- typeof parsed.inputPrice === "number"
2014
+ if (parsed?.handoff && typeof parsed.content === "string" && parsed.content.trim()) {
2015
+ finalContent = parsed.content;
2016
+ if (canonicalizeToolName(tc.function.name) !== "streamParallelAgents") {
2017
+ messages.push({
2018
+ role: "assistant",
2019
+ content: parsed.content,
2020
+ });
2021
+ }
2022
+ return buildLoopCompletion({
2023
+ content: finalContent,
2024
+ model: parsed.model || lastResolvedModel,
2025
+ provider: parsed.provider || lastResolvedProvider,
2026
+ inputPrice:
2027
+ typeof parsed.inputPrice === "number"
1646
2028
  ? parsed.inputPrice
1647
2029
  : lastResolvedInputPrice,
1648
2030
  outputPrice:
1649
2031
  typeof parsed.outputPrice === "number"
1650
- ? parsed.outputPrice
1651
- : lastResolvedOutputPrice,
1652
- usage: parsed.usage ?? lastUsage,
1653
- trace: messages.slice(traceStartIndex),
1654
- runtimeToolNames,
1655
- toolCallCount,
1656
- policyState: {
1657
- autoKnowledgeCaptureCount:
1658
- (runtimePolicy?.knowledgeCapture.currentAutoCreates ?? 0) +
1659
- runtimeState.autoKnowledgeCaptureCountThisRun,
1660
- autoSpaceReadCount:
1661
- (runtimePolicy?.spaceContext.currentAutoReads ?? 0) +
1662
- runtimeState.autoSpaceReadCountThisRun,
1663
- updatedAt: Date.now(),
1664
- },
1665
- latencyProfile: buildLatencyProfile({
1666
- startedAt: startTime,
1667
- llmRequestCount,
1668
- llmWaitMs,
1669
- llmJsonParseMs,
1670
- toolExecutionMs,
1671
- firstAssistantAt,
1672
- firstToolResultAt,
1673
- }),
1674
- };
1675
- }
1676
- }
1677
- }
1678
- }
1679
-
1680
- return {
1681
- content: finalContent,
1682
- model: lastResolvedModel,
1683
- provider: lastResolvedProvider,
1684
- inputPrice: lastResolvedInputPrice,
1685
- outputPrice: lastResolvedOutputPrice,
1686
- usage: lastUsage,
1687
- trace: messages.slice(traceStartIndex),
1688
- runtimeToolNames,
1689
- toolCallCount,
1690
- policyState: {
1691
- autoKnowledgeCaptureCount:
1692
- (runtimePolicy?.knowledgeCapture.currentAutoCreates ?? 0) +
1693
- runtimeState.autoKnowledgeCaptureCountThisRun,
1694
- autoSpaceReadCount:
1695
- (runtimePolicy?.spaceContext.currentAutoReads ?? 0) +
1696
- runtimeState.autoSpaceReadCountThisRun,
1697
- updatedAt: Date.now(),
1698
- },
1699
- latencyProfile: buildLatencyProfile({
1700
- startedAt: startTime,
1701
- llmRequestCount,
1702
- llmWaitMs,
1703
- llmJsonParseMs,
1704
- toolExecutionMs,
1705
- firstAssistantAt,
1706
- firstToolResultAt,
1707
- }),
1708
- };
1709
- }
2032
+ ? parsed.outputPrice
2033
+ : lastResolvedOutputPrice,
2034
+ usage: parsed.usage ?? lastUsage,
2035
+ trace: messages.slice(traceStartIndex),
2036
+ runtimeToolNames,
2037
+ toolCallCount,
2038
+ runtimePolicy,
2039
+ runtimeState,
2040
+ startedAt: startTime,
2041
+ llmRequestCount,
2042
+ llmWaitMs,
2043
+ llmJsonParseMs,
2044
+ toolExecutionMs,
2045
+ firstAssistantAt,
2046
+ firstToolResultAt,
2047
+ providerCallIds,
2048
+ });
2049
+ }
2050
+ }
2051
+ }
2052
+ }
2053
+
2054
+ return buildLoopCompletion({
2055
+ content: finalContent,
2056
+ model: lastResolvedModel,
2057
+ provider: lastResolvedProvider,
2058
+ inputPrice: lastResolvedInputPrice,
2059
+ outputPrice: lastResolvedOutputPrice,
2060
+ usage: lastUsage,
2061
+ trace: messages.slice(traceStartIndex),
2062
+ runtimeToolNames,
2063
+ toolCallCount,
2064
+ runtimePolicy,
2065
+ runtimeState,
2066
+ startedAt: startTime,
2067
+ llmRequestCount,
2068
+ llmWaitMs,
2069
+ llmJsonParseMs,
2070
+ toolExecutionMs,
2071
+ firstAssistantAt,
2072
+ firstToolResultAt,
2073
+ providerCallIds,
2074
+ });
2075
+ };
2076
+ return await dispatchModelLoop();
2077
+ }
1710
2078
 
1711
2079
  // ── 流式(SSE)────────────────────────────────────────────────────────────────
1712
2080
 
@@ -1724,104 +2092,121 @@ export function buildSseStream(
1724
2092
  currentSpaceId?: string | null,
1725
2093
  userInput?: string | null,
1726
2094
  runtimeContext?: AgentRunRuntimeContext | null,
2095
+ providerCallStore?: ProviderCallStore,
1727
2096
  ): ReadableStream {
1728
2097
  const encoder = new TextEncoder();
1729
2098
 
1730
- return new ReadableStream({
1731
- async start(controller) {
1732
- const send: Send = (data) =>
1733
- controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`));
1734
-
1735
- try {
1736
- if (agentConfig.apiSource === "cli") {
1737
- // CLI 流式输出仅依赖 provider 自己的 stdout 语义,不合成本地 tool 事件。
1738
- const provider: CliProvider = (agentConfig.cliProvider ?? "copilot") as CliProvider;
1739
- const taskPrompt = buildCliTaskPrompt(initialMessages, userInput);
1740
- const fullPrompt = buildCliPrompt(agentConfig.prompt, taskPrompt);
1741
-
1742
- const result = await executeCliStreaming(provider, fullPrompt, {
1743
- model: agentConfig.model || undefined,
1744
- yolo: true,
1745
- onChunk: (chunk: string) => {
1746
- if (!chunk) return;
1747
- send({ type: "text", content: chunk });
1748
- },
1749
- });
1750
-
1751
- send({ type: "done", usage: undefined, elapsed: result.elapsed });
1752
- controller.close();
1753
- return;
1754
- }
1755
-
1756
- const endpoint = getApiEndpoint(agentConfig);
1757
- const tools = buildTools(agentConfig);
1758
- // maxRounds 仅为兼容旧请求字段保留;工具轮次不再做硬限制。
1759
- // 后续如需控制,应改为基于成本/预算的策略,而不是轮次数量。
1760
- void _maxRounds;
1761
- const messages: ChatMessage[] = [...initialMessages];
1762
- const runtimeState: ToolPolicyRuntimeState = {
1763
- autoKnowledgeCaptureCountThisRun: 0,
1764
- autoSpaceReadCountThisRun: 0,
1765
- };
1766
- const startTime = Date.now();
1767
- let lastUsage: Record<string, any> | undefined;
1768
- let lastResolvedProvider = agentConfig.provider ?? undefined;
1769
- let lastResolvedModel = agentConfig.model ?? "unknown";
1770
- let lastResolvedInputPrice = agentConfig.inputPrice;
1771
- let lastResolvedOutputPrice = agentConfig.outputPrice;
1772
- let finalParallelSummaryRepairPending = false;
1773
- let finalParallelSummaryRepairUsed = false;
1774
- let emptyAssistantRepairPending = false;
1775
- let emptyAssistantRepairUsed = false;
1776
-
1777
- await runWereadGatewayPreflightIfNeeded({
1778
- agentConfig,
1779
- tools,
1780
- messages,
1781
- serverBase,
1782
- authToken,
1783
- runtimePolicy,
1784
- runtimeState,
1785
- userId,
1786
- dialogId,
1787
- currentSpaceId,
1788
- userInput,
1789
- runtimeContext,
1790
- send,
1791
- });
2099
+ return new ReadableStream({
2100
+ async start(controller) {
2101
+ const send: Send = (data) =>
2102
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`));
1792
2103
 
1793
- for (;;) {
1794
- if (Date.now() - startTime > MAX_TIME_MS) {
1795
- send({ type: "error", message: "Max execution time exceeded" });
1796
- break;
1797
- }
1798
-
1799
- const requestMessages = finalParallelSummaryRepairPending
1800
- ? [
1801
- ...messages,
1802
- { role: "system", content: FINAL_PARALLEL_SUMMARY_REPAIR_PROMPT } satisfies ChatMessage,
1803
- ]
1804
- : emptyAssistantRepairPending
1805
- ? [
1806
- ...messages,
1807
- { role: "system", content: EMPTY_ASSISTANT_REPAIR_PROMPT } satisfies ChatMessage,
1808
- ]
1809
- : messages;
1810
- const body = buildLoopRequestBody(agentConfig, requestMessages, tools, true);
2104
+ try {
2105
+ const dispatchCliStreamingLoop = async () => {
2106
+ // CLI 流式输出仅依赖 provider 自己的 stdout 语义,不合成本地 tool 事件。
2107
+ const provider: CliProvider = (agentConfig.cliProvider ?? "copilot") as CliProvider;
2108
+ const taskPrompt = buildCliTaskPrompt(initialMessages, userInput);
2109
+ const fullPrompt = buildCliPrompt(agentConfig.prompt, taskPrompt);
2110
+
2111
+ const result = await executeCliStreaming(provider, fullPrompt, {
2112
+ model: agentConfig.model || undefined,
2113
+ yolo: true,
2114
+ onChunk: (chunk: string) => {
2115
+ if (!chunk) return;
2116
+ send({ type: "text", content: chunk });
2117
+ },
2118
+ });
2119
+
2120
+ send(buildStreamDoneEvent({ elapsed: result.elapsed }));
2121
+ controller.close();
2122
+ };
2123
+ if (agentConfig.apiSource === "cli") {
2124
+ return await dispatchCliStreamingLoop();
2125
+ }
2126
+ const dispatchModelStreamingLoop = async () => {
2127
+
2128
+ const endpoint = getApiEndpoint(agentConfig);
2129
+ const tools = buildTools(agentConfig);
2130
+ // maxRounds 仅为兼容旧请求字段保留;工具轮次不再做硬限制。
2131
+ // 后续如需控制,应改为基于成本/预算的策略,而不是轮次数量。
2132
+ void _maxRounds;
2133
+ const messages: ChatMessage[] = [...initialMessages];
2134
+ const runtimeState: ToolPolicyRuntimeState = {
2135
+ autoKnowledgeCaptureCountThisRun: 0,
2136
+ autoSpaceReadCountThisRun: 0,
2137
+ };
2138
+ const startTime = Date.now();
2139
+ let lastUsage: Record<string, any> | undefined;
2140
+ let lastResolvedProvider = agentConfig.provider ?? undefined;
2141
+ let lastResolvedModel = agentConfig.model ?? "unknown";
2142
+ let lastResolvedInputPrice = agentConfig.inputPrice;
2143
+ let lastResolvedOutputPrice = agentConfig.outputPrice;
2144
+ const providerCallIds: string[] = [];
2145
+ let finalParallelSummaryRepairPending = false;
2146
+ let finalParallelSummaryRepairUsed = false;
2147
+ let emptyAssistantRepairPending = false;
2148
+ let emptyAssistantRepairUsed = false;
2149
+
2150
+ await runWereadGatewayPreflightIfNeeded({
2151
+ agentConfig,
2152
+ tools,
2153
+ messages,
2154
+ serverBase,
2155
+ authToken,
2156
+ runtimePolicy,
2157
+ runtimeState,
2158
+ userId,
2159
+ dialogId,
2160
+ currentSpaceId,
2161
+ userInput,
2162
+ runtimeContext,
2163
+ send,
2164
+ });
2165
+
2166
+ for (;;) {
2167
+ if (Date.now() - startTime > MAX_TIME_MS) {
2168
+ send({ type: "error", message: "Max execution time exceeded" });
2169
+ break;
2170
+ }
2171
+ const repairPendingState = {
2172
+ finalParallelSummaryRepairPending,
2173
+ emptyAssistantRepairPending,
2174
+ };
1811
2175
  finalParallelSummaryRepairPending = false;
1812
2176
  emptyAssistantRepairPending = false;
1813
-
1814
- let res: Response;
1815
- try {
1816
- const upstreamResult = await fetchLoopUpstream(endpoint, agentConfig, body);
1817
- res = upstreamResult.response;
1818
- lastResolvedProvider = upstreamResult.provider ?? lastResolvedProvider;
1819
- lastResolvedModel = upstreamResult.model ?? lastResolvedModel;
1820
- lastResolvedInputPrice = upstreamResult.inputPrice ?? lastResolvedInputPrice;
1821
- lastResolvedOutputPrice = upstreamResult.outputPrice ?? lastResolvedOutputPrice;
1822
- } catch (error: any) {
1823
- send({ type: "error", message: error?.message ?? "Unexpected error" });
1824
- break;
2177
+
2178
+ let res: Response;
2179
+ try {
2180
+ const upstreamTurn = await requestLoopUpstreamTurn({
2181
+ endpoint,
2182
+ agentConfig,
2183
+ messages,
2184
+ tools,
2185
+ stream: true,
2186
+ finalParallelSummaryRepairPending: repairPendingState.finalParallelSummaryRepairPending,
2187
+ emptyAssistantRepairPending: repairPendingState.emptyAssistantRepairPending,
2188
+ resolvedProvider: lastResolvedProvider,
2189
+ resolvedModel: lastResolvedModel,
2190
+ resolvedInputPrice: lastResolvedInputPrice,
2191
+ resolvedOutputPrice: lastResolvedOutputPrice,
2192
+ providerCallContext: {
2193
+ userId,
2194
+ dialogId,
2195
+ agentId: agentConfig.dbKey ?? agentConfig.id ?? null,
2196
+ },
2197
+ providerCallStore,
2198
+ });
2199
+ res = upstreamTurn.response;
2200
+ lastResolvedProvider = upstreamTurn.provider;
2201
+ lastResolvedModel = upstreamTurn.model;
2202
+ lastResolvedInputPrice = upstreamTurn.inputPrice;
2203
+ lastResolvedOutputPrice = upstreamTurn.outputPrice;
2204
+ if (upstreamTurn.providerCallIds?.length) {
2205
+ providerCallIds.push(...upstreamTurn.providerCallIds);
2206
+ }
2207
+ } catch (error: any) {
2208
+ send({ type: "error", message: error?.message ?? "Unexpected error" });
2209
+ break;
1825
2210
  }
1826
2211
  if (!res.ok || !res.body) {
1827
2212
  send({ type: "error", message: `LLM API error (${res.status}): ${await res.text()}` });
@@ -1839,7 +2224,10 @@ export function buildSseStream(
1839
2224
  assistantContent,
1840
2225
  reasoningContent,
1841
2226
  } = streamResult;
1842
- lastUsage = streamResult.lastUsage ?? lastUsage;
2227
+ lastUsage = addUsageTotal(
2228
+ lastUsage,
2229
+ addUsageTotal(streamResult.lastUsage, buildProviderAuditUsageMetadata(res)),
2230
+ );
1843
2231
 
1844
2232
  const assistantMsg: ChatMessage = {
1845
2233
  role: "assistant",
@@ -1847,38 +2235,35 @@ export function buildSseStream(
1847
2235
  ...(reasoningContent ? { reasoning_content: reasoningContent } : {}),
1848
2236
  ...(toolCalls.length > 0 ? { tool_calls: toolCalls } : {}),
1849
2237
  };
1850
- if (
1851
- !hasToolCalls &&
1852
- !extractAssistantText(assistantMsg).trim() &&
1853
- !finalParallelSummaryRepairUsed &&
1854
- hasParallelBranchMessages(messages)
1855
- ) {
1856
- finalParallelSummaryRepairPending = true;
2238
+ const repairDecision = resolveAssistantRepairDecision({
2239
+ messages,
2240
+ assistantMsg,
2241
+ finalParallelSummaryRepairUsed,
2242
+ emptyAssistantRepairUsed,
2243
+ });
2244
+ if (repairDecision === "parallel-summary") {
2245
+ finalParallelSummaryRepairPending = true;
1857
2246
  finalParallelSummaryRepairUsed = true;
1858
2247
  continue;
1859
2248
  }
1860
- if (
1861
- !hasToolCalls &&
1862
- !extractAssistantText(assistantMsg).trim() &&
1863
- !emptyAssistantRepairUsed
1864
- ) {
2249
+ if (repairDecision === "empty-assistant") {
1865
2250
  emptyAssistantRepairPending = true;
1866
2251
  emptyAssistantRepairUsed = true;
1867
2252
  continue;
1868
2253
  }
1869
2254
  messages.push(assistantMsg);
1870
2255
 
1871
- if (!hasToolCalls) {
1872
- send({
1873
- type: "done",
1874
- usage: lastUsage,
1875
- provider: lastResolvedProvider,
1876
- model: lastResolvedModel,
1877
- inputPrice: lastResolvedInputPrice,
1878
- outputPrice: lastResolvedOutputPrice,
1879
- });
1880
- break;
1881
- }
2256
+ if (!hasToolCalls) {
2257
+ send(buildStreamDoneEvent({
2258
+ usage: lastUsage,
2259
+ provider: lastResolvedProvider,
2260
+ model: lastResolvedModel,
2261
+ inputPrice: lastResolvedInputPrice,
2262
+ outputPrice: lastResolvedOutputPrice,
2263
+ providerCallIds,
2264
+ }));
2265
+ break;
2266
+ }
1882
2267
 
1883
2268
  send({
1884
2269
  type: "assistant_tool_calls",
@@ -1887,22 +2272,22 @@ export function buildSseStream(
1887
2272
  });
1888
2273
  send({ type: "tool_start", calls: toolCalls.map((t) => t.function.name) });
1889
2274
  for (const tc of toolCalls) {
1890
- const toolResult = await executeToolOnServer(tc, serverBase, authToken, {
1891
- policy: runtimePolicy,
1892
- runtimeState,
1893
- userId,
1894
- agentKey:
1895
- typeof agentConfig?.dbKey === "string" && agentConfig.dbKey.trim()
1896
- ? agentConfig.dbKey.trim()
1897
- : typeof agentConfig?.id === "string" && agentConfig.id.trim()
1898
- ? agentConfig.id.trim()
1899
- : null,
1900
- dialogId,
1901
- currentSpaceId,
1902
- userInput,
1903
- runtimeContext,
1904
- imageUrls: collectLatestImageUrls(messages),
1905
- });
2275
+ const toolResult = await executeToolOnServer(
2276
+ tc,
2277
+ serverBase,
2278
+ authToken,
2279
+ buildLoopToolExecutionOptions({
2280
+ agentConfig,
2281
+ messages,
2282
+ runtimePolicy,
2283
+ runtimeState,
2284
+ userId,
2285
+ dialogId,
2286
+ currentSpaceId,
2287
+ userInput,
2288
+ runtimeContext,
2289
+ }),
2290
+ );
1906
2291
  messages.push({
1907
2292
  role: "tool",
1908
2293
  tool_call_id: tc.id,
@@ -1916,46 +2301,46 @@ export function buildSseStream(
1916
2301
  });
1917
2302
  const directCompletion = buildDirectCompletionFromToolResult(tc.function.name, toolResult);
1918
2303
  if (directCompletion) {
1919
- messages.push({
1920
- role: "assistant",
1921
- content: directCompletion,
1922
- });
1923
- send({ type: "text", content: directCompletion });
1924
- send({
1925
- type: "done",
1926
- usage: lastUsage,
1927
- provider: lastResolvedProvider,
1928
- model: lastResolvedModel,
1929
- inputPrice: lastResolvedInputPrice,
1930
- outputPrice: lastResolvedOutputPrice,
1931
- });
1932
- controller.close();
1933
- return;
1934
- }
2304
+ messages.push({
2305
+ role: "assistant",
2306
+ content: directCompletion,
2307
+ });
2308
+ send({ type: "text", content: directCompletion });
2309
+ send(buildStreamDoneEvent({
2310
+ usage: lastUsage,
2311
+ provider: lastResolvedProvider,
2312
+ model: lastResolvedModel,
2313
+ inputPrice: lastResolvedInputPrice,
2314
+ outputPrice: lastResolvedOutputPrice,
2315
+ providerCallIds,
2316
+ }));
2317
+ controller.close();
2318
+ return;
2319
+ }
1935
2320
  if (["runStreamingAgent", "streamParallelAgents"].includes(canonicalizeToolName(tc.function.name))) {
1936
2321
  const parsed = parseToolResult(toolResult);
1937
2322
  if (canonicalizeToolName(tc.function.name) === "streamParallelAgents") {
1938
2323
  appendParallelBranchMessages(messages, parsed);
1939
2324
  }
1940
- if (parsed?.handoff && typeof parsed.content === "string" && parsed.content.trim()) {
1941
- send({ type: "text", content: parsed.content });
1942
- send({
1943
- type: "done",
1944
- usage: parsed.usage ?? lastUsage,
1945
- provider: parsed.provider || lastResolvedProvider,
1946
- model: parsed.model || lastResolvedModel,
1947
- inputPrice:
1948
- typeof parsed.inputPrice === "number"
1949
- ? parsed.inputPrice
1950
- : lastResolvedInputPrice,
1951
- outputPrice:
1952
- typeof parsed.outputPrice === "number"
1953
- ? parsed.outputPrice
1954
- : lastResolvedOutputPrice,
1955
- });
1956
- controller.close();
1957
- return;
1958
- }
2325
+ if (parsed?.handoff && typeof parsed.content === "string" && parsed.content.trim()) {
2326
+ send({ type: "text", content: parsed.content });
2327
+ send(buildStreamDoneEvent({
2328
+ usage: parsed.usage ?? lastUsage,
2329
+ provider: parsed.provider || lastResolvedProvider,
2330
+ model: parsed.model || lastResolvedModel,
2331
+ inputPrice:
2332
+ typeof parsed.inputPrice === "number"
2333
+ ? parsed.inputPrice
2334
+ : lastResolvedInputPrice,
2335
+ outputPrice:
2336
+ typeof parsed.outputPrice === "number"
2337
+ ? parsed.outputPrice
2338
+ : lastResolvedOutputPrice,
2339
+ providerCallIds,
2340
+ }));
2341
+ controller.close();
2342
+ return;
2343
+ }
1959
2344
  }
1960
2345
  // 如果是创建文档类工具,解析结果并推送 doc_created 事件让前端感知
1961
2346
  if (["createDoc", "createSkillDoc", "importSkill"].includes(canonicalizeToolName(tc.function.name))) {
@@ -1966,13 +2351,16 @@ export function buildSseStream(
1966
2351
  }
1967
2352
  } catch { /* 忽略解析失败 */ }
1968
2353
  }
1969
- }
1970
- send({ type: "tool_end" });
1971
- }
1972
- } catch (e: any) {
1973
- controller.enqueue(
1974
- encoder.encode(`data: ${JSON.stringify({ type: "error", message: e.message ?? "Unexpected error" })}\n\n`)
1975
- );
2354
+ }
2355
+ send({ type: "tool_end" });
2356
+ }
2357
+ controller.close();
2358
+ };
2359
+ return await dispatchModelStreamingLoop();
2360
+ } catch (e: any) {
2361
+ controller.enqueue(
2362
+ encoder.encode(`data: ${JSON.stringify({ type: "error", message: e.message ?? "Unexpected error" })}\n\n`)
2363
+ );
1976
2364
  }
1977
2365
  controller.close();
1978
2366
  },
@@ -2028,8 +2416,15 @@ async function consumeSseStream(
2028
2416
  let chunk: any;
2029
2417
  try { chunk = JSON.parse(raw); } catch { continue; }
2030
2418
 
2031
- const delta = chunk.choices?.[0]?.delta;
2032
- if (!delta) { if (chunk.usage) lastUsage = chunk.usage; continue; }
2419
+ const delta = chunk.choices?.[0]?.delta;
2420
+ const chunkAuditUsage = buildProviderAuditUsageMetadata(null, chunk);
2421
+ if (!delta) {
2422
+ lastUsage = mergeUsageSnapshot(
2423
+ lastUsage,
2424
+ mergeUsageSnapshot(chunk.usage, chunkAuditUsage),
2425
+ ) ?? null;
2426
+ continue;
2427
+ }
2033
2428
 
2034
2429
  if (typeof delta.content === "string" && delta.content) {
2035
2430
  appendTextPart(assistantContentParts, delta.content);
@@ -2064,8 +2459,11 @@ async function consumeSseStream(
2064
2459
  }
2065
2460
  }
2066
2461
 
2067
- if (chunk.usage) lastUsage = chunk.usage;
2068
- }
2462
+ lastUsage = mergeUsageSnapshot(
2463
+ lastUsage,
2464
+ mergeUsageSnapshot(chunk.usage, chunkAuditUsage),
2465
+ ) ?? null;
2466
+ }
2069
2467
  }
2070
2468
 
2071
2469
  const assistantContent = finalizeAssistantContent(assistantContentParts);
@@ -2115,7 +2513,15 @@ async function consumeResponsesSseStream(
2115
2513
  continue;
2116
2514
  }
2117
2515
 
2118
- if (event?.usage) lastUsage = event.usage;
2516
+ const eventAuditUsage = buildProviderAuditUsageMetadata(null, event);
2517
+ const eventUsage =
2518
+ event?.type === "response.completed" && event.response?.usage
2519
+ ? undefined
2520
+ : event?.usage;
2521
+ lastUsage = mergeUsageSnapshot(
2522
+ lastUsage,
2523
+ mergeUsageSnapshot(eventUsage, eventAuditUsage),
2524
+ ) ?? null;
2119
2525
 
2120
2526
  switch (event?.type) {
2121
2527
  case "response.output_text.delta":
@@ -2183,10 +2589,16 @@ async function consumeResponsesSseStream(
2183
2589
  acc.set(key, current);
2184
2590
  break;
2185
2591
  }
2186
- case "response.completed":
2187
- completedResponse = event.response ?? null;
2188
- if (event.response?.usage) lastUsage = event.response.usage;
2189
- break;
2592
+ case "response.completed":
2593
+ completedResponse = event.response ?? null;
2594
+ lastUsage = mergeUsageSnapshot(
2595
+ lastUsage,
2596
+ mergeUsageSnapshot(
2597
+ event.response?.usage,
2598
+ buildProviderAuditUsageMetadata(null, event.response),
2599
+ ),
2600
+ ) ?? null;
2601
+ break;
2190
2602
  default:
2191
2603
  break;
2192
2604
  }