@agent-native/core 0.147.0 → 0.148.1

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 (197) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/frontend-design/SKILL.md +115 -68
  3. package/corpus/templates/analytics/DEVELOPING.md +50 -3
  4. package/corpus/templates/analytics/actions/purge-first-party-analytics-postgres.ts +190 -0
  5. package/corpus/templates/analytics/app/i18n/index.ts +14 -29
  6. package/corpus/templates/analytics/app/i18n-data.ts +10 -10
  7. package/corpus/templates/analytics/app/pages/Settings.tsx +5 -21
  8. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +1 -3
  9. package/corpus/templates/analytics/scripts/first-party-analytics-bigquery-backfill.ts +197 -18
  10. package/corpus/templates/analytics/server/db/schema.ts +23 -0
  11. package/corpus/templates/analytics/server/handlers/first-party-analytics.ts +3 -1
  12. package/corpus/templates/analytics/server/jobs/analytics-bigquery-backfill.ts +1 -1
  13. package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +113 -13
  14. package/corpus/templates/analytics/server/lib/first-party-analytics-purge.ts +140 -0
  15. package/corpus/templates/analytics/server/lib/first-party-analytics-volume.ts +207 -0
  16. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +40 -9
  17. package/corpus/templates/analytics/server/lib/gcloud.ts +148 -20
  18. package/corpus/templates/analytics/server/plugins/db.ts +42 -0
  19. package/corpus/templates/assets/.agents/skills/frontend-design/SKILL.md +115 -68
  20. package/corpus/templates/assets/app/i18n/index.ts +14 -29
  21. package/corpus/templates/brain/.agents/skills/frontend-design/SKILL.md +115 -68
  22. package/corpus/templates/brain/app/i18n/index.ts +14 -29
  23. package/corpus/templates/calendar/.agents/skills/frontend-design/SKILL.md +115 -68
  24. package/corpus/templates/chat/.agents/skills/frontend-design/SKILL.md +115 -68
  25. package/corpus/templates/chat/app/i18n/index.ts +14 -29
  26. package/corpus/templates/clips/.agents/skills/frontend-design/SKILL.md +115 -68
  27. package/corpus/templates/clips/app/components/player/transcript-panel.tsx +2 -7
  28. package/corpus/templates/clips/app/components/recorder/storage-setup-card.tsx +18 -13
  29. package/corpus/templates/clips/app/components/settings/ai-setup-section.tsx +87 -370
  30. package/corpus/templates/clips/app/components/settings/slack-section.tsx +59 -101
  31. package/corpus/templates/clips/app/components/settings/video-storage-section.tsx +136 -213
  32. package/corpus/templates/clips/app/i18n/en-US.ts +11 -8
  33. package/corpus/templates/clips/desktop/src/app.tsx +1 -1
  34. package/corpus/templates/content/.agents/skills/frontend-design/SKILL.md +115 -68
  35. package/corpus/templates/content/app/i18n/index.ts +14 -29
  36. package/corpus/templates/crm/app/i18n/index.ts +14 -29
  37. package/corpus/templates/design/.agents/skills/frontend-design/SKILL.md +115 -68
  38. package/corpus/templates/design/actions/analyze-brand-assets.ts +15 -15
  39. package/corpus/templates/design/actions/import-code.ts +2 -43
  40. package/corpus/templates/design/app/i18n/index.ts +14 -29
  41. package/corpus/templates/dispatch/.agents/skills/frontend-design/SKILL.md +115 -68
  42. package/corpus/templates/dispatch/app/i18n-data.ts +1 -1
  43. package/corpus/templates/factory/.agents/skills/frontend-design/SKILL.md +115 -68
  44. package/corpus/templates/factory/AGENTS.md +40 -49
  45. package/corpus/templates/factory/actions/babysit-agent-native-pull-request.ts +66 -0
  46. package/corpus/templates/factory/actions/evaluate-triage-item.ts +37 -0
  47. package/corpus/templates/factory/actions/get-slack-feedback-context.ts +20 -0
  48. package/corpus/templates/factory/actions/get-triage-item.ts +25 -4
  49. package/corpus/templates/factory/actions/govern-agent-native-pull-request.ts +55 -0
  50. package/corpus/templates/factory/actions/ingest-github-observation.ts +21 -0
  51. package/corpus/templates/factory/actions/list-factory-audit.ts +118 -0
  52. package/corpus/templates/factory/actions/list-factory-automations.ts +2 -1
  53. package/corpus/templates/factory/actions/list-triage-items.ts +24 -2
  54. package/corpus/templates/factory/actions/poll-github-sources.ts +60 -0
  55. package/corpus/templates/factory/actions/poll-sentry-errors.ts +39 -0
  56. package/corpus/templates/factory/actions/poll-slack-channel.ts +45 -1
  57. package/corpus/templates/factory/actions/run-factory-automation.ts +1 -1
  58. package/corpus/templates/factory/actions/save-factory-automation.ts +2 -2
  59. package/corpus/templates/factory/actions/start-builder-for-item.ts +51 -0
  60. package/corpus/templates/factory/app/components/factory/FactoryAuditView.tsx +685 -0
  61. package/corpus/templates/factory/app/hooks/use-navigation-state.ts +6 -0
  62. package/corpus/templates/factory/app/i18n/en-US.ts +25 -0
  63. package/corpus/templates/factory/app/routes/factory.tsx +95 -42
  64. package/corpus/templates/factory/server/db/schema.ts +37 -0
  65. package/corpus/templates/factory/server/lib/require-factory-automation.ts +5 -1
  66. package/corpus/templates/factory/server/plugins/factory-migrations.ts +29 -0
  67. package/corpus/templates/factory/server/triage/audit.ts +79 -0
  68. package/corpus/templates/forms/.agents/skills/frontend-design/SKILL.md +115 -68
  69. package/corpus/templates/forms/app/i18n/index.ts +14 -29
  70. package/corpus/templates/macros/.agents/skills/frontend-design/SKILL.md +115 -68
  71. package/corpus/templates/macros/app/i18n/index.ts +14 -29
  72. package/corpus/templates/mail/.agents/skills/frontend-design/SKILL.md +115 -68
  73. package/corpus/templates/mail/app/i18n/index.ts +14 -29
  74. package/corpus/templates/plan/.agents/skills/frontend-design/SKILL.md +115 -68
  75. package/corpus/templates/plan/app/components/plan/wireframe/Wireframe.tsx +7 -7
  76. package/corpus/templates/plan/app/i18n/index.ts +14 -29
  77. package/corpus/templates/slides/.agents/skills/frontend-design/SKILL.md +115 -68
  78. package/corpus/templates/slides/actions/analyze-brand-assets.ts +15 -15
  79. package/corpus/templates/slides/actions/import-code.ts +2 -43
  80. package/corpus/templates/slides/app/i18n/index.ts +14 -29
  81. package/corpus/templates/slides/server/routes/api/preview-images.get.ts +4 -1
  82. package/corpus/templates/tasks/.agents/skills/frontend-design/SKILL.md +115 -68
  83. package/corpus/templates/tasks/app/components/layout/HeaderActions.tsx +1 -84
  84. package/corpus/templates/tasks/app/hooks/use-mobile.tsx +1 -21
  85. package/corpus/templates/tasks/app/i18n/index.ts +14 -29
  86. package/dist/agent/engine/ai-sdk-engine.js +2 -1
  87. package/dist/agent/engine/openai-compatible-endpoint.d.ts +2 -0
  88. package/dist/agent/engine/openai-compatible-endpoint.js +5 -1
  89. package/dist/agent/engine/provider-endpoint-validation.d.ts +9 -0
  90. package/dist/agent/engine/provider-endpoint-validation.js +26 -0
  91. package/dist/agent/engine/provider-env-vars.d.ts +1 -1
  92. package/dist/agent/engine/provider-env-vars.js +1 -1
  93. package/dist/agent/engine/registry.d.ts +8 -6
  94. package/dist/agent/engine/registry.js +37 -24
  95. package/dist/agent/tool-search.d.ts +1 -0
  96. package/dist/agent/tool-search.js +17 -1
  97. package/dist/automations/service.js +4 -10
  98. package/dist/brand-kit/brand-signals.d.ts +13 -0
  99. package/dist/brand-kit/brand-signals.js +15 -0
  100. package/dist/brand-kit/index.d.ts +2 -2
  101. package/dist/brand-kit/index.js +1 -1
  102. package/dist/cli/plan-blocks.d.ts +3 -18
  103. package/dist/cli/plan-blocks.js +2 -95
  104. package/dist/cli/plan-publish-store.d.ts +2 -61
  105. package/dist/cli/plan-publish-store.js +2 -127
  106. package/dist/cli/skills-content/connection.d.ts +1 -1
  107. package/dist/cli/skills-content/connection.js +1 -52
  108. package/dist/cli/skills-content/visual-recap-skill.d.ts +1 -1
  109. package/dist/cli/skills-content/visual-recap-skill.js +1 -553
  110. package/dist/client/agent-engine-key.d.ts +14 -4
  111. package/dist/client/agent-engine-key.js +51 -5
  112. package/dist/client/agent-provider-catalog.d.ts +20 -0
  113. package/dist/client/agent-provider-catalog.js +100 -0
  114. package/dist/client/blocks/index.d.ts +1 -0
  115. package/dist/client/blocks/index.js +1 -0
  116. package/dist/client/blocks/library/diagram.js +1 -1
  117. package/dist/client/blocks/library/sanitize-html.js +66 -2
  118. package/dist/client/blocks/library/wireframe-kit.js +69 -0
  119. package/dist/client/chat/run-recovery.js +4 -48
  120. package/dist/client/chat-first/apps-rail.d.ts +1 -1
  121. package/dist/client/chat-first/apps-rail.js +4 -4
  122. package/dist/client/chat-first/copy.js +1 -0
  123. package/dist/client/chat-first/types.d.ts +1 -0
  124. package/dist/client/chat-first.js +4 -3
  125. package/dist/client/i18n.d.ts +14 -0
  126. package/dist/client/i18n.js +18 -0
  127. package/dist/client/settings/ProviderSetupForm.d.ts +21 -0
  128. package/dist/client/settings/ProviderSetupForm.js +145 -0
  129. package/dist/client/settings/SettingsPanel.js +52 -49
  130. package/dist/client/settings/index.d.ts +2 -0
  131. package/dist/client/settings/index.js +2 -0
  132. package/dist/coding-tools/run-code.d.ts +5 -0
  133. package/dist/coding-tools/run-code.js +196 -36
  134. package/dist/coding-tools/tool-orchestration.d.ts +10 -0
  135. package/dist/coding-tools/tool-orchestration.js +107 -0
  136. package/dist/collab/routes.d.ts +1 -1
  137. package/dist/jobs/frontmatter.d.ts +9 -0
  138. package/dist/jobs/frontmatter.js +16 -0
  139. package/dist/jobs/scheduler.js +8 -8
  140. package/dist/jobs/tools.d.ts +1 -1
  141. package/dist/jobs/tools.js +2 -7
  142. package/dist/localization/default-messages.d.ts +20 -0
  143. package/dist/localization/default-messages.js +25 -5
  144. package/dist/notifications/routes.d.ts +3 -3
  145. package/dist/observability/routes.d.ts +2 -2
  146. package/dist/onboarding/app-profile.js +1 -1
  147. package/dist/progress/routes.d.ts +1 -1
  148. package/dist/provider-api/actions/custom-provider-registration.d.ts +8 -8
  149. package/dist/provider-api/actions/provider-api.d.ts +11 -11
  150. package/dist/scripts/agent-engines/test-agent-engine.js +34 -10
  151. package/dist/server/agent-chat/run-code-tools.d.ts +1 -1
  152. package/dist/server/agent-chat/run-code-tools.js +3 -1
  153. package/dist/server/agent-chat-plugin.js +6 -6
  154. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  155. package/dist/server/agent-engine-api-key-route.js +43 -12
  156. package/dist/server/agents-bundle.js +2 -1
  157. package/dist/server/auth.js +21 -5
  158. package/dist/server/credential-provider.js +1 -0
  159. package/dist/server/design-token-utils.d.ts +18 -0
  160. package/dist/server/design-token-utils.js +31 -0
  161. package/dist/server/onboarding-html.js +22 -2
  162. package/dist/server/realtime-token.d.ts +1 -1
  163. package/dist/styles/blocks.css +132 -0
  164. package/dist/templates/chat/.agents/skills/frontend-design/SKILL.md +115 -68
  165. package/dist/templates/chat/app/i18n/index.ts +14 -29
  166. package/dist/templates/default/.agents/skills/frontend-design/SKILL.md +115 -68
  167. package/dist/templates/default/app/i18n/index.ts +14 -29
  168. package/dist/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +22 -9
  169. package/dist/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +115 -68
  170. package/dist/triggers/dispatcher.js +1 -8
  171. package/docs/content/actions.mdx +4 -1
  172. package/docs/content/agent-surfaces.mdx +8 -6
  173. package/docs/content/automations.mdx +1 -1
  174. package/docs/content/database.mdx +6 -1
  175. package/docs/content/docs-components.mdx +22 -14
  176. package/docs/content/drop-in-agent.mdx +1 -1
  177. package/docs/content/environment-variables.mdx +39 -38
  178. package/docs/content/getting-started-actions.mdx +5 -4
  179. package/docs/content/getting-started-pages.mdx +7 -6
  180. package/docs/content/getting-started.mdx +4 -3
  181. package/docs/content/notifications.mdx +6 -6
  182. package/docs/content/progress.mdx +5 -5
  183. package/docs/content/routing.mdx +3 -1
  184. package/docs/content/server-database.mdx +13 -4
  185. package/docs/content/server-overview.mdx +9 -9
  186. package/docs/content/server-plugins.mdx +10 -4
  187. package/docs/content/server-routes.mdx +7 -2
  188. package/docs/content/tracking.mdx +4 -4
  189. package/docs/content/what-is-agent-native.mdx +4 -4
  190. package/package.json +4 -4
  191. package/src/templates/chat/.agents/skills/frontend-design/SKILL.md +115 -68
  192. package/src/templates/chat/app/i18n/index.ts +14 -29
  193. package/src/templates/default/.agents/skills/frontend-design/SKILL.md +115 -68
  194. package/src/templates/default/app/i18n/index.ts +14 -29
  195. package/src/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +22 -9
  196. package/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +115 -68
  197. package/corpus/templates/plan/app/components/plan/wireframe/sanitize-html.ts +0 -343
