@agent-native/toolkit 0.205.0 → 0.206.0-nightly-20261009011246

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 (143) hide show
  1. package/dist/app/agentkit/react/components.d.ts.map +1 -1
  2. package/dist/app/agentkit/react/components.js +17 -9
  3. package/dist/app/agentkit/react/components.js.map +1 -1
  4. package/dist/app/auth/AuthPage.d.ts.map +1 -1
  5. package/dist/app/auth/AuthPage.js +11 -0
  6. package/dist/app/auth/AuthPage.js.map +1 -1
  7. package/dist/app/chat/AgentKitAssistantChat.d.ts +2 -0
  8. package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
  9. package/dist/app/chat/AgentKitAssistantChat.js +24 -14
  10. package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
  11. package/dist/app/chat/AgentPanel.d.ts.map +1 -1
  12. package/dist/app/chat/AgentPanel.js +3 -2
  13. package/dist/app/chat/AgentPanel.js.map +1 -1
  14. package/dist/app/chat/MultiTabAssistantChat.d.ts +4 -1
  15. package/dist/app/chat/MultiTabAssistantChat.d.ts.map +1 -1
  16. package/dist/app/chat/MultiTabAssistantChat.js +61 -3
  17. package/dist/app/chat/MultiTabAssistantChat.js.map +1 -1
  18. package/dist/app/chat/agentkit-chat/approval-card.d.ts.map +1 -1
  19. package/dist/app/chat/agentkit-chat/approval-card.js +2 -1
  20. package/dist/app/chat/agentkit-chat/approval-card.js.map +1 -1
  21. package/dist/app/chat/chat/run-recovery.d.ts.map +1 -1
  22. package/dist/app/chat/chat/run-recovery.js +74 -8
  23. package/dist/app/chat/chat/run-recovery.js.map +1 -1
  24. package/dist/app/chat/chat/tool-call-display.d.ts.map +1 -1
  25. package/dist/app/chat/chat/tool-call-display.js +6 -4
  26. package/dist/app/chat/chat/tool-call-display.js.map +1 -1
  27. package/dist/app/chat/chat-first/ChatFirstAgentActivityPanel.d.ts.map +1 -1
  28. package/dist/app/chat/chat-first/ChatFirstAgentActivityPanel.js +2 -1
  29. package/dist/app/chat/chat-first/ChatFirstAgentActivityPanel.js.map +1 -1
  30. package/dist/app/chat/conversation/AgentConversation.d.ts.map +1 -1
  31. package/dist/app/chat/conversation/AgentConversation.js +10 -7
  32. package/dist/app/chat/conversation/AgentConversation.js.map +1 -1
  33. package/dist/app/chat/fallback-chat-title.d.ts.map +1 -1
  34. package/dist/app/chat/fallback-chat-title.js +2 -4
  35. package/dist/app/chat/fallback-chat-title.js.map +1 -1
  36. package/dist/app/chat/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  37. package/dist/app/chat/mcp-apps/McpAppRenderer.js +3 -2
  38. package/dist/app/chat/mcp-apps/McpAppRenderer.js.map +1 -1
  39. package/dist/app/chat/session-replay-privacy.d.ts +7 -0
  40. package/dist/app/chat/session-replay-privacy.d.ts.map +1 -0
  41. package/dist/app/chat/session-replay-privacy.js +6 -0
  42. package/dist/app/chat/session-replay-privacy.js.map +1 -0
  43. package/dist/app/extensions/ExtensionViewer.d.ts +4 -1
  44. package/dist/app/extensions/ExtensionViewer.d.ts.map +1 -1
  45. package/dist/app/extensions/ExtensionViewer.js +3 -3
  46. package/dist/app/extensions/ExtensionViewer.js.map +1 -1
  47. package/dist/app/extensions/ExtensionViewerPage.d.ts +4 -1
  48. package/dist/app/extensions/ExtensionViewerPage.d.ts.map +1 -1
  49. package/dist/app/extensions/ExtensionViewerPage.js +2 -2
  50. package/dist/app/extensions/ExtensionViewerPage.js.map +1 -1
  51. package/dist/app/onboarding/FirstRunOnboarding.d.ts.map +1 -1
  52. package/dist/app/onboarding/FirstRunOnboarding.js +53 -12
  53. package/dist/app/onboarding/FirstRunOnboarding.js.map +1 -1
  54. package/dist/app/onboarding/OnboardingPanel.d.ts.map +1 -1
  55. package/dist/app/onboarding/OnboardingPanel.js +14 -4
  56. package/dist/app/onboarding/OnboardingPanel.js.map +1 -1
  57. package/dist/app/resources/ResourceEditor.d.ts +4 -1
  58. package/dist/app/resources/ResourceEditor.d.ts.map +1 -1
  59. package/dist/app/resources/ResourceEditor.js +18 -7
  60. package/dist/app/resources/ResourceEditor.js.map +1 -1
  61. package/dist/app/resources/ResourcesPanel.d.ts +4 -1
  62. package/dist/app/resources/ResourcesPanel.d.ts.map +1 -1
  63. package/dist/app/resources/ResourcesPanel.js +18 -18
  64. package/dist/app/resources/ResourcesPanel.js.map +1 -1
  65. package/dist/app/resources/custom-agent-model-options.d.ts +16 -0
  66. package/dist/app/resources/custom-agent-model-options.d.ts.map +1 -0
  67. package/dist/app/resources/custom-agent-model-options.js +53 -0
  68. package/dist/app/resources/custom-agent-model-options.js.map +1 -0
  69. package/dist/app/settings/SettingsPanel.d.ts.map +1 -1
  70. package/dist/app/settings/SettingsPanel.js +34 -3
  71. package/dist/app/settings/SettingsPanel.js.map +1 -1
  72. package/dist/app/settings/SettingsTabsPage.d.ts.map +1 -1
  73. package/dist/app/settings/SettingsTabsPage.js +2 -0
  74. package/dist/app/settings/SettingsTabsPage.js.map +1 -1
  75. package/dist/app/settings/api-keys/ApiKeyDialogs.d.ts.map +1 -1
  76. package/dist/app/settings/api-keys/ApiKeyDialogs.js +49 -9
  77. package/dist/app/settings/api-keys/ApiKeyDialogs.js.map +1 -1
  78. package/dist/app/settings/api-keys/ApiKeysSettingsPage.js +2 -1
  79. package/dist/app/settings/api-keys/ApiKeysSettingsPage.js.map +1 -1
  80. package/dist/app/settings/model/ModelSettingsPage.d.ts.map +1 -1
  81. package/dist/app/settings/model/ModelSettingsPage.js +43 -9
  82. package/dist/app/settings/model/ModelSettingsPage.js.map +1 -1
  83. package/dist/app/settings/model/ProviderDialog.d.ts +4 -0
  84. package/dist/app/settings/model/ProviderDialog.d.ts.map +1 -1
  85. package/dist/app/settings/model/ProviderDialog.js +107 -11
  86. package/dist/app/settings/model/ProviderDialog.js.map +1 -1
  87. package/dist/app/settings/useBuilderStatus.d.ts.map +1 -1
  88. package/dist/app/settings/useBuilderStatus.js +13 -10
  89. package/dist/app/settings/useBuilderStatus.js.map +1 -1
  90. package/dist/composer/TiptapComposer.d.ts.map +1 -1
  91. package/dist/composer/TiptapComposer.js +51 -13
  92. package/dist/composer/TiptapComposer.js.map +1 -1
  93. package/package.json +4 -4
  94. package/src/app/agentkit/react/components.interaction.spec.tsx +229 -1
  95. package/src/app/agentkit/react/components.spec.tsx +42 -1
  96. package/src/app/agentkit/react/components.tsx +46 -12
  97. package/src/app/auth/AuthPage.tsx +13 -0
  98. package/src/app/chat/AgentKitAssistantChat.spec.tsx +116 -5
  99. package/src/app/chat/AgentKitAssistantChat.tsx +41 -20
  100. package/src/app/chat/AgentPanel.tsx +5 -1
  101. package/src/app/chat/MultiTabAssistantChat.spec.tsx +152 -7
  102. package/src/app/chat/MultiTabAssistantChat.tsx +71 -4
  103. package/src/app/chat/agentkit-chat/approval-card.spec.tsx +17 -0
  104. package/src/app/chat/agentkit-chat/approval-card.tsx +2 -0
  105. package/src/app/chat/chat/run-recovery.spec.tsx +354 -9
  106. package/src/app/chat/chat/run-recovery.tsx +178 -10
  107. package/src/app/chat/chat/tool-call-display.spec.tsx +77 -0
  108. package/src/app/chat/chat/tool-call-display.tsx +23 -6
  109. package/src/app/chat/chat-first/ChatFirstAgentActivityPanel.tsx +2 -1
  110. package/src/app/chat/conversation/AgentConversation.spec.tsx +190 -1
  111. package/src/app/chat/conversation/AgentConversation.tsx +32 -8
  112. package/src/app/chat/fallback-chat-title.spec.ts +44 -0
  113. package/src/app/chat/fallback-chat-title.ts +3 -4
  114. package/src/app/chat/mcp-apps/McpAppRenderer.spec.ts +24 -0
  115. package/src/app/chat/mcp-apps/McpAppRenderer.tsx +10 -2
  116. package/src/app/chat/session-replay-privacy.spec.ts +14 -0
  117. package/src/app/chat/session-replay-privacy.ts +5 -0
  118. package/src/app/extensions/ExtensionViewer.tsx +15 -3
  119. package/src/app/extensions/ExtensionViewerPage.tsx +7 -3
  120. package/src/app/onboarding/FirstRunOnboarding.spec.tsx +252 -2
  121. package/src/app/onboarding/FirstRunOnboarding.tsx +56 -9
  122. package/src/app/onboarding/OnboardingPanel.save-scope.spec.tsx +1 -0
  123. package/src/app/onboarding/OnboardingPanel.tsx +15 -3
  124. package/src/app/resources/ResourceEditor.spec.tsx +240 -5
  125. package/src/app/resources/ResourceEditor.tsx +57 -10
  126. package/src/app/resources/ResourcesPanel.spec.ts +86 -4
  127. package/src/app/resources/ResourcesPanel.tsx +49 -16
  128. package/src/app/resources/custom-agent-model-options.ts +92 -0
  129. package/src/app/settings/SettingsPanel.model-defaults.spec.tsx +26 -13
  130. package/src/app/settings/SettingsPanel.tsx +35 -3
  131. package/src/app/settings/SettingsTabsPage.spec.tsx +97 -0
  132. package/src/app/settings/SettingsTabsPage.tsx +2 -0
  133. package/src/app/settings/api-keys/ApiKeyDialogs.tsx +84 -22
  134. package/src/app/settings/api-keys/ApiKeysSettingsPage.spec.tsx +229 -1
  135. package/src/app/settings/api-keys/ApiKeysSettingsPage.tsx +3 -2
  136. package/src/app/settings/model/ModelSettingsPage.spec.tsx +99 -9
  137. package/src/app/settings/model/ModelSettingsPage.tsx +79 -11
  138. package/src/app/settings/model/ProviderDialog.spec.tsx +192 -1
  139. package/src/app/settings/model/ProviderDialog.tsx +225 -29
  140. package/src/app/settings/useBuilderStatus.spec.tsx +49 -0
  141. package/src/app/settings/useBuilderStatus.ts +16 -10
  142. package/src/composer/TiptapComposer.spec.ts +97 -2
  143. package/src/composer/TiptapComposer.tsx +67 -13
