@agent-native/toolkit 0.202.0 → 0.202.1-nightly-20261007011022

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 (35) hide show
  1. package/agent-native.eject.json +4 -0
  2. package/dist/app/settings/BuilderConnectPopover.d.ts +4 -2
  3. package/dist/app/settings/BuilderConnectPopover.d.ts.map +1 -1
  4. package/dist/app/settings/BuilderConnectPopover.js +11 -13
  5. package/dist/app/settings/BuilderConnectPopover.js.map +1 -1
  6. package/dist/app/settings/model/ModelSettingsPage.d.ts.map +1 -1
  7. package/dist/app/settings/model/ModelSettingsPage.js +27 -11
  8. package/dist/app/settings/model/ModelSettingsPage.js.map +1 -1
  9. package/dist/app/settings/model/model-page-state.d.ts +2 -0
  10. package/dist/app/settings/model/model-page-state.d.ts.map +1 -1
  11. package/dist/app/settings/model/model-page-state.js +9 -1
  12. package/dist/app/settings/model/model-page-state.js.map +1 -1
  13. package/dist/app/settings/shell/app-identity.d.ts +1 -1
  14. package/dist/app/settings/shell/app-identity.d.ts.map +1 -1
  15. package/dist/app/settings/shell/registry.d.ts +2 -3
  16. package/dist/app/settings/shell/registry.d.ts.map +1 -1
  17. package/dist/app/settings/shell/registry.js.map +1 -1
  18. package/dist/app/settings/shell/types.d.ts +5 -0
  19. package/dist/app/settings/shell/types.d.ts.map +1 -0
  20. package/dist/app/settings/shell/types.js +2 -0
  21. package/dist/app/settings/shell/types.js.map +1 -0
  22. package/dist/composer/TiptapComposer.js +2 -2
  23. package/dist/composer/TiptapComposer.js.map +1 -1
  24. package/package.json +4 -4
  25. package/src/app/settings/BuilderConnectPopover.spec.tsx +37 -35
  26. package/src/app/settings/BuilderConnectPopover.tsx +16 -13
  27. package/src/app/settings/model/ModelSettingsPage.spec.tsx +244 -3
  28. package/src/app/settings/model/ModelSettingsPage.tsx +32 -12
  29. package/src/app/settings/model/ProviderDialog.spec.tsx +1 -0
  30. package/src/app/settings/model/model-page-state.spec.ts +3 -3
  31. package/src/app/settings/model/model-page-state.ts +21 -2
  32. package/src/app/settings/shell/app-identity.ts +1 -1
  33. package/src/app/settings/shell/registry.ts +2 -1
  34. package/src/app/settings/shell/types.ts +3 -0
  35. package/src/composer/TiptapComposer.tsx +2 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/toolkit",
3
- "version": "0.202.0",
3
+ "version": "0.202.1-nightly-20261007011022",
4
4
  "description": "Reusable app-building UI and helpers for Agent-Native apps.",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -673,7 +673,7 @@
673
673
  "y-protocols": "^1.0.7",
674
674
  "yjs": "^13.6.33",
675
675
  "zod": "^4.3.6",
676
- "@agent-native/agentkit": "^0.202.0"
676
+ "@agent-native/agentkit": "^0.202.1-nightly-20261007011022"
677
677
  },
678
678
  "devDependencies": {
679
679
  "@excalidraw/excalidraw": "0.18.1",
@@ -697,10 +697,10 @@
697
697
  "typescript-7": "npm:typescript@^7.0.2",
698
698
  "vite": "8.1.0",
699
699
  "vitest": "^4.1.5",
700
- "@agent-native/core": "^0.202.0"
700
+ "@agent-native/core": "^0.202.1-nightly-20261007011022"
701
701
  },
