nolo-cli 0.1.21 → 0.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/agent-runtime/agentRecordConfig.ts +4 -0
  2. package/agent-runtime/hostAdapter.ts +2 -0
  3. package/agent-runtime/index.ts +7 -0
  4. package/agent-runtime/localLoop.ts +2 -0
  5. package/agent-runtime/platformChatProvider.ts +3 -0
  6. package/agent-runtime/runtimeToolPolicy.ts +92 -0
  7. package/agent-runtime/types.ts +42 -0
  8. package/agentRunCommand.ts +74 -1
  9. package/agentRuntimeCommands.ts +17 -89
  10. package/ai/agent/streamAgentChatTurn.ts +104 -20
  11. package/ai/chat/fetchUtils.native.ts +2 -0
  12. package/ai/chat/fetchUtils.ts +2 -0
  13. package/ai/chat/sendOpenAICompletionsRequest.ts +56 -0
  14. package/ai/chat/sendOpenAIResponseRequest.ts +64 -0
  15. package/ai/llm/kimi.ts +1 -1
  16. package/ai/llm/providers.ts +3 -0
  17. package/ai/llm/reasoningModels.ts +1 -0
  18. package/ai/skills/skillDocProtocol.ts +95 -3
  19. package/ai/taskRun/taskRunProtocol.ts +1 -0
  20. package/ai/tools/agent/agentTools.ts +17 -0
  21. package/ai/tools/agent/startAgentDialogTool.ts +53 -0
  22. package/ai/tools/modelUsageTools.ts +5 -0
  23. package/client/agentRun.test.ts +257 -7
  24. package/client/agentRun.ts +133 -34
  25. package/client/localRuntimeAdapter.test.ts +2 -0
  26. package/client/localRuntimeAdapter.ts +15 -2
  27. package/database/actions/common.ts +4 -3
  28. package/database/config.ts +19 -0
  29. package/machineCommands.ts +400 -45
  30. package/package.json +4 -2
  31. package/render/canvas/canvasEditContext.ts +127 -0
  32. package/render/canvas/canvasRuntime.ts +57 -0
  33. package/render/canvas/canvasSnapshotParser.ts +76 -0
  34. package/render/canvas/canvasTree.ts +308 -0
  35. package/render/canvas/types.ts +46 -0
  36. package/render/layout/deleteBehavior.ts +52 -0
  37. package/render/layout/mainLayoutSidebar.ts +17 -0
  38. package/render/layout/mainLayoutViewMode.ts +56 -0
  39. package/render/layout/topbarUtils.ts +87 -0
  40. package/render/layout/useDevReloadPending.ts +30 -0
  41. package/render/page/createPageAction.ts +183 -0
  42. package/render/page/docSlice.ts +468 -0
  43. package/render/page/server/createPage.ts +174 -0
  44. package/render/page/server/handleCreatePage.ts +91 -0
  45. package/render/page/server/index.ts +4 -0
  46. package/render/page/types.ts +17 -0
  47. package/render/page/useKeyboardSave.ts +48 -0
  48. package/render/styles/zIndex.ts +12 -0
  49. package/render/surf/WeatherIconStyles.ts +17 -0
  50. package/render/surf/color.ts +9 -0
  51. package/render/surf/config.ts +46 -0
  52. package/render/surf/screens/style.ts +1 -0
  53. package/render/surf/styles/ToggleButtonStyles.ts +8 -0
  54. package/render/surf/utils/groupedWeatherData.ts +32 -0
  55. package/render/surf/weatherUtils.ts +50 -0
  56. package/render/table/activityColumns.ts +6 -0
  57. package/render/table/createTableAction.ts +270 -0
  58. package/render/table/deleteTableAction.ts +129 -0
  59. package/render/table/fetchAndCacheTableRows.ts +174 -0
  60. package/render/table/tableSlice.ts +1106 -0
  61. package/render/table/tableView.ts +289 -0
  62. package/render/table/toolValueUtils.ts +363 -0
  63. package/render/table/types.ts +252 -0
  64. package/render/table/useCreateTable.ts +72 -0
  65. package/render/table/useTable.ts +61 -0
  66. package/render/table/utils/tableSerialization.ts +50 -0
  67. package/render/web/elements/artifactPreviewCode.ts +43 -0
  68. package/render/web/elements/artifactRuntimePreload.ts +52 -0
  69. package/render/web/elements/codeBlockAutoPreview.ts +10 -0
  70. package/render/web/elements/mermaidPreview.ts +21 -0
  71. package/render/web/ui/useInlineEdit.ts +135 -0
  72. package/tableCommands.ts +42 -5
