@openhands/agent-canvas 1.11.0 → 1.12.0
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/README.md +1 -1
- package/README.windows.md +2 -2
- package/build/assets/{active-backend-context-BBvP-bI2.js → active-backend-context-B_bg5h9O.js} +1 -1
- package/build/assets/{agent-context-settings-Bno1nwPL.js → agent-context-settings-CzWS1DgR.js} +1 -1
- package/build/assets/{agent-profiles-settings-DqoS7RL3.js → agent-profiles-settings-hd2cAIZM.js} +1 -1
- package/build/assets/{agent-server-conversation-service.api-0RztSdJJ.js → agent-server-conversation-service.api-BheU4HX3.js} +3 -3
- package/build/assets/{agent-settings-h8hQz-IU.js → agent-settings-D2J1dKVd.js} +1 -1
- package/build/assets/agent-settings-D_SVrWhI.js +1 -0
- package/build/assets/{api-key-entry-screen-Qsb83VX1.js → api-key-entry-screen-D70oJj2c.js} +1 -1
- package/build/assets/app-settings-D0OKHB3M.js +1 -0
- package/build/assets/{automation-detail-7LkL91j9.js → automation-detail-C0SxWTnQ.js} +1 -1
- package/build/assets/{automation-export-U2QNyNy6.js → automation-export-BjQa_fT6.js} +1 -1
- package/build/assets/{automation-setup-route-Bel9_Lue.js → automation-setup-route-BzCNpljP.js} +1 -1
- package/build/assets/{automation-templates-CRLDssaI.js → automation-templates-D9gs6ihu.js} +1 -1
- package/build/assets/{automations-list-B2iV3ZKW.js → automations-list-dZg8zOCl.js} +1 -1
- package/build/assets/backend-form-modal-HwclKrUS.js +1 -0
- package/build/assets/{backend-synced-settings-badge-DB4717Iu.js → backend-synced-settings-badge-YI3k09Wr.js} +1 -1
- package/build/assets/{badge-yR53enbU.js → badge-Dpn3-LTV.js} +1 -1
- package/build/assets/{browser-tab-A94o33Vn.js → browser-tab-4CPdj3J3.js} +1 -1
- package/build/assets/{cloud-funnel-analytics-CBQdPZl_.js → cloud-funnel-analytics-CcSez21r.js} +1 -1
- package/build/assets/{command-menu-PsRXVBz2.js → command-menu-BKOLj-TC.js} +1 -1
- package/build/assets/{condenser-settings-w7L3EFvm.js → condenser-settings-DLQogFnB.js} +1 -1
- package/build/assets/{conversation-BUzBZAc-.js → conversation-Cmyw9bFh.js} +3 -3
- package/build/assets/conversation-CvoGZ5vC.js +1 -0
- package/build/assets/conversation-panel-CvoGZ5vC.js +1 -0
- package/build/assets/{conversation-state-store-Bs8gA263.js → conversation-state-store-CtgILxWF.js} +1 -1
- package/build/assets/{entry.client-DzbDyLkV.js → entry.client-EBn3ZtNP.js} +1 -1
- package/build/assets/{extensions-hub-DOmHPGTA.js → extensions-hub-BZ3HYztk.js} +1 -1
- package/build/assets/{extensions-navigation-6zU5M1pa.js → extensions-navigation-Bnx32WAb.js} +1 -1
- package/build/assets/{files-tab-BzofBmF4.js → files-tab-D5zuU5TY.js} +1 -1
- package/build/assets/format-model-name-BN_rx1Y3.js +1 -0
- package/build/assets/{format-relative-time-DGPQlPx0.js → format-relative-time-BerVamGl.js} +1 -1
- package/build/assets/{git-provider-icon-BLMlwy02.js → git-provider-icon-zuKz2cwo.js} +1 -1
- package/build/assets/{git-repo-dropdown-B7RvO70z.js → git-repo-dropdown-B5VnMftu.js} +1 -1
- package/build/assets/git-repo-dropdown-Bo7ciSNX.js +1 -0
- package/build/assets/home-DqyeeFA_.js +1 -0
- package/build/assets/{home-DEZaZqYv.js → home-Y0aDou-a.js} +1 -1
- package/build/assets/{home-store-D7CjMN_-.js → home-store-Cubdq0uP.js} +1 -1
- package/build/assets/index-home-DqyeeFA_.js +1 -0
- package/build/assets/info-C9lpK5pw.js +1 -0
- package/build/assets/{install-server-modal-BTwK5lbi.js → install-server-modal-DW6dSyGr.js} +1 -1
- package/build/assets/{kebab-menu-item-content-C4CkeiJO.js → kebab-menu-item-content-CKydZrbH.js} +1 -1
- package/build/assets/{launch-DVlx4v9o.js → launch-c4GEP8oo.js} +1 -1
- package/build/assets/llm-not-configured-banner-DCE_0B4I.js +27 -0
- package/build/assets/llm-settings-8eWBF3Ij.js +1 -0
- package/build/assets/llm-settings-D9ko42Pi.js +1 -0
- package/build/assets/{llm-subscription-service-7eVfDIE1.js → llm-subscription-service-B3K-97lP.js} +1 -1
- package/build/assets/{manage-backends-modal-HNWKLYhq.js → manage-backends-modal-DTCME_Pu.js} +1 -1
- package/build/assets/manifest-e1c3bc3b.js +1 -0
- package/build/assets/{manifest-subpage-layout-D-m-h8_t.js → manifest-subpage-layout-Ds7G5GZ8.js} +1 -1
- package/build/assets/{markdown-renderer-BiiiIznl.js → markdown-renderer-DzvgdZey.js} +1 -1
- package/build/assets/{mcp-gLgPP7AS.js → mcp-Da7wFU7A.js} +1 -1
- package/build/assets/{mcp-marketplace-utils-Dr_HG_ol.js → mcp-marketplace-utils-DSLou4n7.js} +1 -1
- package/build/assets/messages-B2l0qLVk.js +40 -0
- package/build/assets/{npm-DRzJF5Zn.js → npm-Cswluf6-.js} +1 -1
- package/build/assets/{onboarding-DSr8hOpk.js → onboarding-BKbTYlW1.js} +1 -1
- package/build/assets/{onboarding-modal-C6Euf7N4.js → onboarding-modal-DrrGlEQq.js} +1 -1
- package/build/assets/{organization-service.api-C4GYWwj9.js → organization-service.api-DK8csGvK.js} +1 -1
- package/build/assets/{planner-tab-CZcguxTK.js → planner-tab-D-mZ1I7n.js} +1 -1
- package/build/assets/{plugins-management-service-BmDzOQsd.js → plugins-management-service-CywqyfLK.js} +1 -1
- package/build/assets/{profiles-service.api-CoLTwjc1.js → profiles-service.api-vcE_jJCe.js} +1 -1
- package/build/assets/{providers-tBzTpz1w.js → providers-CdrPPm0z.js} +1 -1
- package/build/assets/{proxy-DCI9S-LA.js → proxy-b4aqL4SX.js} +1 -1
- package/build/assets/{recommended-automations-launcher-BbJAJVED.js → recommended-automations-launcher-DIYgUdMm.js} +1 -1
- package/build/assets/root-CBBi6IAe.js +2 -0
- package/build/assets/root-CRK9fOFd.css +1 -0
- package/build/assets/root-layout-BFN2rCOy.js +2 -0
- package/build/assets/{schema-field-zhsVDllR.js → schema-field-Z-rMg9ti.js} +1 -1
- package/build/assets/{sdk-section-page-CS4GWn0y.js → sdk-section-page-m_cjygxR.js} +1 -1
- package/build/assets/{secrets-service-DjgGduXk.js → secrets-service-BHF4fxML.js} +1 -1
- package/build/assets/{secrets-settings-f-qahfbi.js → secrets-settings-aBGXtQhX.js} +1 -1
- package/build/assets/{settings-DQd0xLEw.js → settings-CZPGNens.js} +1 -1
- package/build/assets/{settings-index-BVwy9JEp.js → settings-index-Dtlegd6s.js} +1 -1
- package/build/assets/{settings-modal-D6D5ngET.js → settings-modal-CalzJP2l.js} +1 -1
- package/build/assets/{settings-service.api-DZp7E7A9.js → settings-service.api-DfPYaxZJ.js} +1 -1
- package/build/assets/{shared-conversation-DIxZjElr.js → shared-conversation-BIAcuORk.js} +1 -1
- package/build/assets/{sidebar-mobile-menu-toggle-OoZBHxaT.js → sidebar-mobile-menu-toggle-C6HpQ55l.js} +1 -1
- package/build/assets/{skill-scope-B1Per5XI.js → skill-scope-Dt1V5X2p.js} +1 -1
- package/build/assets/{skills-plugins-B3AO6sMz.js → skills-plugins-B-H0cCOP.js} +1 -1
- package/build/assets/{skills-settings-DmmgJ6CT.js → skills-settings-DthdIDUb.js} +1 -1
- package/build/assets/{task-list-tab-C19Q_JNf.js → task-list-tab-BTde81Iu.js} +1 -1
- package/build/assets/{terminal-CbuMRYal.js → terminal-DRrJo-cR.js} +1 -1
- package/build/assets/{use-acp-credential-form-DJmmJKMo.js → use-acp-credential-form-DGyo0QvL.js} +1 -1
- package/build/assets/{use-activate-agent-profile-CeFfYwu_.js → use-activate-agent-profile-DrPzIKJF.js} +1 -1
- package/build/assets/{use-agent-profiles-BHLkLbRR.js → use-agent-profiles-B37XWDLq.js} +1 -1
- package/build/assets/{use-can-manage-org-profiles-Bu4y8gSx.js → use-can-manage-org-profiles-BzE28H3x.js} +1 -1
- package/build/assets/{use-cloud-current-user-id-QD9x3ySO.js → use-cloud-current-user-id-U6HPjy50.js} +1 -1
- package/build/assets/{use-config-CPwqciXN.js → use-config-DapX3pjC.js} +1 -1
- package/build/assets/{use-create-conversation-DWaSVUOB.js → use-create-conversation-CRf1dCm5.js} +1 -1
- package/build/assets/{use-create-secret-BsOfOLvg.js → use-create-secret-DcpT3RPw.js} +1 -1
- package/build/assets/{use-llm-profiles-CU8ah1pz.js → use-llm-profiles-CHxqoHiN.js} +1 -1
- package/build/assets/{use-manifest-capabilities-DBYPjyV6.js → use-manifest-capabilities-zihHDbMW.js} +1 -1
- package/build/assets/{use-onboarding-completion-CtNJxkXG.js → use-onboarding-completion-6kWOW3ul.js} +1 -1
- package/build/assets/{use-plugins-marketplace-ZEA-gyM9.js → use-plugins-marketplace-CqkR8kLE.js} +1 -1
- package/build/assets/{use-save-agent-profile-DgI_Uz9a.js → use-save-agent-profile-CH9_EXQG.js} +1 -1
- package/build/assets/{use-save-settings-pSjRhD_m.js → use-save-settings-DIEurJ3D.js} +1 -1
- package/build/assets/{use-settings-BHWsOcIG.js → use-settings-BmLzQjnC.js} +1 -1
- package/build/assets/{use-settings-nav-items-CQgkxUbV.js → use-settings-nav-items-DV9qgeu7.js} +1 -1
- package/build/assets/{use-tracking-CGCsBWhg.js → use-tracking-salbPJxT.js} +1 -1
- package/build/assets/{use-user-conversation-DRfEU8x0.js → use-user-conversation-Dt-W6uNw.js} +1 -1
- package/build/assets/{verification-settings-BItwt998.js → verification-settings-oi54wEc1.js} +1 -1
- package/build/index.html +4 -4
- package/config/defaults.json +1 -1
- package/dist/components/features/chat/components/chat-input-llm-profile-picker.cjs +1 -1
- package/dist/components/features/chat/components/chat-input-llm-profile-picker.cjs.map +1 -1
- package/dist/components/features/chat/components/chat-input-llm-profile-picker.js +61 -60
- package/dist/components/features/chat/components/chat-input-llm-profile-picker.js.map +1 -1
- package/dist/components/features/chat/model-messages.cjs +1 -1
- package/dist/components/features/chat/model-messages.cjs.map +1 -1
- package/dist/components/features/chat/model-messages.js +26 -25
- package/dist/components/features/chat/model-messages.js.map +1 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-card-footer.cjs +1 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-card-footer.js +4 -4
- package/dist/components/features/settings/llm-profiles/profile-row.cjs +1 -1
- package/dist/components/features/settings/llm-profiles/profile-row.js +1 -0
- package/dist/components/shared/modals/settings/model-selector.cjs +1 -1
- package/dist/components/shared/modals/settings/model-selector.cjs.map +1 -1
- package/dist/components/shared/modals/settings/model-selector.js +154 -98
- package/dist/components/shared/modals/settings/model-selector.js.map +1 -1
- package/dist/config/defaults.cjs +1 -1
- package/dist/config/defaults.cjs.map +1 -1
- package/dist/config/defaults.js +1 -1
- package/dist/config/defaults.js.map +1 -1
- package/dist/hooks/chat/use-slash-command.cjs +1 -1
- package/dist/hooks/chat/use-slash-command.cjs.map +1 -1
- package/dist/hooks/chat/use-slash-command.js +61 -60
- package/dist/hooks/chat/use-slash-command.js.map +1 -1
- package/dist/package.cjs +1 -1
- package/dist/package.cjs.map +1 -1
- package/dist/package.js +1 -1
- package/dist/package.js.map +1 -1
- package/dist/routes/app-settings.cjs +1 -1
- package/dist/routes/app-settings.cjs.map +1 -1
- package/dist/routes/app-settings.js +94 -93
- package/dist/routes/app-settings.js.map +1 -1
- package/dist/routes/llm-settings.cjs +1 -1
- package/dist/routes/llm-settings.cjs.map +1 -1
- package/dist/routes/llm-settings.js +131 -119
- package/dist/routes/llm-settings.js.map +1 -1
- package/dist/utils/format-model-name.cjs +1 -1
- package/dist/utils/format-model-name.cjs.map +1 -1
- package/dist/utils/format-model-name.d.ts +11 -0
- package/dist/utils/format-model-name.js +11 -3
- package/dist/utils/format-model-name.js.map +1 -1
- package/package.json +1 -1
- package/build/assets/agent-settings-BJHryp65.js +0 -1
- package/build/assets/app-settings-r1RUbgGi.js +0 -1
- package/build/assets/backend-form-modal-OroTqnAv.js +0 -1
- package/build/assets/conversation-panel-pzAKsSDb.js +0 -1
- package/build/assets/conversation-pzAKsSDb.js +0 -1
- package/build/assets/git-repo-dropdown-B_PTq4ol.js +0 -1
- package/build/assets/home-r89Vj2td.js +0 -1
- package/build/assets/index-home-r89Vj2td.js +0 -1
- package/build/assets/llm-not-configured-banner-CwKu8C6g.js +0 -27
- package/build/assets/llm-settings-BJotLQND.js +0 -1
- package/build/assets/llm-settings-CDM-5H2A.js +0 -1
- package/build/assets/manifest-2a36a8f8.js +0 -1
- package/build/assets/messages-D1cbVhRK.js +0 -40
- package/build/assets/root-BfM_kC_d.css +0 -1
- package/build/assets/root-gstBEjty.js +0 -2
- package/build/assets/root-layout-BIAFLP9p.js +0 -2
|
@@ -1,50 +1,52 @@
|
|
|
1
1
|
import { useTranslation as e } from "../node_modules/react-i18next/dist/es/useTranslation.js";
|
|
2
2
|
import { I18nKey as t } from "../i18n/declaration.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
3
|
+
import { Info as n } from "../node_modules/lucide-react/dist/esm/icons/info.js";
|
|
4
|
+
import { DEFAULT_SETTINGS as r } from "../services/settings.js";
|
|
5
|
+
import { LLM_AUTH_TYPE_API_KEY as i, LLM_AUTH_TYPE_KEY as a, LLM_AUTH_TYPE_SUBSCRIPTION as o, LLM_SUBSCRIPTION_VENDOR_KEY as s, OPENAI_SUBSCRIPTION_VENDOR as c, resolveLlmAuthType as l } from "../constants/llm-subscription.js";
|
|
6
|
+
import { FREE_OPENHANDS_MODEL_NOTE as u, isFreeOpenHandsModel as d } from "../utils/format-model-name.js";
|
|
7
|
+
import { useSettings as f } from "../hooks/query/use-settings.js";
|
|
8
|
+
import { SettingsInput as p } from "../components/features/settings/settings-input.js";
|
|
9
|
+
import { SettingsDropdownInput as m } from "../components/features/settings/settings-dropdown-input.js";
|
|
10
|
+
import { extractModelAndProvider as h } from "../utils/extract-model-and-provider.js";
|
|
11
|
+
import { HelpLink as g } from "../ui/help-link.js";
|
|
12
|
+
import { ModelSelector as _ } from "../components/shared/modals/settings/model-selector.js";
|
|
13
|
+
import { useAgentSettingsSchema as v } from "../hooks/query/use-agent-settings-schema.js";
|
|
14
|
+
import { OpenAISubscriptionAuthCard as y } from "../components/features/settings/llm-settings/openai-subscription-auth-card.js";
|
|
15
|
+
import { KeyStatusIcon as b } from "../components/features/settings/key-status-icon.js";
|
|
16
|
+
import { inferInitialView as x } from "../utils/sdk-settings-schema.js";
|
|
17
|
+
import { SdkSectionPage as S } from "../components/features/settings/sdk-settings/sdk-section-page.js";
|
|
16
18
|
import "../components/features/settings/llm-profiles/index.js";
|
|
17
|
-
import { useOpenAISubscriptionModels as
|
|
18
|
-
import
|
|
19
|
-
import { Fragment as
|
|
19
|
+
import { useOpenAISubscriptionModels as C } from "../hooks/query/use-llm-subscription-models.js";
|
|
20
|
+
import w from "react";
|
|
21
|
+
import { Fragment as T, jsx as E, jsxs as D } from "react/jsx-runtime";
|
|
20
22
|
//#region src/routes/llm-settings.tsx
|
|
21
|
-
var
|
|
23
|
+
var O = new Set([
|
|
22
24
|
"llm.model",
|
|
23
25
|
"llm.api_key",
|
|
24
26
|
"llm.base_url",
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
]),
|
|
27
|
+
a,
|
|
28
|
+
s
|
|
29
|
+
]), k = (e, t) => !e || !t ? null : `${e}/${t}`, A = (e, t) => e?.sections.flatMap((e) => e.fields).find((e) => e.key === t)?.default ?? null, j = {
|
|
28
30
|
openai: new Set(["https://api.openai.com", "https://api.openai.com/v1"]),
|
|
29
31
|
moonshot: new Set(["https://api.kimi.com/coding/v1"])
|
|
30
|
-
},
|
|
32
|
+
}, M = (e) => {
|
|
31
33
|
try {
|
|
32
34
|
let t = new URL(e), n = t.pathname.replace(/\/+$/, "") || "";
|
|
33
35
|
return `${t.origin}${n}`;
|
|
34
36
|
} catch {
|
|
35
37
|
return e.trim().replace(/\/+$/, "");
|
|
36
38
|
}
|
|
37
|
-
},
|
|
38
|
-
let n =
|
|
39
|
+
}, N = (e, t) => {
|
|
40
|
+
let n = M(t), { provider: r } = h(e);
|
|
39
41
|
if (r) {
|
|
40
|
-
let e =
|
|
42
|
+
let e = j[r];
|
|
41
43
|
if (e) return e.has(n);
|
|
42
44
|
}
|
|
43
|
-
return Object.values(
|
|
45
|
+
return Object.values(j).some((e) => e?.has(n));
|
|
44
46
|
};
|
|
45
|
-
function
|
|
47
|
+
function P({ testId: n }) {
|
|
46
48
|
let { t: r } = e("openhands");
|
|
47
|
-
return /* @__PURE__ */
|
|
49
|
+
return /* @__PURE__ */ E(g, {
|
|
48
50
|
testId: n,
|
|
49
51
|
text: r(t.SETTINGS$OPENHANDS_API_KEY_HELP_TEXT),
|
|
50
52
|
linkText: r(t.SETTINGS$NAV_API_KEYS),
|
|
@@ -52,164 +54,174 @@ function j({ testId: n }) {
|
|
|
52
54
|
suffix: ` ${r(t.SETTINGS$OPENHANDS_API_KEY_HELP_SUFFIX)}`
|
|
53
55
|
});
|
|
54
56
|
}
|
|
55
|
-
function
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
function F() {
|
|
58
|
+
return /* @__PURE__ */ D("p", {
|
|
59
|
+
"data-testid": "openhands-free-models-note",
|
|
60
|
+
className: "flex items-start gap-2 text-xs text-warning",
|
|
61
|
+
children: [/* @__PURE__ */ E(n, {
|
|
62
|
+
className: "mt-0.5 size-4 shrink-0 text-warning",
|
|
63
|
+
"aria-hidden": !0
|
|
64
|
+
}), /* @__PURE__ */ E("span", { children: u })]
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
function I({ scope: n = "personal", onSaveSuccess: u, initialValueOverrides: h, embedded: j, hideSaveButton: M, suppressSuccessToast: I, onSaveControlChange: L }) {
|
|
68
|
+
let { t: R } = e("openhands"), { data: z } = f(n), { data: B } = v(z?.agent_settings_schema), V = z?.agent_settings?.llm, H = l(h?.["llm.auth_type"] ?? V?.auth_type), [U, W] = w.useState(H === o), { data: G, isLoading: K, isFetching: q } = C({ enabled: U }), J = U && !G && (K || q), Y = w.useRef(null), X = w.useRef(null);
|
|
69
|
+
w.useEffect(() => {
|
|
70
|
+
H === "subscription" && W(!0);
|
|
71
|
+
}, [H]);
|
|
72
|
+
let Z = String(r.agent_settings?.llm?.model ?? ""), Q = w.useCallback((e, t) => {
|
|
73
|
+
let n = x(e, t);
|
|
62
74
|
if (n !== "basic") return n;
|
|
63
75
|
let r = e.llm_model ?? "", i = e.llm_base_url?.trim() ?? "";
|
|
64
|
-
return i.length > 0 && !
|
|
65
|
-
}, []),
|
|
66
|
-
let
|
|
76
|
+
return i.length > 0 && !N(r, i) ? "all" : "basic";
|
|
77
|
+
}, []), $ = w.useCallback(({ values: e, isDisabled: n, view: r, onChange: u }) => {
|
|
78
|
+
let f = typeof e["llm.model"] == "string" ? e["llm.model"] : "", h = typeof e["llm.base_url"] == "string" ? e["llm.base_url"] : "", v = f.startsWith("openhands/"), x = l(e[a]), S = x === o, C = n || S && J, w = G?.includes(f) ? f : G?.[0] ?? "", O = typeof e["llm.api_key"] == "string" ? e["llm.api_key"] : "", A = j ? O.length > 0 : !!z?.llm_api_key_set, M = (e, r) => /* @__PURE__ */ D(T, { children: [/* @__PURE__ */ E(p, {
|
|
67
79
|
testId: e,
|
|
68
|
-
label:
|
|
80
|
+
label: R(t.SETTINGS_FORM$API_KEY),
|
|
69
81
|
type: "password",
|
|
70
82
|
className: "w-full",
|
|
71
83
|
value: O,
|
|
72
|
-
placeholder:
|
|
73
|
-
onChange: (e) =>
|
|
84
|
+
placeholder: A ? "<hidden>" : "",
|
|
85
|
+
onChange: (e) => u("llm.api_key", e),
|
|
74
86
|
isDisabled: n,
|
|
75
|
-
startContent:
|
|
76
|
-
}), /* @__PURE__ */
|
|
87
|
+
startContent: A ? /* @__PURE__ */ E(b, { isSet: A }) : void 0
|
|
88
|
+
}), /* @__PURE__ */ E(g, {
|
|
77
89
|
testId: r,
|
|
78
|
-
text:
|
|
79
|
-
linkText:
|
|
90
|
+
text: R(t.SETTINGS$DONT_KNOW_API_KEY),
|
|
91
|
+
linkText: R(t.SETTINGS$CLICK_FOR_INSTRUCTIONS),
|
|
80
92
|
href: "https://docs.openhands.dev/usage/local-setup#getting-an-api-key"
|
|
81
93
|
})] }), N = (e) => {
|
|
82
|
-
let t = e === "subscription" ?
|
|
83
|
-
if (
|
|
84
|
-
|
|
85
|
-
let e =
|
|
86
|
-
|
|
94
|
+
let t = e === "subscription" ? o : i;
|
|
95
|
+
if (u(a, t), t === "subscription") {
|
|
96
|
+
W(!0), f && !G?.includes(f) && (Y.current = f);
|
|
97
|
+
let e = X.current && G?.includes(X.current) ? X.current : G?.[0];
|
|
98
|
+
u(s, c), !G?.includes(f) && e && u("llm.model", e);
|
|
87
99
|
return;
|
|
88
100
|
}
|
|
89
|
-
|
|
90
|
-
},
|
|
101
|
+
f && G?.includes(f) && (X.current = f, u("llm.model", Y.current ?? Z));
|
|
102
|
+
}, I = () => /* @__PURE__ */ E(m, {
|
|
91
103
|
testId: "llm-auth-type-input",
|
|
92
|
-
name:
|
|
93
|
-
label:
|
|
104
|
+
name: a,
|
|
105
|
+
label: R(t.SETTINGS$LLM_AUTH_TYPE),
|
|
94
106
|
items: [{
|
|
95
|
-
key:
|
|
96
|
-
label:
|
|
107
|
+
key: i,
|
|
108
|
+
label: R(t.SETTINGS$LLM_AUTH_TYPE_API_KEY)
|
|
97
109
|
}, {
|
|
98
|
-
key:
|
|
99
|
-
label:
|
|
110
|
+
key: o,
|
|
111
|
+
label: R(t.SETTINGS$LLM_AUTH_TYPE_SUBSCRIPTION)
|
|
100
112
|
}],
|
|
101
|
-
selectedKey:
|
|
113
|
+
selectedKey: x,
|
|
102
114
|
isClearable: !1,
|
|
103
115
|
required: !0,
|
|
104
|
-
isDisabled:
|
|
116
|
+
isDisabled: C,
|
|
105
117
|
onSelectionChange: N
|
|
106
|
-
}),
|
|
118
|
+
}), L = () => /* @__PURE__ */ D("div", {
|
|
107
119
|
className: "flex flex-col gap-6",
|
|
108
120
|
"data-testid": "llm-subscription-settings",
|
|
109
|
-
children: [/* @__PURE__ */
|
|
121
|
+
children: [/* @__PURE__ */ E(m, {
|
|
110
122
|
testId: "llm-subscription-model-input",
|
|
111
123
|
name: "llm.subscription_model",
|
|
112
|
-
label:
|
|
113
|
-
items: (
|
|
124
|
+
label: R(t.SETTINGS$SUBSCRIPTION_MODEL),
|
|
125
|
+
items: (G ?? []).map((e) => ({
|
|
114
126
|
key: e,
|
|
115
127
|
label: e
|
|
116
128
|
})),
|
|
117
|
-
selectedKey:
|
|
129
|
+
selectedKey: w,
|
|
118
130
|
isClearable: !1,
|
|
119
131
|
required: !0,
|
|
120
|
-
isDisabled:
|
|
132
|
+
isDisabled: C || !G?.length,
|
|
121
133
|
onSelectionChange: (e) => {
|
|
122
|
-
let t = typeof e == "string" ? e :
|
|
123
|
-
t &&
|
|
134
|
+
let t = typeof e == "string" ? e : G?.[0];
|
|
135
|
+
t && u("llm.model", t);
|
|
124
136
|
}
|
|
125
|
-
}), /* @__PURE__ */
|
|
137
|
+
}), /* @__PURE__ */ E(y, { isDisabled: n })]
|
|
126
138
|
});
|
|
127
|
-
return /* @__PURE__ */
|
|
139
|
+
return /* @__PURE__ */ E("div", {
|
|
128
140
|
className: "flex flex-col gap-6",
|
|
129
|
-
children:
|
|
141
|
+
children: r === "basic" ? /* @__PURE__ */ D("div", {
|
|
130
142
|
className: "flex flex-col gap-6",
|
|
131
143
|
"data-testid": "llm-settings-form-basic",
|
|
132
|
-
children: [
|
|
133
|
-
/* @__PURE__ */
|
|
134
|
-
currentModel:
|
|
144
|
+
children: [I(), S ? L() : /* @__PURE__ */ D(T, { children: [
|
|
145
|
+
/* @__PURE__ */ E(_, {
|
|
146
|
+
currentModel: f || void 0,
|
|
135
147
|
onChange: (e, t) => {
|
|
136
|
-
let n =
|
|
137
|
-
n &&
|
|
148
|
+
let n = k(e, t);
|
|
149
|
+
n && u("llm.model", n);
|
|
138
150
|
},
|
|
139
151
|
wrapperClassName: "!flex-col !gap-6",
|
|
140
152
|
isDisabled: n
|
|
141
153
|
}),
|
|
142
|
-
|
|
143
|
-
|
|
154
|
+
v ? /* @__PURE__ */ E(P, { testId: "openhands-api-key-help" }) : null,
|
|
155
|
+
M("llm-api-key-input", "llm-api-key-help-anchor")
|
|
144
156
|
] })]
|
|
145
|
-
}) : /* @__PURE__ */
|
|
157
|
+
}) : /* @__PURE__ */ D("div", {
|
|
146
158
|
className: "flex flex-col gap-6",
|
|
147
159
|
"data-testid": "llm-settings-form-advanced",
|
|
148
|
-
children: [
|
|
149
|
-
/* @__PURE__ */
|
|
160
|
+
children: [I(), S ? L() : /* @__PURE__ */ D(T, { children: [
|
|
161
|
+
/* @__PURE__ */ E(p, {
|
|
150
162
|
testId: "llm-custom-model-input",
|
|
151
|
-
label:
|
|
163
|
+
label: R(t.SETTINGS$CUSTOM_MODEL),
|
|
152
164
|
type: "text",
|
|
153
165
|
className: "w-full",
|
|
154
|
-
value:
|
|
155
|
-
placeholder:
|
|
156
|
-
onChange: (e) =>
|
|
166
|
+
value: f,
|
|
167
|
+
placeholder: Z,
|
|
168
|
+
onChange: (e) => u("llm.model", e),
|
|
157
169
|
isDisabled: n
|
|
158
170
|
}),
|
|
159
|
-
|
|
160
|
-
/* @__PURE__ */
|
|
171
|
+
v ? /* @__PURE__ */ D(T, { children: [d(f) ? /* @__PURE__ */ E(F, {}) : null, /* @__PURE__ */ E(P, { testId: "openhands-api-key-help-2" })] }) : null,
|
|
172
|
+
/* @__PURE__ */ E(p, {
|
|
161
173
|
testId: "base-url-input",
|
|
162
|
-
label:
|
|
174
|
+
label: R(t.SETTINGS$BASE_URL),
|
|
163
175
|
type: "text",
|
|
164
176
|
className: "w-full",
|
|
165
|
-
value:
|
|
177
|
+
value: h,
|
|
166
178
|
placeholder: "https://api.openai.com",
|
|
167
|
-
onChange: (e) =>
|
|
179
|
+
onChange: (e) => u("llm.base_url", e),
|
|
168
180
|
isDisabled: n
|
|
169
181
|
}),
|
|
170
|
-
|
|
182
|
+
M("llm-api-key-input", "llm-api-key-help-anchor-advanced")
|
|
171
183
|
] })]
|
|
172
184
|
})
|
|
173
185
|
});
|
|
174
186
|
}, [
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
]),
|
|
182
|
-
let n = structuredClone(e.agent_settings_diff ?? {}),
|
|
183
|
-
if (
|
|
184
|
-
|
|
185
|
-
let e = typeof
|
|
186
|
-
if (!
|
|
187
|
-
|
|
188
|
-
} else t.dirty["llm.auth_type"] && (
|
|
189
|
-
return n.llm =
|
|
190
|
-
}, [
|
|
191
|
-
return /* @__PURE__ */
|
|
192
|
-
scope:
|
|
187
|
+
Z,
|
|
188
|
+
j,
|
|
189
|
+
J,
|
|
190
|
+
z?.llm_api_key_set,
|
|
191
|
+
G,
|
|
192
|
+
R
|
|
193
|
+
]), ee = w.useCallback((e, t) => {
|
|
194
|
+
let n = structuredClone(e.agent_settings_diff ?? {}), r = n.llm ?? {};
|
|
195
|
+
if (l(t.values["llm.auth_type"]) === "subscription") {
|
|
196
|
+
r.auth_type = o, r.subscription_vendor = c;
|
|
197
|
+
let e = typeof r.model == "string" ? r.model : String(t.values["llm.model"] ?? ""), n = G?.[0];
|
|
198
|
+
if (!G?.includes(e) && !n) throw Error("Subscription models are not loaded yet.");
|
|
199
|
+
r.model = G?.includes(e) ? e : n, delete r.api_key, delete r.base_url;
|
|
200
|
+
} else t.dirty["llm.auth_type"] && (r.auth_type = i, r.subscription_vendor = null), t.view === "basic" && r.model !== void 0 && (r.base_url = A(B, "llm.base_url"));
|
|
201
|
+
return n.llm = r, { agent_settings_diff: n };
|
|
202
|
+
}, [B, G]);
|
|
203
|
+
return /* @__PURE__ */ E(S, {
|
|
204
|
+
scope: n,
|
|
193
205
|
settingsSources: [{
|
|
194
206
|
settingsSource: "agent_settings",
|
|
195
207
|
sectionKeys: ["llm"],
|
|
196
|
-
excludeKeys:
|
|
208
|
+
excludeKeys: O
|
|
197
209
|
}],
|
|
198
|
-
header:
|
|
199
|
-
buildPayload:
|
|
200
|
-
getInitialView:
|
|
210
|
+
header: $,
|
|
211
|
+
buildPayload: ee,
|
|
212
|
+
getInitialView: Q,
|
|
201
213
|
forceShowAdvancedView: !0,
|
|
202
214
|
allowAllView: !0,
|
|
203
|
-
onSaveSuccess:
|
|
204
|
-
initialValueOverrides:
|
|
205
|
-
embedded:
|
|
206
|
-
hideSaveButton:
|
|
207
|
-
suppressSuccessToast:
|
|
208
|
-
onSaveControlChange:
|
|
215
|
+
onSaveSuccess: u,
|
|
216
|
+
initialValueOverrides: h,
|
|
217
|
+
embedded: j,
|
|
218
|
+
hideSaveButton: M,
|
|
219
|
+
suppressSuccessToast: I,
|
|
220
|
+
onSaveControlChange: L,
|
|
209
221
|
testId: "llm-settings-screen"
|
|
210
222
|
});
|
|
211
223
|
}
|
|
212
224
|
//#endregion
|
|
213
|
-
export {
|
|
225
|
+
export { I as LlmSettingsScreen };
|
|
214
226
|
|
|
215
227
|
//# sourceMappingURL=llm-settings.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"llm-settings.js","names":[],"sources":["../../src/routes/llm-settings.tsx"],"sourcesContent":["import React from \"react\";\nimport { useTranslation } from \"react-i18next\";\nimport { ModelSelector } from \"#/components/shared/modals/settings/model-selector\";\nimport { useAgentSettingsSchema } from \"#/hooks/query/use-agent-settings-schema\";\nimport { useSettings } from \"#/hooks/query/use-settings\";\nimport { SettingsInput } from \"#/components/features/settings/settings-input\";\nimport { SettingsDropdownInput } from \"#/components/features/settings/settings-dropdown-input\";\nimport { OpenAISubscriptionAuthCard } from \"#/components/features/settings/llm-settings/openai-subscription-auth-card\";\nimport { HelpLink } from \"#/ui/help-link\";\nimport { KeyStatusIcon } from \"#/components/features/settings/key-status-icon\";\nimport {\n SdkSectionHeaderProps,\n SdkSectionPage,\n SdkSectionSaveControl,\n} from \"#/components/features/settings/sdk-settings/sdk-section-page\";\nimport { LlmSettingsLocalView } from \"#/components/features/settings/llm-profiles\";\nimport { I18nKey } from \"#/i18n/declaration\";\nimport { Settings, SettingsSchema, SettingsScope } from \"#/types/settings\";\nimport { extractModelAndProvider } from \"#/utils/extract-model-and-provider\";\nimport {\n inferInitialView,\n type SettingsFormValues,\n type SettingsView,\n} from \"#/utils/sdk-settings-schema\";\nimport { DEFAULT_SETTINGS } from \"#/services/settings\";\nimport {\n LLM_AUTH_TYPE_API_KEY,\n LLM_AUTH_TYPE_KEY,\n LLM_AUTH_TYPE_SUBSCRIPTION,\n LLM_SUBSCRIPTION_VENDOR_KEY,\n OPENAI_SUBSCRIPTION_VENDOR,\n resolveLlmAuthType,\n} from \"#/constants/llm-subscription\";\nimport { useOpenAISubscriptionModels } from \"#/hooks/query/use-llm-subscription-models\";\n\nconst LLM_EXCLUDED_KEYS = new Set([\n \"llm.model\",\n \"llm.api_key\",\n \"llm.base_url\",\n LLM_AUTH_TYPE_KEY,\n LLM_SUBSCRIPTION_VENDOR_KEY,\n]);\n\nconst buildModelId = (provider: string | null, model: string | null) => {\n if (!provider || !model) return null;\n return `${provider}/${model}`;\n};\n\nconst getSchemaFieldDefaultValue = (\n schema: SettingsSchema | null | undefined,\n fieldKey: string,\n) =>\n schema?.sections\n .flatMap((section) => section.fields)\n .find((field) => field.key === fieldKey)?.default ?? null;\n\nconst KNOWN_PROVIDER_DEFAULT_BASE_URLS: Partial<Record<string, Set<string>>> = {\n openai: new Set([\"https://api.openai.com\", \"https://api.openai.com/v1\"]),\n moonshot: new Set([\"https://api.kimi.com/coding/v1\"]),\n};\n\nconst normalizeBaseUrl = (baseUrl: string) => {\n try {\n const parsedUrl = new URL(baseUrl);\n const normalizedPath = parsedUrl.pathname.replace(/\\/+$/, \"\") || \"\";\n return `${parsedUrl.origin}${normalizedPath}`;\n } catch {\n return baseUrl.trim().replace(/\\/+$/, \"\");\n }\n};\n\nconst isProviderDefaultBaseUrl = (model: string, baseUrl: string) => {\n const normalizedBaseUrl = normalizeBaseUrl(baseUrl);\n const { provider } = extractModelAndProvider(model);\n\n if (provider) {\n const knownDefaults = KNOWN_PROVIDER_DEFAULT_BASE_URLS[provider];\n if (knownDefaults) {\n return knownDefaults.has(normalizedBaseUrl);\n }\n }\n\n return Object.values(KNOWN_PROVIDER_DEFAULT_BASE_URLS).some((knownDefaults) =>\n knownDefaults?.has(normalizedBaseUrl),\n );\n};\n\ninterface OpenHandsApiKeyHelpProps {\n testId: string;\n}\n\nfunction OpenHandsApiKeyHelp({ testId }: OpenHandsApiKeyHelpProps) {\n const { t } = useTranslation(\"openhands\");\n\n return (\n <HelpLink\n testId={testId}\n text={t(I18nKey.SETTINGS$OPENHANDS_API_KEY_HELP_TEXT)}\n linkText={t(I18nKey.SETTINGS$NAV_API_KEYS)}\n href=\"https://app.all-hands.dev/settings/api-keys\"\n suffix={` ${t(I18nKey.SETTINGS$OPENHANDS_API_KEY_HELP_SUFFIX)}`}\n />\n );\n}\n\nexport function LlmSettingsScreen({\n scope = \"personal\",\n onSaveSuccess,\n initialValueOverrides,\n embedded,\n hideSaveButton,\n suppressSuccessToast,\n onSaveControlChange,\n}: {\n scope?: SettingsScope;\n /** Optional hook fired after a successful save (e.g. advance an onboarding step). */\n onSaveSuccess?: () => void;\n /** Forwarded to {@link SdkSectionPage}. */\n initialValueOverrides?: SettingsFormValues;\n /** Forwarded to {@link SdkSectionPage}. */\n embedded?: boolean;\n /** Forwarded to {@link SdkSectionPage}. */\n hideSaveButton?: boolean;\n /** Forwarded to {@link SdkSectionPage}. */\n suppressSuccessToast?: boolean;\n /** Forwarded to {@link SdkSectionPage}. */\n onSaveControlChange?: (control: SdkSectionSaveControl) => void;\n}) {\n const { t } = useTranslation(\"openhands\");\n\n const { data: settings } = useSettings(scope);\n const { data: schema } = useAgentSettingsSchema(\n settings?.agent_settings_schema,\n );\n const persistedLlmSettings = settings?.agent_settings?.llm as\n | Record<string, unknown>\n | undefined;\n const initialAuthType = resolveLlmAuthType(\n initialValueOverrides?.[LLM_AUTH_TYPE_KEY] ??\n persistedLlmSettings?.auth_type,\n );\n const [enableSubscriptionModels, setEnableSubscriptionModels] =\n React.useState(initialAuthType === LLM_AUTH_TYPE_SUBSCRIPTION);\n const {\n data: subscriptionModels,\n isLoading: isSubscriptionModelsLoading,\n isFetching: isSubscriptionModelsFetching,\n } = useOpenAISubscriptionModels({ enabled: enableSubscriptionModels });\n const isWaitingForSubscriptionModels =\n enableSubscriptionModels &&\n !subscriptionModels &&\n (isSubscriptionModelsLoading || isSubscriptionModelsFetching);\n const lastApiKeyModelRef = React.useRef<string | null>(null);\n const lastSubscriptionModelRef = React.useRef<string | null>(null);\n\n React.useEffect(() => {\n if (initialAuthType === LLM_AUTH_TYPE_SUBSCRIPTION) {\n setEnableSubscriptionModels(true);\n }\n }, [initialAuthType]);\n\n const defaultModel = String(\n (DEFAULT_SETTINGS.agent_settings?.llm as Record<string, unknown>)?.model ??\n \"\",\n );\n\n const getInitialView = React.useCallback(\n (\n currentSettings: Settings,\n filteredSchema: SettingsSchema,\n ): SettingsView => {\n const schemaView = inferInitialView(currentSettings, filteredSchema);\n if (schemaView !== \"basic\") {\n return schemaView;\n }\n\n const currentModel = currentSettings.llm_model ?? \"\";\n const trimmedBaseUrl = currentSettings.llm_base_url?.trim() ?? \"\";\n const hasCustomBaseUrl =\n trimmedBaseUrl.length > 0 &&\n !isProviderDefaultBaseUrl(currentModel, trimmedBaseUrl);\n\n return hasCustomBaseUrl ? \"all\" : \"basic\";\n },\n [],\n );\n\n const buildHeader = React.useCallback(\n ({ values, isDisabled, view, onChange }: SdkSectionHeaderProps) => {\n const modelValue =\n typeof values[\"llm.model\"] === \"string\" ? values[\"llm.model\"] : \"\";\n const baseUrlValue =\n typeof values[\"llm.base_url\"] === \"string\"\n ? values[\"llm.base_url\"]\n : \"\";\n const showOpenHandsApiKeyHelp = modelValue.startsWith(\"openhands/\");\n const authType = resolveLlmAuthType(values[LLM_AUTH_TYPE_KEY]);\n const isSubscriptionAuth = authType === LLM_AUTH_TYPE_SUBSCRIPTION;\n const shouldDisableSubscriptionControls =\n isDisabled || (isSubscriptionAuth && isWaitingForSubscriptionModels);\n const subscriptionModelValue = subscriptionModels?.includes(modelValue)\n ? modelValue\n : (subscriptionModels?.[0] ?? \"\");\n\n const apiKeyValue =\n typeof values[\"llm.api_key\"] === \"string\" ? values[\"llm.api_key\"] : \"\";\n // For embedded profile forms (create/edit) the global\n // `llm_api_key_set` flag is misleading: a brand-new profile would show a\n // \"key set\" indicator just because some other profile has a key. Reflect\n // the form's own key state instead so create mode starts visibly unset.\n const apiKeyIsSet = embedded\n ? apiKeyValue.length > 0\n : Boolean(settings?.llm_api_key_set);\n\n const renderApiKeyInput = (testId: string, helpTestId: string) => (\n <>\n <SettingsInput\n testId={testId}\n label={t(I18nKey.SETTINGS_FORM$API_KEY)}\n type=\"password\"\n className=\"w-full\"\n value={apiKeyValue}\n // eslint-disable-next-line i18next/no-literal-string -- masked-key sentinel, not translatable\n placeholder={apiKeyIsSet ? \"<hidden>\" : \"\"}\n onChange={(value) => onChange(\"llm.api_key\", value)}\n isDisabled={isDisabled}\n startContent={\n apiKeyIsSet ? <KeyStatusIcon isSet={apiKeyIsSet} /> : undefined\n }\n />\n\n <HelpLink\n testId={helpTestId}\n text={t(I18nKey.SETTINGS$DONT_KNOW_API_KEY)}\n linkText={t(I18nKey.SETTINGS$CLICK_FOR_INSTRUCTIONS)}\n href=\"https://docs.openhands.dev/usage/local-setup#getting-an-api-key\"\n />\n </>\n );\n\n const handleAuthTypeChange = (selectedKey: React.Key | null) => {\n const nextAuthType =\n selectedKey === LLM_AUTH_TYPE_SUBSCRIPTION\n ? LLM_AUTH_TYPE_SUBSCRIPTION\n : LLM_AUTH_TYPE_API_KEY;\n onChange(LLM_AUTH_TYPE_KEY, nextAuthType);\n\n if (nextAuthType === LLM_AUTH_TYPE_SUBSCRIPTION) {\n setEnableSubscriptionModels(true);\n if (modelValue && !subscriptionModels?.includes(modelValue)) {\n lastApiKeyModelRef.current = modelValue;\n }\n const restoredSubscriptionModel =\n lastSubscriptionModelRef.current &&\n subscriptionModels?.includes(lastSubscriptionModelRef.current)\n ? lastSubscriptionModelRef.current\n : subscriptionModels?.[0];\n onChange(LLM_SUBSCRIPTION_VENDOR_KEY, OPENAI_SUBSCRIPTION_VENDOR);\n if (\n !subscriptionModels?.includes(modelValue) &&\n restoredSubscriptionModel\n ) {\n onChange(\"llm.model\", restoredSubscriptionModel);\n }\n return;\n }\n\n if (modelValue && subscriptionModels?.includes(modelValue)) {\n lastSubscriptionModelRef.current = modelValue;\n onChange(\"llm.model\", lastApiKeyModelRef.current ?? defaultModel);\n }\n };\n\n const renderAuthTypeInput = () => (\n <SettingsDropdownInput\n testId=\"llm-auth-type-input\"\n name={LLM_AUTH_TYPE_KEY}\n label={t(I18nKey.SETTINGS$LLM_AUTH_TYPE)}\n items={[\n {\n key: LLM_AUTH_TYPE_API_KEY,\n label: t(I18nKey.SETTINGS$LLM_AUTH_TYPE_API_KEY),\n },\n {\n key: LLM_AUTH_TYPE_SUBSCRIPTION,\n label: t(I18nKey.SETTINGS$LLM_AUTH_TYPE_SUBSCRIPTION),\n },\n ]}\n selectedKey={authType}\n isClearable={false}\n required\n isDisabled={shouldDisableSubscriptionControls}\n onSelectionChange={handleAuthTypeChange}\n />\n );\n\n const renderSubscriptionSettings = () => (\n <div\n className=\"flex flex-col gap-6\"\n data-testid=\"llm-subscription-settings\"\n >\n <SettingsDropdownInput\n testId=\"llm-subscription-model-input\"\n name=\"llm.subscription_model\"\n label={t(I18nKey.SETTINGS$SUBSCRIPTION_MODEL)}\n items={(subscriptionModels ?? []).map((model) => ({\n key: model,\n label: model,\n }))}\n selectedKey={subscriptionModelValue}\n isClearable={false}\n required\n isDisabled={\n shouldDisableSubscriptionControls || !subscriptionModels?.length\n }\n onSelectionChange={(selectedKey) => {\n const nextModel =\n typeof selectedKey === \"string\"\n ? selectedKey\n : subscriptionModels?.[0];\n if (nextModel) {\n onChange(\"llm.model\", nextModel);\n }\n }}\n />\n <OpenAISubscriptionAuthCard isDisabled={isDisabled} />\n </div>\n );\n\n return (\n <div className=\"flex flex-col gap-6\">\n {view === \"basic\" ? (\n <div\n className=\"flex flex-col gap-6\"\n data-testid=\"llm-settings-form-basic\"\n >\n {renderAuthTypeInput()}\n\n {isSubscriptionAuth ? (\n renderSubscriptionSettings()\n ) : (\n <>\n <ModelSelector\n currentModel={modelValue || undefined}\n onChange={(provider, model) => {\n const nextModel = buildModelId(provider, model);\n if (nextModel) {\n onChange(\"llm.model\", nextModel);\n }\n }}\n wrapperClassName=\"!flex-col !gap-6\"\n isDisabled={isDisabled}\n />\n\n {showOpenHandsApiKeyHelp ? (\n <OpenHandsApiKeyHelp testId=\"openhands-api-key-help\" />\n ) : null}\n\n {renderApiKeyInput(\n // eslint-disable-next-line i18next/no-literal-string -- DOM id, not user-facing\n \"llm-api-key-input\",\n // eslint-disable-next-line i18next/no-literal-string -- DOM id, not user-facing\n \"llm-api-key-help-anchor\",\n )}\n </>\n )}\n </div>\n ) : (\n <div\n className=\"flex flex-col gap-6\"\n data-testid=\"llm-settings-form-advanced\"\n >\n {renderAuthTypeInput()}\n\n {isSubscriptionAuth ? (\n renderSubscriptionSettings()\n ) : (\n <>\n <SettingsInput\n testId=\"llm-custom-model-input\"\n label={t(I18nKey.SETTINGS$CUSTOM_MODEL)}\n type=\"text\"\n className=\"w-full\"\n value={modelValue}\n placeholder={defaultModel}\n onChange={(value) => onChange(\"llm.model\", value)}\n isDisabled={isDisabled}\n />\n\n {showOpenHandsApiKeyHelp ? (\n <OpenHandsApiKeyHelp testId=\"openhands-api-key-help-2\" />\n ) : null}\n\n <SettingsInput\n testId=\"base-url-input\"\n label={t(I18nKey.SETTINGS$BASE_URL)}\n type=\"text\"\n className=\"w-full\"\n value={baseUrlValue}\n // eslint-disable-next-line i18next/no-literal-string -- example value, not translatable\n placeholder=\"https://api.openai.com\"\n onChange={(value) => onChange(\"llm.base_url\", value)}\n isDisabled={isDisabled}\n />\n\n {renderApiKeyInput(\n // eslint-disable-next-line i18next/no-literal-string -- DOM id, not user-facing\n \"llm-api-key-input\",\n // eslint-disable-next-line i18next/no-literal-string -- DOM id, not user-facing\n \"llm-api-key-help-anchor-advanced\",\n )}\n </>\n )}\n </div>\n )}\n </div>\n );\n },\n [\n defaultModel,\n embedded,\n isWaitingForSubscriptionModels,\n settings?.llm_api_key_set,\n subscriptionModels,\n t,\n ],\n );\n\n const buildPayload = React.useCallback(\n (\n defaultPayload: Record<string, unknown>,\n context: {\n values: Record<string, string | boolean>;\n dirty: Record<string, boolean>;\n view: SettingsView;\n },\n ) => {\n // defaultPayload is the wrapped diff (e.g.\n // `{ agent_settings_diff: { llm: { model: \"gpt-4\" } } }`); we only\n // mutate the inner `llm` object below.\n const agentSettings = structuredClone(\n (defaultPayload.agent_settings_diff as Record<string, unknown>) ?? {},\n );\n const llm = (agentSettings.llm ?? {}) as Record<string, unknown>;\n const authType = resolveLlmAuthType(context.values[LLM_AUTH_TYPE_KEY]);\n\n if (authType === LLM_AUTH_TYPE_SUBSCRIPTION) {\n llm.auth_type = LLM_AUTH_TYPE_SUBSCRIPTION;\n llm.subscription_vendor = OPENAI_SUBSCRIPTION_VENDOR;\n const model =\n typeof llm.model === \"string\"\n ? llm.model\n : String(context.values[\"llm.model\"] ?? \"\");\n const fallbackSubscriptionModel = subscriptionModels?.[0];\n if (\n !subscriptionModels?.includes(model) &&\n !fallbackSubscriptionModel\n ) {\n throw new Error(\"Subscription models are not loaded yet.\");\n }\n llm.model = subscriptionModels?.includes(model)\n ? model\n : fallbackSubscriptionModel;\n delete llm.api_key;\n delete llm.base_url;\n } else {\n if (context.dirty[LLM_AUTH_TYPE_KEY]) {\n llm.auth_type = LLM_AUTH_TYPE_API_KEY;\n llm.subscription_vendor = null;\n }\n if (context.view === \"basic\" && llm.model !== undefined) {\n llm.base_url = getSchemaFieldDefaultValue(schema, \"llm.base_url\");\n }\n }\n\n agentSettings.llm = llm;\n return { agent_settings_diff: agentSettings };\n },\n [schema, subscriptionModels],\n );\n\n return (\n <SdkSectionPage\n scope={scope}\n settingsSources={[\n {\n settingsSource: \"agent_settings\",\n sectionKeys: [\"llm\"],\n excludeKeys: LLM_EXCLUDED_KEYS,\n },\n ]}\n header={buildHeader}\n buildPayload={buildPayload}\n getInitialView={getInitialView}\n forceShowAdvancedView\n allowAllView\n onSaveSuccess={onSaveSuccess}\n initialValueOverrides={initialValueOverrides}\n embedded={embedded}\n hideSaveButton={hideSaveButton}\n suppressSuccessToast={suppressSuccessToast}\n onSaveControlChange={onSaveControlChange}\n testId=\"llm-settings-screen\"\n />\n );\n}\n\n/**\n * Default export for the route renders the LLM-profile management view for both\n * backend types. Both manage the LLM through named profiles — local via the\n * agent-server (`/api/profiles`), cloud via the app-server\n * (`/api/v1/settings/profiles`) — and the view is backend-agnostic because it\n * goes through ProfilesService, which routes per active backend.\n *\n * The LlmSettingsScreen component is also exported for embedded use cases\n * (e.g., onboarding, the profile create/edit form).\n *\n * Note: This is a route file, only the router should import the default export.\n * Other consumers should use the named export `LlmSettingsScreen` for embedded\n * use cases.\n */\nexport default function LlmSettingsRoute() {\n return <LlmSettingsLocalView />;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAmCA,IAAM,IAAoB,IAAI,IAAI;CAChC;CACA;CACA;CACA;CACA;AACF,CAAC,GAEK,KAAgB,GAAyB,MACzC,CAAC,KAAY,CAAC,IAAc,OACzB,GAAG,EAAS,GAAG,KAGlB,KACJ,GACA,MAEA,GAAQ,SACL,SAAS,MAAY,EAAQ,MAAM,EACnC,MAAM,MAAU,EAAM,QAAQ,CAAQ,GAAG,WAAW,MAEnD,IAAyE;CAC7E,QAAQ,IAAI,IAAI,CAAC,0BAA0B,2BAA2B,CAAC;CACvE,UAAU,IAAI,IAAI,CAAC,gCAAgC,CAAC;AACtD,GAEM,KAAoB,MAAoB;CAC5C,IAAI;EACF,IAAM,IAAY,IAAI,IAAI,CAAO,GAC3B,IAAiB,EAAU,SAAS,QAAQ,QAAQ,EAAE,KAAK;EACjE,OAAO,GAAG,EAAU,SAAS;CAC/B,QAAQ;EACN,OAAO,EAAQ,KAAK,EAAE,QAAQ,QAAQ,EAAE;CAC1C;AACF,GAEM,KAA4B,GAAe,MAAoB;CACnE,IAAM,IAAoB,EAAiB,CAAO,GAC5C,EAAE,gBAAa,EAAwB,CAAK;CAElD,IAAI,GAAU;EACZ,IAAM,IAAgB,EAAiC;EACvD,IAAI,GACF,OAAO,EAAc,IAAI,CAAiB;CAE9C;CAEA,OAAO,OAAO,OAAO,CAAgC,EAAE,MAAM,MAC3D,GAAe,IAAI,CAAiB,CACtC;AACF;AAMA,SAAS,EAAoB,EAAE,aAAoC;CACjE,IAAM,EAAE,SAAM,EAAe,WAAW;CAExC,OACE,kBAAC,GAAD;EACU;EACR,MAAM,EAAE,EAAQ,oCAAoC;EACpD,UAAU,EAAE,EAAQ,qBAAqB;EACzC,MAAK;EACL,QAAQ,IAAI,EAAE,EAAQ,sCAAsC;CAC7D,CAAA;AAEL;AAEA,SAAgB,EAAkB,EAChC,WAAQ,YACR,kBACA,0BACA,aACA,mBACA,yBACA,0BAeC;CACD,IAAM,EAAE,SAAM,EAAe,WAAW,GAElC,EAAE,MAAM,MAAa,EAAY,CAAK,GACtC,EAAE,MAAM,MAAW,EACvB,GAAU,qBACZ,GACM,IAAuB,GAAU,gBAAgB,KAGjD,IAAkB,EACtB,IAAA,oBACE,GAAsB,SAC1B,GACM,CAAC,GAA0B,KAC/B,EAAM,SAAS,MAAoB,CAA0B,GACzD,EACJ,MAAM,GACN,WAAW,GACX,YAAY,MACV,EAA4B,EAAE,SAAS,EAAyB,CAAC,GAC/D,IACJ,KACA,CAAC,MACA,KAA+B,IAC5B,IAAqB,EAAM,OAAsB,IAAI,GACrD,IAA2B,EAAM,OAAsB,IAAI;CAEjE,EAAM,gBAAgB;EACpB,AAAI,MAAA,kBACF,EAA4B,EAAI;CAEpC,GAAG,CAAC,CAAe,CAAC;CAEpB,IAAM,IAAe,OAClB,EAAiB,gBAAgB,KAAiC,SACjE,EACJ,GAEM,IAAiB,EAAM,aAEzB,GACA,MACiB;EACjB,IAAM,IAAa,EAAiB,GAAiB,CAAc;EACnE,IAAI,MAAe,SACjB,OAAO;EAGT,IAAM,IAAe,EAAgB,aAAa,IAC5C,IAAiB,EAAgB,cAAc,KAAK,KAAK;EAK/D,OAHE,EAAe,SAAS,KACxB,CAAC,EAAyB,GAAc,CAAc,IAE9B,QAAQ;CACpC,GACA,CAAC,CACH,GAEM,IAAc,EAAM,aACvB,EAAE,WAAQ,eAAY,SAAM,kBAAsC;EACjE,IAAM,IACJ,OAAO,EAAO,gBAAiB,WAAW,EAAO,eAAe,IAC5D,IACJ,OAAO,EAAO,mBAAoB,WAC9B,EAAO,kBACP,IACA,IAA0B,EAAW,WAAW,YAAY,GAC5D,IAAW,EAAmB,EAAO,EAAkB,GACvD,IAAqB,MAAa,GAClC,IACJ,KAAe,KAAsB,GACjC,IAAyB,GAAoB,SAAS,CAAU,IAClE,IACC,IAAqB,MAAM,IAE1B,IACJ,OAAO,EAAO,kBAAmB,WAAW,EAAO,iBAAiB,IAKhE,IAAc,IAChB,EAAY,SAAS,IACrB,EAAQ,GAAU,iBAEhB,KAAqB,GAAgB,MACzC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD;GACU;GACR,OAAO,EAAE,EAAQ,qBAAqB;GACtC,MAAK;GACL,WAAU;GACV,OAAO;GAEP,aAAa,IAAc,aAAa;GACxC,WAAW,MAAU,EAAS,eAAe,CAAK;GACtC;GACZ,cACE,IAAc,kBAAC,GAAD,EAAe,OAAO,EAAc,CAAA,IAAI,KAAA;EAEzD,CAAA,GAED,kBAAC,GAAD;GACE,QAAQ;GACR,MAAM,EAAE,EAAQ,0BAA0B;GAC1C,UAAU,EAAE,EAAQ,+BAA+B;GACnD,MAAK;EACN,CAAA,CACD,EAAA,CAAA,GAGE,KAAwB,MAAkC;GAC9D,IAAM,IACJ,MAAA,iBACI,IACA;GAGN,IAFA,EAAS,GAAmB,CAAY,GAEpC,MAAA,gBAA6C;IAE/C,AADA,EAA4B,EAAI,GAC5B,KAAc,CAAC,GAAoB,SAAS,CAAU,MACxD,EAAmB,UAAU;IAE/B,IAAM,IACJ,EAAyB,WACzB,GAAoB,SAAS,EAAyB,OAAO,IACzD,EAAyB,UACzB,IAAqB;IAE3B,AADA,EAAS,GAA6B,CAA0B,GAE9D,CAAC,GAAoB,SAAS,CAAU,KACxC,KAEA,EAAS,aAAa,CAAyB;IAEjD;GACF;GAEA,AAAI,KAAc,GAAoB,SAAS,CAAU,MACvD,EAAyB,UAAU,GACnC,EAAS,aAAa,EAAmB,WAAW,CAAY;EAEpE,GAEM,UACJ,kBAAC,GAAD;GACE,QAAO;GACP,MAAM;GACN,OAAO,EAAE,EAAQ,sBAAsB;GACvC,OAAO,CACL;IACE,KAAK;IACL,OAAO,EAAE,EAAQ,8BAA8B;GACjD,GACA;IACE,KAAK;IACL,OAAO,EAAE,EAAQ,mCAAmC;GACtD,CACF;GACA,aAAa;GACb,aAAa;GACb,UAAA;GACA,YAAY;GACZ,mBAAmB;EACpB,CAAA,GAGG,UACJ,kBAAC,OAAD;GACE,WAAU;GACV,eAAY;aAFd,CAIE,kBAAC,GAAD;IACE,QAAO;IACP,MAAK;IACL,OAAO,EAAE,EAAQ,2BAA2B;IAC5C,QAAQ,KAAsB,CAAC,GAAG,KAAK,OAAW;KAChD,KAAK;KACL,OAAO;IACT,EAAE;IACF,aAAa;IACb,aAAa;IACb,UAAA;IACA,YACE,KAAqC,CAAC,GAAoB;IAE5D,oBAAoB,MAAgB;KAClC,IAAM,IACJ,OAAO,KAAgB,WACnB,IACA,IAAqB;KAC3B,AAAI,KACF,EAAS,aAAa,CAAS;IAEnC;GACD,CAAA,GACD,kBAAC,GAAD,EAAwC,cAAa,CAAA,CAClD;;EAGP,OACE,kBAAC,OAAD;GAAK,WAAU;aACZ,MAAS,UACR,kBAAC,OAAD;IACE,WAAU;IACV,eAAY;cAFd,CAIG,EAAoB,GAEpB,IACC,EAA2B,IAE3B,kBAAA,GAAA,EAAA,UAAA;KACE,kBAAC,GAAD;MACE,cAAc,KAAc,KAAA;MAC5B,WAAW,GAAU,MAAU;OAC7B,IAAM,IAAY,EAAa,GAAU,CAAK;OAC9C,AAAI,KACF,EAAS,aAAa,CAAS;MAEnC;MACA,kBAAiB;MACL;KACb,CAAA;KAEA,IACC,kBAAC,GAAD,EAAqB,QAAO,yBAA0B,CAAA,IACpD;KAEH,EAEC,qBAEA,yBACF;IACA,EAAA,CAAA,CAED;QAEL,kBAAC,OAAD;IACE,WAAU;IACV,eAAY;cAFd,CAIG,EAAoB,GAEpB,IACC,EAA2B,IAE3B,kBAAA,GAAA,EAAA,UAAA;KACE,kBAAC,GAAD;MACE,QAAO;MACP,OAAO,EAAE,EAAQ,qBAAqB;MACtC,MAAK;MACL,WAAU;MACV,OAAO;MACP,aAAa;MACb,WAAW,MAAU,EAAS,aAAa,CAAK;MACpC;KACb,CAAA;KAEA,IACC,kBAAC,GAAD,EAAqB,QAAO,2BAA4B,CAAA,IACtD;KAEJ,kBAAC,GAAD;MACE,QAAO;MACP,OAAO,EAAE,EAAQ,iBAAiB;MAClC,MAAK;MACL,WAAU;MACV,OAAO;MAEP,aAAY;MACZ,WAAW,MAAU,EAAS,gBAAgB,CAAK;MACvC;KACb,CAAA;KAEA,EAEC,qBAEA,kCACF;IACA,EAAA,CAAA,CAED;;EAEJ,CAAA;CAET,GACA;EACE;EACA;EACA;EACA,GAAU;EACV;EACA;CACF,CACF,GAEM,IAAe,EAAM,aAEvB,GACA,MAKG;EAIH,IAAM,IAAgB,gBACnB,EAAe,uBAAmD,CAAC,CACtE,GACM,IAAO,EAAc,OAAO,CAAC;EAGnC,IAFiB,EAAmB,EAAQ,OAAA,gBAExC,MAAA,gBAAyC;GAE3C,AADA,EAAI,YAAY,GAChB,EAAI,sBAAsB;GAC1B,IAAM,IACJ,OAAO,EAAI,SAAU,WACjB,EAAI,QACJ,OAAO,EAAQ,OAAO,gBAAgB,EAAE,GACxC,IAA4B,IAAqB;GACvD,IACE,CAAC,GAAoB,SAAS,CAAK,KACnC,CAAC,GAED,MAAU,MAAM,yCAAyC;GAM3D,AAJA,EAAI,QAAQ,GAAoB,SAAS,CAAK,IAC1C,IACA,GACJ,OAAO,EAAI,SACX,OAAO,EAAI;EACb,OAKE,AAJI,EAAQ,MAAA,qBACV,EAAI,YAAY,GAChB,EAAI,sBAAsB,OAExB,EAAQ,SAAS,WAAW,EAAI,UAAU,KAAA,MAC5C,EAAI,WAAW,EAA2B,GAAQ,cAAc;EAKpE,OADA,EAAc,MAAM,GACb,EAAE,qBAAqB,EAAc;CAC9C,GACA,CAAC,GAAQ,CAAkB,CAC7B;CAEA,OACE,kBAAC,GAAD;EACS;EACP,iBAAiB,CACf;GACE,gBAAgB;GAChB,aAAa,CAAC,KAAK;GACnB,aAAa;EACf,CACF;EACA,QAAQ;EACM;EACE;EAChB,uBAAA;EACA,cAAA;EACe;EACQ;EACb;EACM;EACM;EACD;EACrB,QAAO;CACR,CAAA;AAEL"}
|
|
1
|
+
{"version":3,"file":"llm-settings.js","names":[],"sources":["../../src/routes/llm-settings.tsx"],"sourcesContent":["import React from \"react\";\nimport { Info } from \"lucide-react\";\nimport { useTranslation } from \"react-i18next\";\nimport { ModelSelector } from \"#/components/shared/modals/settings/model-selector\";\nimport { useAgentSettingsSchema } from \"#/hooks/query/use-agent-settings-schema\";\nimport { useSettings } from \"#/hooks/query/use-settings\";\nimport { SettingsInput } from \"#/components/features/settings/settings-input\";\nimport { SettingsDropdownInput } from \"#/components/features/settings/settings-dropdown-input\";\nimport { OpenAISubscriptionAuthCard } from \"#/components/features/settings/llm-settings/openai-subscription-auth-card\";\nimport { HelpLink } from \"#/ui/help-link\";\nimport { KeyStatusIcon } from \"#/components/features/settings/key-status-icon\";\nimport {\n SdkSectionHeaderProps,\n SdkSectionPage,\n SdkSectionSaveControl,\n} from \"#/components/features/settings/sdk-settings/sdk-section-page\";\nimport { LlmSettingsLocalView } from \"#/components/features/settings/llm-profiles\";\nimport { I18nKey } from \"#/i18n/declaration\";\nimport { Settings, SettingsSchema, SettingsScope } from \"#/types/settings\";\nimport { extractModelAndProvider } from \"#/utils/extract-model-and-provider\";\nimport {\n inferInitialView,\n type SettingsFormValues,\n type SettingsView,\n} from \"#/utils/sdk-settings-schema\";\nimport { DEFAULT_SETTINGS } from \"#/services/settings\";\nimport {\n LLM_AUTH_TYPE_API_KEY,\n LLM_AUTH_TYPE_KEY,\n LLM_AUTH_TYPE_SUBSCRIPTION,\n LLM_SUBSCRIPTION_VENDOR_KEY,\n OPENAI_SUBSCRIPTION_VENDOR,\n resolveLlmAuthType,\n} from \"#/constants/llm-subscription\";\nimport { useOpenAISubscriptionModels } from \"#/hooks/query/use-llm-subscription-models\";\nimport {\n FREE_OPENHANDS_MODEL_NOTE,\n isFreeOpenHandsModel,\n} from \"#/utils/format-model-name\";\n\nconst LLM_EXCLUDED_KEYS = new Set([\n \"llm.model\",\n \"llm.api_key\",\n \"llm.base_url\",\n LLM_AUTH_TYPE_KEY,\n LLM_SUBSCRIPTION_VENDOR_KEY,\n]);\n\nconst buildModelId = (provider: string | null, model: string | null) => {\n if (!provider || !model) return null;\n return `${provider}/${model}`;\n};\n\nconst getSchemaFieldDefaultValue = (\n schema: SettingsSchema | null | undefined,\n fieldKey: string,\n) =>\n schema?.sections\n .flatMap((section) => section.fields)\n .find((field) => field.key === fieldKey)?.default ?? null;\n\nconst KNOWN_PROVIDER_DEFAULT_BASE_URLS: Partial<Record<string, Set<string>>> = {\n openai: new Set([\"https://api.openai.com\", \"https://api.openai.com/v1\"]),\n moonshot: new Set([\"https://api.kimi.com/coding/v1\"]),\n};\n\nconst normalizeBaseUrl = (baseUrl: string) => {\n try {\n const parsedUrl = new URL(baseUrl);\n const normalizedPath = parsedUrl.pathname.replace(/\\/+$/, \"\") || \"\";\n return `${parsedUrl.origin}${normalizedPath}`;\n } catch {\n return baseUrl.trim().replace(/\\/+$/, \"\");\n }\n};\n\nconst isProviderDefaultBaseUrl = (model: string, baseUrl: string) => {\n const normalizedBaseUrl = normalizeBaseUrl(baseUrl);\n const { provider } = extractModelAndProvider(model);\n\n if (provider) {\n const knownDefaults = KNOWN_PROVIDER_DEFAULT_BASE_URLS[provider];\n if (knownDefaults) {\n return knownDefaults.has(normalizedBaseUrl);\n }\n }\n\n return Object.values(KNOWN_PROVIDER_DEFAULT_BASE_URLS).some((knownDefaults) =>\n knownDefaults?.has(normalizedBaseUrl),\n );\n};\n\ninterface OpenHandsApiKeyHelpProps {\n testId: string;\n}\n\nfunction OpenHandsApiKeyHelp({ testId }: OpenHandsApiKeyHelpProps) {\n const { t } = useTranslation(\"openhands\");\n\n return (\n <HelpLink\n testId={testId}\n text={t(I18nKey.SETTINGS$OPENHANDS_API_KEY_HELP_TEXT)}\n linkText={t(I18nKey.SETTINGS$NAV_API_KEYS)}\n href=\"https://app.all-hands.dev/settings/api-keys\"\n suffix={` ${t(I18nKey.SETTINGS$OPENHANDS_API_KEY_HELP_SUFFIX)}`}\n />\n );\n}\n\nfunction OpenHandsFreeModelsNote() {\n return (\n <p\n data-testid=\"openhands-free-models-note\"\n className=\"flex items-start gap-2 text-xs text-warning\"\n >\n <Info className=\"mt-0.5 size-4 shrink-0 text-warning\" aria-hidden />\n <span>{FREE_OPENHANDS_MODEL_NOTE}</span>\n </p>\n );\n}\n\nexport function LlmSettingsScreen({\n scope = \"personal\",\n onSaveSuccess,\n initialValueOverrides,\n embedded,\n hideSaveButton,\n suppressSuccessToast,\n onSaveControlChange,\n}: {\n scope?: SettingsScope;\n /** Optional hook fired after a successful save (e.g. advance an onboarding step). */\n onSaveSuccess?: () => void;\n /** Forwarded to {@link SdkSectionPage}. */\n initialValueOverrides?: SettingsFormValues;\n /** Forwarded to {@link SdkSectionPage}. */\n embedded?: boolean;\n /** Forwarded to {@link SdkSectionPage}. */\n hideSaveButton?: boolean;\n /** Forwarded to {@link SdkSectionPage}. */\n suppressSuccessToast?: boolean;\n /** Forwarded to {@link SdkSectionPage}. */\n onSaveControlChange?: (control: SdkSectionSaveControl) => void;\n}) {\n const { t } = useTranslation(\"openhands\");\n\n const { data: settings } = useSettings(scope);\n const { data: schema } = useAgentSettingsSchema(\n settings?.agent_settings_schema,\n );\n const persistedLlmSettings = settings?.agent_settings?.llm as\n | Record<string, unknown>\n | undefined;\n const initialAuthType = resolveLlmAuthType(\n initialValueOverrides?.[LLM_AUTH_TYPE_KEY] ??\n persistedLlmSettings?.auth_type,\n );\n const [enableSubscriptionModels, setEnableSubscriptionModels] =\n React.useState(initialAuthType === LLM_AUTH_TYPE_SUBSCRIPTION);\n const {\n data: subscriptionModels,\n isLoading: isSubscriptionModelsLoading,\n isFetching: isSubscriptionModelsFetching,\n } = useOpenAISubscriptionModels({ enabled: enableSubscriptionModels });\n const isWaitingForSubscriptionModels =\n enableSubscriptionModels &&\n !subscriptionModels &&\n (isSubscriptionModelsLoading || isSubscriptionModelsFetching);\n const lastApiKeyModelRef = React.useRef<string | null>(null);\n const lastSubscriptionModelRef = React.useRef<string | null>(null);\n\n React.useEffect(() => {\n if (initialAuthType === LLM_AUTH_TYPE_SUBSCRIPTION) {\n setEnableSubscriptionModels(true);\n }\n }, [initialAuthType]);\n\n const defaultModel = String(\n (DEFAULT_SETTINGS.agent_settings?.llm as Record<string, unknown>)?.model ??\n \"\",\n );\n\n const getInitialView = React.useCallback(\n (\n currentSettings: Settings,\n filteredSchema: SettingsSchema,\n ): SettingsView => {\n const schemaView = inferInitialView(currentSettings, filteredSchema);\n if (schemaView !== \"basic\") {\n return schemaView;\n }\n\n const currentModel = currentSettings.llm_model ?? \"\";\n const trimmedBaseUrl = currentSettings.llm_base_url?.trim() ?? \"\";\n const hasCustomBaseUrl =\n trimmedBaseUrl.length > 0 &&\n !isProviderDefaultBaseUrl(currentModel, trimmedBaseUrl);\n\n return hasCustomBaseUrl ? \"all\" : \"basic\";\n },\n [],\n );\n\n const buildHeader = React.useCallback(\n ({ values, isDisabled, view, onChange }: SdkSectionHeaderProps) => {\n const modelValue =\n typeof values[\"llm.model\"] === \"string\" ? values[\"llm.model\"] : \"\";\n const baseUrlValue =\n typeof values[\"llm.base_url\"] === \"string\"\n ? values[\"llm.base_url\"]\n : \"\";\n const showOpenHandsApiKeyHelp = modelValue.startsWith(\"openhands/\");\n const authType = resolveLlmAuthType(values[LLM_AUTH_TYPE_KEY]);\n const isSubscriptionAuth = authType === LLM_AUTH_TYPE_SUBSCRIPTION;\n const shouldDisableSubscriptionControls =\n isDisabled || (isSubscriptionAuth && isWaitingForSubscriptionModels);\n const subscriptionModelValue = subscriptionModels?.includes(modelValue)\n ? modelValue\n : (subscriptionModels?.[0] ?? \"\");\n\n const apiKeyValue =\n typeof values[\"llm.api_key\"] === \"string\" ? values[\"llm.api_key\"] : \"\";\n // For embedded profile forms (create/edit) the global\n // `llm_api_key_set` flag is misleading: a brand-new profile would show a\n // \"key set\" indicator just because some other profile has a key. Reflect\n // the form's own key state instead so create mode starts visibly unset.\n const apiKeyIsSet = embedded\n ? apiKeyValue.length > 0\n : Boolean(settings?.llm_api_key_set);\n\n const renderApiKeyInput = (testId: string, helpTestId: string) => (\n <>\n <SettingsInput\n testId={testId}\n label={t(I18nKey.SETTINGS_FORM$API_KEY)}\n type=\"password\"\n className=\"w-full\"\n value={apiKeyValue}\n // eslint-disable-next-line i18next/no-literal-string -- masked-key sentinel, not translatable\n placeholder={apiKeyIsSet ? \"<hidden>\" : \"\"}\n onChange={(value) => onChange(\"llm.api_key\", value)}\n isDisabled={isDisabled}\n startContent={\n apiKeyIsSet ? <KeyStatusIcon isSet={apiKeyIsSet} /> : undefined\n }\n />\n\n <HelpLink\n testId={helpTestId}\n text={t(I18nKey.SETTINGS$DONT_KNOW_API_KEY)}\n linkText={t(I18nKey.SETTINGS$CLICK_FOR_INSTRUCTIONS)}\n href=\"https://docs.openhands.dev/usage/local-setup#getting-an-api-key\"\n />\n </>\n );\n\n const handleAuthTypeChange = (selectedKey: React.Key | null) => {\n const nextAuthType =\n selectedKey === LLM_AUTH_TYPE_SUBSCRIPTION\n ? LLM_AUTH_TYPE_SUBSCRIPTION\n : LLM_AUTH_TYPE_API_KEY;\n onChange(LLM_AUTH_TYPE_KEY, nextAuthType);\n\n if (nextAuthType === LLM_AUTH_TYPE_SUBSCRIPTION) {\n setEnableSubscriptionModels(true);\n if (modelValue && !subscriptionModels?.includes(modelValue)) {\n lastApiKeyModelRef.current = modelValue;\n }\n const restoredSubscriptionModel =\n lastSubscriptionModelRef.current &&\n subscriptionModels?.includes(lastSubscriptionModelRef.current)\n ? lastSubscriptionModelRef.current\n : subscriptionModels?.[0];\n onChange(LLM_SUBSCRIPTION_VENDOR_KEY, OPENAI_SUBSCRIPTION_VENDOR);\n if (\n !subscriptionModels?.includes(modelValue) &&\n restoredSubscriptionModel\n ) {\n onChange(\"llm.model\", restoredSubscriptionModel);\n }\n return;\n }\n\n if (modelValue && subscriptionModels?.includes(modelValue)) {\n lastSubscriptionModelRef.current = modelValue;\n onChange(\"llm.model\", lastApiKeyModelRef.current ?? defaultModel);\n }\n };\n\n const renderAuthTypeInput = () => (\n <SettingsDropdownInput\n testId=\"llm-auth-type-input\"\n name={LLM_AUTH_TYPE_KEY}\n label={t(I18nKey.SETTINGS$LLM_AUTH_TYPE)}\n items={[\n {\n key: LLM_AUTH_TYPE_API_KEY,\n label: t(I18nKey.SETTINGS$LLM_AUTH_TYPE_API_KEY),\n },\n {\n key: LLM_AUTH_TYPE_SUBSCRIPTION,\n label: t(I18nKey.SETTINGS$LLM_AUTH_TYPE_SUBSCRIPTION),\n },\n ]}\n selectedKey={authType}\n isClearable={false}\n required\n isDisabled={shouldDisableSubscriptionControls}\n onSelectionChange={handleAuthTypeChange}\n />\n );\n\n const renderSubscriptionSettings = () => (\n <div\n className=\"flex flex-col gap-6\"\n data-testid=\"llm-subscription-settings\"\n >\n <SettingsDropdownInput\n testId=\"llm-subscription-model-input\"\n name=\"llm.subscription_model\"\n label={t(I18nKey.SETTINGS$SUBSCRIPTION_MODEL)}\n items={(subscriptionModels ?? []).map((model) => ({\n key: model,\n label: model,\n }))}\n selectedKey={subscriptionModelValue}\n isClearable={false}\n required\n isDisabled={\n shouldDisableSubscriptionControls || !subscriptionModels?.length\n }\n onSelectionChange={(selectedKey) => {\n const nextModel =\n typeof selectedKey === \"string\"\n ? selectedKey\n : subscriptionModels?.[0];\n if (nextModel) {\n onChange(\"llm.model\", nextModel);\n }\n }}\n />\n <OpenAISubscriptionAuthCard isDisabled={isDisabled} />\n </div>\n );\n\n return (\n <div className=\"flex flex-col gap-6\">\n {view === \"basic\" ? (\n <div\n className=\"flex flex-col gap-6\"\n data-testid=\"llm-settings-form-basic\"\n >\n {renderAuthTypeInput()}\n\n {isSubscriptionAuth ? (\n renderSubscriptionSettings()\n ) : (\n <>\n <ModelSelector\n currentModel={modelValue || undefined}\n onChange={(provider, model) => {\n const nextModel = buildModelId(provider, model);\n if (nextModel) {\n onChange(\"llm.model\", nextModel);\n }\n }}\n wrapperClassName=\"!flex-col !gap-6\"\n isDisabled={isDisabled}\n />\n\n {showOpenHandsApiKeyHelp ? (\n <OpenHandsApiKeyHelp testId=\"openhands-api-key-help\" />\n ) : null}\n\n {renderApiKeyInput(\n // eslint-disable-next-line i18next/no-literal-string -- DOM id, not user-facing\n \"llm-api-key-input\",\n // eslint-disable-next-line i18next/no-literal-string -- DOM id, not user-facing\n \"llm-api-key-help-anchor\",\n )}\n </>\n )}\n </div>\n ) : (\n <div\n className=\"flex flex-col gap-6\"\n data-testid=\"llm-settings-form-advanced\"\n >\n {renderAuthTypeInput()}\n\n {isSubscriptionAuth ? (\n renderSubscriptionSettings()\n ) : (\n <>\n <SettingsInput\n testId=\"llm-custom-model-input\"\n label={t(I18nKey.SETTINGS$CUSTOM_MODEL)}\n type=\"text\"\n className=\"w-full\"\n value={modelValue}\n placeholder={defaultModel}\n onChange={(value) => onChange(\"llm.model\", value)}\n isDisabled={isDisabled}\n />\n\n {showOpenHandsApiKeyHelp ? (\n <>\n {isFreeOpenHandsModel(modelValue) ? (\n <OpenHandsFreeModelsNote />\n ) : null}\n <OpenHandsApiKeyHelp testId=\"openhands-api-key-help-2\" />\n </>\n ) : null}\n\n <SettingsInput\n testId=\"base-url-input\"\n label={t(I18nKey.SETTINGS$BASE_URL)}\n type=\"text\"\n className=\"w-full\"\n value={baseUrlValue}\n // eslint-disable-next-line i18next/no-literal-string -- example value, not translatable\n placeholder=\"https://api.openai.com\"\n onChange={(value) => onChange(\"llm.base_url\", value)}\n isDisabled={isDisabled}\n />\n\n {renderApiKeyInput(\n // eslint-disable-next-line i18next/no-literal-string -- DOM id, not user-facing\n \"llm-api-key-input\",\n // eslint-disable-next-line i18next/no-literal-string -- DOM id, not user-facing\n \"llm-api-key-help-anchor-advanced\",\n )}\n </>\n )}\n </div>\n )}\n </div>\n );\n },\n [\n defaultModel,\n embedded,\n isWaitingForSubscriptionModels,\n settings?.llm_api_key_set,\n subscriptionModels,\n t,\n ],\n );\n\n const buildPayload = React.useCallback(\n (\n defaultPayload: Record<string, unknown>,\n context: {\n values: Record<string, string | boolean>;\n dirty: Record<string, boolean>;\n view: SettingsView;\n },\n ) => {\n // defaultPayload is the wrapped diff (e.g.\n // `{ agent_settings_diff: { llm: { model: \"gpt-4\" } } }`); we only\n // mutate the inner `llm` object below.\n const agentSettings = structuredClone(\n (defaultPayload.agent_settings_diff as Record<string, unknown>) ?? {},\n );\n const llm = (agentSettings.llm ?? {}) as Record<string, unknown>;\n const authType = resolveLlmAuthType(context.values[LLM_AUTH_TYPE_KEY]);\n\n if (authType === LLM_AUTH_TYPE_SUBSCRIPTION) {\n llm.auth_type = LLM_AUTH_TYPE_SUBSCRIPTION;\n llm.subscription_vendor = OPENAI_SUBSCRIPTION_VENDOR;\n const model =\n typeof llm.model === \"string\"\n ? llm.model\n : String(context.values[\"llm.model\"] ?? \"\");\n const fallbackSubscriptionModel = subscriptionModels?.[0];\n if (\n !subscriptionModels?.includes(model) &&\n !fallbackSubscriptionModel\n ) {\n throw new Error(\"Subscription models are not loaded yet.\");\n }\n llm.model = subscriptionModels?.includes(model)\n ? model\n : fallbackSubscriptionModel;\n delete llm.api_key;\n delete llm.base_url;\n } else {\n if (context.dirty[LLM_AUTH_TYPE_KEY]) {\n llm.auth_type = LLM_AUTH_TYPE_API_KEY;\n llm.subscription_vendor = null;\n }\n if (context.view === \"basic\" && llm.model !== undefined) {\n llm.base_url = getSchemaFieldDefaultValue(schema, \"llm.base_url\");\n }\n }\n\n agentSettings.llm = llm;\n return { agent_settings_diff: agentSettings };\n },\n [schema, subscriptionModels],\n );\n\n return (\n <SdkSectionPage\n scope={scope}\n settingsSources={[\n {\n settingsSource: \"agent_settings\",\n sectionKeys: [\"llm\"],\n excludeKeys: LLM_EXCLUDED_KEYS,\n },\n ]}\n header={buildHeader}\n buildPayload={buildPayload}\n getInitialView={getInitialView}\n forceShowAdvancedView\n allowAllView\n onSaveSuccess={onSaveSuccess}\n initialValueOverrides={initialValueOverrides}\n embedded={embedded}\n hideSaveButton={hideSaveButton}\n suppressSuccessToast={suppressSuccessToast}\n onSaveControlChange={onSaveControlChange}\n testId=\"llm-settings-screen\"\n />\n );\n}\n\n/**\n * Default export for the route renders the LLM-profile management view for both\n * backend types. Both manage the LLM through named profiles — local via the\n * agent-server (`/api/profiles`), cloud via the app-server\n * (`/api/v1/settings/profiles`) — and the view is backend-agnostic because it\n * goes through ProfilesService, which routes per active backend.\n *\n * The LlmSettingsScreen component is also exported for embedded use cases\n * (e.g., onboarding, the profile create/edit form).\n *\n * Note: This is a route file, only the router should import the default export.\n * Other consumers should use the named export `LlmSettingsScreen` for embedded\n * use cases.\n */\nexport default function LlmSettingsRoute() {\n return <LlmSettingsLocalView />;\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
require("../_virtual/_rolldown/runtime.cjs");function e(e){return e?e.split(`/`).pop()||e:null}exports.formatNativeModelName=
|
|
1
|
+
require("../_virtual/_rolldown/runtime.cjs");var e=`Free`,t={"openhands/glm-5.2":`OpenHands GLM-5.2 (free)`,"openhands/deepseek-v4-flash":`OpenHands DeepSeek V4 Flash (free)`,"openhands/minimax-m2.7":`OpenHands MiniMax M2.7 (free)`},n=`Free OpenHands models: ${Object.keys(t).join(`, `)}. Other provider endpoints with similar model names may require separate billing.`,r=e=>!!(e&&e in t);function i(e){return e?r(e)?t[e]:e:null}function a(e){return e?r(e)?t[e]:e.split(`/`).pop()||e:null}exports.FREE_MODEL_BADGE_LABEL=e,exports.FREE_OPENHANDS_MODEL_NOTE=n,exports.formatModelNameForDisplay=i,exports.formatNativeModelName=a,exports.isFreeOpenHandsModel=r;
|
|
2
2
|
//# sourceMappingURL=format-model-name.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format-model-name.cjs","names":[],"sources":["../../src/utils/format-model-name.ts"],"sourcesContent":["/**\n * Format a native (OpenHands-kind) routing model string for display, stripping\n * the provider route prefix (e.g. ``\"anthropic/claude-sonnet-4-5-20250929\"`` →\n * ``\"claude-sonnet-4-5-20250929\"``, ``\"litellm_proxy/openai/gpt-4o\"`` →\n * ``\"gpt-4o\"``) so a conversation chip shows a meaningful model name rather than\n * the full routing path.\n *\n * Returns ``null`` for an empty/nullish input, and falls back to the original\n * string when stripping the prefix would leave nothing (e.g. a trailing slash)\n * — never an empty string, which would collapse the chip text.\n *\n * Display-only: unlike {@link deriveProfileNameFromModel} this does not sanitize\n * to an identifier, so it keeps the real model id intact for the chip.\n */\nexport function formatNativeModelName(\n model: string | null | undefined,\n): string | null {\n if (!model) return null;\n const lastSegment = model.split(\"/\").pop();\n return lastSegment || model;\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"format-model-name.cjs","names":[],"sources":["../../src/utils/format-model-name.ts"],"sourcesContent":["export const FREE_MODEL_BADGE_LABEL = \"Free\";\n\nexport const FREE_OPENHANDS_MODELS = {\n \"openhands/glm-5.2\": \"OpenHands GLM-5.2 (free)\",\n \"openhands/deepseek-v4-flash\": \"OpenHands DeepSeek V4 Flash (free)\",\n \"openhands/minimax-m2.7\": \"OpenHands MiniMax M2.7 (free)\",\n} as const;\n\nexport const FREE_OPENHANDS_MODEL_IDS = Object.keys(FREE_OPENHANDS_MODELS);\nexport const FREE_OPENHANDS_MODEL_NOTE = `Free OpenHands models: ${FREE_OPENHANDS_MODEL_IDS.join(\n \", \",\n)}. Other provider endpoints with similar model names may require separate billing.`;\n\nexport const isFreeOpenHandsModel = (\n model: string | null | undefined,\n): model is keyof typeof FREE_OPENHANDS_MODELS =>\n Boolean(model && model in FREE_OPENHANDS_MODELS);\n\nexport function formatModelNameForDisplay(\n model: string | null | undefined,\n): string | null {\n if (!model) return null;\n return isFreeOpenHandsModel(model) ? FREE_OPENHANDS_MODELS[model] : model;\n}\n\nexport function formatProviderModelNameForDisplay(\n provider: string | null | undefined,\n model: string | null | undefined,\n): string | null {\n if (!model) return null;\n const fullModel = provider ? `${provider}/${model}` : model;\n return isFreeOpenHandsModel(fullModel)\n ? FREE_OPENHANDS_MODELS[fullModel]\n : model;\n}\n\n/**\n * Format a native (OpenHands-kind) routing model string for display, stripping\n * the provider route prefix (e.g. ``\"anthropic/claude-sonnet-4-5-20250929\"`` →\n * ``\"claude-sonnet-4-5-20250929\"``, ``\"litellm_proxy/openai/gpt-4o\"`` →\n * ``\"gpt-4o\"``) so a conversation chip shows a meaningful model name rather than\n * the full routing path.\n *\n * Returns ``null`` for an empty/nullish input, and falls back to the original\n * string when stripping the prefix would leave nothing (e.g. a trailing slash)\n * — never an empty string, which would collapse the chip text.\n *\n * Display-only: unlike {@link deriveProfileNameFromModel} this does not sanitize\n * to an identifier, so it keeps the real model id intact for the chip.\n */\nexport function formatNativeModelName(\n model: string | null | undefined,\n): string | null {\n if (!model) return null;\n if (isFreeOpenHandsModel(model)) return FREE_OPENHANDS_MODELS[model];\n const lastSegment = model.split(\"/\").pop();\n return lastSegment || model;\n}\n"],"mappings":"6CAAA,IAAa,EAAyB,OAEzB,EAAwB,CACnC,oBAAqB,2BACrB,8BAA+B,qCAC/B,yBAA0B,+BAC5B,EAGa,EAA4B,0BADD,OAAO,KAAK,CACe,EAAyB,KAC1F,IACF,EAAE,mFAEW,EACX,GAEA,GAAQ,GAAS,KAAS,GAE5B,SAAgB,EACd,EACe,CAEf,OADK,EACE,EAAqB,CAAK,EAAI,EAAsB,GAAS,EADjD,IAErB,CA2BA,SAAgB,EACd,EACe,CAIf,OAHK,EACD,EAAqB,CAAK,EAAU,EAAsB,GAC1C,EAAM,MAAM,GAAG,EAAE,IAC9B,GAAe,EAHH,IAIrB"}
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
export declare const FREE_MODEL_BADGE_LABEL = "Free";
|
|
2
|
+
export declare const FREE_OPENHANDS_MODELS: {
|
|
3
|
+
readonly "openhands/glm-5.2": "OpenHands GLM-5.2 (free)";
|
|
4
|
+
readonly "openhands/deepseek-v4-flash": "OpenHands DeepSeek V4 Flash (free)";
|
|
5
|
+
readonly "openhands/minimax-m2.7": "OpenHands MiniMax M2.7 (free)";
|
|
6
|
+
};
|
|
7
|
+
export declare const FREE_OPENHANDS_MODEL_IDS: string[];
|
|
8
|
+
export declare const FREE_OPENHANDS_MODEL_NOTE: string;
|
|
9
|
+
export declare const isFreeOpenHandsModel: (model: string | null | undefined) => model is keyof typeof FREE_OPENHANDS_MODELS;
|
|
10
|
+
export declare function formatModelNameForDisplay(model: string | null | undefined): string | null;
|
|
11
|
+
export declare function formatProviderModelNameForDisplay(provider: string | null | undefined, model: string | null | undefined): string | null;
|
|
1
12
|
/**
|
|
2
13
|
* Format a native (OpenHands-kind) routing model string for display, stripping
|
|
3
14
|
* the provider route prefix (e.g. ``"anthropic/claude-sonnet-4-5-20250929"`` →
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
//#region src/utils/format-model-name.ts
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
var e = "Free", t = {
|
|
3
|
+
"openhands/glm-5.2": "OpenHands GLM-5.2 (free)",
|
|
4
|
+
"openhands/deepseek-v4-flash": "OpenHands DeepSeek V4 Flash (free)",
|
|
5
|
+
"openhands/minimax-m2.7": "OpenHands MiniMax M2.7 (free)"
|
|
6
|
+
}, n = `Free OpenHands models: ${Object.keys(t).join(", ")}. Other provider endpoints with similar model names may require separate billing.`, r = (e) => !!(e && e in t);
|
|
7
|
+
function i(e) {
|
|
8
|
+
return e ? r(e) ? t[e] : e : null;
|
|
9
|
+
}
|
|
10
|
+
function a(e) {
|
|
11
|
+
return e ? r(e) ? t[e] : e.split("/").pop() || e : null;
|
|
4
12
|
}
|
|
5
13
|
//#endregion
|
|
6
|
-
export { e as formatNativeModelName };
|
|
14
|
+
export { e as FREE_MODEL_BADGE_LABEL, n as FREE_OPENHANDS_MODEL_NOTE, i as formatModelNameForDisplay, a as formatNativeModelName, r as isFreeOpenHandsModel };
|
|
7
15
|
|
|
8
16
|
//# sourceMappingURL=format-model-name.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format-model-name.js","names":[],"sources":["../../src/utils/format-model-name.ts"],"sourcesContent":["/**\n * Format a native (OpenHands-kind) routing model string for display, stripping\n * the provider route prefix (e.g. ``\"anthropic/claude-sonnet-4-5-20250929\"`` →\n * ``\"claude-sonnet-4-5-20250929\"``, ``\"litellm_proxy/openai/gpt-4o\"`` →\n * ``\"gpt-4o\"``) so a conversation chip shows a meaningful model name rather than\n * the full routing path.\n *\n * Returns ``null`` for an empty/nullish input, and falls back to the original\n * string when stripping the prefix would leave nothing (e.g. a trailing slash)\n * — never an empty string, which would collapse the chip text.\n *\n * Display-only: unlike {@link deriveProfileNameFromModel} this does not sanitize\n * to an identifier, so it keeps the real model id intact for the chip.\n */\nexport function formatNativeModelName(\n model: string | null | undefined,\n): string | null {\n if (!model) return null;\n const lastSegment = model.split(\"/\").pop();\n return lastSegment || model;\n}\n"],"mappings":";
|
|
1
|
+
{"version":3,"file":"format-model-name.js","names":[],"sources":["../../src/utils/format-model-name.ts"],"sourcesContent":["export const FREE_MODEL_BADGE_LABEL = \"Free\";\n\nexport const FREE_OPENHANDS_MODELS = {\n \"openhands/glm-5.2\": \"OpenHands GLM-5.2 (free)\",\n \"openhands/deepseek-v4-flash\": \"OpenHands DeepSeek V4 Flash (free)\",\n \"openhands/minimax-m2.7\": \"OpenHands MiniMax M2.7 (free)\",\n} as const;\n\nexport const FREE_OPENHANDS_MODEL_IDS = Object.keys(FREE_OPENHANDS_MODELS);\nexport const FREE_OPENHANDS_MODEL_NOTE = `Free OpenHands models: ${FREE_OPENHANDS_MODEL_IDS.join(\n \", \",\n)}. Other provider endpoints with similar model names may require separate billing.`;\n\nexport const isFreeOpenHandsModel = (\n model: string | null | undefined,\n): model is keyof typeof FREE_OPENHANDS_MODELS =>\n Boolean(model && model in FREE_OPENHANDS_MODELS);\n\nexport function formatModelNameForDisplay(\n model: string | null | undefined,\n): string | null {\n if (!model) return null;\n return isFreeOpenHandsModel(model) ? FREE_OPENHANDS_MODELS[model] : model;\n}\n\nexport function formatProviderModelNameForDisplay(\n provider: string | null | undefined,\n model: string | null | undefined,\n): string | null {\n if (!model) return null;\n const fullModel = provider ? `${provider}/${model}` : model;\n return isFreeOpenHandsModel(fullModel)\n ? FREE_OPENHANDS_MODELS[fullModel]\n : model;\n}\n\n/**\n * Format a native (OpenHands-kind) routing model string for display, stripping\n * the provider route prefix (e.g. ``\"anthropic/claude-sonnet-4-5-20250929\"`` →\n * ``\"claude-sonnet-4-5-20250929\"``, ``\"litellm_proxy/openai/gpt-4o\"`` →\n * ``\"gpt-4o\"``) so a conversation chip shows a meaningful model name rather than\n * the full routing path.\n *\n * Returns ``null`` for an empty/nullish input, and falls back to the original\n * string when stripping the prefix would leave nothing (e.g. a trailing slash)\n * — never an empty string, which would collapse the chip text.\n *\n * Display-only: unlike {@link deriveProfileNameFromModel} this does not sanitize\n * to an identifier, so it keeps the real model id intact for the chip.\n */\nexport function formatNativeModelName(\n model: string | null | undefined,\n): string | null {\n if (!model) return null;\n if (isFreeOpenHandsModel(model)) return FREE_OPENHANDS_MODELS[model];\n const lastSegment = model.split(\"/\").pop();\n return lastSegment || model;\n}\n"],"mappings":";AAAA,IAAa,IAAyB,QAEzB,IAAwB;CACnC,qBAAqB;CACrB,+BAA+B;CAC/B,0BAA0B;AAC5B,GAGa,IAA4B,0BADD,OAAO,KAAK,CACe,EAAyB,KAC1F,IACF,EAAE,oFAEW,KACX,MAEA,GAAQ,KAAS,KAAS;AAE5B,SAAgB,EACd,GACe;CAEf,OADK,IACE,EAAqB,CAAK,IAAI,EAAsB,KAAS,IADjD;AAErB;AA2BA,SAAgB,EACd,GACe;CAIf,OAHK,IACD,EAAqB,CAAK,IAAU,EAAsB,KAC1C,EAAM,MAAM,GAAG,EAAE,IAC9B,KAAe,IAHH;AAIrB"}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{n as e,r as t}from"./agent-settings-h8hQz-IU.js";export{e as default,t as handle};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{a as e}from"./rolldown-runtime-Cyuzqnbw.js";import{St as t,yt as n}from"./vendor-styling-Ck5D_A2Q.js";import{U as r}from"./vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~lq5hicxx-cHFhpUBq.js";import{t as i}from"./vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~extensions-h~gxu4j2ea-Cwhh3zLe.js";import{At as a,Pt as o,_ as s,jt as c,n as l,zt as u}from"./active-backend-context-BBvP-bI2.js";import{z as d}from"./utils-DPQfkd-7.js";import{t as f}from"./settings-CWFxymAf.js";import{n as p}from"./use-settings-BHWsOcIG.js";import{t as m}from"./use-save-settings-pSjRhD_m.js";import{t as h}from"./brand-button-CJcwjnfb.js";import{i as g,n as _,r as v,t as y}from"./color-themes-DTj5Ylbr.js";import{t as b}from"./navigation-link-Bxo3-4vo.js";import{t as x}from"./settings-input-D61Vzp9e.js";import{t as S}from"./use-llm-profiles-CU8ah1pz.js";import{t as C}from"./settings-dropdown-input-TWIeiG02.js";import{n as w,t as T}from"./switch-skeleton-DwOoCqaL.js";import{t as E}from"./settings-switch-Drt-rF5s.js";var D=e(t(),1),O=n();function k({defaultKey:e,onChange:t,name:n}){let{t:r}=i(`openhands`);return(0,O.jsx)(C,{testId:n,name:n,onInputChange:t,label:r(d.SETTINGS$LANGUAGE),items:u.map(e=>({key:e.value,label:e.label})),defaultSelectedKey:e,isClearable:!1,wrapperClassName:`w-full min-w-0`})}function A(){let{t:e}=i(`openhands`),t=D.useCallback(e=>{if(!e)return;let t=e;_(t),v(t)},[]);return(0,O.jsx)(C,{testId:`color-theme-input`,name:`color-theme-input`,label:e(d.SETTINGS$COLOR_THEME),items:y.map(e=>({key:e.key,label:e.label})),defaultSelectedKey:g(),onSelectionChange:t,isClearable:!1,wrapperClassName:`w-full min-w-0`})}function j(){return(0,O.jsxs)(`div`,{"data-testid":`app-settings-skeleton`,className:`px-11 py-9 flex flex-col gap-6`,children:[(0,O.jsx)(w,{}),(0,O.jsx)(T,{}),(0,O.jsx)(T,{})]})}var M=`__automatic__`;function N(){let{t:e}=i(`openhands`),{mutate:t,isPending:n}=m(),{data:r,isLoading:g}=p(),_=l().backend.kind===`cloud`,{data:v,isLoading:y}=S(),[w,T]=D.useState(!1),[N,P]=D.useState(!1),[F,I]=D.useState(!1),[L,R]=D.useState(!1),[z,B]=D.useState(!1),[V,H]=D.useState(void 0),U=D.useMemo(()=>{let e=r?.title_llm_profile??null;return!e||!v||v.profiles.some(t=>t.name===e)?e:null},[v,r?.title_llm_profile]),W=V===void 0?U:V,G=D.useMemo(()=>[{key:M,label:e(d.SETTINGS$TITLE_GENERATION_AUTOMATIC)},...v?.profiles.map(t=>({key:t.name,label:t.model?e(d.SETTINGS$TITLE_GENERATION_PROFILE_OPTION,{name:t.name,model:t.model}):t.name}))??[]],[v?.profiles,e]),K=n=>{let r=n.get(`language-input`)?.toString(),i=u.find(({label:e})=>e===r)?.value||f.language,l=_?!0:n.get(`enable-analytics-switch`)?.toString()===`on`,p=n.get(`enable-sound-notifications-switch`)?.toString()===`on`,m=n.get(`git-user-name-input`)?.toString()||f.git_user_name,h=n.get(`git-user-email-input`)?.toString()||f.git_user_email;t({language:i,...!_&&{user_consents_to_analytics:l},enable_sound_notifications:p,git_user_name:m,git_user_email:h,title_llm_profile:W},{onSuccess:()=>{s(l?`granted`:`denied`),c(e(d.SETTINGS$SAVED))},onError:t=>{a(o(t)||e(d.ERROR$GENERIC))},onSettled:()=>{T(!1),P(!1),I(!1),R(!1),B(!1),H(void 0)}})},q=e=>{let t=u.find(({label:t})=>t===e)?.label,n=u.find(({value:e})=>e===r?.language)?.label;T(t!==n)},J=e=>{P(e!==(r?.user_consents_to_analytics??!0))},Y=e=>{I(e!==!!r?.enable_sound_notifications)},X=e=>{let t=r?.git_user_name;R(e!==t)},Z=e=>{let t=r?.git_user_email;B(e!==t)},Q=!w&&!N&&!F&&W===U&&!L&&!z,$=!r||g||y||n;return(0,O.jsxs)(`form`,{"data-testid":`app-settings-screen`,action:K,className:`flex flex-col gap-6`,children:[$&&(0,O.jsx)(j,{}),!$&&(0,O.jsxs)(`div`,{className:`flex flex-col gap-6`,children:[(0,O.jsx)(k,{name:`language-input`,defaultKey:r.language,onChange:q}),(0,O.jsx)(A,{}),(0,O.jsx)(E,{testId:`enable-analytics-switch`,name:_?void 0:`enable-analytics-switch`,defaultIsToggled:_?!0:r.user_consents_to_analytics??!0,isToggled:_?!0:void 0,isDisabled:_,onToggle:_?void 0:J,children:e(d.ANALYTICS$SEND_ANONYMOUS_DATA)}),(0,O.jsx)(E,{testId:`enable-sound-notifications-switch`,name:`enable-sound-notifications-switch`,defaultIsToggled:!!r.enable_sound_notifications,onToggle:Y,children:e(d.SETTINGS$SOUND_NOTIFICATIONS)}),(0,O.jsxs)(`div`,{className:`border-t border-[var(--oh-border)] pt-6 mt-2`,children:[(0,O.jsx)(`h3`,{className:`text-lg font-medium mb-2`,children:e(d.SETTINGS$CONVERSATION_TITLES)}),(0,O.jsx)(`p`,{className:`mb-4 text-sm leading-5 text-tertiary-light`,children:e(d.SETTINGS$TITLE_GENERATION_DESCRIPTION)}),(0,O.jsx)(C,{testId:`title-llm-profile-input`,name:`title-llm-profile-input`,label:e(d.SETTINGS$TITLE_GENERATION_MODEL),items:G,selectedKey:W??M,onSelectionChange:e=>{let t=e?.toString();H(!t||t===M?null:t)}}),(0,O.jsx)(b,{to:`/settings/llm`,className:`mt-3 inline-block text-sm text-primary hover:underline`,children:e(d.SETTINGS$MANAGE_LLM_PROFILES)})]}),(0,O.jsxs)(`div`,{className:`border-t border-[var(--oh-border)] pt-6 mt-2`,children:[(0,O.jsx)(`h3`,{className:`text-lg font-medium mb-2`,children:e(d.SETTINGS$GIT_SETTINGS)}),(0,O.jsx)(`p`,{className:`mb-4 text-sm leading-5 text-tertiary-light`,children:e(d.SETTINGS$GIT_SETTINGS_DESCRIPTION)}),(0,O.jsxs)(`div`,{className:`flex flex-col gap-6`,children:[(0,O.jsx)(x,{testId:`git-user-name-input`,name:`git-user-name-input`,type:`text`,label:e(d.SETTINGS$GIT_USERNAME),defaultValue:r.git_user_name||``,onChange:X,placeholder:e(d.SETTINGS$GIT_USERNAME_PLACEHOLDER),className:`w-full min-w-0`}),(0,O.jsx)(x,{testId:`git-user-email-input`,name:`git-user-email-input`,type:`email`,label:e(d.SETTINGS$GIT_EMAIL),defaultValue:r.git_user_email||``,onChange:Z,placeholder:e(d.SETTINGS$GIT_EMAIL_PLACEHOLDER),className:`w-full min-w-0`})]}),(0,O.jsx)(`div`,{className:`flex justify-start pt-4`,children:(0,O.jsxs)(h,{testId:`submit-button`,variant:`primary`,type:`submit`,isDisabled:n||Q,children:[!n&&e(d.SETTINGS$SAVE_CHANGES),n&&e(d.SETTINGS$SAVING)]})})]})]})]})}var P=r(N);export{P as default};
|