702
702
  "peerDependencies": {
703
- "@agent-native/core": ">=0.196.0",
703
+ "@agent-native/core": "0.202.1-nightly-20261007011022",
704
704
  "@excalidraw/excalidraw": ">=0.18",
705
705
  "@excalidraw/mermaid-to-excalidraw": ">=2",
706
706
  "@tanstack/react-query": "^5.99.2",
@@ -1,5 +1,6 @@
1
1
  // @vitest-environment happy-dom
2
2
 
3
+ import { getOnboardingAppProfileForId } from "@agent-native/core/onboarding/app-profile-data";
3
4
  import { WORKSPACE_SERVICES } from "@agent-native/core/onboarding/workspace-services";
4
5
  import { TooltipProvider } from "@agent-native/toolkit/ui/tooltip";
5
6
  import React, { act } from "react";
@@ -15,48 +16,21 @@ vi.mock("@agent-native/core/client/i18n", () => ({
15
16
  ),
16
17
  }));
17
18
 
18
- vi.mock("@agent-native/core/client/onboarding/use-onboarding", () => ({
19
- useOnboarding: () => ({
20
- loading: false,
21
- error: null,
22
- profile: {
23
- capabilities: [
24
- {
25
- id: "llm",
26
- label: "AI model",
27
- required: true,
28
- builderIncluded: true,
29
- service: "model",
30
- keySummary: "Connect your own AI model",
31
- why: "The agent uses a language model.",
32
- },
33
- {
34
- id: "design-system-intelligence",
35
- label: "Design system intelligence",
36
- required: false,
37
- builderIncluded: true,
38
- service: "design-system-intelligence",
39
- },
40
- {
41
- id: "background-agents",
42
- label: "Background agents",
43
- required: false,
44
- builderIncluded: true,
45
- service: "background-agents",
46
- },
47
- ],
48
- },
49
- }),
50
- }));
51
-
52
19
  import { BuilderConnectPopover } from "./BuilderConnectPopover.js";
53
20
  import { getBuilderIncludedBenefitCapabilities } from "./BuilderIncludedBenefitsDisclosure.js";
54
21
  import { DeferredBuilderConnectPopover } from "./deferred-builder-connect-popover.js";
55
22
 
23
+ const appIdentity = vi.hoisted(() => ({ templateId: null as string | null }));
24
+
25
+ vi.mock("./shell/app-identity.js", () => ({
26
+ currentTemplateId: () => appIdentity.templateId,
27
+ }));
28
+
56
29
  let container: HTMLDivElement;
57
30
  let root: Root;
58
31
 
