@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
@@ -5,6 +5,18 @@ import { createRoot, type Root } from "react-dom/client";
5
5
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6
6
 
7
7
  const sharedEditorProps = vi.hoisted(() => ({ current: null as any }));
8
+ const modelCatalogMocks = vi.hoisted(() => ({ load: vi.fn() }));
9
+ const resourceI18nMocks = vi.hoisted(() => ({
10
+ labels: {} as Record<string, string>,
11
+ }));
12
+
13
+ vi.mock("@agent-native/core/client/i18n", () => ({
14
+ useT: () => (key: string) => resourceI18nMocks.labels[key] ?? key,
15
+ }));
16
+
17
+ vi.mock("@agent-native/core/client/use-chat-models", () => ({
18
+ loadChatModelCatalog: modelCatalogMocks.load,
19
+ }));
8
20
 
9
21
  vi.mock("@agent-native/toolkit/editor/SharedRichEditor", async () => {
10
22
  const React = await import("react");
@@ -20,10 +32,23 @@ vi.mock("@agent-native/toolkit/editor/SharedRichEditor", async () => {
20
32
  };
21
33
  });
22
34
 
35
+ import { BUILDER_MODEL_CONFIG } from "@agent-native/core/agent/model-config";
23
36
  import type { Resource } from "@agent-native/core/client/resources/use-resources";
24
37
 
25
38
  import { ResourceEditor } from "./ResourceEditor.js";
26
39
 
40
+ const builderModelEngine = {
41
+ name: "builder",
42
+ label: "Builder.io Gateway",
43
+ ...BUILDER_MODEL_CONFIG,
44
+ };
45
+ const anthropicModelEngine = {
46
+ name: "anthropic",
47
+ label: "Anthropic",
48
+ defaultModel: "claude-sonnet-5-5",
49
+ supportedModels: ["claude-sonnet-5-5", "claude-haiku-5-5", "claude-fable-5"],
50
+ };
51
+
27
52
  const resource: Resource = {
28
53
  id: "resource-1",
29
54
  path: "skills/release/SKILL.md",
@@ -48,6 +73,16 @@ describe("ResourceEditor markdown editing", () => {
48
73
  beforeEach(() => {
49
74
  vi.useFakeTimers();
50
75
  vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
76
+ resourceI18nMocks.labels = {
77
+ "agentResources.defaultModel": "Default model",
78
+ "agentResources.builderModelFallback": "Builder fallback",
79
+ "agentResources.modelOptionsUnavailable": "Model options unavailable",
80
+ };
81
+ modelCatalogMocks.load.mockReset();
82
+ modelCatalogMocks.load.mockResolvedValue({
83
+ state: "unavailable",
84
+ enginesUnavailable: true,
85
+ });
51
86
  container = document.createElement("div");
52
87
  document.body.appendChild(container);
53
88
  root = createRoot(container);
@@ -96,7 +131,7 @@ describe("ResourceEditor markdown editing", () => {
96
131
  );
97
132
  });
98
133
 
99
- it("offers the gateway-compatible Sonnet model for custom agents", () => {
134
+ it("offers the current Claude models for custom agents", () => {
100
135
  const onSave = vi.fn();
101
136
  act(() => {
102
137
  root.render(
@@ -105,22 +140,222 @@ describe("ResourceEditor markdown editing", () => {
105
140
  ...resource,
106
141
  path: "agents/researcher.md",
107
142
  content:
108
- "---\nname: Researcher\nmodel: claude-sonnet-5\n---\n# Research\n",
143
+ "---\nname: Researcher\nmodel: claude-sonnet-5-5\n---\n# Research\n",
109
144
  }}
110
145
  onSave={onSave}
111
146
  view="visual"
147
+ modelEngine={builderModelEngine}
148
+ />,
149
+ );
150
+ });
151
+
152
+ const modelPicker = container.querySelector("select")!;
153
+ expect(modelPicker.value).toBe("claude-sonnet-5-5");
154
+ expect(
155
+ Array.from(modelPicker.options).some(
156
+ (option) =>
157
+ option.value === "claude-sonnet-5-5" &&
158
+ option.textContent === "Claude Sonnet 5.5",
159
+ ),
160
+ ).toBe(true);
161
+ expect(
162
+ Array.from(modelPicker.options).some(
163
+ (option) => option.value === "claude-haiku-5-5",
164
+ ),
165
+ ).toBe(true);
166
+ });
167
+
168
+ it("preserves a saved legacy Claude model in the picker", () => {
169
+ act(() => {
170
+ root.render(
171
+ <ResourceEditor
172
+ resource={{
173
+ ...resource,
174
+ path: "agents/researcher.md",
175
+ content:
176
+ "---\nname: Researcher\nmodel: claude-haiku-4-5-20251001\n---\n# Research\n",
177
+ }}
178
+ onSave={vi.fn()}
179
+ view="visual"
180
+ modelEngine={builderModelEngine}
112
181
  />,
113
182
  );
114
183
  });
115
184
 
116
185
  const modelPicker = container.querySelector("select")!;
117
- expect(modelPicker.value).toBe("claude-sonnet-5");
186
+ expect(modelPicker.value).toBe("claude-haiku-4-5-20251001");
118
187
  expect(
119
188
  Array.from(modelPicker.options).some(
120
189
  (option) =>
121
- option.value === "claude-sonnet-5" &&
122
- option.textContent === "Claude Sonnet 5",
190
+ option.value === "claude-haiku-4-5-20251001" &&
191
+ option.textContent ===
192
+ "Claude Haiku 4.5 → Claude Haiku 5.5 · Builder fallback",
123
193
  ),
124
194
  ).toBe(true);
195
+ expect(
196
+ Array.from(modelPicker.options).some(
197
+ (option) => option.value === "claude-haiku-5-5",
198
+ ),
199
+ ).toBe(true);
200
+ });
201
+
202
+ it("shows the saved model label outside its curated options", () => {
203
+ resourceI18nMocks.labels["agentResources.builderModelFallback"] =
204
+ "Modelo alternativo";
205
+ act(() => {
206
+ root.render(
207
+ <ResourceEditor
208
+ resource={{
209
+ ...resource,
210
+ path: "agents/researcher.md",
211
+ content:
212
+ "---\nname: Researcher\nmodel: claude-fable-5\n---\n# Research\n",
213
+ }}
214
+ onSave={vi.fn()}
215
+ view="visual"
216
+ modelEngine={builderModelEngine}
217
+ />,
218
+ );
219
+ });
220
+
221
+ const modelPicker = container.querySelector("select")!;
222
+ expect(modelPicker.value).toBe("claude-fable-5");
223
+ expect(
224
+ Array.from(modelPicker.options).find(
225
+ (option) => option.value === "claude-fable-5",
226
+ )?.textContent,
227
+ ).toBe("Claude Fable 5 → GPT-6 Luna · Modelo alternativo");
228
+ });
229
+
230
+ it("keeps an unchecked but runtime-supported model from showing a false fallback", () => {
231
+ act(() => {
232
+ root.render(
233
+ <ResourceEditor
234
+ resource={{
235
+ ...resource,
236
+ path: "agents/researcher.md",
237
+ content:
238
+ "---\nname: Researcher\nmodel: claude-fable-5\n---\n# Research\n",
239
+ }}
240
+ onSave={vi.fn()}
241
+ view="visual"
242
+ modelEngine={{
243
+ name: "anthropic",
244
+ label: "Anthropic",
245
+ defaultModel: "claude-sonnet-5-5",
246
+ supportedModels: [
247
+ "claude-haiku-5-5",
248
+ "claude-sonnet-5-5",
249
+ "claude-fable-5",
250
+ ],
251
+ selectableModels: ["claude-haiku-5-5"],
252
+ }}
253
+ />,
254
+ );
255
+ });
256
+
257
+ const modelOption = Array.from(
258
+ container.querySelector("select")!.options,
259
+ ).find((option) => option.value === "claude-fable-5");
260
+ expect(modelOption?.textContent).toBe("Claude Fable 5");
261
+ });
262
+
263
+ it("loads model choices when a standalone custom-agent editor opens", async () => {
264
+ modelCatalogMocks.load.mockResolvedValue({
265
+ state: "available",
266
+ groups: [],
267
+ modelEngines: { anthropic: anthropicModelEngine },
268
+ currentModelEngine: anthropicModelEngine,
269
+ defaultModel: "claude-sonnet-5-5",
270
+ loadLiveGroups: async () => null,
271
+ });
272
+ await act(async () => {
273
+ root.render(
274
+ <ResourceEditor
275
+ resource={{
276
+ ...resource,
277
+ path: "agents/researcher.md",
278
+ content: "---\nname: Researcher\nmodel: inherit\n---\n# Research\n",
279
+ }}
280
+ onSave={vi.fn()}
281
+ view="visual"
282
+ />,
283
+ );
284
+ await Promise.resolve();
285
+ await Promise.resolve();
286
+ });
287
+
288
+ expect(modelCatalogMocks.load).toHaveBeenCalledOnce();
289
+ expect(
290
+ Array.from(container.querySelector("select")!.options).map(
291
+ (option) => option.value,
292
+ ),
293
+ ).toContain("claude-haiku-5-5");
294
+ });
295
+
296
+ it("offers Anthropic Fable and labels it without a Builder fallback", () => {
297
+ act(() => {
298
+ root.render(
299
+ <ResourceEditor
300
+ resource={{
301
+ ...resource,
302
+ path: "agents/researcher.md",
303
+ content:
304
+ "---\nname: Researcher\nmodel: claude-fable-5\n---\n# Research\n",
305
+ }}
306
+ onSave={vi.fn()}
307
+ view="visual"
308
+ modelEngine={anthropicModelEngine}
309
+ />,
310
+ );
311
+ });
312
+
313
+ const modelPicker = container.querySelector("select")!;
314
+ expect(
315
+ Array.from(modelPicker.options).some(
316
+ (option) =>
317
+ option.value === "claude-fable-5" &&
318
+ option.textContent === "Claude Fable 5",
319
+ ),
320
+ ).toBe(true);
321
+ });
322
+
323
+ it("uses friendly names for provider-prefixed custom-agent model choices", () => {
324
+ const engine = {
325
+ name: "ai-sdk:openrouter",
326
+ label: "OpenRouter",
327
+ defaultModel: "openai/gpt-5.5",
328
+ supportedModels: [
329
+ "openai/gpt-5.5",
330
+ "google/gemini-3.1-pro-preview",
331
+ "deepseek/deepseek-v4-pro",
332
+ "qwen/qwen3-coder",
333
+ ],
334
+ };
335
+
336
+ act(() => {
337
+ root.render(
338
+ <ResourceEditor
339
+ resource={{
340
+ ...resource,
341
+ path: "agents/researcher.md",
342
+ content:
343
+ "---\nname: Researcher\nmodel: openai/gpt-5.5\n---\n# Research\n",
344
+ }}
345
+ onSave={vi.fn()}
346
+ view="visual"
347
+ modelEngine={engine}
348
+ />,
349
+ );
350
+ });
351
+
352
+ const options = Array.from(container.querySelector("select")!.options);
353
+ expect(options.map((option) => option.textContent)).toEqual([
354
+ "Default model",
355
+ "GPT-5.5",
356
+ "Gemini 3.1 Pro",
357
+ "DeepSeek V4 Pro",
358
+ "Qwen3 Coder",
359
+ ]);
125
360
  });
126
361
  });
@@ -1,8 +1,9 @@
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 { agentNativePath } from "@agent-native/core/client/api-path";
6
+ import { useT } from "@agent-native/core/client/i18n";
6
7
  import type { Resource } from "@agent-native/core/client/resources/use-resources";
7
8
  import {
8
9
  type ParsedFrontmatter,
@@ -24,6 +25,11 @@ import React, {
24
25
  useMemo,
25
26
  } from "react";
26
27
 
28
+ import {
29
+ getCustomAgentModelOptions,
30
+ useCustomAgentModelEngine,
31
+ } from "./custom-agent-model-options.js";
32
+
27
33
  export interface ResourceEditorProps {
28
34
  resource: Resource;
29
35
  onSave: (content: string) => void;
@@ -31,6 +37,8 @@ export interface ResourceEditorProps {
31
37
  onViewChange?: (v: "visual" | "code") => void;
32
38
  hideToolbar?: boolean;
33
39
  readOnly?: boolean;
40
+ modelEngine?: ModelEngineConfig | null;
41
+ builderFallbackLabel?: string;
34
42
  }
35
43
 
36
44
  const CONTROL_STYLE = { fontSize: 12, lineHeight: 1 } as const;
@@ -67,11 +75,15 @@ function FrontmatterBar({
67
75
  frontmatter,
68
76
  onChange,
69
77
  readOnly,
78
+ modelEngine,
79
+ builderFallbackLabel,
70
80
  }: {
71
81
  resourcePath: string;
72
82
  frontmatter: ParsedFrontmatter;
73
83
  onChange: (updated: ParsedFrontmatter) => void;
74
84
  readOnly?: boolean;
85
+ modelEngine?: ModelEngineConfig | null;
86
+ builderFallbackLabel?: string;
75
87
  }) {
76
88
  const getField = (key: string) => getFrontmatterValue(frontmatter, key) ?? "";
77
89
 
@@ -96,6 +108,18 @@ function FrontmatterBar({
96
108
  const tools = getField("tools") || "inherit";
97
109
  const isCustomAgent = isCustomAgentPath(resourcePath);
98
110
  const isSkill = isSkillPath(resourcePath);
111
+ const t = useT();
112
+ const modelEngineLoad = useCustomAgentModelEngine(
113
+ modelEngine,
114
+ !readOnly && isCustomAgent,
115
+ );
116
+ const effectiveModelEngine = modelEngineLoad.engine;
117
+ const effectiveBuilderFallbackLabel =
118
+ builderFallbackLabel ?? t("agentResources.builderModelFallback");
119
+ const modelOptions = getCustomAgentModelOptions(effectiveModelEngine, {
120
+ defaultModel: t("agentResources.defaultModel"),
121
+ builderFallback: effectiveBuilderFallbackLabel,
122
+ });
99
123
 
100
124
  return (
101
125
  <div
@@ -177,13 +201,26 @@ function FrontmatterBar({
177
201
  padding: "2px 6px",
178
202
  }}
179
203
  >
180
- <option value="inherit">Default model</option>
181
- <option value="claude-fable-5">Claude Fable 5</option>
182
- <option value="claude-opus-5-5">Claude Opus 5.5</option>
183
- <option value={BUILDER_CLAUDE_SONNET_MODEL_ID}>
184
- {BUILDER_CLAUDE_SONNET_MODEL_LABEL}
185
- </option>
186
- <option value="claude-haiku-4-5-20251001">Claude Haiku 4.5</option>
204
+ {modelOptions.map((option) => (
205
+ <option key={option.value} value={option.value}>
206
+ {option.label}
207
+ </option>
208
+ ))}
209
+ {modelEngineLoad.state === "unavailable" && (
210
+ <option value="__model-options-unavailable" disabled>
211
+ {t("agentResources.modelOptionsUnavailable")}
212
+ </option>
213
+ )}
214
+ {model !== "inherit" &&
215
+ !modelOptions.some((option) => option.value === model) && (
216
+ <option value={model}>
217
+ {getModelOptionLabel(
218
+ model,
219
+ effectiveModelEngine ?? undefined,
220
+ effectiveBuilderFallbackLabel,
221
+ )}
222
+ </option>
223
+ )}
187
224
  </select>
188
225
  ) : null}
189
226
  </div>
@@ -367,11 +404,15 @@ function VisualMarkdownEditor({
367
404
  onChange,
368
405
  resourcePath,
369
406
  readOnly,
407
+ modelEngine,
408
+ builderFallbackLabel,
370
409
  }: {
371
410
  content: string;
372
411
  onChange: (md: string) => void;
373
412
  resourcePath: string;
374
413
  readOnly?: boolean;
414
+ modelEngine?: ModelEngineConfig | null;
415
+ builderFallbackLabel?: string;
375
416
  }) {
376
417
  const parsed = useMemo(() => parseFrontmatter(content), [content]);
377
418
  const frontmatterRef = useRef(parsed);
@@ -393,6 +434,8 @@ function VisualMarkdownEditor({
393
434
  resourcePath={resourcePath}
394
435
  frontmatter={parsed}
395
436
  readOnly={readOnly}
437
+ modelEngine={modelEngine}
438
+ builderFallbackLabel={builderFallbackLabel}
396
439
  onChange={(updated) => {
397
440
  if (readOnly) return;
398
441
  frontmatterRef.current = updated;
@@ -569,6 +612,8 @@ export function ResourceEditor({
569
612
  onViewChange,
570
613
  hideToolbar,
571
614
  readOnly,
615
+ modelEngine,
616
+ builderFallbackLabel,
572
617
  }: ResourceEditorProps) {
573
618
  const [content, setContent] = useState(resource.content);
574
619
  const [internalView, setInternalView] = useState<"visual" | "code">(
@@ -687,6 +732,8 @@ export function ResourceEditor({
687
732
  onChange={handleChange}
688
733
  resourcePath={resource.path}
689
734
  readOnly={readOnly}
735
+ modelEngine={modelEngine}
736
+ builderFallbackLabel={builderFallbackLabel}
690
737
  />
691
738
  </div>
692
739
  ) : (
@@ -24,6 +24,11 @@ const storageMocks = vi.hoisted(() => ({
24
24
  dismiss: null as (() => void) | null,
25
25
  setup: null as (() => void) | null,
26
26
  }));
27
+ const modelCatalogMocks = vi.hoisted(() => ({ load: vi.fn() }));
28
+
29
+ vi.mock("@agent-native/core/client/use-chat-models", () => ({
30
+ loadChatModelCatalog: modelCatalogMocks.load,
31
+ }));
27
32
 
28
33
  vi.mock("@agent-native/core/client/uploads/use-file-upload-status", () => ({
29
34
  useFileUploadStatus: () => ({
@@ -92,6 +97,7 @@ vi.mock("../chat/FileStorageSetupPopover.js", () => ({
92
97
  },
93
98
  }));
94
99
 
100
+ import type { ModelEngineConfig } from "@agent-native/core/agent/model-version";
95
101
  import type { TreeNode } from "@agent-native/core/client/resources/use-resources";
96
102
 
97
103
  import { isResourceRowReadOnly } from "./ResourceSettingsGroups.js";
@@ -462,6 +468,11 @@ describe("ResourcesPanel storage retries", () => {
462
468
  storageMocks.retry = null;
463
469
  storageMocks.dismiss = null;
464
470
  storageMocks.setup = null;
471
+ modelCatalogMocks.load.mockReset();
472
+ modelCatalogMocks.load.mockResolvedValue({
473
+ state: "unavailable",
474
+ enginesUnavailable: true,
475
+ });
465
476
  container = document.createElement("div");
466
477
  document.body.appendChild(container);
467
478
  root = createRoot(container);
@@ -476,6 +487,7 @@ describe("ResourcesPanel storage retries", () => {
476
487
 
477
488
  function renderPanel(
478
489
  resourceFilter: "instructions" | "agents" = "instructions",
490
+ modelEngine?: ModelEngineConfig | null,
479
491
  ) {
480
492
  act(() =>
481
493
  root.render(
@@ -484,6 +496,7 @@ describe("ResourcesPanel storage retries", () => {
484
496
  showOnlyRequestedScope: true,
485
497
  resourceFilter,
486
498
  showMcpServers: false,
499
+ modelEngine,
487
500
  }),
488
501
  ),
489
502
  );
@@ -761,8 +774,17 @@ describe("ResourcesPanel storage retries", () => {
761
774
  ).toBe("current.png");
762
775
  });
763
776
 
764
- it("offers the gateway-compatible Sonnet model when creating custom agents", () => {
765
- renderPanel("agents");
777
+ it("offers the current Claude models when creating custom agents", () => {
778
+ renderPanel("agents", {
779
+ name: "anthropic",
780
+ label: "Anthropic",
781
+ defaultModel: "claude-sonnet-5-5",
782
+ supportedModels: [
783
+ "claude-haiku-5-5",
784
+ "claude-sonnet-5-5",
785
+ "claude-fable-5",
786
+ ],
787
+ });
766
788
  const addAgent = Array.from(document.querySelectorAll("button")).find(
767
789
  (button) => button.textContent?.includes("Add agent"),
768
790
  );
@@ -784,10 +806,70 @@ describe("ResourcesPanel storage retries", () => {
784
806
  const modelPicker = document.querySelector("select")!;
785
807
  expect(Array.from(modelPicker.options)).toContainEqual(
786
808
  expect.objectContaining({
787
- value: "claude-sonnet-5",
788
- textContent: "Claude Sonnet 5",
809
+ value: "claude-sonnet-5-5",
810
+ textContent: "Claude Sonnet 5.5",
789
811
  }),
790
812
  );
813
+ expect(Array.from(modelPicker.options)).toContainEqual(
814
+ expect.objectContaining({
815
+ value: "claude-haiku-5-5",
816
+ textContent: "Claude Haiku 5.5",
817
+ }),
818
+ );
819
+ expect(Array.from(modelPicker.options)).toContainEqual(
820
+ expect.objectContaining({
821
+ value: "claude-fable-5",
822
+ textContent: "Claude Fable 5",
823
+ }),
824
+ );
825
+ });
826
+
827
+ it("loads the model catalog only after opening the custom-agent form", async () => {
828
+ modelCatalogMocks.load.mockResolvedValue({
829
+ state: "available",
830
+ groups: [],
831
+ modelEngines: {},
832
+ currentModelEngine: {
833
+ name: "anthropic",
834
+ label: "Anthropic",
835
+ defaultModel: "claude-sonnet-5-5",
836
+ supportedModels: ["claude-haiku-5-5", "claude-sonnet-5-5"],
837
+ selectableModels: ["claude-haiku-5-5", "claude-sonnet-5-5"],
838
+ },
839
+ defaultModel: "claude-sonnet-5-5",
840
+ loadLiveGroups: async () => null,
841
+ });
842
+ renderPanel("agents");
843
+ expect(modelCatalogMocks.load).not.toHaveBeenCalled();
844
+
845
+ const addAgent = Array.from(document.querySelectorAll("button")).find(
846
+ (button) => button.textContent?.includes("Add agent"),
847
+ );
848
+ expect(addAgent).toBeDefined();
849
+ act(() => addAgent!.click());
850
+ const createAgent = Array.from(document.querySelectorAll("button")).find(
851
+ (button) => button.textContent?.includes("Create Custom Agent"),
852
+ );
853
+ expect(createAgent).toBeDefined();
854
+ act(() => createAgent!.click());
855
+ expect(modelCatalogMocks.load).not.toHaveBeenCalled();
856
+
857
+ const fillForm = Array.from(document.querySelectorAll("button")).find(
858
+ (button) => button.textContent?.includes("Fill Form"),
859
+ );
860
+ expect(fillForm).toBeDefined();
861
+ await act(async () => {
862
+ fillForm!.click();
863
+ await Promise.resolve();
864
+ await Promise.resolve();
865
+ });
866
+
867
+ expect(modelCatalogMocks.load).toHaveBeenCalledOnce();
868
+ expect(
869
+ Array.from(document.querySelector("select")!.options).map(
870
+ (option) => option.value,
871
+ ),
872
+ ).toContain("claude-haiku-5-5");
791
873
  });
792
874
  });
793
875