@@ -1,7 +1,7 @@
1
1
  import {
2
- BUILDER_CLAUDE_SONNET_MODEL_ID,
3
- BUILDER_CLAUDE_SONNET_MODEL_LABEL,
4
- } from "@agent-native/core/agent/model-config";
2
+ getModelOptionLabel,
3
+ type ModelEngineConfig,
4
+ } from "@agent-native/core/agent/model-version";
5
5
  import { sendToAgentChat } from "@agent-native/core/client/agent-chat";
6
6
  import { agentNativePath } from "@agent-native/core/client/api-path";
7
7
  import { useT } from "@agent-native/core/client/i18n";
@@ -94,6 +94,10 @@ import {
94
94
  type FileStorageSetupCloseReason,
95
95
  } from "../chat/FileStorageSetupPopover.js";
96
96
  import { BuiltinCapabilityDetail } from "./BuiltinCapabilityDetail.js";
97
+ import {
98
+ getCustomAgentModelOptions,
99
+ useCustomAgentModelEngine,
100
+ } from "./custom-agent-model-options.js";
97
101
  import { McpIntegrationDialog } from "./McpIntegrationDialog.js";
98
102
  import { McpServerDetail } from "./McpServerDetail.js";
99
103
  import { ResourceEditor } from "./ResourceEditor.js";