@@ -4,7 +4,7 @@ import { createDialogMessageKeyAndId } from "database/keys";
4
4
  import { DataType } from "create/types";
5
5
 
6
6
  import type { RootState } from "app/store";
7
- import { patch, read } from "database/dbSlice";
7
+ import { patch, read, selectById } from "database/dbSlice";
8
8
  import { generateRequestBody } from "ai/llm/generateRequestBody";
9
9
  import {
10
10
  selectCurrentDialogConfig,
@@ -146,8 +146,23 @@ export interface StreamAgentChatTurnArgs {
146
146
  isStreaming?: boolean;
147
147
  parentMessageId?: string;
148
148
  runtimeOptions?: AgentRuntimeOptions;
149
+ quickChatPerfStartedAt?: number;
149
150
  }
150
151
 
152
+ const logQuickChatPerfStage = (
153
+ startedAt: number | undefined,
154
+ stage: string,
155
+ details: Record<string, unknown> = {}
156
+ ) => {
157
+ if (!startedAt) return;
158
+ console.info("[QuickChatPerf]", {
159
+ stage,
160
+ elapsedMs: Date.now() - startedAt,
161
+ ...(typeof performance !== "undefined" ? { atMs: performance.now() } : {}),
162
+ ...details,
163
+ });
164
+ };
165
+
151
166
  const normalizeAgentRunUserInput = (userInput: string | any[]) => {
152
167
  if (typeof userInput === "string") {
153
168
  return userInput;
@@ -166,6 +181,16 @@ const normalizeAgentRunUserInput = (userInput: string | any[]) => {
166
181
  });
167
182
  };
168
183
 
184
+ const isUsableAgentConfig = (value: unknown): value is Agent =>
185
+ !!value &&
186
+ typeof value === "object" &&
187
+ typeof (value as Agent).dbKey === "string" &&
188
+ !!(value as Agent).dbKey &&
189
+ typeof (value as Agent).model === "string" &&
190
+ !!(value as Agent).model &&
191
+ typeof (value as Agent).provider === "string" &&
192
+ !!(value as Agent).provider;
193
+
169
194
  const extractAgentRunUserText = (userInput: string | any[]) => {
170
195
  if (typeof userInput === "string") {
171
196
  return userInput;
@@ -180,17 +205,6 @@ const extractAgentRunUserText = (userInput: string | any[]) => {
180
205
  .trim();
181
206
  };
182
207
 
183
- const requiresServerResolvedProviderConfig = (agentConfig: Agent): boolean => {
184
- if (agentConfig.useServerProxy !== true) return false;
185
-
186
- const provider = String(agentConfig.provider ?? "").toLowerCase();
187
- const apiSource = String((agentConfig as any).apiSource ?? "").toLowerCase();
188
- const isCustomProvider = provider === "custom" || apiSource === "custom";
189
- if (!isCustomProvider) return false;
190
-
191
- return !agentConfig.customProviderUrl?.trim() || !agentConfig.apiKey?.trim();
192
- };
193
-
194
208
  const hasAgentRunUserInputContent = (userInput: string | any[]) => {
195
209
  if (typeof userInput === "string") {
196
210
  return userInput.trim().length > 0;
@@ -209,7 +223,14 @@ export const streamAgentChatTurnHandler = async (
209
223
  args: StreamAgentChatTurnArgs,
210
224
  thunkApi: any,
211
225
  ) => {
212
- const { agentKey, userInput, dialogKey: explicitDialogKey, parentMessageId, runtimeOptions } = args;
226
+ const {
227
+ agentKey,
228
+ userInput,
229
+ dialogKey: explicitDialogKey,
230
+ parentMessageId,
231
+ runtimeOptions,
232
+ quickChatPerfStartedAt,
233
+ } = args;
213
234
  const { getState, dispatch, rejectWithValue } = thunkApi;
214
235
  const state = getState() as RootState;
215
236
 
@@ -227,11 +248,25 @@ export const streamAgentChatTurnHandler = async (
227
248
  try {
228
249
  let totalTurnUsage: any = null;
229
250
  const agentRunUserInput = normalizeAgentRunUserInput(userInput);
230
- // 1. 读取 Agent 配置
231
- const agentConfig = (await dispatch(read({ dbKey: agentKey })).unwrap()) as Agent;
251
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-entered", {
252
+ agentKey,
253
+ dialogKey: explicitDialogKey ?? null,
254
+ });
255
+ // 1. 读取 Agent 配置。Quick Chat 会提前预热默认 agent;命中 Redux DB 缓存时避免重复读。
256
+ const cachedAgentConfig = selectById(getState() as RootState, agentKey);
257
+ const agentConfig = isUsableAgentConfig(cachedAgentConfig)
258
+ ? cachedAgentConfig
259
+ : ((await dispatch(read({ dbKey: agentKey })).unwrap()) as Agent);
232
260
  if (!agentConfig) {
233
261
  return rejectWithValue(`Agent config not found for ID: ${agentKey}`);
234
262
  }
263
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-config-read", {
264
+ agentKey,
265
+ model: agentConfig.model,
266
+ provider: agentConfig.provider,
267
+ apiSource: agentConfig.apiSource,
268
+ source: cachedAgentConfig === agentConfig ? "cache" : "read",
269
+ });
235
270
 
236
271
  // ── CLI Agent 专用路由 ────────────────────────────────────────────────
237
272
  // CLI 共享 prompt / model 这些入口能力,但不复用本地 tool-call 循环。
@@ -330,6 +365,8 @@ export const streamAgentChatTurnHandler = async (
330
365
  userInput: agentRunUserInput,
331
366
  messages: cleanedMessages,
332
367
  stream: true,
368
+ persistDialog: false,
369
+ clientDialogId: dialogId,
333
370
  runtimeContext: {
334
371
  surface: "web",
335
372
  host: "browser",
@@ -616,6 +653,10 @@ export const streamAgentChatTurnHandler = async (
616
653
  }
617
654
  runtimeDialogKey = dialogKey;
618
655
  const dialogId = extractCustomId(dialogKey);
656
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-dialog-resolved", {
657
+ dialogKey,
658
+ dialogId,
659
+ });
619
660
 
620
661
  const userInputText = extractAgentRunUserText(userInput);
621
662
 
@@ -625,12 +666,10 @@ export const streamAgentChatTurnHandler = async (
625
666
  : null;
626
667
  const currentServer = selectCurrentServer(state);
627
668
  const declaredRuntimeServerBase = extractAgentRuntimeServerBase(agentConfig);
628
- const needsServerResolvedProviderConfig =
629
- requiresServerResolvedProviderConfig(agentConfig);
630
669
  const requestedServerBase =
631
670
  explicitServerBase ??
632
671
  declaredRuntimeServerBase ??
633
- (needsServerResolvedProviderConfig ? currentServer : null);
672
+ null;
634
673
  const normalizedRequestedServerBase =
635
674
  requestedServerBase && normalizeServerOrigin(requestedServerBase);
636
675
  const normalizedCurrentServer = normalizeServerOrigin(
@@ -643,12 +682,13 @@ export const streamAgentChatTurnHandler = async (
643
682
  !runtimeOptions?.imageConfigOverride;
644
683
  if (requestedServerBase && canAutoRouteRemotely) {
645
684
  if (
646
- !needsServerResolvedProviderConfig &&
647
685
  normalizedRequestedServerBase &&
648
686
  normalizedCurrentServer &&
649
687
  normalizedRequestedServerBase === normalizedCurrentServer
650
688
  ) {
651
- // same server as current workspace; keep local flow
689
+ // Same server as the current workspace; keep the UI-managed
690
+ // chat/tool loop and let /api/chat hydrate redacted provider
691
+ // credentials server-side when needed.
652
692
  } else {
653
693
  const token = selectCurrentToken(state);
654
694
  const authHeader = token ? `Bearer ${token}` : "";
@@ -711,6 +751,8 @@ export const streamAgentChatTurnHandler = async (
711
751
  userInput: agentRunUserInput,
712
752
  messages: cleanedMessages,
713
753
  stream: true,
754
+ persistDialog: false,
755
+ clientDialogId: dialogId,
714
756
  runtimeContext: {
715
757
  surface: "web",
716
758
  host: "browser",
@@ -811,6 +853,10 @@ export const streamAgentChatTurnHandler = async (
811
853
  recommendedSkillHints: referenceRecommendedSkillHints,
812
854
  skillPromptPatches: referenceSkillPromptPatches,
813
855
  } = await resolveReferenceAssets(agentConfig.references, dispatch);
856
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-references-resolved", {
857
+ referenceCount: normalizedReferences?.length ?? 0,
858
+ referencedToolCount: referenceTools?.length ?? 0,
859
+ });
814
860
 
815
861
  const agentConfigWithReferences = {
816
862
  ...agentConfig,
@@ -906,6 +952,10 @@ export const streamAgentChatTurnHandler = async (
906
952
  currentDialog ?? undefined,
907
953
  mergedContentCache,
908
954
  );
955
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-static-context-ready", {
956
+ model: agentConfigForCall.model,
957
+ responseApi: true,
958
+ });
909
959
 
910
960
  let appendTempUserInput = true;
911
961
  let currentParentMessageId = parentMessageId ?? undefined;
@@ -947,6 +997,9 @@ export const streamAgentChatTurnHandler = async (
947
997
  mergedContentCache,
948
998
  dialogKey,
949
999
  );
1000
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-dynamic-context-ready", {
1001
+ responseApi: true,
1002
+ });
950
1003
  const contexts = mergeContexts(staticContexts, dynamicContexts);
951
1004
 
952
1005
  const rawMessages = filterMessagesForParallelBranch(
@@ -1014,6 +1067,11 @@ export const streamAgentChatTurnHandler = async (
1014
1067
  userInput: userInputText,
1015
1068
  contexts,
1016
1069
  });
1070
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-model-request-starting", {
1071
+ responseApi: true,
1072
+ dynamicMessageCount: dynamicMessages.length,
1073
+ stableMessageCount: stableMessages.length,
1074
+ });
1017
1075
 
1018
1076
  const meta: CompletionMeta = await sendOpenAIResponseRequest({
1019
1077
  bodyData,
@@ -1025,6 +1083,13 @@ export const streamAgentChatTurnHandler = async (
1025
1083
  agentConfigForCall,
1026
1084
  runtimeOptions,
1027
1085
  ),
1086
+ quickChatPerfStartedAt,
1087
+ });
1088
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-model-request-finished", {
1089
+ responseApi: true,
1090
+ hasToolCalls: meta.hasToolCalls,
1091
+ hasHandedOff: meta.hasHandedOff,
1092
+ hasPendingInteraction: meta.hasPendingInteraction,
1028
1093
  });
1029
1094
 
1030
1095
  appendTempUserInput = false;
@@ -1093,6 +1158,10 @@ export const streamAgentChatTurnHandler = async (
1093
1158
  currentDialog ?? undefined,
1094
1159
  mergedContentCache,
1095
1160
  );
1161
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-static-context-ready", {
1162
+ model: agentConfigForCall.model,
1163
+ responseApi: false,
1164
+ });
1096
1165
 
