@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.
- package/agent-native.eject.json +4 -0
- package/dist/app/settings/BuilderConnectPopover.d.ts +4 -2
- package/dist/app/settings/BuilderConnectPopover.d.ts.map +1 -1
- package/dist/app/settings/BuilderConnectPopover.js +11 -13
- package/dist/app/settings/BuilderConnectPopover.js.map +1 -1
- package/dist/app/settings/model/ModelSettingsPage.d.ts.map +1 -1
- package/dist/app/settings/model/ModelSettingsPage.js +27 -11
- package/dist/app/settings/model/ModelSettingsPage.js.map +1 -1
- package/dist/app/settings/model/model-page-state.d.ts +2 -0
- package/dist/app/settings/model/model-page-state.d.ts.map +1 -1
- package/dist/app/settings/model/model-page-state.js +9 -1
- package/dist/app/settings/model/model-page-state.js.map +1 -1
- package/dist/app/settings/shell/app-identity.d.ts +1 -1
- package/dist/app/settings/shell/app-identity.d.ts.map +1 -1
- package/dist/app/settings/shell/registry.d.ts +2 -3
- package/dist/app/settings/shell/registry.d.ts.map +1 -1
- package/dist/app/settings/shell/registry.js.map +1 -1
- package/dist/app/settings/shell/types.d.ts +5 -0
- package/dist/app/settings/shell/types.d.ts.map +1 -0
- package/dist/app/settings/shell/types.js +2 -0
- package/dist/app/settings/shell/types.js.map +1 -0
- package/dist/composer/TiptapComposer.js +2 -2
- package/dist/composer/TiptapComposer.js.map +1 -1
- package/package.json +4 -4
- package/src/app/settings/BuilderConnectPopover.spec.tsx +37 -35
- package/src/app/settings/BuilderConnectPopover.tsx +16 -13
- package/src/app/settings/model/ModelSettingsPage.spec.tsx +244 -3
- package/src/app/settings/model/ModelSettingsPage.tsx +32 -12
- package/src/app/settings/model/ProviderDialog.spec.tsx +1 -0
- package/src/app/settings/model/model-page-state.spec.ts +3 -3
- package/src/app/settings/model/model-page-state.ts +21 -2
- package/src/app/settings/shell/app-identity.ts +1 -1
- package/src/app/settings/shell/registry.ts +2 -1
- package/src/app/settings/shell/types.ts +3 -0
- 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.
|
|
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.
|
|
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.
|
|
700
|
+
"@agent-native/core": "^0.202.1-nightly-20261007011022"
|
|
701
701
|
},
|
|
702
702
|
"peerDependencies": {
|
|
703
|
-
"@agent-native/core": "
|
|
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
|
-
"+
|
|
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 {
|
|
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
|
|
296
|
-
|
|
297
|
-
|
|
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-
|
|
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:
|
|
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("
|
|
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
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
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
|
|
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
|
-
//
|
|
815
|
-
//
|
|
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={
|
|
901
|
+
placeholder={
|
|
902
|
+
current
|
|
903
|
+
? currentLabel
|
|
904
|
+
: waitingForProvider
|
|
905
|
+
? ""
|
|
906
|
+
: t(`${K}chooseModel`)
|
|
907
|
+
}
|
|
888
908
|
/>
|
|
889
909
|
</SelectTrigger>
|
|
890
910
|
</Select>
|
|
@@ -63,8 +63,8 @@ const models: ProviderModelsRead = {
|
|
|
63
63
|
providers: [
|
|
64
64
|
{
|
|
65
65
|
provider: "builder",
|
|
66
|
-
recommendedModels: ["auto", "gpt-
|
|
67
|
-
rows: { org: { models: ["gpt-
|
|
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-
|
|
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<
|
|
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 (
|
|
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 (
|