@@ -0,0 +1,145 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from "@agent-native/toolkit/ui/command";
3
+ import { IconBolt, IconCheck, IconChevronDown, IconCloud, IconDeviceDesktop, IconExternalLink, IconKey, IconLoader2, IconRoute, IconServer2, } from "@tabler/icons-react";
4
+ import { useEffect, useState } from "react";
5
+ import { saveAgentEngineProviderSettings, setAgentEngineProvider, } from "../agent-engine-key.js";
6
+ import { AGENT_PROVIDER_CATALOG, getAgentProviderOption, } from "../agent-provider-catalog.js";
7
+ import { Popover, PopoverContent, PopoverTrigger, } from "../components/ui/popover.js";
8
+ import { useT } from "../i18n.js";
9
+ import { cn } from "../utils.js";
10
+ function ProviderIcon({ option, size = 15, }) {
11
+ if (option.kind === "local")
12
+ return _jsx(IconDeviceDesktop, { size: size });
13
+ if (option.kind === "gateway")
14
+ return _jsx(IconRoute, { size: size });
15
+ return _jsx(IconCloud, { size: size });
16
+ }
17
+ export function AgentProviderPicker({ value, onChange, options = AGENT_PROVIDER_CATALOG, configuredProviders, disabled = false, className, layout = "compact", }) {
18
+ const t = useT();
19
+ const [open, setOpen] = useState(false);
20
+ const active = getAgentProviderOption(value);
21
+ const isPage = layout === "page";
22
+ return (_jsxs("div", { className: cn("space-y-1.5", className), children: [_jsx("p", { className: cn("font-medium text-foreground", isPage ? "text-sm" : "text-[11px]"), children: t("agentPanel.chooseProvider", {
23
+ defaultValue: "Choose a provider",
24
+ }) }), _jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { type: "button", disabled: disabled, "aria-label": t("agentPanel.chooseProvider", {
25
+ defaultValue: "Choose a provider",
26
+ }), className: cn("flex w-full items-center gap-2 rounded-md border border-border bg-background text-start text-foreground transition-colors hover:bg-accent/40 disabled:cursor-not-allowed disabled:opacity-60", isPage ? "min-h-10 px-3 text-sm" : "min-h-9 px-2.5 text-[12px]"), children: [_jsx("span", { className: "flex size-6 shrink-0 items-center justify-center rounded-md bg-accent/50 text-muted-foreground", children: _jsx(ProviderIcon, { option: active, size: isPage ? 16 : 14 }) }), _jsx("span", { className: "min-w-0 flex-1", children: _jsx("span", { className: "block truncate font-medium", children: active.label }) }), _jsx(IconChevronDown, { size: isPage ? 16 : 14, className: "shrink-0 text-muted-foreground" })] }) }), _jsx(PopoverContent, { align: "start", sideOffset: 6, className: "w-[min(390px,calc(100vw-2rem))] p-0", children: _jsxs(Command, { filter: (candidate, search) => candidate.toLowerCase().includes(search.toLowerCase()) ? 1 : 0, children: [_jsx(CommandInput, { placeholder: t("agentPanel.searchProviders", {
27
+ defaultValue: "Search providers...",
28
+ }) }), _jsxs(CommandList, { className: "max-h-[min(440px,calc(100vh-8rem))]", children: [_jsx(CommandEmpty, { children: t("agentPanel.noProvidersFound", {
29
+ defaultValue: "No providers found.",
30
+ }) }), _jsx(CommandGroup, { heading: t("agentPanel.availableProviders", {
31
+ defaultValue: "Available providers",
32
+ }), children: options.map((option) => {
33
+ const configured = configuredProviders?.has(option.id);
34
+ return (_jsxs(CommandItem, { value: `${option.label} ${option.description} ${option.key ?? "local"}`, onSelect: () => {
35
+ onChange(option.id);
36
+ setOpen(false);
37
+ }, className: "items-center gap-2.5 py-2.5", children: [_jsx("span", { className: "flex size-7 shrink-0 items-center justify-center rounded-md bg-accent/50 text-muted-foreground", children: _jsx(ProviderIcon, { option: option }) }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsxs("span", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "truncate font-medium", children: option.label }), configured ? (_jsxs("span", { className: "inline-flex shrink-0 items-center gap-1 text-[10px] font-medium text-primary", children: [_jsx(IconCheck, { size: 11 }), t("agentPanel.configured", {
38
+ defaultValue: "Configured",
39
+ })] })) : null] }), _jsx("span", { className: "block truncate text-[11px] text-muted-foreground", children: option.description })] }), _jsx("span", { className: "shrink-0 text-[10px] text-muted-foreground", children: option.key
40
+ ? t("agentPanel.apiKey", {
41
+ defaultValue: "API key",
42
+ })
43
+ : t("agentPanel.localRuntime", {
44
+ defaultValue: "Local",
45
+ }) }), _jsx(IconCheck, { size: 15, className: cn("shrink-0", option.id === value ? "opacity-100" : "opacity-0") })] }, option.id));
46
+ }) })] })] }) })] })] }));
47
+ }
48
+ export function AgentProviderSetupForm({ initialProvider = "anthropic", configuredProviders, onConnected, scope = "user", layout = "compact", showTitle = true, className, }) {
49
+ const t = useT();
50
+ const isPage = layout === "page";
51
+ const [provider, setProvider] = useState(initialProvider);
52
+ const [apiKey, setApiKey] = useState("");
53
+ const [model, setModel] = useState("");
54
+ const [endpoint, setEndpoint] = useState("");
55
+ const [endpointOpen, setEndpointOpen] = useState(initialProvider === "ollama");
56
+ const [saving, setSaving] = useState(false);
57
+ const [saved, setSaved] = useState(false);
58
+ const [error, setError] = useState(null);
59
+ const active = getAgentProviderOption(provider);
60
+ useEffect(() => {
61
+ setModel(active.defaultModel);
62
+ setApiKey("");
63
+ setEndpoint("");
64
+ setEndpointOpen(provider === "ollama");
65
+ setError(null);
66
+ setSaved(false);
67
+ }, [active.defaultModel, provider]);
68
+ const handleProviderChange = (nextProvider) => {
69
+ setProvider(nextProvider);
70
+ };
71
+ const handleSave = async () => {
72
+ if (saving)
73
+ return;
74
+ if (active.key && !apiKey.trim()) {
75
+ setError(t("agentPanel.enterApiKey", {
76
+ defaultValue: `Enter your ${active.label} API key.`,
77
+ }));
78
+ return;
79
+ }
80
+ setSaving(true);
81
+ setError(null);
82
+ try {
83
+ if (active.key || endpoint.trim()) {
84
+ await saveAgentEngineProviderSettings({
85
+ provider,
86
+ ...(active.key ? { key: active.key } : {}),
87
+ ...(apiKey.trim() ? { apiKey } : {}),
88
+ ...(endpoint.trim() ? { baseUrl: endpoint } : {}),
89
+ scope,
90
+ });
91
+ }
92
+ await setAgentEngineProvider({
93
+ provider,
94
+ model: model.trim() || active.defaultModel,
95
+ });
96
+ setApiKey("");
97
+ setSaved(true);
98
+ onConnected?.(provider);
99
+ window.setTimeout(() => setSaved(false), 2200);
100
+ }
101
+ catch (err) {
102
+ setError(err instanceof Error
103
+ ? err.message
104
+ : t("agentPanel.providerSetupFailed", {
105
+ defaultValue: "Could not configure this provider.",
106
+ }));
107
+ }
108
+ finally {
109
+ setSaving(false);
110
+ }
111
+ };
112
+ const isConfigured = configuredProviders?.has(provider) || saved;
113
+ const modelInputVisible = active.supportsCustomModel;
114
+ const endpointVisible = active.supportsEndpoint;
115
+ return (_jsxs("form", { className: cn("space-y-3", className), onSubmit: (event) => {
116
+ event.preventDefault();
117
+ void handleSave();
118
+ }, children: [showTitle ? (_jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(IconKey, { size: isPage ? 16 : 13, className: "text-muted-foreground" }), _jsx("h4", { className: cn("font-medium text-foreground", isPage ? "text-sm" : "text-[11px]"), children: t("agentPanel.addOwnKeys", {
119
+ defaultValue: "Custom keys",
120
+ }) })] }), _jsx("p", { className: cn("mt-1 leading-relaxed text-muted-foreground", isPage ? "text-xs" : "text-[11px]"), children: t("agentPanel.configureProviderKeys", {
121
+ defaultValue: "Choose a provider.",
122
+ }) })] })) : null, _jsx(AgentProviderPicker, { value: provider, onChange: handleProviderChange, configuredProviders: configuredProviders, disabled: saving, layout: layout }), _jsxs("div", { className: cn("rounded-md border border-border bg-accent/20", isPage ? "space-y-3 p-3.5" : "space-y-2.5 p-2.5"), children: [_jsxs("div", { className: "flex items-start gap-2", children: [_jsx("span", { className: "mt-0.5 flex size-6 shrink-0 items-center justify-center rounded-md bg-background text-muted-foreground", children: active.kind === "local" ? (_jsx(IconDeviceDesktop, { size: isPage ? 15 : 13 })) : active.kind === "gateway" ? (_jsx(IconRoute, { size: isPage ? 15 : 13 })) : (_jsx(IconBolt, { size: isPage ? 15 : 13 })) }), _jsx("div", { className: "min-w-0 flex-1", title: active.description, children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("p", { className: cn("font-medium text-foreground", isPage ? "text-sm" : "text-[12px]"), children: active.label }), isConfigured ? (_jsxs("span", { className: "inline-flex items-center gap-1 text-[10px] font-medium text-primary", children: [_jsx(IconCheck, { size: 11 }), t("agentPanel.configured", {
123
+ defaultValue: "Configured",
124
+ })] })) : null] }) })] }), active.key ? (_jsxs("label", { className: "block space-y-1.5", children: [_jsx("span", { className: cn("font-medium text-foreground", isPage ? "text-xs" : "text-[11px]"), children: t("agentPanel.apiKey", { defaultValue: "API key" }) }), _jsx("input", { type: "password", value: apiKey, autoComplete: "off", spellCheck: false, placeholder: active.placeholder, disabled: saving, onChange: (event) => {
125
+ setApiKey(event.target.value);
126
+ if (error)
127
+ setError(null);
128
+ }, className: cn("w-full rounded-md border border-input bg-background text-foreground outline-none transition-colors hover:bg-accent/40 focus:ring-1 focus:ring-ring focus:ring-offset-1 focus:ring-offset-background placeholder:text-muted-foreground/50", isPage ? "h-10 px-3 text-sm" : "h-8 px-2.5 text-[12px]") })] })) : (_jsxs("div", { className: "flex items-start gap-2 rounded-md border border-border/70 bg-background/60 px-2.5 py-2", children: [_jsx(IconServer2, { size: isPage ? 15 : 13, className: "mt-0.5 shrink-0 text-muted-foreground" }), _jsx("p", { className: "text-[11px] leading-relaxed text-muted-foreground", children: t("agentPanel.noApiKeyNeeded", {
129
+ defaultValue: "No API key required.",
130
+ }) })] })), modelInputVisible ? (_jsxs("label", { className: "block space-y-1.5", children: [_jsx("span", { className: cn("font-medium text-foreground", isPage ? "text-xs" : "text-[11px]"), children: t("agentPanel.modelId", { defaultValue: "Model ID" }) }), _jsx("input", { type: "text", value: model, list: `agent-provider-models-${provider}`, disabled: saving, spellCheck: false, autoComplete: "off", placeholder: active.defaultModel, onChange: (event) => setModel(event.target.value), className: cn("w-full rounded-md border border-input bg-background text-foreground outline-none transition-colors hover:bg-accent/40 focus:ring-1 focus:ring-ring focus:ring-offset-1 focus:ring-offset-background placeholder:text-muted-foreground/50", isPage ? "h-10 px-3 text-sm" : "h-8 px-2.5 text-[12px]") }), _jsx("datalist", { id: `agent-provider-models-${provider}`, children: active.supportedModels.map((modelOption) => (_jsx("option", { value: modelOption }, modelOption))) })] })) : null, endpointVisible ? (_jsxs("div", { className: "border-t border-border/70 pt-2", children: [_jsxs("button", { type: "button", onClick: () => setEndpointOpen((open) => !open), className: "flex w-full items-center justify-between gap-2 text-start text-[11px] font-medium text-foreground", "aria-expanded": endpointOpen, title: provider === "ollama"
131
+ ? "Defaults to Ollama at http://localhost:11434."
132
+ : "Use for LiteLLM or another OpenAI-compatible gateway.", children: [_jsxs("span", { className: "inline-flex items-center gap-1.5", children: [_jsx(IconChevronDown, { size: 13, className: cn("text-muted-foreground transition-transform", !endpointOpen && "-rotate-90") }), t("agentPanel.endpointUrl", {
133
+ defaultValue: "Endpoint URL",
134
+ })] }), _jsx("span", { className: "text-[10px] font-normal text-muted-foreground", children: t("agentPanel.optional", { defaultValue: "Optional" }) })] }), endpointOpen ? (_jsx("div", { className: "mt-2 space-y-1.5", children: _jsx("input", { type: "url", value: endpoint, disabled: saving, spellCheck: false, autoComplete: "off", placeholder: active.endpointPlaceholder, onChange: (event) => setEndpoint(event.target.value), className: cn("w-full rounded-md border border-input bg-background text-foreground outline-none transition-colors hover:bg-accent/40 focus:ring-1 focus:ring-ring focus:ring-offset-1 focus:ring-offset-background placeholder:text-muted-foreground/50", isPage ? "h-10 px-3 text-sm" : "h-8 px-2.5 text-[12px]") }) })) : null] })) : null, _jsxs("div", { className: "flex flex-wrap items-center gap-2 pt-0.5", children: [_jsx("button", { type: "submit", disabled: saving || Boolean(active.key && !apiKey.trim()), className: cn("inline-flex items-center justify-center gap-1.5 rounded-md bg-foreground font-medium text-background transition-opacity hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50", isPage ? "h-9 px-3 text-xs" : "h-8 px-3 text-[11px]"), children: saving ? (_jsxs(_Fragment, { children: [_jsx(IconLoader2, { size: isPage ? 14 : 11, className: "animate-spin" }), t("agentPanel.savingProvider", {
135
+ defaultValue: "Saving...",
136
+ })] })) : saved ? (_jsxs(_Fragment, { children: [_jsx(IconCheck, { size: isPage ? 14 : 11 }), t("agentPanel.providerSaved", {
137
+ defaultValue: "Connected",
138
+ })] })) : provider === "ollama" ? (t("agentPanel.useProvider", {
139
+ provider: active.label,
140
+ defaultValue: `Use ${active.label}`,
141
+ })) : (t("agentPanel.saveAndUseProvider", {
142
+ provider: active.label,
143
+ defaultValue: `Save and use ${active.label}`,
144
+ })) }), active.docsUrl ? (_jsxs("a", { href: active.docsUrl, target: "_blank", rel: "noopener noreferrer", className: "inline-flex items-center gap-1 rounded-md px-1.5 py-1 text-[10px] font-medium text-muted-foreground no-underline transition-colors hover:bg-accent/40 hover:text-foreground", children: [t("agentPanel.getApiKey", { defaultValue: "Get an API key" }), _jsx(IconExternalLink, { size: 11 })] })) : null] }), error ? (_jsx("p", { className: "text-[11px] leading-relaxed text-destructive", children: error })) : null] })] }));
145
+ }
@@ -6,8 +6,9 @@ import { IconChevronDown, IconChevronRight, IconCheck, IconExternalLink, IconBra
6
6
  import React, { Suspense, lazy, useState, useEffect, useCallback, useMemo, useRef, } from "react";
7
7
  import { PROVIDER_ENV_PLACEHOLDERS } from "../../agent/engine/provider-env-vars.js";
8
8
  import { buildSettingsRoute } from "../../navigation/index.js";
9
- import { saveAgentEngineProviderSettings } from "../agent-engine-key.js";
9
+ import { saveAgentEngineProviderSettings, setAgentEngineProvider, } from "../agent-engine-key.js";
10
10
  import { AgentWorkspaceContent } from "../agent-page/AgentWorkspaceContent.js";
11
+ import { getAgentProviderOption, providerIdForEngine, } from "../agent-provider-catalog.js";
11
12
  import { agentNativePath } from "../api-path.js";
12
13
  import { BuilderBMark } from "../builder-mark.js";
13
14
  import { Popover, PopoverContent, PopoverTrigger, } from "../components/ui/popover.js";
@@ -24,6 +25,7 @@ import { AgentsSection } from "./AgentsSection.js";
24
25
  import { AutomationsSection } from "./AutomationsSection.js";
25
26
  import { DemoModeSection } from "./DemoModeSection.js";
26
27
  import { ExtensionsSettingsContent } from "./ExtensionsSettingsContent.js";
28
+ import { AgentProviderPicker } from "./ProviderSetupForm.js";
27
29
  import { SecretsSection } from "./SecretsSection.js";
28
30
  import { SettingsGroup, SettingsRow } from "./SettingsRow.js";
29
31
  import { SettingsSection, SettingsSurfaceProvider, useSettingsSurface, } from "./SettingsSection.js";
@@ -343,8 +345,8 @@ function LLMSectionInner({ builderFlow, builderLoading, connectUrl, connected, o
343
345
  const [baseUrlConfigured, setBaseUrlConfigured] = useState(false);
344
346
  const [clearBaseUrl, setClearBaseUrl] = useState(false);
345
347
  const [advancedOpen, setAdvancedOpen] = useState(false);
346
- const [manualSetupOpen, setManualSetupOpen] = useState(false);
347
348
  const [applyNote, setApplyNote] = useState(false);
349
+ const [applyError, setApplyError] = useState(null);
348
350
  const [testing, setTesting] = useState(false);
349
351
  const [testResult, setTestResult] = useState(null);
350
352
  const [settingsStatus, setSettingsStatus] = useState(null);
@@ -420,32 +422,26 @@ function LLMSectionInner({ builderFlow, builderLoading, connectUrl, connected, o
420
422
  envVar,
421
423
  builderConnected,
422
424
  });
423
- const manualSetupHint = selectedEngine === "ai-sdk:openrouter"
424
- ? "Provide an OpenRouter key to use OpenRouter models like GLM 5.2."
425
- : "Choose your AI provider and model.";
425
+ const manualSetupHint = "Select a provider.";
426
426
  const engineChanged = selectedEngine !== currentEngine || selectedModel !== currentModel;
427
- const isOpenAiEngine = selectedEngine === "ai-sdk:openai";
428
- const endpointChanged = isOpenAiEngine && (!!baseUrl.trim() || clearBaseUrl);
427
+ const selectedProvider = providerIdForEngine(selectedEngine) ?? "anthropic";
428
+ const selectedProviderOption = getAgentProviderOption(selectedProvider);
429
+ const isEndpointProvider = selectedProviderOption.supportsEndpoint === true;
430
+ const endpointChanged = isEndpointProvider && (!!baseUrl.trim() || clearBaseUrl);
429
431
  const providerSettingsChanged = !!apiKey.trim() || endpointChanged;
430
- // Hide the Anthropic-via-AI-SDK alias (redundant with the native entry)
431
- // and Ollama (no API key to set here). The currently-selected engine is
432
- // always kept so a stale setting doesn't vanish from the picker.
433
- const providerOptions = engines
434
- .filter((e) => e.name === selectedEngine ||
435
- (e.name !== "ai-sdk:anthropic" && e.name !== "ai-sdk:ollama"))
436
- .map((e) => ({ value: e.name, label: e.label }));
437
432
  const modelOptions = latestModelsOnly(selectedEngineInfo?.supportedModels ?? []).map((m) => ({ value: m, label: friendlyModelName(m) }));
438
433
  const handleSave = async () => {
439
- if (!providerSettingsChanged || !envVar)
434
+ if (!providerSettingsChanged || (!envVar && !isEndpointProvider))
440
435
  return;
441
436
  setSaving(true);
442
437
  try {
443
- const nextBaseUrl = isOpenAiEngine ? baseUrl.trim() : "";
438
+ const nextBaseUrl = isEndpointProvider ? baseUrl.trim() : "";
444
439
  await saveAgentEngineProviderSettings({
445
- key: envVar,
440
+ provider: selectedProvider,
441
+ ...(envVar ? { key: envVar } : {}),
446
442
  ...(apiKey.trim() ? { apiKey } : {}),
447
443
  ...(nextBaseUrl ? { baseUrl: nextBaseUrl } : {}),
448
- ...(isOpenAiEngine && clearBaseUrl ? { clearBaseUrl: true } : {}),
444
+ ...(isEndpointProvider && clearBaseUrl ? { clearBaseUrl: true } : {}),
449
445
  });
450
446
  setSaved(true);
451
447
  setApiKey("");
@@ -527,40 +523,46 @@ function LLMSectionInner({ builderFlow, builderLoading, connectUrl, connected, o
527
523
  }
528
524
  };
529
525
  const handleApply = async () => {
526
+ setApplyError(null);
530
527
  try {
531
- const res = await fetch(agentNativePath("/_agent-native/actions/manage-agent-engine"), {
532
- method: "POST",
533
- headers: { "Content-Type": "application/json" },
534
- body: JSON.stringify({
535
- action: "set",
536
- engine: selectedEngine,
537
- model: selectedModel,
538
- }),
528
+ await setAgentEngineProvider({
529
+ provider: selectedProvider,
530
+ model: selectedModel,
539
531
  });
540
- if (res.ok) {
541
- setCurrentEngine(selectedEngine);
542
- setCurrentModel(selectedModel);
543
- setApplyNote(true);
544
- refreshSettingsStatus();
545
- notifyConfigChanged();
546
- setTimeout(() => setApplyNote(false), 4000);
547
- }
532
+ setCurrentEngine(selectedEngine);
533
+ setCurrentModel(selectedModel);
534
+ setApplyNote(true);
535
+ refreshSettingsStatus();
536
+ notifyConfigChanged();
537
+ setTimeout(() => setApplyNote(false), 4000);
538
+ }
539
+ catch (err) {
540
+ setApplyError(err instanceof Error ? err.message : String(err));
548
541
  }
549
- catch { }
550
542
  };
551
- return (_jsx(SettingsSection, { id: settingsSectionDomId("llm"), icon: _jsx(IconBrain, { size: 14 }), title: "LLM", subtitle: "Use Builder.io free credits or your own LLM provider.", required: true, connected: initialLoading ? undefined : anyKeyConfigured, grouped: isPage && grouped, open: open, onToggle: onToggle, children: initialLoading ? (_jsx(SettingsLoadingRow, { controlCount: 2 })) : (_jsxs("div", { className: cn(isPage
543
+ return (_jsx(SettingsSection, { id: settingsSectionDomId("llm"), icon: _jsx(IconBrain, { size: 14 }), title: "LLM", required: true, connected: initialLoading ? undefined : anyKeyConfigured, subtitle: isPage
544
+ ? undefined
545
+ : t("agentPanel.builderOrOwnKeys", {
546
+ defaultValue: "Choose Builder.io or custom keys.",
547
+ }), grouped: isPage && grouped, open: open, onToggle: onToggle, children: initialLoading ? (_jsx(SettingsLoadingRow, { controlCount: 2 })) : (_jsxs("div", { className: cn(isPage
552
548
  ? "flex items-center justify-between gap-4 px-5 py-4 sm:px-6"
553
- : "space-y-2"), children: [isPage && (_jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "text-sm font-medium text-foreground", children: "Connect an LLM" }), _jsx("p", { className: "mt-1 text-sm leading-6 text-muted-foreground", children: "Use Builder.io free credits or your own provider." })] })), _jsxs("div", { className: cn(isPage && "flex flex-wrap items-center justify-end gap-2"), children: [_jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "llm_settings", trackingFlow: "connect_llm", label: "Connect Builder.io", compact: isPage }), builderConnected && !isPage && (_jsxs(Button, { type: "button", intent: "neutral", emphasis: "ghost", "aria-expanded": manualSetupOpen, "aria-controls": "llm-manual-setup", onClick: () => setManualSetupOpen((open) => !open), className: cn("inline-flex items-center gap-1 px-0.5 text-muted-foreground hover:text-foreground", isPage ? "text-xs" : "text-[10px]"), children: [t("agentPanel.addOwnKeys", {
554
- defaultValue: "Add your own keys",
555
- }), _jsx(IconChevronDown, { size: isPage ? 14 : 11, className: cn("transition-transform", manualSetupOpen && "rotate-180") })] })), (!builderConnected || manualSetupOpen || isPage) && (_jsx(ManualSetupCard, { id: "llm-manual-setup", title: "Custom keys", hint: manualSetupHint, sourceBadge: builderConnected ? undefined : sourceBadge, bare: isPage, popover: isPage, popoverLabel: "Custom keys", children: _jsxs("div", { className: "space-y-2 mb-1", children: [_jsx(SettingsSelect, { label: "Provider", value: selectedEngine, options: providerOptions, onValueChange: (val) => {
556
- setSelectedEngine(val);
557
- const info = engines.find((e) => e.name === val);
558
- setSelectedModel(info?.defaultModel ?? "");
549
+ : "flex items-center justify-between gap-2"), children: [isPage && (_jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "text-sm font-medium text-foreground", children: "AI provider" }), _jsx("p", { className: "mt-1 text-sm leading-6 text-muted-foreground", children: t("agentPanel.builderOrOwnKeys", {
550
+ defaultValue: "Choose Builder.io or custom keys.",
551
+ }) })] })), _jsxs("div", { className: cn("flex flex-wrap items-center justify-end gap-2"), children: [_jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "llm_settings", trackingFlow: "connect_llm", label: "Connect Builder.io", compact: true }), _jsx(ManualSetupCard, { id: "llm-manual-setup", title: "Custom keys", hint: manualSetupHint, sourceBadge: builderConnected ? undefined : sourceBadge, bare: isPage, popover: true, popoverLabel: settingsConfigured
552
+ ? "Manage"
553
+ : t("agentPanel.addOwnKeys", {
554
+ defaultValue: "Custom keys",
555
+ }), children: _jsxs("div", { className: "space-y-2 mb-1", children: [_jsx(AgentProviderPicker, { value: selectedProvider, layout: isPage ? "page" : "compact", onChange: (provider) => {
556
+ const option = getAgentProviderOption(provider);
557
+ setSelectedEngine(option.engine);
558
+ setSelectedModel(option.defaultModel);
559
559
  setApiKey("");
560
560
  setBaseUrl("");
561
561
  setClearBaseUrl(false);
562
562
  setAdvancedOpen(false);
563
- } }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("p", { className: fieldLabelClass(isPage), children: "Model" }), _jsx("input", { type: "text", list: `model-suggestions-${selectedEngine}`, value: selectedModel, onChange: (e) => setSelectedModel(e.target.value), placeholder: selectedEngineInfo?.defaultModel ?? "e.g. model-id", spellCheck: false, autoComplete: "off", className: textInputClass(isPage), style: isPage ? CONTROL_STYLE_PAGE : CONTROL_STYLE }), modelOptions.length > 0 && (_jsx("datalist", { id: `model-suggestions-${selectedEngine}`, children: modelOptions.map((opt) => (_jsx("option", { value: opt.value, label: opt.label }, opt.value))) }))] }), isOpenAiEngine && (_jsxs("div", { className: "border-t border-border/70 pt-2", children: [_jsxs(Button, { type: "button", onClick: () => setAdvancedOpen((v) => !v), className: "flex w-full cursor-pointer items-center justify-between gap-2 rounded px-0.5 py-1 text-left hover:text-foreground", children: [_jsxs("span", { className: "inline-flex min-w-0 items-center gap-1.5 text-[11px] font-medium text-foreground", children: [advancedOpen ? (_jsx(IconChevronDown, { size: 12 })) : (_jsx(IconChevronRight, { size: 12, className: "rtl:-scale-x-100" })), "Advanced"] }), _jsx("span", { className: "truncate text-[10px] text-muted-foreground", children: "OpenAI-compatible endpoint" })] }), advancedOpen && (_jsxs("div", { className: "mt-1.5 space-y-1.5", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("p", { className: "text-[12px] font-medium text-foreground", children: "Endpoint URL" }), _jsx("span", { className: "text-[10px] text-muted-foreground", children: baseUrlConfigured ? "Configured" : "Optional" })] }), _jsx("input", { type: "url", value: baseUrl, onChange: (e) => {
563
+ } }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("p", { className: fieldLabelClass(isPage), children: "Model" }), _jsx("input", { type: "text", list: `model-suggestions-${selectedEngine}`, value: selectedModel, onChange: (e) => setSelectedModel(e.target.value), placeholder: selectedEngineInfo?.defaultModel ?? "e.g. model-id", spellCheck: false, autoComplete: "off", className: textInputClass(isPage), style: isPage ? CONTROL_STYLE_PAGE : CONTROL_STYLE }), modelOptions.length > 0 && (_jsx("datalist", { id: `model-suggestions-${selectedEngine}`, children: modelOptions.map((opt) => (_jsx("option", { value: opt.value, label: opt.label }, opt.value))) }))] }), isEndpointProvider && (_jsxs("div", { className: "border-t border-border/70 pt-2", children: [_jsxs(Button, { type: "button", onClick: () => setAdvancedOpen((v) => !v), className: "flex w-full cursor-pointer items-center justify-between gap-2 rounded px-0.5 py-1 text-left hover:text-foreground", children: [_jsxs("span", { className: "inline-flex min-w-0 items-center gap-1.5 text-[11px] font-medium text-foreground", children: [advancedOpen ? (_jsx(IconChevronDown, { size: 12 })) : (_jsx(IconChevronRight, { size: 12, className: "rtl:-scale-x-100" })), "Advanced"] }), _jsx("span", { className: "truncate text-[10px] text-muted-foreground", children: selectedProvider === "ollama"
564
+ ? "Local Ollama endpoint"
565
+ : "OpenAI-compatible endpoint" })] }), advancedOpen && (_jsxs("div", { className: "mt-1.5 space-y-1.5", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("p", { className: "text-[12px] font-medium text-foreground", children: "Endpoint URL" }), _jsx("span", { className: "text-[10px] text-muted-foreground", children: baseUrlConfigured ? "Configured" : "Optional" })] }), _jsx("input", { type: "url", value: baseUrl, onChange: (e) => {
564
566
  setBaseUrl(e.target.value);
565
567
  if (e.target.value.trim())
566
568
  setClearBaseUrl(false);
@@ -569,14 +571,16 @@ function LLMSectionInner({ builderFlow, builderLoading, connectUrl, connected, o
569
571
  handleSave();
570
572
  }, placeholder: baseUrlConfigured
571
573
  ? "Leave blank to keep current endpoint"
572
- : "https://gateway.example/v1", disabled: clearBaseUrl, spellCheck: false, autoComplete: "off", className: "flex h-9 w-full rounded-md border border-border bg-background px-3 text-[12px] text-foreground outline-none transition-colors hover:bg-accent/40 focus:ring-1 focus:ring-accent disabled:opacity-50 placeholder:text-muted-foreground/50", style: CONTROL_STYLE }), _jsx("p", { className: "text-[10px] leading-relaxed text-muted-foreground", children: "Use for LiteLLM or another OpenAI-compatible chat gateway. Leave blank for OpenAI." }), baseUrlConfigured && (_jsxs("label", { className: "flex items-center gap-1.5 text-[10px] text-muted-foreground", children: [_jsx(Checkbox, { checked: clearBaseUrl, onChange: (checked) => {
574
+ : selectedProvider === "ollama"
575
+ ? "http://localhost:11434"
576
+ : "https://gateway.example/v1", disabled: clearBaseUrl, spellCheck: false, autoComplete: "off", className: "flex h-9 w-full rounded-md border border-border bg-background px-3 text-[12px] text-foreground outline-none transition-colors hover:bg-accent/40 focus:ring-1 focus:ring-accent disabled:opacity-50 placeholder:text-muted-foreground/50", style: CONTROL_STYLE }), baseUrlConfigured && (_jsxs("label", { className: "flex items-center gap-1.5 text-[10px] text-muted-foreground", children: [_jsx(Checkbox, { checked: clearBaseUrl, onChange: (checked) => {
573
577
  setClearBaseUrl(checked);
574
578
  if (checked)
575
579
  setBaseUrl("");
576
- }, "aria-label": "Clear saved endpoint override", className: "shrink-0" }), "Clear saved endpoint override"] })), envVar && envConfigured && endpointChanged && (_jsx(Button, { type: "button", intent: "neutral", emphasis: "solid", onClick: handleSave, disabled: saving, className: "rounded bg-accent px-2.5 py-1 text-[10px] font-medium text-foreground hover:bg-accent/80 disabled:opacity-40", children: saving ? (_jsx(IconLoader2, { size: 10, className: "animate-spin" })) : saved ? (_jsx(IconCheck, { size: 10 })) : ("Save endpoint") }))] }))] })), envVar && envConfigured ? (_jsxs("div", { className: cn("flex items-center gap-1.5 text-primary", isPage ? "text-xs" : "text-[10px]"), children: [_jsx(IconCheck, { size: isPage ? 14 : 10 }), envVar, " configured"] })) : envVar ? (_jsxs("div", { className: "flex gap-1.5", children: [_jsx("input", { type: "password", value: apiKey, onChange: (e) => setApiKey(e.target.value), onKeyDown: (e) => {
580
+ }, "aria-label": "Clear saved endpoint override", className: "shrink-0" }), "Clear saved endpoint override"] })), endpointChanged && (_jsx(Button, { type: "button", intent: "neutral", emphasis: "solid", onClick: handleSave, disabled: saving, className: "rounded bg-accent px-2.5 py-1 text-[10px] font-medium text-foreground hover:bg-accent/80 disabled:opacity-40", children: saving ? (_jsx(IconLoader2, { size: 10, className: "animate-spin" })) : saved ? (_jsx(IconCheck, { size: 10 })) : ("Save endpoint") }))] }))] })), envVar && envConfigured ? (_jsxs("div", { className: cn("flex items-center gap-1.5 text-primary", isPage ? "text-xs" : "text-[10px]"), children: [_jsx(IconCheck, { size: isPage ? 14 : 10 }), envVar, " configured"] })) : envVar ? (_jsxs("div", { className: "flex gap-1.5", children: [_jsx("input", { type: "password", value: apiKey, onChange: (e) => setApiKey(e.target.value), onKeyDown: (e) => {
577
581
  if (e.key === "Enter")
578
582
  handleSave();
579
- }, placeholder: PROVIDER_ENV_PLACEHOLDERS[envVar] ?? "...", className: cn(textInputClass(isPage), "flex-1"), style: isPage ? CONTROL_STYLE_PAGE : undefined }), _jsx(Button, { intent: "primary", emphasis: "solid", onClick: handleSave, disabled: !providerSettingsChanged || saving, className: pillButtonClass(isPage, "solid"), children: saving ? (_jsx(IconLoader2, { size: isPage ? 14 : 10, className: "animate-spin" })) : saved ? (_jsx(IconCheck, { size: isPage ? 14 : 10 })) : ("Save") })] })) : null, _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { intent: "neutral", emphasis: "outline", onClick: handleTest, disabled: testing, className: pillButtonClass(isPage, "outline"), children: testing ? (_jsxs("span", { className: "flex items-center gap-1", children: [_jsx(IconLoader2, { size: isPage ? 14 : 10, className: "animate-spin" }), "Testing\u2026"] })) : ("Test") }), PROVIDER_DOCS[selectedEngine] ? (_jsxs("a", { href: PROVIDER_DOCS[selectedEngine], target: "_blank", rel: "noopener noreferrer", className: cn(pillButtonClass(isPage, "outline"), "no-underline"), children: ["Get an API key", _jsx(IconExternalLink, { size: isPage ? 14 : 10 })] })) : null, engineChanged && (_jsx(Button, { intent: "primary", emphasis: "solid", onClick: handleApply, className: pillButtonClass(isPage, "solid"), children: "Apply" })), settingsStatus != null && (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { intent: "danger", emphasis: "outline", onClick: handleDisconnect, className: cn(pillButtonClass(isPage, "outline"), "ms-auto text-muted-foreground hover:bg-destructive/10 hover:text-destructive hover:border-destructive/40"), children: "Disconnect" }) }), _jsx(TooltipContent, { children: "Clear the saved engine \u2014 the app will fall back to the default until you re-apply." })] }))] }), testResult && testResult.ok && (_jsxs("p", { className: cn("flex items-center gap-1 text-primary", isPage ? "text-xs" : "text-[10px]"), children: [_jsx(IconCheck, { size: isPage ? 14 : 10 }), "Test passed \u2014 ", testResult.latencyMs, "ms"] })), testResult && testResult.ok === false && (_jsxs("p", { className: cn("text-destructive", isPage ? "text-xs" : "text-[10px]"), children: ["Test failed: ", testResult.error] })), disconnectError && (_jsxs("p", { className: cn("text-destructive", isPage ? "text-xs" : "text-[10px]"), children: ["Disconnect failed: ", disconnectError] })), applyNote && (_jsx("p", { className: cn("text-muted-foreground", isPage ? "text-xs" : "text-[10px]"), children: "Changes take effect on next conversation" }))] }) }))] })] })) }));
583
+ }, placeholder: PROVIDER_ENV_PLACEHOLDERS[envVar] ?? "...", className: cn(textInputClass(isPage), "flex-1"), style: isPage ? CONTROL_STYLE_PAGE : undefined }), _jsx(Button, { intent: "primary", emphasis: "solid", onClick: handleSave, disabled: !providerSettingsChanged || saving, className: pillButtonClass(isPage, "solid"), children: saving ? (_jsx(IconLoader2, { size: isPage ? 14 : 10, className: "animate-spin" })) : saved ? (_jsx(IconCheck, { size: isPage ? 14 : 10 })) : ("Save") })] })) : null, _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { intent: "neutral", emphasis: "outline", onClick: handleTest, disabled: testing, className: pillButtonClass(isPage, "outline"), children: testing ? (_jsxs("span", { className: "flex items-center gap-1", children: [_jsx(IconLoader2, { size: isPage ? 14 : 10, className: "animate-spin" }), "Testing\u2026"] })) : ("Test") }), PROVIDER_DOCS[selectedEngine] ? (_jsxs("a", { href: PROVIDER_DOCS[selectedEngine], target: "_blank", rel: "noopener noreferrer", className: cn(pillButtonClass(isPage, "outline"), "no-underline"), children: ["Get an API key", _jsx(IconExternalLink, { size: isPage ? 14 : 10 })] })) : null, engineChanged && (_jsx(Button, { intent: "primary", emphasis: "solid", onClick: handleApply, className: pillButtonClass(isPage, "solid"), children: "Apply" })), settingsStatus != null && (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { intent: "danger", emphasis: "outline", onClick: handleDisconnect, className: cn(pillButtonClass(isPage, "outline"), "ms-auto text-muted-foreground hover:bg-destructive/10 hover:text-destructive hover:border-destructive/40"), children: "Disconnect" }) }), _jsx(TooltipContent, { children: "Clear the saved engine \u2014 the app will fall back to the default until you re-apply." })] }))] }), testResult && testResult.ok && (_jsxs("p", { className: cn("flex items-center gap-1 text-primary", isPage ? "text-xs" : "text-[10px]"), children: [_jsx(IconCheck, { size: isPage ? 14 : 10 }), "Test passed \u2014 ", testResult.latencyMs, "ms"] })), testResult && testResult.ok === false && (_jsxs("p", { className: cn("text-destructive", isPage ? "text-xs" : "text-[10px]"), children: ["Test failed: ", testResult.error] })), disconnectError && (_jsxs("p", { className: cn("text-destructive", isPage ? "text-xs" : "text-[10px]"), children: ["Disconnect failed: ", disconnectError] })), applyError && (_jsxs("p", { className: cn("text-destructive", isPage ? "text-xs" : "text-[10px]"), children: ["Apply failed: ", applyError] })), applyNote && (_jsx("p", { className: cn("text-muted-foreground", isPage ? "text-xs" : "text-[10px]"), children: "Changes take effect on next conversation" }))] }) })] })] })) }));
580
584
  }