1097
1166
  let appendTempUserInput = true;
1098
1167
  let currentParentMessageId = parentMessageId ?? undefined;
@@ -1139,6 +1208,9 @@ export const streamAgentChatTurnHandler = async (
1139
1208
  mergedContentCache,
1140
1209
  dialogKey,
1141
1210
  );
1211
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-dynamic-context-ready", {
1212
+ responseApi: false,
1213
+ });
1142
1214
 
1143
1215
  // 合并静态和动态上下文
1144
1216
  const contexts = mergeContexts(staticContexts, dynamicContexts);
@@ -1209,6 +1281,11 @@ export const streamAgentChatTurnHandler = async (
1209
1281
  userInput: userInputText,
1210
1282
  contexts,
1211
1283
  });
1284
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-model-request-starting", {
1285
+ responseApi: false,
1286
+ dynamicMessageCount: dynamicMessages.length,
1287
+ stableMessageCount: stableMessages.length,
1288
+ });
1212
1289
 
1213
1290
  const meta: CompletionMeta = await sendOpenAICompletionsRequest({
1214
1291
  bodyData,
@@ -1220,6 +1297,13 @@ export const streamAgentChatTurnHandler = async (
1220
1297
  agentConfigForCall,
1221
1298
  runtimeOptions,
1222
1299
  ),
1300
+ quickChatPerfStartedAt,
1301
+ });
1302
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-model-request-finished", {
1303
+ responseApi: false,
1304
+ hasToolCalls: meta.hasToolCalls,
1305
+ hasHandedOff: meta.hasHandedOff,
1306
+ hasPendingInteraction: meta.hasPendingInteraction,
1223
1307
  });