@@ -233,17 +237,6 @@ type CreateMenuView =
233
237
  | "agent-prompt"
234
238
  | "agent-form";
235
239
 
236
- const AGENT_MODEL_OPTIONS = [
237
- { value: "inherit", label: "Default model" },
238
- { value: "claude-fable-5", label: "Claude Fable 5" },
239
- { value: "claude-opus-5-5", label: "Claude Opus 5.5" },
240
- {
241
- value: BUILDER_CLAUDE_SONNET_MODEL_ID,
242
- label: BUILDER_CLAUDE_SONNET_MODEL_LABEL,
243
- },
244
- { value: "claude-haiku-4-5-20251001", label: "Claude Haiku 4.5" },
245
- ] as const;
246
-
247
240
  export function slugifyName(value: string): string {
248
241
  return (
249
242
  value
@@ -433,6 +426,7 @@ function CreateMenu({
433
426
  triggerVariant = "icon",
434
427
  triggerLabel,
435
428
  initialView = "menu",
429
+ modelEngine,
436
430
  }: {
437
431
  scope: ResourceScope;
438
432
  resourceFilter?: ResourceView;
@@ -466,11 +460,25 @@ function CreateMenu({
466
460
  triggerVariant?: "icon" | "outline";
467
461
  triggerLabel?: string;
468
462
  initialView?: CreateMenuView;
463
+ modelEngine?: ModelEngineConfig | null;
469
464
  }) {
470
465
  const t = useT();
471
466
  const [open, setOpen] = useState(false);
472
467
  const [mcpDialogOpen, setMcpDialogOpen] = useState(false);
473
468
  const [view, setView] = useState<CreateMenuView>("menu");
469
+ const modelEngineLoad = useCustomAgentModelEngine(
470
+ modelEngine,
471
+ open && view === "agent-form",
472
+ );
473
+ const effectiveModelEngine = modelEngineLoad.engine;
474
+ const modelLabels = {
475
+ defaultModel: t("agentResources.defaultModel"),
476
+ builderFallback: t("agentResources.builderModelFallback"),
477
+ };
478
+ const agentModelOptions = getCustomAgentModelOptions(
479
+ effectiveModelEngine,
480
+ modelLabels,
481
+ );
474
482
  const showMcpIntegrations = useMemo(
475
483
  () => hasAvailableMcpIntegrations(mcpIntegrations),
476
484
  [mcpIntegrations],
@@ -1108,11 +1116,28 @@ The job will run automatically on the schedule. Make the instructions specific
1108
1116
  onChange={(e) => setAgentModel(e.target.value)}
1109
1117
  className="w-full rounded-md border border-border bg-background px-2.5 py-1.5 text-[13px] text-foreground outline-none focus:ring-1 focus:ring-accent"
1110
1118
  >
1111
- {AGENT_MODEL_OPTIONS.map((option) => (
1119
+ {agentModelOptions.map((option) => (
1112
1120
  <option key={option.value} value={option.value}>
1113
1121
  {option.label}
1114
1122
  </option>
1115
1123
  ))}
1124
+ {modelEngineLoad.state === "unavailable" && (
1125
+ <option value="__model-options-unavailable" disabled>
1126
+ {t("agentResources.modelOptionsUnavailable")}
1127
+ </option>
1128
+ )}
1129
+ {agentModel !== "inherit" &&
1130
+ !agentModelOptions.some(
1131
+ (option) => option.value === agentModel,
1132
+ ) && (
1133
+ <option value={agentModel}>
1134
+ {getModelOptionLabel(
1135
+ agentModel,
1136
+ effectiveModelEngine ?? undefined,
1137
+ modelLabels.builderFallback,
1138
+ )}
1139
+ </option>
1140
+ )}
1116
1141
  </select>
1117
1142
  <label className="block text-[11px] font-medium text-muted-foreground">
1118
1143
  Instructions
@@ -1240,6 +1265,8 @@ export interface ResourcesPanelProps {
1240
1265
  openResourceRef?: { current: ((id: string) => void) | null };
1241
1266
  /** Called when the editor opens or closes, so a page can yield to it. */
1242
1267
  onEditingChange?: (editing: boolean) => void;
1268
+ /** Exact selected chat engine, when this panel is paired with a chat. */
1269
+ modelEngine?: ModelEngineConfig | null;
1243
1270
  }
1244
1271
 
1245
1272
  /** Owners, admins, and solo deployments (no organization) edit org resources. */
@@ -1348,11 +1375,12 @@ export function ResourcesPanel({
1348
1375
  settingsGroups,
1349
1376
  openResourceRef,
1350
1377
  onEditingChange,
1378
+ modelEngine,
1351
1379
  }: ResourcesPanelProps = {}) {
1352
1380
  const t = useT();
1353
1381
  const { data: org } = useOrg();
1354
1382
  const canEditOrg = canEditOrganizationResources(org);
1355
-
1383
+ const effectiveModelEngine = modelEngine;
1356
1384
  const [activeScope, setActiveScope] = useState<ResourceScope>(() =>
1357
1385
  resolveInitialResourceScope(requestedScope, canEditOrg),
1358
1386
  );
@@ -1918,6 +1946,7 @@ export function ResourcesPanel({
1918
1946
  return (
1919
1947
  <CreateMenu
1920
1948
  scope={targetScope}
1949
+ modelEngine={effectiveModelEngine}
1921
1950
  resourceFilter={resourceFilter}
1922
1951
  personalMcpOnly={mode === "personal-mcp"}
1923
1952
  onCreateFile={(name) => handleCreateFromToolbar(targetScope, name)}
@@ -1962,6 +1991,7 @@ export function ResourcesPanel({
1962
1991
  return (
1963
1992
  <CreateMenu
1964
1993
  scope={targetScope}
1994
+ modelEngine={effectiveModelEngine}
1965
1995
  resourceFilter={resourceFilter}
1966
1996
  personalMcpOnly={mode === "personal-mcp"}
1967
1997
  onCreateFile={(name) => handleCreateFromToolbar(targetScope, name)}
@@ -2232,6 +2262,7 @@ export function ResourcesPanel({
2232
2262
  (!resourceFilter || resourceFilter === "files") && (
2233
2263
  <CreateMenu
2234
2264
  scope={activeScope}
2265
+ modelEngine={effectiveModelEngine}
2235
2266
  resourceFilter={resourceFilter}
2236
2267
  personalMcpOnly={activeCreateMenuMode === "personal-mcp"}
2237
2268
  onCreateFile={(name) =>
@@ -2349,6 +2380,8 @@ export function ResourcesPanel({
2349
2380
  onViewChange={setEditorView}
2350
2381
  hideToolbar
2351
2382
  readOnly={selectedResourceReadOnly}
2383
+ modelEngine={effectiveModelEngine}
2384
+ builderFallbackLabel={t("agentResources.builderModelFallback")}
2352
2385
  />
2353
2386
  </div>
2354
2387
  ) : resourceQuery.isError ? (
@@ -0,0 +1,92 @@
1
+ import { CURRENT_BUILDER_CLAUDE_MODEL_OPTIONS } from "@agent-native/core/agent/model-config";
2
+ import {
3
+ getModelOptionLabel,
4
+ type ModelEngineConfig,
5
+ } from "@agent-native/core/agent/model-version";
6
+ import { loadChatModelCatalog } from "@agent-native/core/client/use-chat-models";
7
+ import { useEffect, useState } from "react";
8
+
9
+ export interface CustomAgentModelOption {
10
+ value: string;
11
+ label: string;
12
+ }
13
+
14
+ export interface CustomAgentModelOptionLabels {
15
+ defaultModel: string;
16
+ builderFallback: string;
17
+ }
18
+
19
+ export type CustomAgentModelEngineLoadState =
20
+ | "idle"
21
+ | "loading"
22
+ | "available"
23
+ | "unavailable";
24
+
25
+ export function useCustomAgentModelEngine(
26
+ suppliedEngine: ModelEngineConfig | null | undefined,
27
+ enabled: boolean,
28
+ ): {
29
+ engine: ModelEngineConfig | null;
30
+ state: CustomAgentModelEngineLoadState;
31
+ } {
32
+ const [loaded, setLoaded] = useState<{
33
+ engine: ModelEngineConfig | null;
34
+ state: CustomAgentModelEngineLoadState;
35
+ }>({ engine: null, state: "idle" });
36
+
37
+ useEffect(() => {
38
+ if (suppliedEngine !== undefined || !enabled) return;
39
+ let cancelled = false;
40
+ setLoaded({ engine: null, state: "loading" });
41
+ void loadChatModelCatalog()
42
+ .then((catalog) => {
43
+ if (cancelled) return;
44
+ const engine =
45
+ catalog.state === "available" ? catalog.currentModelEngine : null;
46
+ setLoaded({
47
+ engine,
48
+ state: engine ? "available" : "unavailable",
49
+ });
50
+ })
51
+ .catch((error: unknown) => {
52
+ if (cancelled) return;
53
+ console.error(
54
+ "[agent-resources] model catalog could not be loaded",
55
+ error,
56
+ );
57
+ setLoaded({ engine: null, state: "unavailable" });
58
+ });
59
+ return () => {
60
+ cancelled = true;
61
+ };
62
+ }, [enabled, suppliedEngine]);
63
+
64
+ if (suppliedEngine !== undefined) {
65
+ return {
66
+ engine: suppliedEngine,
67
+ state: suppliedEngine ? "available" : "unavailable",
68
+ };
69
+ }
70
+ return loaded;
71
+ }
72
+
73
+ export function getCustomAgentModelOptions(
74
+ engine: ModelEngineConfig | null | undefined,
75
+ labels: CustomAgentModelOptionLabels,
76
+ ): CustomAgentModelOption[] {
77
+ const selectableModels = engine?.selectableModels ?? engine?.supportedModels;
78
+ const models =
79
+ engine?.name === "builder"
80
+ ? CURRENT_BUILDER_CLAUDE_MODEL_OPTIONS.filter((option) =>
81
+ selectableModels?.includes(option.value),
82
+ ).map((option) => option.value)
83
+ : (selectableModels ?? []);
84
+
85
+ return [
86
+ { value: "inherit", label: labels.defaultModel },
87
+ ...[...new Set(models)].map((value) => ({
88
+ value,
89
+ label: getModelOptionLabel(value),
90
+ })),
91
+ ];
92
+ }
@@ -4,20 +4,31 @@ import React, { act } from "react";
4
4
  import { createRoot, type Root } from "react-dom/client";
5
5
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6
6
 
7
- import { AppDefaultModelField } from "./SettingsPanel.js";
7
+ import { AppDefaultModelField, friendlyModelName } from "./SettingsPanel.js";
8
8
 
9
9
  const BUILDER_MODELS = [
10
10
  "auto",
11
+ "claude-haiku-5-5",
12
+ "claude-sonnet-5-5",
11
13
  "claude-opus-5-5",
12
- "claude-sonnet-5",
13
- "claude-haiku-4-5",
14
- "gpt-5-6-sol",
14
+ "gpt-6-1-sol",
15
15
  "gpt-5-6-terra",
16
- "gpt-5-6-luna",
16
+ "gpt-6-luna",
17
17
  "gemini-3-1-pro",
18
- "gemini-3-5-flash",
18
+ "gemini-3-8-flash",
19
+ "gemini-3-5-flash-lite",
20
+ "gemini-3-1-flash-lite",
19
21
  ];
20
22
 
23
+ describe("friendlyModelName", () => {
24
+ it("keeps custom model ids safe and formats dated Claude ids", () => {
25
+ expect(friendlyModelName("toString")).toBe("toString");
26
+ expect(friendlyModelName("claude-haiku-4-20251001")).toBe("Haiku 4");
27
+ expect(friendlyModelName("claude-fable-5-1")).toBe("Fable 5.1");
28
+ expect(friendlyModelName("x-ai/grok-build-0.1")).toBe("Grok Build 0.1");
29
+ });
30
+ });
31
+
21
32
  describe("AppDefaultModelField", () => {
22
33
  let container: HTMLDivElement;
23
34
  let root: Root;
@@ -42,7 +53,7 @@ describe("AppDefaultModelField", () => {
42
53
  <AppDefaultModelField
43
54
  engine="builder"
44
55
  models={BUILDER_MODELS}
45
- value="gpt-5-6-sol"
56
+ value="gpt-6-1-sol"
46
57
  onValueChange={vi.fn()}
47
58
  />,
48
59
  );
@@ -52,7 +63,7 @@ describe("AppDefaultModelField", () => {
52
63
  'button[role="combobox"][aria-label="Model"]',
53
64
  );
54
65
  expect(trigger).not.toBeNull();
55
- expect(trigger?.textContent).toContain("GPT-5.6 Sol");
66
+ expect(trigger?.textContent).toContain("GPT-6.1 Sol");
56
67
  expect(container.querySelector("input[list]")).toBeNull();
57
68
 
58
69
  act(() => {
@@ -71,14 +82,16 @@ describe("AppDefaultModelField", () => {
71
82
  );
72
83
  expect(options).toEqual([
73
84
  "auto",
85
+ "Haiku 5.5",
86
+ "Sonnet 5.5",
74
87
  "Opus 5.5",
75
- "Sonnet 5",
76
- "Haiku 4.5",
77
- "GPT-5.6 Sol",
88
+ "GPT-6.1 Sol",
78
89
  "GPT-5.6 Terra",
79
- "GPT-5.6 Luna",
90
+ "GPT-6 Luna",
80
91
  "Gemini 3.1 Pro",
81
- "Gemini 3.5 Flash",
92
+ "Gemini 3.8 Flash",
93
+ "Gemini 3.5 Flash Lite",
94
+ "Gemini 3.1 Flash Lite",
82
95
  ]);
83
96
  });
84
97
 
@@ -584,11 +584,43 @@ function ManualSetupCard({
584
584
  );
585
585
  }
586
586
 
587
+ const FRIENDLY_MODEL_NAMES: Record<string, string> = {
588
+ "z-ai/glm-5.2": "GLM 5.2",
589
+ "grok-code-fast": "Grok Code Fast",
590
+ "x-ai/grok-4.7": "Grok 4.7",
591
+ "x-ai/grok-build-0.1": "Grok Build 0.1",
592
+ "qwen3-coder": "Qwen3 Coder",
593
+ "qwen/qwen3-coder": "Qwen3 Coder",
594
+ "kimi-k2-5": "Kimi K2.5",
595
+ "moonshotai/kimi-k2.5": "Kimi K2.5",
596
+ "deepseek-v4-pro": "DeepSeek V4 Pro",
597
+ "deepseek-v4-1-flash": "DeepSeek V4.1 Flash",
598
+ "deepseek-v3-1": "DeepSeek v3.1",
599
+ "deepseek/deepseek-v4-pro": "DeepSeek V4 Pro",
600
+ "deepseek/deepseek-v4.1-flash": "DeepSeek V4.1 Flash",
601
+ "deepseek/deepseek-chat-v3.1": "DeepSeek v3.1",
602
+ "z-ai-glm-4-5": "Z-AI GLM 4.5",
603
+ "z-ai-glm-5-1": "Z-AI GLM 5.1",
604
+ "z-ai-glm-5-3-flash": "Z-AI GLM 5.3 Flash",
605
+ "z-ai/glm-4.5": "GLM 4.5",
606
+ "z-ai/glm-5.1": "GLM 5.1",
607
+ "z-ai/glm-5.3-flash": "GLM 5.3 Flash",
608
+ "anthropic/claude-haiku-5.5": "Claude Haiku 5.5",
609
+ "google/gemini-3.8-flash": "Gemini 3.8 Flash",
610
+ "google/gemini-3.5-flash-lite": "Gemini 3.5 Flash-Lite",
611
+ "google/gemini-3.1-flash-lite": "Gemini 3.1 Flash-Lite",
612
+ "google/gemini-3.1-pro-preview": "Gemini 3.1 Pro",
613
+ };
614
+
587
615
  export function friendlyModelName(model: string): string {
588
- if (model === "z-ai/glm-5.2") return "GLM 5.2";
616
+ const friendlyName = Object.hasOwn(FRIENDLY_MODEL_NAMES, model)
617
+ ? FRIENDLY_MODEL_NAMES[model]
618
+ : undefined;
619
+ if (friendlyName !== undefined) return friendlyName;
589
620
  const normalizedModel = model.replace(/^(?:anthropic|openai)\//, "");
590
- const claude = normalizedModel.match(
591
- /^claude-(opus|sonnet|haiku)-(\d+)(?:[-.](\d+))?(?:-\d{8,})?$/,
621
+ const undatedModel = normalizedModel.replace(/-\d{8,}$/, "");
622
+ const claude = undatedModel.match(
623
+ /^claude-(opus|sonnet|haiku|fable)-(\d+)(?:[-.](\d+))?$/,
592
624
  );
593
625
  if (claude) {
594
626
  const tier = claude[1][0].toUpperCase() + claude[1].slice(1);
@@ -1,5 +1,9 @@
1
1
  // @vitest-environment happy-dom
2
2
 
3
+ import {
4
+ setCustomKeyOnboardingAttempt,
5
+ trackCustomKeyOnboardingOutcome,
6
+ } from "@agent-native/core/client/onboarding/use-onboarding";
3
7
  import { SIGN_OUT_SEARCH_TERMS } from "@agent-native/core/client/sign-out";
4
8
  import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
5
9
  import React, { act } from "react";
@@ -78,6 +82,30 @@ function captureAnimationFrame() {
78
82
  }
79
83
 
80
84
  let activeQueryClient: QueryClient | null = null;
85
+ let restoreLocalStorage: (() => void) | null = null;
86
+
87
+ function installTestLocalStorage() {
88
+ const descriptor = Object.getOwnPropertyDescriptor(window, "localStorage");
89
+ const values = new Map<string, string>();
90
+ Object.defineProperty(window, "localStorage", {
91
+ configurable: true,
92
+ value: {
93
+ get length() {
94
+ return values.size;
95
+ },
96
+ clear: () => values.clear(),
97
+ getItem: (key: string) => values.get(key) ?? null,
98
+ key: (index: number) => [...values.keys()][index] ?? null,
99
+ removeItem: (key: string) => values.delete(key),
100
+ setItem: (key: string, value: string) => values.set(key, String(value)),
101
+ } satisfies Storage,
102
+ });
103
+ restoreLocalStorage = () => {
104
+ if (descriptor) Object.defineProperty(window, "localStorage", descriptor);
105
+ else Reflect.deleteProperty(window, "localStorage");
106
+ restoreLocalStorage = null;
107
+ };
108
+ }
81
109
 
82
110
  function createQueryClientWithLabs(labs: Record<string, unknown>) {
83
111
  const queryClient = new QueryClient({
@@ -104,6 +132,7 @@ describe("SettingsTabsPage", () => {
104
132
  act(() => root.unmount());
105
133
  activeQueryClient?.clear();
106
134
  activeQueryClient = null;
135
+ restoreLocalStorage?.();
107
136
  container.remove();
108
137
  document.body.innerHTML = "";
109
138
  vi.unstubAllGlobals();
@@ -256,6 +285,74 @@ describe("SettingsTabsPage", () => {
256
285
  expect(document.activeElement).toBe(teamTab);
257
286
  });
258
287
 
288
+ it("keeps an onboarding attempt through legacy settings tab navigation", async () => {
289
+ installTestLocalStorage();
290
+ vi.stubGlobal(
291
+ "fetch",
292
+ vi.fn(
293
+ async () =>
294
+ new Response(JSON.stringify({ error: "not authenticated" }), {
295
+ headers: { "Content-Type": "application/json" },
296
+ }),
297
+ ),
298
+ );
299
+ expect(await setCustomKeyOnboardingAttempt("legacy-settings-nav")).toBe(
300
+ "stored",
301
+ );
302
+
303
+ act(() => {
304
+ root.render(
305
+ <SettingsTabsPage
306
+ general={<div>General content</div>}
307
+ team={<div>Team members</div>}
308
+ redesign={false}
309
+ />,
310
+ );
311
+ });
312
+
313
+ const teamTab =
314
+ container.querySelector<HTMLButtonElement>("#settings-tab-team");
315
+ expect(teamTab).not.toBeNull();
316
+ act(() => teamTab!.click());
317
+
318
+ expect(trackCustomKeyOnboardingOutcome("credential_saved")).toBe("tracked");
319
+ });
320
+
321
+ it("records abandonment when a legacy settings route exits", async () => {
322
+ installTestLocalStorage();
323
+ vi.stubGlobal(
324
+ "fetch",
325
+ vi.fn(
326
+ async () =>
327
+ new Response(JSON.stringify({ error: "not authenticated" }), {
328
+ headers: { "Content-Type": "application/json" },
329
+ }),
330
+ ),
331
+ );
332
+ expect(await setCustomKeyOnboardingAttempt("legacy-settings-exit")).toBe(
333
+ "stored",
334
+ );
335
+
336
+ act(() => {
337
+ root.render(
338
+ <SettingsTabsPage
339
+ general={<div>General content</div>}
340
+ team={<div>Team members</div>}
341
+ redesign={false}
342
+ />,
343
+ );
344
+ });
345
+
346
+ const renderedRoot = root;
347
+ await act(async () => {
348
+ renderedRoot.unmount();
349
+ await Promise.resolve();
350
+ });
351
+ root = createRoot(container);
352
+
353
+ expect(trackCustomKeyOnboardingOutcome("credential_saved")).toBe("missing");
354
+ });
355
+
259
356
  it("opens general settings by default when the route has no hash", () => {
260
357
  act(() => {
261
358
  root.render(
@@ -5,6 +5,7 @@ import {
5
5
  import { useFeatureFlagState } from "@agent-native/core/client/feature-flags/use-feature-flag";
6
6
  import { useT } from "@agent-native/core/client/i18n";
7
7
  import type { LabStates } from "@agent-native/core/client/labs/use-lab";
8
+ import { useCustomKeyOnboardingAttemptLifecycle } from "@agent-native/core/client/onboarding/use-onboarding";
8
9
  import { useActionQuery } from "@agent-native/core/client/use-action";
9
10
  import { SETTINGS_REDESIGN_FLAG } from "@agent-native/core/feature-flags/registry";
10
11
  import { CHATGPT_SUBSCRIPTION_LAB } from "@agent-native/core/labs/core-labs";
@@ -1058,6 +1059,7 @@ function RedesignedSettingsTabsPage(props: SettingsTabsPageProps) {
1058
1059
  }
1059
1060
 
1060
1061
  export function SettingsTabsPage(props: SettingsTabsPageProps) {
1062
+ useCustomKeyOnboardingAttemptLifecycle();
1061
1063
  // No query client means no action surface to read the flag from, so the
1062
1064
  // flag fails closed exactly as it does for a signed-out viewer. It also
1063
1065
  // means the server's registered labs are unknown, so core labs stay hidden.