581
585
  function friendlyAppName(appId) {
582
586
  return appId
@@ -587,7 +591,7 @@ function friendlyAppName(appId) {
587
591
  }
588
592
  function AppDefaultModelPicker({ engines, value, disabled, onChange, }) {
589
593
  const [open, setOpen] = useState(false);
590
- const visibleEngines = engines.filter((engine) => engine.name !== "ai-sdk:anthropic" && engine.name !== "ai-sdk:ollama");
594
+ const visibleEngines = engines.filter((engine) => engine.name !== "ai-sdk:anthropic");
591
595
  const selectedModel = value.includes("::")
592
596
  ? value.slice(value.indexOf("::") + 2)
593
597
  : null;
@@ -664,8 +668,7 @@ function AppModelDefaultsSectionInner({ grouped = false, open, onToggle, }) {
664
668
  useEffect(() => load(), [load]);
665
669
  const selectedEngineInfo = settings?.engines.find((engine) => engine.name === selectedEngine) ?? null;
666
670
  const engineOptions = (settings?.engines ?? [])
667
- .filter((engine) => engine.name === selectedEngine ||
668
- (engine.name !== "ai-sdk:anthropic" && engine.name !== "ai-sdk:ollama"))
671
+ .filter((engine) => engine.name === selectedEngine || engine.name !== "ai-sdk:anthropic")
669
672
  .map((engine) => ({
670
673
  value: engine.name,
671
674
  label: engine.name === "builder"
@@ -8,3 +8,5 @@ export { SettingsGroup, SettingsRow, type SettingsGroupProps, type SettingsRowPr
8
8
  export { SettingsSection, SettingsSurfaceProvider, useSettingsSurface, type SettingsSurface, } from "./SettingsSection.js";
9
9
  export { SettingsLoadingRow, SettingsSkeleton, type SettingsLoadingRowProps, type SettingsSkeletonProps, } from "./SettingsSkeleton.js";
10
10
  export { normalizeSettingsSection, settingsSectionDomId, useSettingsPanelController, type SettingsPanelController, type SettingsPanelControllerOptions, } from "./useSettingsPanelController.js";
11
+ export { AGENT_PROVIDER_CATALOG, getAgentProviderOption, providerIdForEngine, type AgentProviderId, type AgentProviderOption, } from "../agent-provider-catalog.js";
12
+ export { AgentProviderPicker, AgentProviderSetupForm, type AgentProviderPickerProps, type AgentProviderSetupFormProps, } from "./ProviderSetupForm.js";
@@ -8,3 +8,5 @@ export { SettingsGroup, SettingsRow, } from "./SettingsRow.js";
8
8
  export { SettingsSection, SettingsSurfaceProvider, useSettingsSurface, } from "./SettingsSection.js";
9
9
  export { SettingsLoadingRow, SettingsSkeleton, } from "./SettingsSkeleton.js";
10
10
  export { normalizeSettingsSection, settingsSectionDomId, useSettingsPanelController, } from "./useSettingsPanelController.js";
11
+ export { AGENT_PROVIDER_CATALOG, getAgentProviderOption, providerIdForEngine, } from "../agent-provider-catalog.js";
12
+ export { AgentProviderPicker, AgentProviderSetupForm, } from "./ProviderSetupForm.js";
@@ -30,6 +30,7 @@
30
30
  */
31
31
  import type { ActionRunContext } from "../action.js";
32
32
  import type { ActionEntry } from "../agent/production-agent.js";
33
+ export type SandboxCodeMode = "run-code" | "tool-orchestration";
33
34
  export interface RunCodeOptions {
34
35
  /**
35
36
  * Extra tool names (beyond the default set) that the sandbox bridge will
@@ -54,6 +55,10 @@ export interface ExecuteSandboxCodeOptions {
54
55
  getActions: () => Record<string, ActionEntry>;
55
56
  /** Extra bridge tool names beyond the defaults. */
56
57
  extraBridgeTools?: Set<string>;
58
+ /** Host policy and helper surface to use inside the sandbox. */
59
+ mode?: SandboxCodeMode;
60
+ /** Maximum number of bridged child-tool calls for orchestration mode. */
61
+ maxToolCalls?: number;
57
62
  /** Request context (owner/org) applied to bridged tool calls. */
58
63
  context?: ActionRunContext;
59
64
  }