1224
1308
 
1225
1309
  appendTempUserInput = false;
@@ -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
  };
@@ -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,20 @@ export type CompletionMeta = {
160
160
  usage?: any;
161
161
  };
162
162
 
163
+ const logQuickChatPerfStage = (
164
+ startedAt: number | undefined,
165
+ stage: string,
166
+ details?: Record<string, unknown>
167
+ ) => {
168
+ if (!startedAt) return;
169
+ console.info("[QuickChatPerf]", {
170
+ stage,
171
+ elapsedMs: Date.now() - startedAt,
172
+ ...(typeof performance !== "undefined" ? { atMs: performance.now() } : {}),
173
+ ...(details ?? {}),
174
+ });
175
+ };
176
+
163
177
  /**
164
178
  * 初始化流式状态
165
179
  */
@@ -455,6 +469,7 @@ export const sendOpenAICompletionsRequest = async ({
455
469
  parentMessageId,
456
470
  messageMetadata,
457
471
  disableToolsForThisRequest = false,
472
+ quickChatPerfStartedAt,
458
473
  }: {
459
474
  bodyData: any;
460
475
  agentConfig: any;
@@ -463,6 +478,7 @@ export const sendOpenAICompletionsRequest = async ({
463
478
  parentMessageId?: string;
464
479
  messageMetadata?: Partial<Message>;
465
480
  disableToolsForThisRequest?: boolean;
481
+ quickChatPerfStartedAt?: number;
466
482
  }): Promise<CompletionMeta> => {
467
483
  const { dispatch, getState, signal: thunkSignal } = thunkApi;
468
484
 
@@ -549,6 +565,10 @@ export const sendOpenAICompletionsRequest = async ({
549
565
 
550
566
  const api = getApiEndpoint(agentConfig);
551
567
  const token = selectCurrentToken(getState() as RootState);
568
+ logQuickChatPerfStage(quickChatPerfStartedAt, "openai-completions-fetch-starting", {
569
+ api,
570
+ dialogKey,
571
+ });
552
572
  const response = await performFetchRequest({
553
573
  agentConfig,
554
574
  api,
@@ -557,6 +577,11 @@ export const sendOpenAICompletionsRequest = async ({
557
577
  signal,
558
578
  token,
559
579
  });
580
+ logQuickChatPerfStage(quickChatPerfStartedAt, "openai-completions-fetch-response", {
581
+ ok: response.ok,
582
+ status: response.status,
583
+ dialogKey,
584
+ });
560
585
 
561
586
  if (!response.ok) {
562
587
  const errorMessage = await parseApiError(response);
@@ -578,6 +603,9 @@ export const sendOpenAICompletionsRequest = async ({
578
603
  }
579
604
 
580
605
  const decoder = new TextDecoder();
606
+ let loggedFirstStreamChunk = false;
607
+ let loggedFirstParsedEvent = false;
608
+ let loggedFirstVisibleDelta = false;
581
609
 
582
610
  while (true) {
583
611
  const { done, value } = await reader.read();
@@ -602,8 +630,25 @@ export const sendOpenAICompletionsRequest = async ({
602
630
  break;
603
631
  }
604
632
 
633
+ if (!loggedFirstStreamChunk) {
634
+ loggedFirstStreamChunk = true;
635
+ logQuickChatPerfStage(
636
+ quickChatPerfStartedAt,
637
+ "openai-completions-first-stream-chunk",
638
+ { dialogKey, byteLength: value.byteLength }
639
+ );
640
+ }
641
+
605
642
  const chunk = decoder.decode(value, { stream: true });
606
643
  const parsedResults = parseSSE(chunk);
644
+ if (parsedResults.length > 0 && !loggedFirstParsedEvent) {
645
+ loggedFirstParsedEvent = true;
646
+ logQuickChatPerfStage(
647
+ quickChatPerfStartedAt,
648
+ "openai-completions-first-sse-event",
649
+ { dialogKey, eventCount: parsedResults.length }
650
+ );
651
+ }
607
652
 
608
653
  for (const parsedData of parsedResults) {
609
654
  const dataList = Array.isArray(parsedData) ? parsedData : [parsedData];
@@ -641,6 +686,14 @@ export const sendOpenAICompletionsRequest = async ({
641
686
  delta
642
687
  );
643
688
  streamState = updatedState;
689
+ if (hasNewVisibleContent && !loggedFirstVisibleDelta) {
690
+ loggedFirstVisibleDelta = true;
691
+ logQuickChatPerfStage(
692
+ quickChatPerfStartedAt,
693
+ "openai-completions-first-visible-delta",
694
+ { dialogKey }
695
+ );
696
+ }
644
697
 
645
698
  emitStreamingUpdate(hasNewVisibleContent, streamState, {
646
699
  dispatch,
@@ -712,6 +765,9 @@ export const sendOpenAICompletionsRequest = async ({
712
765
  };
713
766
  streamState = await finalizeStream(streamState, finalizeCtx);
714
767
  } finally {
768
+ logQuickChatPerfStage(quickChatPerfStartedAt, "openai-completions-stream-finished", {
769
+ dialogKey,
770
+ });
715
771
  dispatch(removeActiveController({ messageId, dialogKey }));
716
772
  try {
717
773
  await reader?.cancel();
@@ -42,6 +42,20 @@ 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
+ console.info("[QuickChatPerf]", {
52
+ stage,
53
+ elapsedMs: Date.now() - startedAt,
54
+ ...(typeof performance !== "undefined" ? { atMs: performance.now() } : {}),
55
+ ...(details ?? {}),
56
+ });
57
+ };
58
+
45
59
  type StreamState = {
46
60
  content: string;
47
61
  contentBuffer: Array<
@@ -140,6 +154,7 @@ export const sendOpenAIResponseRequest = async ({
140
154
  dialogKey,
141
155
  parentMessageId,
142
156
  messageMetadata,
157
+ quickChatPerfStartedAt,
143
158
  }: {
144
159
  bodyData: any;
145
160
  agentConfig: any;
@@ -147,6 +162,7 @@ export const sendOpenAIResponseRequest = async ({
147
162
  dialogKey: string;
148
163
  parentMessageId?: string;
149
164
  messageMetadata?: Partial<Message>;
165
+ quickChatPerfStartedAt?: number;
150
166
  }): Promise<CompletionMeta> => {
151
167
  const { dispatch, getState, signal: thunkSignal } = thunkApi;
152
168
  const dialogId = extractCustomId(dialogKey);
@@ -354,6 +370,10 @@ export const sendOpenAIResponseRequest = async ({
354
370
 
355
371
  const api = getApiEndpoint(agentConfig);
356
372
  const token = selectCurrentToken(getState() as RootState);
373
+ logQuickChatPerfStage(quickChatPerfStartedAt, "openai-response-fetch-starting", {
374
+ api,
375
+ dialogKey,
376
+ });
357
377
  const response = await performFetchRequest({
358
378
  agentConfig,
359
379
  api,
@@ -362,6 +382,11 @@ export const sendOpenAIResponseRequest = async ({
362
382
  signal,
363
383
  token,
364
384
  });
385
+ logQuickChatPerfStage(quickChatPerfStartedAt, "openai-response-fetch-response", {
386
+ ok: response.ok,
387
+ status: response.status,
388
+ dialogKey,
389
+ });
365
390
 
366
391
  if (!response.ok) {
367
392
  const errorMessage = await parseApiError(response);
@@ -379,13 +404,33 @@ export const sendOpenAIResponseRequest = async ({
379
404
  const parseSSE = createSSEParser();
380
405
  const decoder = new TextDecoder();
381
406
  let finishReason: string | null = null;
407
+ let loggedFirstStreamChunk = false;
408
+ let loggedFirstParsedEvent = false;
409
+ let loggedFirstVisibleDelta = false;
382
410
 
383
411
  while (true) {
384
412
  const { done, value } = await reader.read();
385
413
  if (done) break;
386
414
 
415
+ if (!loggedFirstStreamChunk) {
416
+ loggedFirstStreamChunk = true;
417
+ logQuickChatPerfStage(
418
+ quickChatPerfStartedAt,
419
+ "openai-response-first-stream-chunk",
420
+ { dialogKey, byteLength: value.byteLength }
421
+ );
422
+ }
423
+
387
424
  const chunk = decoder.decode(value, { stream: true });
388
425
  const events = parseSSE(chunk);
426
+ if (events.length > 0 && !loggedFirstParsedEvent) {
427
+ loggedFirstParsedEvent = true;
428
+ logQuickChatPerfStage(
429
+ quickChatPerfStartedAt,
430
+ "openai-response-first-sse-event",
431
+ { dialogKey, eventCount: events.length }
432
+ );
433
+ }
389
434
  const eventList = Array.isArray(events) ? events : [events];
390
435
 
391
436
  for (const event of eventList) {
@@ -404,6 +449,14 @@ export const sendOpenAIResponseRequest = async ({
404
449
  if (event.delta) {
405
450
  state.content += event.delta;
406
451
  state.contentBuffer = seg(state.content);
452
+ if (!loggedFirstVisibleDelta) {
453
+ loggedFirstVisibleDelta = true;
454
+ logQuickChatPerfStage(
455
+ quickChatPerfStartedAt,
456
+ "openai-response-first-visible-delta",
457
+ { dialogKey }
458
+ );
459
+ }
407
460
  flush();
408
461
  }
409
462
  break;
@@ -427,6 +480,14 @@ export const sendOpenAIResponseRequest = async ({
427
480
  if (itemText) {
428
481
  state.content = itemText;
429
482
  state.contentBuffer = seg(state.content);
483
+ if (!loggedFirstVisibleDelta) {
484
+ loggedFirstVisibleDelta = true;
485
+ logQuickChatPerfStage(
486
+ quickChatPerfStartedAt,
487
+ "openai-response-first-visible-delta",
488
+ { dialogKey }
489
+ );
490
+ }
430
491
  flush();
431
492
  }
432
493
  }
@@ -506,6 +567,9 @@ export const sendOpenAIResponseRequest = async ({
506
567
  await finalize();
507
568
  return buildMeta(false, false, "error");
508
569
  } finally {
570
+ logQuickChatPerfStage(quickChatPerfStartedAt, "openai-response-stream-finished", {
571
+ dialogKey,
572
+ });
509
573
  dispatch(removeActiveController({ messageId, dialogKey }));
510
574
  await safeCancel(reader);
511
575
  }
package/ai/llm/kimi.ts CHANGED
@@ -2,7 +2,7 @@ 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;
@@ -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
  );
@@ -4,6 +4,7 @@ const REASONING_MODEL_NAMES = new Set([
4
4
  "deepseek-reasoner",
5
5
  "gemini-2.5-pro",
6
6
  "gemini-2.5-flash",
7
+ "gemini-3.5-flash",
7
8
  "gemini-3-flash-preview",
8
9
  "gemini-3-pro-preview",
9
10
  "gemini-3.1-pro-preview",