59
32
  beforeEach(() => {
33
+ appIdentity.templateId = null;
60
34
  (
61
35
  globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }
62
36
  ).IS_REACT_ACT_ENVIRONMENT = true;
@@ -69,6 +43,7 @@ afterEach(() => {
69
43
  act(() => root.unmount());
70
44
  container.remove();
71
45
  vi.clearAllMocks();
46
+ vi.unstubAllGlobals();
72
47
  });
73
48
 
74
49
  it("shows all eight included services when an app profile omits design intelligence", () => {
@@ -424,6 +399,7 @@ describe("BuilderConnectPopover", () => {
424
399
  const props = {
425
400
  flow,
426
401
  onConnect: vi.fn(),
402
+ appId: "calendar",
427
403
  contentTestId: "consent",
428
404
  primaryTestId: "create",
429
405
  secondaryTestId: "sign-in",
@@ -464,11 +440,37 @@ describe("BuilderConnectPopover", () => {
464
440
  ).toEqual([
465
441
  "Included free",
466
442
  "60 monthly Agent Credits",
467
- "+ 2 more services",
443
+ "+ 8 more services",
468
444
  ]);
469
445
  click(servicesToggle!);
470
446
  expect(servicesToggle?.getAttribute("aria-expanded")).toBe("true");
471
447
  });
448
+
449
+ it("prefers the assigned workspace profile over the template profile", () => {
450
+ appIdentity.templateId = "calendar";
451
+ vi.stubGlobal("__AGENT_NATIVE_APP_ID__", "assets");
452
+ const flow = {
453
+ connecting: false,
454
+ start: vi.fn(),
455
+ statusResolved: true,
456
+ agentNativeProvisioningEnabled: true,
457
+ };
458
+
459
+ render(React.createElement(BuilderConnectPopover, { flow }, trigger()));
460
+ click(connectButton());
461
+
462
+ const expectedMoreCount = getBuilderIncludedBenefitCapabilities(
463
+ getOnboardingAppProfileForId("assets").capabilities,
464
+ ).filter(
465
+ (capability) => capability.id !== "llm" && capability.service !== "model",
466
+ ).length;
467
+ const servicesToggle = document.querySelector<HTMLButtonElement>(
468
+ "[data-testid='builder-included-services'] button[aria-expanded]",
469
+ );
470
+ expect(servicesToggle?.textContent?.replace(/\s+/g, " ").trim()).toContain(
471
+ `+ ${expectedMoreCount} more services`,
472
+ );
473
+ });
472
474
  });
473
475
 
474
476
  it("shows a cancel action while the Builder connection is waiting", () => {
@@ -1,5 +1,6 @@
1
+ import { injectedAgentNativeAppId } from "@agent-native/core/client/app-config";
1
2
  import { useT } from "@agent-native/core/client/i18n";
2
- import { useOnboarding } from "@agent-native/core/client/onboarding/use-onboarding";
3
+ import { getOnboardingAppProfileForId } from "@agent-native/core/onboarding/app-profile-data";
3
4
  import { Button } from "@agent-native/toolkit/ui/button";
4
5
  import {
5
6
  Popover,
@@ -13,6 +14,7 @@ import {
13
14
  BuilderIncludedBenefitsDisclosure,
14
15
  getBuilderIncludedBenefitCapabilities,
15
16
  } from "./BuilderIncludedBenefitsDisclosure.js";
17
+ import { currentTemplateId } from "./shell/app-identity.js";
16
18
  import type { BuilderConnectFlow } from "./useBuilderStatus.js";
17
19
 
18
20
  type BuilderConnectTrigger = React.ReactElement<{
@@ -39,6 +41,7 @@ export interface BuilderConnectPopoverProps {
39
41
  onConnect?: (provisionAccount: boolean) => void;
40
42
  onTriggerClick?: React.MouseEventHandler<HTMLElement>;
41
43
  openOnMount?: boolean;
44
+ appId?: string;
42
45
  contentTestId?: string;
43
46
  primaryTestId?: string;
44
47
  secondaryTestId?: string;
@@ -49,6 +52,7 @@ export interface BuilderConnectChoicePanelProps {
49
52
  canProvisionAccount: boolean;
50
53
  onCreateAndActivate: () => void;
51
54
  onExistingAccount: () => void;
55
+ appId?: string;
52
56
  contentTestId?: string;
53
57
  primaryTestId?: string;
54
58
  secondaryTestId?: string;
@@ -59,6 +63,7 @@ export function BuilderConnectChoicePanel({
59
63
  canProvisionAccount,
60
64
  onCreateAndActivate,
61
65
  onExistingAccount,
66
+ appId,
62
67
  contentTestId,
63
68
  primaryTestId,
64
69
  secondaryTestId,
@@ -126,7 +131,7 @@ export function BuilderConnectChoicePanel({
126
131
  ) : null}
127
132
  </div>
128
133
  ) : null}
129
- <BuilderConnectIncludedServices />
134
+ <BuilderConnectIncludedServices appId={appId} />
130
135
  <div className="flex flex-col gap-2">
131
136
  <Button
132
137
  type="button"
@@ -197,6 +202,7 @@ export function BuilderConnectPopover({
197
202
  onConnect,
198
203
  onTriggerClick,
199
204
  openOnMount = false,
205
+ appId,
200
206
  contentTestId,
201
207
  primaryTestId,
202
208
  secondaryTestId,
@@ -272,6 +278,7 @@ export function BuilderConnectPopover({
272
278
  }
273
279
  onCreateAndActivate={() => start(true)}
274
280
  onExistingAccount={() => start(false)}
281
+ appId={appId}
275
282
  contentTestId={contentTestId}
276
283
  primaryTestId={primaryTestId}
277
284
  secondaryTestId={secondaryTestId}
@@ -290,12 +297,14 @@ export function BuilderConnectPopover({
290
297
  );
291
298
  }
292
299
 
293
- function BuilderConnectIncludedServices() {
300
+ function BuilderConnectIncludedServices({ appId }: { appId?: string }) {
294
301
  const t = useT();
295
- const { profile, loading, error } = useOnboarding();
296
- const capabilities = profile
297
- ? getBuilderIncludedBenefitCapabilities(profile.capabilities)
298
- : [];
302
+ const profile = getOnboardingAppProfileForId(
303
+ appId ?? injectedAgentNativeAppId() ?? currentTemplateId() ?? undefined,
304
+ );
305
+ const capabilities = getBuilderIncludedBenefitCapabilities(
306
+ profile.capabilities,
307
+ );
299
308
 
300
309
  return (
301
310
  <BuilderIncludedBenefitsDisclosure
@@ -306,13 +315,7 @@ function BuilderConnectIncludedServices() {
306
315
  creditsLabel={t("agentChat.onboarding.builderMonthlyCredits", {
307
316
  defaultValue: "60 monthly Agent Credits",
308
317
  })}
309
- loading={loading}
310
318
  loadingLabel={t("agentChat.common.loading")}
311
- error={
312
- error || (!loading && !profile)
313
- ? t("agentChat.common.chunkLoadFailed")
314
- : null
315
- }
316
319
  testId="builder-included-services"
317
320
  />
318
321
  );
@@ -179,6 +179,7 @@ function listing(
179
179
  providers: PROVIDERS.map((provider) => ({
180
180
  provider,
181
181
  label: LABELS[provider],
182
+ deploymentConfigured: false,
182
183
  org: null,
183
184
  personal: null,
184
185
  ...entries[provider],
@@ -206,7 +207,7 @@ function models(): ProviderModelsRead {
206
207
  })),
207
208
  {
208
209
  provider: "builder",
209
- recommendedModels: ["auto", "gpt-5.6-luna"],
210
+ recommendedModels: ["auto", "gpt-6-luna"],
210
211
  rows: { user: { models: null }, org: { models: null } },
211
212
  },
212
213
  ],
@@ -618,7 +619,7 @@ describe("ModelSettingsPage", () => {
618
619
  state.listing = listing({
619
620
  canManageOrg: true,
620
621
  canUpdateDefault: true,
621
- defaultModel: null,
622
+ defaultModel: { engine: "ai-sdk:openai", model: "gpt-5.6-sol" },
622
623
  });
623
624
  state.builder = builderFlow({
624
625
  configured: false,
@@ -641,6 +642,11 @@ describe("ModelSettingsPage", () => {
641
642
  expect(document.getElementById("provider-org-builder")).toBeNull();
642
643
 
643
644
  const defaultRow = row("default-model");
645
+ await vi.waitFor(() => {
646
+ expect(
647
+ defaultRow.querySelector("[data-default-model-loading]"),
648
+ ).toBeNull();
649
+ });
644
650
  expect(defaultRow.textContent).toContain(
645
651
  "Add a provider to choose a default model.",
646
652
  );
@@ -648,7 +654,10 @@ describe("ModelSettingsPage", () => {
648
654
  defaultRow.querySelector<HTMLButtonElement>('[role="combobox"]')
649
655
  ?.disabled,
650
656
  ).toBe(true);
651
- expect(defaultRow.textContent).not.toContain("Not set");
657
+ expect(defaultRow.textContent).not.toContain("gpt-5.6-sol");
658
+ expect(
659
+ defaultRow.querySelector('[role="combobox"]')?.textContent?.trim(),
660
+ ).toBe("");
652
661
 
653
662
  await act(async () => {
654
663
  (
@@ -666,6 +675,238 @@ describe("ModelSettingsPage", () => {
666
675
  expect(builderButton?.disabled).toBe(false);
667
676
  });
668
677
 
678
+ it("keeps a stored deployment-configured provider default visible and upgrades it", async () => {
679
+ state.listing = listing(
680
+ {
681
+ canManageOrg: true,
682
+ canUpdateDefault: true,
683
+ defaultModel: { engine: "ai-sdk:openai", model: "gpt-5.6-sol" },
684
+ },
685
+ { openai: { deploymentConfigured: true } },
686
+ );
687
+ state.models = {
688
+ providers: models().providers.map((provider) =>
689
+ provider.provider === "openai"
690
+ ? { ...provider, recommendedModels: ["gpt-6-sol", "gpt-6.1-sol"] }
691
+ : provider,
692
+ ),
693
+ };
694
+ state.builder = builderFlow({
695
+ configured: false,
696
+ grants: { org: null, personal: null },
697
+ canConnect: { org: true, personal: true },
698
+ });
699
+ await render();
700
+
701
+ const defaultRow = row("default-model");
702
+ await vi.waitFor(() => {
703
+ expect(defaultRow.textContent).toContain("gpt-6.1-sol · OpenAI");
704
+ });
705
+ expect(
706
+ defaultRow.querySelector<HTMLButtonElement>('[role="combobox"]')
707
+ ?.disabled,
708
+ ).toBe(false);
709
+ expect(row("llm").textContent).not.toContain("Add a model provider");
710
+ });
711
+
712
+ it("offers a deployment-configured provider when there is no saved key or default", async () => {
713
+ state.listing = listing(
714
+ { canManageOrg: true, canUpdateDefault: true, defaultModel: null },
715
+ { anthropic: { deploymentConfigured: true } },
716
+ );
717
+ state.builder = builderFlow({
718
+ configured: false,
719
+ grants: { org: null, personal: null },
720
+ canConnect: { org: true, personal: true },
721
+ });
722
+ await render();
723
+
724
+ expect(row("llm").textContent).not.toContain("Add a model provider");
725
+ const defaultRow = row("default-model");
726
+ expect(defaultRow.textContent).toContain("Choose a model");
727
+ expect(
728
+ defaultRow.querySelector<HTMLButtonElement>('[role="combobox"]')
729
+ ?.disabled,
730
+ ).toBe(false);
731
+ });
732
+
733
+ it("preserves a deployment OpenAI model when its saved model selection allows custom IDs", async () => {
734
+ state.listing = listing(
735
+ {
736
+ canManageOrg: true,
737
+ canUpdateDefault: true,
738
+ defaultModel: { engine: "ai-sdk:openai", model: "gpt-5.6-luna" },
739
+ },
740
+ { openai: { deploymentConfigured: true } },
741
+ );
742
+ state.models = {
743
+ providers: models().providers.map((provider) =>
744
+ provider.provider === "openai"
745
+ ? {
746
+ ...provider,
747
+ recommendedModels: ["gpt-6-luna", "gpt-6.1-luna"],
748
+ rows: {
749
+ ...provider.rows,
750
+ org: { models: null, preserveCustomModels: true },
751
+ },
752
+ }
753
+ : provider,
754
+ ),
755
+ };
756
+ state.builder = builderFlow({
757
+ configured: false,
758
+ grants: { org: null, personal: null },
759
+ canConnect: { org: true, personal: true },
760
+ });
761
+ await render();
762
+
763
+ const defaultRow = row("default-model");
764
+ await vi.waitFor(() => {
765
+ expect(defaultRow.textContent).toContain("gpt-5.6-luna · OpenAI");
766
+ });
767
+ });
768
+
769
+ it("keeps an OpenAI default hidden when the engine is not configured", async () => {
770
+ state.listing = listing({
771
+ canManageOrg: true,
772
+ canUpdateDefault: true,
773
+ defaultModel: { engine: "ai-sdk:openai", model: "gpt-5.6-sol" },
774
+ });
775
+ state.builder = builderFlow({
776
+ configured: false,
777
+ grants: { org: null, personal: null },
778
+ canConnect: { org: true, personal: true },
779
+ });
780
+ callActionMock.mockResolvedValue({
781
+ engines: [
782
+ {
783
+ name: "ai-sdk:openai",
784
+ configured: false,
785
+ credentialRejected: false,
786
+ },
787
+ ],
788
+ });
789
+ await render();
790
+
791
+ const defaultRow = row("default-model");
792
+ await vi.waitFor(() => {
793
+ expect(
794
+ defaultRow.querySelector("[data-default-model-loading]"),
795
+ ).toBeNull();
796
+ });
797
+ expect(defaultRow.textContent).not.toContain("gpt-5.6-sol");
798
+ expect(
799
+ defaultRow.querySelector<HTMLButtonElement>('[role="combobox"]')
800
+ ?.disabled,
801
+ ).toBe(true);
802
+ expect(row("llm").textContent).toContain("Add a model provider");
803
+ });
804
+
805
+ it("keeps custom OpenAI endpoint model IDs when displaying a stored default", async () => {
806
+ state.listing = listing(
807
+ {
808
+ canManageOrg: true,
809
+ canUpdateDefault: true,
810
+ defaultModel: { engine: "ai-sdk:openai", model: "gpt-5.6-luna" },
811
+ },
812
+ {
813
+ openai: {
814
+ org: {
815
+ scope: "org",
816
+ endpoint: "https://gateway.example/v1",
817
+ updatedAt: 1,
818
+ },
819
+ },
820
+ },
821
+ );
822
+ state.builder = builderFlow({
823
+ configured: false,
824
+ grants: { org: null, personal: null },
825
+ });
826
+ state.models = {
827
+ providers: [
828
+ {
829
+ provider: "openai",
830
+ recommendedModels: ["gpt-5.6-luna", "gpt-6-luna"],
831
+ rows: {
832
+ org: { models: null, preserveCustomModels: true },
833
+ },
834
+ },
835
+ ],
836
+ };
837
+ await render();
838
+
839
+ const defaultRow = row("default-model");
840
+ expect(defaultRow.textContent).toContain("gpt-5.6-luna");
841
+ expect(defaultRow.textContent).not.toContain("gpt-6-luna");
842
+ });
843
+
844
+ it("keeps custom endpoint defaults for members who cannot see the endpoint", async () => {
845
+ state.listing = listing(
846
+ {
847
+ canManageOrg: false,
848
+ canUpdateDefault: true,
849
+ defaultModel: { engine: "ai-sdk:openai", model: "gpt-5.6-luna" },
850
+ },
851
+ {
852
+ openai: { org: { scope: "org", updatedAt: 1 } },
853
+ },
854
+ );
855
+ state.builder = builderFlow({
856
+ configured: false,
857
+ grants: { org: null, personal: null },
858
+ });
859
+ state.models = {
860
+ providers: [
861
+ {
862
+ provider: "openai",
863
+ recommendedModels: ["gpt-5.6-luna", "gpt-6-luna"],
864
+ rows: {
865
+ org: { models: null, preserveCustomModels: true },
866
+ },
867
+ },
868
+ ],
869
+ };
870
+ await render();
871
+
872
+ const defaultRow = row("default-model");
873
+ expect(defaultRow.textContent).toContain("gpt-5.6-luna");
874
+ expect(defaultRow.textContent).not.toContain("gpt-6-luna");
875
+ });
876
+
877
+ it("hides a stored default whose provider key was rejected", async () => {
878
+ state.listing = listing(
879
+ {
880
+ canManageOrg: true,
881
+ canUpdateDefault: true,
882
+ defaultModel: { engine: "ai-sdk:openai", model: "gpt-5.6-sol" },
883
+ },
884
+ {
885
+ openai: {
886
+ org: { scope: "org", rejectedAt: 1, updatedAt: 1 },
887
+ },
888
+ },
889
+ );
890
+ state.builder = builderFlow({
891
+ configured: false,
892
+ grants: { org: null, personal: null },
893
+ });
894
+ await render();
895
+
896
+ expect(row("provider-org-openai").textContent).toContain("rejected");
897
+ const defaultRow = row("default-model");
898
+ await vi.waitFor(() => {
899
+ expect(
900
+ defaultRow.querySelector("[data-default-model-loading]"),
901
+ ).toBeNull();
902
+ });
903
+ expect(defaultRow.textContent).not.toContain("gpt-5.6-sol");
904
+ expect(
905
+ defaultRow.querySelector<HTMLButtonElement>('[role="combobox"]')
906
+ ?.disabled,
907
+ ).toBe(true);
908
+ });
909
+
669
910
  it("tells a restricted member with no provider to ask an admin", async () => {
670
911
  state.listing = listing({ personalKeysRestricted: true });
671
912
  state.builder = builderFlow({
@@ -1,5 +1,6 @@
1
1
  import type { ProviderKeyPolicyStatus } from "@agent-native/core/agent/actions/manage-provider-key-policy";
2
2
  import { CHATGPT_SUBSCRIPTION_ENGINE_NAME } from "@agent-native/core/agent/chatgpt-subscription-contract";
3
+ import { upgradeModelToLatestSupportedVersion } from "@agent-native/core/agent/model-version";
3
4
  import {
4
5
  setAgentEngineDefaultModel,
5
6
  type AgentEngineKeyScope,
@@ -347,6 +348,9 @@ function hasAnyProvider(
347
348
  builder: BuilderConnectFlow,
348
349
  chatgptConnected: boolean | null,
349
350
  ): boolean {
351
+ if (listing.providers.some((provider) => provider.deploymentConfigured)) {
352
+ return true;
353
+ }
350
354
  if (chatgptConnected !== false || !builder.hasFetchedStatus) return true;
351
355
  if (!listing.hasOrganization) {
352
356
  return builder.configured || rows.personal.length > 0;
@@ -787,16 +791,26 @@ function DefaultModelRow({
787
791
  chatgpt: chatgpt.status === "ready" ? chatgpt.catalog : undefined,
788
792
  });
789
793
  const stored = listing.defaultModel
790
- ? {
791
- engine: listing.defaultModel.engine,
792
- model:
793
- listing.defaultModel.model ??
794
- groups.find((group) => group.engine === listing.defaultModel?.engine)
795
- ?.models[0] ??
796
- "",
797
- }
794
+ ? (() => {
795
+ const group = groups.find(
796
+ (candidate) => candidate.engine === listing.defaultModel?.engine,
797
+ );
798
+ const supportedModels = group?.models ?? [];
799
+ const preserveCustomModels = group?.preserveCustomModels;
800
+ const model = listing.defaultModel.model ?? supportedModels[0] ?? "";
801
+ return {
802
+ engine: listing.defaultModel.engine,
803
+ model:
804
+ (!preserveCustomModels &&
805
+ upgradeModelToLatestSupportedVersion(model, supportedModels)) ||
806
+ model,
807
+ };
808
+ })()
798
809
  : null;
799
- const current = pending ?? stored;
810
+ const storedGroup = stored
811
+ ? groups.find((group) => group.engine === stored.engine)
812
+ : undefined;
813
+ const current = pending ?? (storedGroup ? stored : null);
800
814
  const engineLabel = (engine: string) => {
801
815
  const provider = providerForEngine(engine);
802
816
  if (provider === "builder") return BUILDER_LABEL;
@@ -811,8 +825,8 @@ function DefaultModelRow({
811
825
  })
812
826
  : engineLabel(current.engine)
813
827
  : t(`${K}notSet`);
814
- // A stored default no longer offered (personal, rejected, or unchecked)
815
- // still shows as the value, so the select never reads blank.
828
+ // Keep a stored model visible if its provider is available but the model is
829
+ // no longer in that provider's checked list.
816
830
  const offered =
817
831
  current &&
818
832
  groups.some(
@@ -884,7 +898,13 @@ function DefaultModelRow({
884
898
  aria-label={t(DEFAULT_MODEL_LABEL)}
885
899
  >
886
900
  <SelectValue
887
- placeholder={current ? currentLabel : t(`${K}chooseModel`)}
901
+ placeholder={
902
+ current
903
+ ? currentLabel
904
+ : waitingForProvider
905
+ ? ""
906
+ : t(`${K}chooseModel`)
907
+ }
888
908
  />
889
909
  </SelectTrigger>
890
910
  </Select>
@@ -102,6 +102,7 @@ function listing(
102
102
  providers: PROVIDERS.map((provider) => ({
103
103
  provider,
104
104
  label: LABELS[provider],
105
+ deploymentConfigured: false,
105
106
  org: null,
106
107
  personal: null,
107
108
  ...entries[provider],
@@ -63,8 +63,8 @@ const models: ProviderModelsRead = {
63
63
  providers: [
64
64
  {
65
65
  provider: "builder",
66
- recommendedModels: ["auto", "gpt-5.6-luna"],
67
- rows: { org: { models: ["gpt-5.6-luna"] } },
66
+ recommendedModels: ["auto", "gpt-6-luna"],
67
+ rows: { org: { models: ["gpt-6-luna"] } },
68
68
  },
69
69
  {
70
70
  provider: "anthropic",
@@ -207,7 +207,7 @@ describe("model page state", () => {
207
207
  engine: "builder",
208
208
  provider: "builder",
209
209
  label: "Builder.io",
210
- models: ["gpt-5.6-luna"],
210
+ models: ["gpt-6-luna"],
211
211
  },
212
212
  {
213
213
  engine: "anthropic",
@@ -33,7 +33,10 @@ export interface ProviderModelsRead {
33
33
  provider: string;
34
34
  recommendedModels: string[];
35
35
  rows: Partial<
36
- Record<ModelProviderKeyScope, { models: string[] | null } | undefined>
36
+ Record<
37
+ ModelProviderKeyScope,
38
+ { models: string[] | null; preserveCustomModels?: boolean } | undefined
39
+ >
37
40
  >;
38
41
  }>;
39
42
  }
@@ -49,6 +52,17 @@ export function selectedModelsAt(
49
52
  return entry.rows[scope]?.models ?? entry.recommendedModels;
50
53
  }
51
54
 
55
+ function preservesCustomModelsAt(
56
+ read: ProviderModelsRead | undefined,
57
+ provider: string,
58
+ scope: ModelProviderKeyScope,
59
+ ): boolean {
60
+ return (
61
+ read?.providers.find((item) => item.provider === provider)?.rows[scope]
62
+ ?.preserveCustomModels === true
63
+ );
64
+ }
65
+
52
66
  /** The models checked at `scope`, or null when nothing is chosen there. */
53
67
  export function explicitSelectionAt(
54
68
  read: ProviderModelsRead | undefined,
@@ -210,6 +224,7 @@ export interface DefaultModelGroup {
210
224
  provider: AgentProviderId | "builder" | "chatgpt";
211
225
  label: string;
212
226
  models: string[];
227
+ preserveCustomModels?: boolean;
213
228
  /** Names to show instead of the model ids, where the provider has them. */
214
229
  modelDisplayNames?: Record<string, string>;
215
230
  }
@@ -252,7 +267,7 @@ export function defaultModelGroups(input: {
252
267
  }
253
268
  for (const entry of listing.providers) {
254
269
  const key = scope === "org" ? entry.org : entry.personal;
255
- if (!key || key.rejectedAt) continue;
270
+ if (key?.rejectedAt || (!key && !entry.deploymentConfigured)) continue;
256
271
  const checked = selectedModelsAt(models, entry.provider, scope) ?? [];
257
272
  if (checked.length === 0) continue;
258
273
  groups.push({
@@ -260,6 +275,10 @@ export function defaultModelGroups(input: {
260
275
  provider: entry.provider,
261
276
  label: entry.label,
262
277
  models: checked,
278
+ ...(entry.provider === "openai" &&
279
+ preservesCustomModelsAt(models, entry.provider, scope)
280
+ ? { preserveCustomModels: true }
281
+ : {}),
263
282
  });
264
283
  }
265
284
  if (