@ottocode/web-sdk 0.1.248 → 0.1.250

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 (32) hide show
  1. package/dist/components/chat/ChatInput.d.ts +1 -0
  2. package/dist/components/chat/ChatInput.d.ts.map +1 -1
  3. package/dist/components/chat/ChatInputContainer.d.ts.map +1 -1
  4. package/dist/components/chat/NewSessionLanding.d.ts.map +1 -1
  5. package/dist/components/chat/UnifiedModelSelector.d.ts.map +1 -1
  6. package/dist/components/index.js +573 -84
  7. package/dist/components/index.js.map +13 -13
  8. package/dist/components/onboarding/OnboardingModal.d.ts.map +1 -1
  9. package/dist/components/onboarding/steps/ProviderSetupStep.d.ts +10 -0
  10. package/dist/components/onboarding/steps/ProviderSetupStep.d.ts.map +1 -1
  11. package/dist/hooks/index.js +64 -3
  12. package/dist/hooks/index.js.map +7 -7
  13. package/dist/hooks/useAuthStatus.d.ts +13 -0
  14. package/dist/hooks/useAuthStatus.d.ts.map +1 -1
  15. package/dist/hooks/useConfig.d.ts +2 -0
  16. package/dist/hooks/useConfig.d.ts.map +1 -1
  17. package/dist/index.js +573 -84
  18. package/dist/index.js.map +13 -13
  19. package/dist/lib/api-client/auth.d.ts +1 -0
  20. package/dist/lib/api-client/auth.d.ts.map +1 -1
  21. package/dist/lib/api-client/config.d.ts +42 -0
  22. package/dist/lib/api-client/config.d.ts.map +1 -1
  23. package/dist/lib/api-client/index.d.ts +31 -0
  24. package/dist/lib/api-client/index.d.ts.map +1 -1
  25. package/dist/lib/index.js +36 -2
  26. package/dist/lib/index.js.map +5 -5
  27. package/dist/stores/index.js.map +2 -2
  28. package/dist/stores/onboardingStore.d.ts +1 -0
  29. package/dist/stores/onboardingStore.d.ts.map +1 -1
  30. package/dist/types/api.d.ts +2 -0
  31. package/dist/types/api.d.ts.map +1 -1
  32. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -1140,7 +1140,9 @@ import {
1140
1140
  getConfig as apiGetConfig,
1141
1141
  getProviderModels as apiGetProviderModels,
1142
1142
  getAllModels as apiGetAllModels,
1143
- updateDefaults as apiUpdateDefaults
1143
+ updateDefaults as apiUpdateDefaults,
1144
+ updateProviderSettings as apiUpdateProviderSettings,
1145
+ deleteProviderSettings as apiDeleteProviderSettings
1144
1146
  } from "@ottocode/api";
1145
1147
  var configMixin = {
1146
1148
  async getConfig() {
@@ -1163,6 +1165,35 @@ var configMixin = {
1163
1165
  throw new Error(extractErrorMessage(response.error));
1164
1166
  return response.data;
1165
1167
  },
1168
+ async discoverProviderModels(data) {
1169
+ const response = await fetch(`${getBaseUrl()}/v1/config/providers/discover-models`, {
1170
+ method: "POST",
1171
+ headers: { "content-type": "application/json" },
1172
+ body: JSON.stringify(data)
1173
+ });
1174
+ const payload = await response.json();
1175
+ if (!response.ok)
1176
+ throw new Error(extractErrorMessage(payload));
1177
+ return payload;
1178
+ },
1179
+ async updateProviderSettings(provider, data) {
1180
+ const response = await apiUpdateProviderSettings({
1181
+ path: { provider },
1182
+ body: data
1183
+ });
1184
+ if (response.error)
1185
+ throw new Error(extractErrorMessage(response.error));
1186
+ return response.data;
1187
+ },
1188
+ async deleteProviderSettings(provider) {
1189
+ const response = await apiDeleteProviderSettings({
1190
+ path: { provider },
1191
+ query: { scope: "local" }
1192
+ });
1193
+ if (response.error)
1194
+ throw new Error(extractErrorMessage(response.error));
1195
+ return response.data;
1196
+ },
1166
1197
  async updateDefaults(data) {
1167
1198
  const response = await apiUpdateDefaults({
1168
1199
  body: data
@@ -1648,6 +1679,9 @@ class ApiClient {
1648
1679
  getConfig = configMixin.getConfig;
1649
1680
  getModels = configMixin.getModels;
1650
1681
  getAllModels = configMixin.getAllModels;
1682
+ discoverProviderModels = configMixin.discoverProviderModels;
1683
+ updateProviderSettings = configMixin.updateProviderSettings;
1684
+ deleteProviderSettings = configMixin.deleteProviderSettings;
1651
1685
  updateDefaults = configMixin.updateDefaults;
1652
1686
  listFiles = filesMixin.listFiles;
1653
1687
  getFileTree = filesMixin.getFileTree;
@@ -3481,6 +3515,7 @@ var ChatInput = memo4(forwardRef5(function ChatInput2({
3481
3515
  attachmentEnabled = false,
3482
3516
  modelName,
3483
3517
  providerName,
3518
+ isCustomProvider = false,
3484
3519
  authType,
3485
3520
  isFreeModel,
3486
3521
  researchContexts = [],
@@ -3964,6 +3999,10 @@ var ChatInput = memo4(forwardRef5(function ChatInput2({
3964
3999
  modelName && /* @__PURE__ */ jsx15("span", {
3965
4000
  children: modelName
3966
4001
  }),
4002
+ isCustomProvider && /* @__PURE__ */ jsx15("span", {
4003
+ className: "opacity-50",
4004
+ children: "(custom)"
4005
+ }),
3967
4006
  authType && authType === "oauth" && /* @__PURE__ */ jsx15("span", {
3968
4007
  className: "opacity-50",
3969
4008
  children: "(pro)"
@@ -5233,6 +5272,8 @@ var UnifiedModelSelector = forwardRef6(function UnifiedModelSelector2({ provider
5233
5272
  modelLabel: modelItem.label,
5234
5273
  toolCall: modelItem.toolCall,
5235
5274
  reasoningText: modelItem.reasoningText,
5275
+ contextWindow: modelItem.contextWindow,
5276
+ maxOutputTokens: modelItem.maxOutputTokens,
5236
5277
  available: modelItem.available,
5237
5278
  unavailableReason: modelItem.unavailableReason
5238
5279
  });
@@ -5292,6 +5333,8 @@ var UnifiedModelSelector = forwardRef6(function UnifiedModelSelector2({ provider
5292
5333
  label: item.modelLabel,
5293
5334
  toolCall: item.toolCall,
5294
5335
  reasoningText: item.reasoningText,
5336
+ contextWindow: item.contextWindow,
5337
+ maxOutputTokens: item.maxOutputTokens,
5295
5338
  available: item.available,
5296
5339
  unavailableReason: item.unavailableReason
5297
5340
  });
@@ -5310,6 +5353,8 @@ var UnifiedModelSelector = forwardRef6(function UnifiedModelSelector2({ provider
5310
5353
  modelLabel: modelItem.label,
5311
5354
  toolCall: modelItem.toolCall,
5312
5355
  reasoningText: modelItem.reasoningText,
5356
+ contextWindow: modelItem.contextWindow,
5357
+ maxOutputTokens: modelItem.maxOutputTokens,
5313
5358
  available: modelItem.available,
5314
5359
  unavailableReason: modelItem.unavailableReason
5315
5360
  });
@@ -5905,6 +5950,7 @@ var ChatInputContainer = memo5(forwardRef8(function ChatInputContainer2({ sessio
5905
5950
  removeResearchContext(sessionId, contextId);
5906
5951
  }, [sessionId, removeResearchContext]);
5907
5952
  const providerAuthType = allModels?.[provider]?.authType;
5953
+ const isCustomProvider = allModels?.[provider]?.label?.includes("(custom)") ?? false;
5908
5954
  useEffect12(() => {
5909
5955
  if (session) {
5910
5956
  setAgent(session.agent);
@@ -6149,6 +6195,7 @@ ${content}` : content;
6149
6195
  attachmentEnabled: modelSupportsAttachment,
6150
6196
  modelName: model,
6151
6197
  providerName: provider,
6198
+ isCustomProvider,
6152
6199
  authType: providerAuthType,
6153
6200
  isFreeModel: modelIsFree,
6154
6201
  researchContexts,
@@ -6345,6 +6392,7 @@ var NewSessionLanding = memo6(forwardRef9(function NewSessionLanding2({ onSessio
6345
6392
  const modelSupportsReasoning = allModels?.[provider]?.models?.find((m) => m.id === model)?.reasoningText;
6346
6393
  const modelIsFree = allModels?.[provider]?.models?.find((m) => m.id === model)?.free;
6347
6394
  const providerAuthType = allModels?.[provider]?.authType;
6395
+ const isCustomProvider = allModels?.[provider]?.label?.includes("(custom)") ?? false;
6348
6396
  const {
6349
6397
  images,
6350
6398
  documents,
@@ -6512,6 +6560,7 @@ var NewSessionLanding = memo6(forwardRef9(function NewSessionLanding2({ onSessio
6512
6560
  attachmentEnabled: modelSupportsAttachment,
6513
6561
  modelName: model,
6514
6562
  providerName: provider,
6563
+ isCustomProvider,
6515
6564
  authType: providerAuthType,
6516
6565
  isFreeModel: modelIsFree,
6517
6566
  onConfigClick: handleToggleConfig,
@@ -21274,11 +21323,37 @@ function useAuthStatus() {
21274
21323
  setLoading(false);
21275
21324
  }
21276
21325
  }, [fetchAuthStatus, setLoading, setError]);
21326
+ const addCustomProvider = useCallback26(async (data) => {
21327
+ setLoading(true);
21328
+ setError(null);
21329
+ try {
21330
+ const result = await apiClient.updateProviderSettings(data.id, {
21331
+ enabled: true,
21332
+ custom: true,
21333
+ label: data.label,
21334
+ baseURL: data.baseURL,
21335
+ ...data.apiKey ? { apiKey: data.apiKey } : {},
21336
+ compatibility: data.compatibility,
21337
+ models: data.models,
21338
+ allowAnyModel: data.allowAnyModel,
21339
+ scope: "local"
21340
+ });
21341
+ await fetchAuthStatus();
21342
+ return result;
21343
+ } catch (err) {
21344
+ const message = err instanceof Error ? err.message : "Failed to add custom provider";
21345
+ setError(message);
21346
+ throw err;
21347
+ } finally {
21348
+ setLoading(false);
21349
+ }
21350
+ }, [fetchAuthStatus, setLoading, setError]);
21277
21351
  const removeProvider = useCallback26(async (provider) => {
21278
21352
  setLoading(true);
21279
21353
  setError(null);
21280
21354
  try {
21281
- const result = await apiClient.removeProvider(provider);
21355
+ const isCustomProvider = useOnboardingStore.getState().authStatus?.providers[provider]?.custom;
21356
+ const result = isCustomProvider ? await apiClient.deleteProviderSettings(provider) : await apiClient.removeProvider(provider);
21282
21357
  await fetchAuthStatus();
21283
21358
  return result;
21284
21359
  } catch (err) {
@@ -21433,6 +21508,7 @@ function useAuthStatus() {
21433
21508
  setupWallet,
21434
21509
  importWallet,
21435
21510
  addProvider,
21511
+ addCustomProvider,
21436
21512
  removeProvider,
21437
21513
  completeOnboarding,
21438
21514
  startOAuth,
@@ -25159,15 +25235,32 @@ import {
25159
25235
  Key as Key2,
25160
25236
  ExternalLink as ExternalLink10,
25161
25237
  ArrowRight as ArrowRight2,
25162
- RefreshCw as RefreshCw11
25238
+ RefreshCw as RefreshCw11,
25239
+ Plus as Plus8
25163
25240
  } from "lucide-react";
25164
25241
  import { QRCodeSVG as QRCodeSVG3 } from "qrcode.react";
25165
25242
  import { jsx as jsx103, jsxs as jsxs91, Fragment as Fragment39 } from "react/jsx-runtime";
25243
+ var CUSTOM_PROVIDER_COMPATIBILITY_OPTIONS = [
25244
+ { value: "openai-compatible", label: "OpenAI-compatible" },
25245
+ { value: "openai", label: "OpenAI" },
25246
+ { value: "anthropic", label: "Anthropic" },
25247
+ { value: "google", label: "Google" },
25248
+ { value: "openrouter", label: "OpenRouter" },
25249
+ { value: "ollama", label: "Ollama" }
25250
+ ];
25251
+ function formatTokenCount(tokens) {
25252
+ if (tokens >= 1e6)
25253
+ return `${(tokens / 1e6).toFixed(1)}M`;
25254
+ if (tokens >= 1000)
25255
+ return `${Math.round(tokens / 1000)}K`;
25256
+ return String(tokens);
25257
+ }
25166
25258
  var ProviderSetupStep = memo46(function ProviderSetupStep2({
25167
25259
  authStatus,
25168
25260
  onSetupWallet,
25169
25261
  onImportWallet,
25170
25262
  onAddProvider,
25263
+ onAddCustomProvider,
25171
25264
  onRemoveProvider,
25172
25265
  onStartOAuth,
25173
25266
  onStartOAuthManual,
@@ -25192,6 +25285,19 @@ var ProviderSetupStep = memo46(function ProviderSetupStep2({
25192
25285
  const [importWalletError, setImportWalletError] = useState44(null);
25193
25286
  const [addingProvider, setAddingProvider] = useState44(null);
25194
25287
  const [apiKeyInput, setApiKeyInput] = useState44("");
25288
+ const [isCustomProviderModalOpen, setIsCustomProviderModalOpen] = useState44(false);
25289
+ const [customProviderId, setCustomProviderId] = useState44("");
25290
+ const [customProviderLabel, setCustomProviderLabel] = useState44("");
25291
+ const [customProviderBaseURL, setCustomProviderBaseURL] = useState44("");
25292
+ const [customProviderApiKey, setCustomProviderApiKey] = useState44("");
25293
+ const [customProviderModels, setCustomProviderModels] = useState44("");
25294
+ const [customProviderCompatibility, setCustomProviderCompatibility] = useState44("openai-compatible");
25295
+ const [customProviderAllowAnyModel, setCustomProviderAllowAnyModel] = useState44(true);
25296
+ const [isAddingCustomProvider, setIsAddingCustomProvider] = useState44(false);
25297
+ const [customProviderError, setCustomProviderError] = useState44(null);
25298
+ const [isDiscoveringCustomModels, setIsDiscoveringCustomModels] = useState44(false);
25299
+ const [discoveredCustomModels, setDiscoveredCustomModels] = useState44([]);
25300
+ const [customProviderDiscoveryMessage, setCustomProviderDiscoveryMessage] = useState44(null);
25195
25301
  const [removingProvider, setRemovingProvider] = useState44(null);
25196
25302
  const [confirmingDelete, setConfirmingDelete] = useState44(null);
25197
25303
  const [oauthSession, setOauthSession] = useState44(null);
@@ -25316,6 +25422,83 @@ var ProviderSetupStep = memo46(function ProviderSetupStep2({
25316
25422
  setApiKeyInput("");
25317
25423
  } catch {}
25318
25424
  };
25425
+ const resetCustomProviderForm = () => {
25426
+ setCustomProviderId("");
25427
+ setCustomProviderLabel("");
25428
+ setCustomProviderBaseURL("");
25429
+ setCustomProviderApiKey("");
25430
+ setCustomProviderModels("");
25431
+ setCustomProviderCompatibility("openai-compatible");
25432
+ setCustomProviderAllowAnyModel(true);
25433
+ setCustomProviderError(null);
25434
+ setDiscoveredCustomModels([]);
25435
+ setCustomProviderDiscoveryMessage(null);
25436
+ };
25437
+ const handleCloseCustomProviderModal = () => {
25438
+ if (isAddingCustomProvider)
25439
+ return;
25440
+ setIsCustomProviderModalOpen(false);
25441
+ resetCustomProviderForm();
25442
+ };
25443
+ const handleDiscoverCustomProviderModels = async () => {
25444
+ const baseURL = customProviderBaseURL.trim();
25445
+ if (!baseURL) {
25446
+ setCustomProviderDiscoveryMessage("Enter a base URL first.");
25447
+ return;
25448
+ }
25449
+ setIsDiscoveringCustomModels(true);
25450
+ setCustomProviderDiscoveryMessage(null);
25451
+ try {
25452
+ const result = await apiClient.discoverProviderModels({
25453
+ compatibility: customProviderCompatibility,
25454
+ baseURL,
25455
+ apiKey: customProviderApiKey.trim() || undefined
25456
+ });
25457
+ if (result.baseURL)
25458
+ setCustomProviderBaseURL(result.baseURL);
25459
+ setDiscoveredCustomModels(result.models);
25460
+ if (result.models.length > 0) {
25461
+ setCustomProviderModels(result.models.map((model) => model.id).join(`
25462
+ `));
25463
+ }
25464
+ setCustomProviderDiscoveryMessage(result.message || (result.models.length > 0 ? `Fetched ${result.models.length} model${result.models.length === 1 ? "" : "s"}.` : "No models found."));
25465
+ } catch (err) {
25466
+ setDiscoveredCustomModels([]);
25467
+ setCustomProviderDiscoveryMessage(err instanceof Error ? err.message : "Failed to fetch models");
25468
+ } finally {
25469
+ setIsDiscoveringCustomModels(false);
25470
+ }
25471
+ };
25472
+ const handleAddCustomProvider = async () => {
25473
+ const id = customProviderId.trim();
25474
+ const label = customProviderLabel.trim() || id;
25475
+ const baseURL = customProviderBaseURL.trim();
25476
+ const apiKey = customProviderApiKey.trim();
25477
+ const models = customProviderModels.split(/[\n,]/).map((model) => model.trim()).filter(Boolean);
25478
+ if (!id || !baseURL) {
25479
+ setCustomProviderError("Provider ID and base URL are required.");
25480
+ return;
25481
+ }
25482
+ setIsAddingCustomProvider(true);
25483
+ setCustomProviderError(null);
25484
+ try {
25485
+ await onAddCustomProvider({
25486
+ id,
25487
+ label,
25488
+ baseURL,
25489
+ apiKey: apiKey || undefined,
25490
+ compatibility: customProviderCompatibility,
25491
+ models,
25492
+ allowAnyModel: customProviderAllowAnyModel
25493
+ });
25494
+ setIsCustomProviderModalOpen(false);
25495
+ resetCustomProviderForm();
25496
+ } catch (err) {
25497
+ setCustomProviderError(err instanceof Error ? err.message : "Failed to add custom provider");
25498
+ } finally {
25499
+ setIsAddingCustomProvider(false);
25500
+ }
25501
+ };
25319
25502
  const handleRemoveProvider = async (providerId) => {
25320
25503
  if (confirmingDelete === providerId) {
25321
25504
  setRemovingProvider(providerId);
@@ -25762,112 +25945,154 @@ var ProviderSetupStep = memo46(function ProviderSetupStep2({
25762
25945
  className: "font-semibold text-foreground mb-4",
25763
25946
  children: "Add Providers"
25764
25947
  }),
25765
- /* @__PURE__ */ jsx103("div", {
25948
+ /* @__PURE__ */ jsxs91("div", {
25766
25949
  className: "grid grid-cols-1 sm:grid-cols-2 gap-3 items-start",
25767
- children: unconfiguredProviders.map(([id, info]) => /* @__PURE__ */ jsx103("div", {
25768
- children: addingProvider === id ? /* @__PURE__ */ jsxs91("div", {
25769
- className: "flex items-center gap-2 p-3 bg-card border border-ring rounded-xl overflow-hidden",
25770
- children: [
25771
- /* @__PURE__ */ jsx103("div", {
25772
- className: "shrink-0",
25773
- children: /* @__PURE__ */ jsx103(ProviderLogo, {
25774
- provider: id,
25775
- size: 18
25776
- })
25777
- }),
25778
- /* @__PURE__ */ jsx103("input", {
25779
- ref: apiKeyInputRef,
25780
- type: "password",
25781
- value: apiKeyInput,
25782
- onChange: (e) => setApiKeyInput(e.target.value),
25783
- placeholder: `${info.label} API key...`,
25784
- className: "flex-1 min-w-0 bg-transparent text-sm outline-none placeholder:text-muted-foreground text-foreground",
25785
- onKeyDown: (e) => {
25786
- if (e.key === "Enter")
25787
- handleAddProvider(id);
25788
- if (e.key === "Escape") {
25950
+ children: [
25951
+ unconfiguredProviders.map(([id, info]) => /* @__PURE__ */ jsx103("div", {
25952
+ children: addingProvider === id ? /* @__PURE__ */ jsxs91("div", {
25953
+ className: "flex items-center gap-2 p-3 bg-card border border-ring rounded-xl overflow-hidden",
25954
+ children: [
25955
+ /* @__PURE__ */ jsx103("div", {
25956
+ className: "shrink-0",
25957
+ children: /* @__PURE__ */ jsx103(ProviderLogo, {
25958
+ provider: id,
25959
+ size: 18
25960
+ })
25961
+ }),
25962
+ /* @__PURE__ */ jsx103("input", {
25963
+ ref: apiKeyInputRef,
25964
+ type: "password",
25965
+ value: apiKeyInput,
25966
+ onChange: (e) => setApiKeyInput(e.target.value),
25967
+ placeholder: `${info.label} API key...`,
25968
+ className: "flex-1 min-w-0 bg-transparent text-sm outline-none placeholder:text-muted-foreground text-foreground",
25969
+ onKeyDown: (e) => {
25970
+ if (e.key === "Enter")
25971
+ handleAddProvider(id);
25972
+ if (e.key === "Escape") {
25973
+ setAddingProvider(null);
25974
+ setApiKeyInput("");
25975
+ }
25976
+ }
25977
+ }),
25978
+ /* @__PURE__ */ jsx103("button", {
25979
+ type: "button",
25980
+ onClick: () => handleAddProvider(id),
25981
+ disabled: !apiKeyInput.trim(),
25982
+ className: "shrink-0 px-3 py-1.5 bg-primary text-primary-foreground text-sm font-medium rounded-lg disabled:opacity-50",
25983
+ children: "Add"
25984
+ }),
25985
+ /* @__PURE__ */ jsx103("button", {
25986
+ type: "button",
25987
+ onClick: () => {
25789
25988
  setAddingProvider(null);
25790
25989
  setApiKeyInput("");
25791
- }
25792
- }
25793
- }),
25794
- /* @__PURE__ */ jsx103("button", {
25795
- type: "button",
25796
- onClick: () => handleAddProvider(id),
25797
- disabled: !apiKeyInput.trim(),
25798
- className: "shrink-0 px-3 py-1.5 bg-primary text-primary-foreground text-sm font-medium rounded-lg disabled:opacity-50",
25799
- children: "Add"
25800
- }),
25801
- /* @__PURE__ */ jsx103("button", {
25802
- type: "button",
25803
- onClick: () => {
25804
- setAddingProvider(null);
25805
- setApiKeyInput("");
25806
- },
25807
- className: "shrink-0 p-1.5 text-muted-foreground hover:text-foreground",
25808
- children: /* @__PURE__ */ jsx103(X18, {
25809
- className: "w-4 h-4"
25990
+ },
25991
+ className: "shrink-0 p-1.5 text-muted-foreground hover:text-foreground",
25992
+ children: /* @__PURE__ */ jsx103(X18, {
25993
+ className: "w-4 h-4"
25994
+ })
25810
25995
  })
25811
- })
25812
- ]
25813
- }) : /* @__PURE__ */ jsxs91("div", {
25814
- className: "flex items-center justify-between p-3 bg-card border border-border hover:border-border/80 rounded-xl transition-colors gap-2",
25996
+ ]
25997
+ }) : /* @__PURE__ */ jsxs91("div", {
25998
+ className: "flex items-center justify-between p-3 bg-card border border-border hover:border-border/80 rounded-xl transition-colors gap-2",
25999
+ children: [
26000
+ /* @__PURE__ */ jsxs91("div", {
26001
+ className: "flex items-center gap-3 min-w-0",
26002
+ children: [
26003
+ /* @__PURE__ */ jsx103(ProviderLogo, {
26004
+ provider: id,
26005
+ size: 20
26006
+ }),
26007
+ /* @__PURE__ */ jsxs91("div", {
26008
+ className: "min-w-0",
26009
+ children: [
26010
+ /* @__PURE__ */ jsx103("div", {
26011
+ className: "font-medium text-foreground truncate",
26012
+ children: info.label
26013
+ }),
26014
+ /* @__PURE__ */ jsxs91("div", {
26015
+ className: "text-xs text-muted-foreground",
26016
+ children: [
26017
+ info.modelCount,
26018
+ " models"
26019
+ ]
26020
+ })
26021
+ ]
26022
+ })
26023
+ ]
26024
+ }),
26025
+ /* @__PURE__ */ jsxs91("div", {
26026
+ className: "flex items-center gap-1",
26027
+ children: [
26028
+ id !== "copilot" && /* @__PURE__ */ jsxs91("button", {
26029
+ type: "button",
26030
+ onClick: () => setAddingProvider(id),
26031
+ className: "flex items-center gap-1.5 px-3 py-1.5 text-sm text-muted-foreground hover:text-foreground hover:bg-muted rounded-lg transition-colors",
26032
+ children: [
26033
+ /* @__PURE__ */ jsx103(Key2, {
26034
+ className: "w-3.5 h-3.5"
26035
+ }),
26036
+ "API"
26037
+ ]
26038
+ }),
26039
+ info.supportsOAuth && /* @__PURE__ */ jsxs91("button", {
26040
+ type: "button",
26041
+ onClick: () => handleStartOAuth(id, id === "anthropic" ? "max" : undefined),
26042
+ className: "flex items-center gap-1.5 px-3 py-1.5 text-sm text-muted-foreground hover:text-foreground hover:bg-muted rounded-lg transition-colors",
26043
+ children: [
26044
+ /* @__PURE__ */ jsx103(ExternalLink10, {
26045
+ className: "w-3.5 h-3.5"
26046
+ }),
26047
+ id === "anthropic" ? "Pro" : id === "copilot" ? "Login" : "OAuth"
26048
+ ]
26049
+ })
26050
+ ]
26051
+ })
26052
+ ]
26053
+ })
26054
+ }, id)),
26055
+ /* @__PURE__ */ jsxs91("button", {
26056
+ type: "button",
26057
+ onClick: () => setIsCustomProviderModalOpen(true),
26058
+ className: "group flex items-center justify-between p-3 bg-card border border-dashed border-border hover:border-primary/60 hover:bg-primary/5 hover:shadow-sm rounded-xl transition-all gap-2 text-left cursor-pointer",
25815
26059
  children: [
25816
26060
  /* @__PURE__ */ jsxs91("div", {
25817
26061
  className: "flex items-center gap-3 min-w-0",
25818
26062
  children: [
25819
- /* @__PURE__ */ jsx103(ProviderLogo, {
25820
- provider: id,
25821
- size: 20
26063
+ /* @__PURE__ */ jsx103("span", {
26064
+ className: "inline-flex items-center justify-center w-5 h-5 rounded-full bg-muted text-muted-foreground group-hover:bg-primary/10 group-hover:text-primary transition-colors",
26065
+ children: /* @__PURE__ */ jsx103(Plus8, {
26066
+ className: "w-3.5 h-3.5"
26067
+ })
25822
26068
  }),
25823
26069
  /* @__PURE__ */ jsxs91("div", {
25824
26070
  className: "min-w-0",
25825
26071
  children: [
25826
26072
  /* @__PURE__ */ jsx103("div", {
25827
26073
  className: "font-medium text-foreground truncate",
25828
- children: info.label
26074
+ children: "Custom Provider"
25829
26075
  }),
25830
- /* @__PURE__ */ jsxs91("div", {
25831
- className: "text-xs text-muted-foreground",
25832
- children: [
25833
- info.modelCount,
25834
- " models"
25835
- ]
26076
+ /* @__PURE__ */ jsx103("div", {
26077
+ className: "text-xs text-muted-foreground group-hover:text-foreground/70 transition-colors",
26078
+ children: "OpenAI-compatible or Ollama endpoint"
25836
26079
  })
25837
26080
  ]
25838
26081
  })
25839
26082
  ]
25840
26083
  }),
25841
- /* @__PURE__ */ jsxs91("div", {
25842
- className: "flex items-center gap-1",
26084
+ /* @__PURE__ */ jsxs91("span", {
26085
+ className: "flex items-center gap-1.5 px-3 py-1.5 text-sm text-muted-foreground group-hover:text-primary group-hover:bg-primary/10 rounded-lg transition-colors",
25843
26086
  children: [
25844
- id !== "copilot" && /* @__PURE__ */ jsxs91("button", {
25845
- type: "button",
25846
- onClick: () => setAddingProvider(id),
25847
- className: "flex items-center gap-1.5 px-3 py-1.5 text-sm text-muted-foreground hover:text-foreground hover:bg-muted rounded-lg transition-colors",
25848
- children: [
25849
- /* @__PURE__ */ jsx103(Key2, {
25850
- className: "w-3.5 h-3.5"
25851
- }),
25852
- "API"
25853
- ]
26087
+ /* @__PURE__ */ jsx103(Key2, {
26088
+ className: "w-3.5 h-3.5"
25854
26089
  }),
25855
- info.supportsOAuth && /* @__PURE__ */ jsxs91("button", {
25856
- type: "button",
25857
- onClick: () => handleStartOAuth(id, id === "anthropic" ? "max" : undefined),
25858
- className: "flex items-center gap-1.5 px-3 py-1.5 text-sm text-muted-foreground hover:text-foreground hover:bg-muted rounded-lg transition-colors",
25859
- children: [
25860
- /* @__PURE__ */ jsx103(ExternalLink10, {
25861
- className: "w-3.5 h-3.5"
25862
- }),
25863
- id === "anthropic" ? "Pro" : id === "copilot" ? "Login" : "OAuth"
25864
- ]
25865
- })
26090
+ "Add"
25866
26091
  ]
25867
26092
  })
25868
26093
  ]
25869
26094
  })
25870
- }, id))
26095
+ ]
25871
26096
  })
25872
26097
  ]
25873
26098
  })
@@ -25906,6 +26131,268 @@ var ProviderSetupStep = memo46(function ProviderSetupStep2({
25906
26131
  ]
25907
26132
  })
25908
26133
  }),
26134
+ isCustomProviderModalOpen && /* @__PURE__ */ jsx103("div", {
26135
+ className: "fixed inset-0 z-[9999] flex items-center justify-center bg-black/60 backdrop-blur-sm",
26136
+ children: /* @__PURE__ */ jsxs91("div", {
26137
+ className: "bg-background border border-border rounded-xl w-full max-w-2xl mx-6 shadow-2xl max-h-[90vh] overflow-y-auto",
26138
+ children: [
26139
+ /* @__PURE__ */ jsxs91("div", {
26140
+ className: "flex items-center gap-3 p-6 border-b border-border",
26141
+ children: [
26142
+ /* @__PURE__ */ jsx103("span", {
26143
+ className: "inline-flex items-center justify-center w-6 h-6 rounded-full bg-muted text-muted-foreground",
26144
+ children: /* @__PURE__ */ jsx103(Plus8, {
26145
+ className: "w-4 h-4"
26146
+ })
26147
+ }),
26148
+ /* @__PURE__ */ jsx103("h3", {
26149
+ className: "text-lg font-semibold",
26150
+ children: "Add Custom Provider"
26151
+ })
26152
+ ]
26153
+ }),
26154
+ /* @__PURE__ */ jsxs91("div", {
26155
+ className: "p-6 space-y-4",
26156
+ children: [
26157
+ /* @__PURE__ */ jsxs91("div", {
26158
+ className: "grid grid-cols-1 sm:grid-cols-2 gap-4",
26159
+ children: [
26160
+ /* @__PURE__ */ jsxs91("label", {
26161
+ className: "space-y-2",
26162
+ children: [
26163
+ /* @__PURE__ */ jsx103("span", {
26164
+ className: "text-sm font-medium text-foreground",
26165
+ children: "Provider ID"
26166
+ }),
26167
+ /* @__PURE__ */ jsx103("input", {
26168
+ type: "text",
26169
+ value: customProviderId,
26170
+ onChange: (e) => setCustomProviderId(e.target.value),
26171
+ placeholder: "my-provider",
26172
+ className: "w-full h-11 px-4 bg-muted/50 border border-border rounded-lg text-foreground placeholder:text-muted-foreground outline-none focus:border-foreground/30 transition-colors font-mono text-sm"
26173
+ })
26174
+ ]
26175
+ }),
26176
+ /* @__PURE__ */ jsxs91("label", {
26177
+ className: "space-y-2",
26178
+ children: [
26179
+ /* @__PURE__ */ jsx103("span", {
26180
+ className: "text-sm font-medium text-foreground",
26181
+ children: "Display Name"
26182
+ }),
26183
+ /* @__PURE__ */ jsx103("input", {
26184
+ type: "text",
26185
+ value: customProviderLabel,
26186
+ onChange: (e) => setCustomProviderLabel(e.target.value),
26187
+ placeholder: "My Provider",
26188
+ className: "w-full h-11 px-4 bg-muted/50 border border-border rounded-lg text-foreground placeholder:text-muted-foreground outline-none focus:border-foreground/30 transition-colors text-sm"
26189
+ })
26190
+ ]
26191
+ })
26192
+ ]
26193
+ }),
26194
+ /* @__PURE__ */ jsxs91("div", {
26195
+ className: "flex items-center justify-between gap-3 p-3 bg-muted/30 border border-border rounded-lg",
26196
+ children: [
26197
+ /* @__PURE__ */ jsxs91("div", {
26198
+ className: "min-w-0",
26199
+ children: [
26200
+ /* @__PURE__ */ jsx103("div", {
26201
+ className: "text-sm font-medium text-foreground",
26202
+ children: "Fetch models from provider"
26203
+ }),
26204
+ /* @__PURE__ */ jsx103("div", {
26205
+ className: "text-xs text-muted-foreground",
26206
+ children: "For Ollama, this reads /api/tags and /api/show to include context windows."
26207
+ })
26208
+ ]
26209
+ }),
26210
+ /* @__PURE__ */ jsxs91("button", {
26211
+ type: "button",
26212
+ onClick: handleDiscoverCustomProviderModels,
26213
+ disabled: !customProviderBaseURL.trim() || isDiscoveringCustomModels,
26214
+ className: "shrink-0 px-3 py-2 bg-primary text-primary-foreground rounded-lg text-sm font-medium hover:bg-primary/90 transition-colors disabled:opacity-50 flex items-center gap-2",
26215
+ children: [
26216
+ isDiscoveringCustomModels && /* @__PURE__ */ jsx103(Loader216, {
26217
+ className: "w-3.5 h-3.5 animate-spin"
26218
+ }),
26219
+ "Fetch Models"
26220
+ ]
26221
+ })
26222
+ ]
26223
+ }),
26224
+ customProviderDiscoveryMessage && /* @__PURE__ */ jsx103("p", {
26225
+ className: "text-xs text-muted-foreground",
26226
+ children: customProviderDiscoveryMessage
26227
+ }),
26228
+ discoveredCustomModels.length > 0 && /* @__PURE__ */ jsx103("div", {
26229
+ className: "space-y-2 max-h-44 overflow-y-auto border border-border rounded-lg p-2",
26230
+ children: discoveredCustomModels.map((model) => /* @__PURE__ */ jsxs91("div", {
26231
+ className: "flex items-center justify-between gap-3 p-2 bg-card rounded-md",
26232
+ children: [
26233
+ /* @__PURE__ */ jsxs91("div", {
26234
+ className: "min-w-0",
26235
+ children: [
26236
+ /* @__PURE__ */ jsx103("div", {
26237
+ className: "text-sm font-medium text-foreground truncate",
26238
+ children: model.label
26239
+ }),
26240
+ /* @__PURE__ */ jsx103("div", {
26241
+ className: "text-xs text-muted-foreground font-mono truncate",
26242
+ children: model.id
26243
+ })
26244
+ ]
26245
+ }),
26246
+ /* @__PURE__ */ jsxs91("div", {
26247
+ className: "flex flex-wrap justify-end gap-1 shrink-0",
26248
+ children: [
26249
+ model.contextWindow && /* @__PURE__ */ jsxs91("span", {
26250
+ className: "text-[10px] px-1.5 py-0.5 bg-blue-600/20 text-blue-400 rounded",
26251
+ children: [
26252
+ formatTokenCount(model.contextWindow),
26253
+ " ctx"
26254
+ ]
26255
+ }),
26256
+ model.maxOutputTokens && /* @__PURE__ */ jsxs91("span", {
26257
+ className: "text-[10px] px-1.5 py-0.5 bg-cyan-600/20 text-cyan-400 rounded",
26258
+ children: [
26259
+ formatTokenCount(model.maxOutputTokens),
26260
+ " out"
26261
+ ]
26262
+ }),
26263
+ model.toolCall && /* @__PURE__ */ jsx103("span", {
26264
+ className: "text-[10px] px-1.5 py-0.5 bg-green-600/20 text-green-400 rounded",
26265
+ children: "Tools"
26266
+ }),
26267
+ model.reasoningText && /* @__PURE__ */ jsx103("span", {
26268
+ className: "text-[10px] px-1.5 py-0.5 bg-purple-600/20 text-purple-400 rounded",
26269
+ children: "Reasoning"
26270
+ }),
26271
+ model.vision && /* @__PURE__ */ jsx103("span", {
26272
+ className: "text-[10px] px-1.5 py-0.5 bg-orange-600/20 text-orange-400 rounded",
26273
+ children: "Vision"
26274
+ })
26275
+ ]
26276
+ })
26277
+ ]
26278
+ }, model.id))
26279
+ }),
26280
+ /* @__PURE__ */ jsxs91("label", {
26281
+ className: "space-y-2 block",
26282
+ children: [
26283
+ /* @__PURE__ */ jsx103("span", {
26284
+ className: "text-sm font-medium text-foreground",
26285
+ children: "Base URL"
26286
+ }),
26287
+ /* @__PURE__ */ jsx103("input", {
26288
+ type: "url",
26289
+ value: customProviderBaseURL,
26290
+ onChange: (e) => setCustomProviderBaseURL(e.target.value),
26291
+ placeholder: "https://api.example.com/v1",
26292
+ className: "w-full h-11 px-4 bg-muted/50 border border-border rounded-lg text-foreground placeholder:text-muted-foreground outline-none focus:border-foreground/30 transition-colors font-mono text-sm"
26293
+ })
26294
+ ]
26295
+ }),
26296
+ /* @__PURE__ */ jsxs91("div", {
26297
+ className: "grid grid-cols-1 sm:grid-cols-2 gap-4",
26298
+ children: [
26299
+ /* @__PURE__ */ jsxs91("label", {
26300
+ className: "space-y-2",
26301
+ children: [
26302
+ /* @__PURE__ */ jsx103("span", {
26303
+ className: "text-sm font-medium text-foreground",
26304
+ children: "API Key"
26305
+ }),
26306
+ /* @__PURE__ */ jsx103("input", {
26307
+ type: "password",
26308
+ value: customProviderApiKey,
26309
+ onChange: (e) => setCustomProviderApiKey(e.target.value),
26310
+ placeholder: "Optional",
26311
+ className: "w-full h-11 px-4 bg-muted/50 border border-border rounded-lg text-foreground placeholder:text-muted-foreground outline-none focus:border-foreground/30 transition-colors font-mono text-sm"
26312
+ })
26313
+ ]
26314
+ }),
26315
+ /* @__PURE__ */ jsxs91("label", {
26316
+ className: "space-y-2",
26317
+ children: [
26318
+ /* @__PURE__ */ jsx103("span", {
26319
+ className: "text-sm font-medium text-foreground",
26320
+ children: "Compatibility"
26321
+ }),
26322
+ /* @__PURE__ */ jsx103("select", {
26323
+ value: customProviderCompatibility,
26324
+ onChange: (e) => setCustomProviderCompatibility(e.target.value),
26325
+ className: "w-full h-11 px-4 bg-muted/50 border border-border rounded-lg text-foreground outline-none focus:border-foreground/30 transition-colors text-sm",
26326
+ children: CUSTOM_PROVIDER_COMPATIBILITY_OPTIONS.map((option) => /* @__PURE__ */ jsx103("option", {
26327
+ value: option.value,
26328
+ children: option.label
26329
+ }, option.value))
26330
+ })
26331
+ ]
26332
+ })
26333
+ ]
26334
+ }),
26335
+ /* @__PURE__ */ jsxs91("label", {
26336
+ className: "space-y-2 block",
26337
+ children: [
26338
+ /* @__PURE__ */ jsx103("span", {
26339
+ className: "text-sm font-medium text-foreground",
26340
+ children: "Models"
26341
+ }),
26342
+ /* @__PURE__ */ jsx103("textarea", {
26343
+ value: customProviderModels,
26344
+ onChange: (e) => setCustomProviderModels(e.target.value),
26345
+ placeholder: "gpt-4o, claude-sonnet-4-5, llama3.3",
26346
+ className: "w-full min-h-[90px] px-4 py-3 bg-muted/50 border border-border rounded-lg text-foreground placeholder:text-muted-foreground outline-none focus:border-foreground/30 transition-colors font-mono text-sm resize-y"
26347
+ }),
26348
+ /* @__PURE__ */ jsx103("span", {
26349
+ className: "text-xs text-muted-foreground",
26350
+ children: "Comma or newline separated. Leave blank to allow any model."
26351
+ })
26352
+ ]
26353
+ }),
26354
+ /* @__PURE__ */ jsxs91("label", {
26355
+ className: "flex items-center gap-3 text-sm text-muted-foreground",
26356
+ children: [
26357
+ /* @__PURE__ */ jsx103("input", {
26358
+ type: "checkbox",
26359
+ checked: customProviderAllowAnyModel,
26360
+ onChange: (e) => setCustomProviderAllowAnyModel(e.target.checked),
26361
+ className: "h-4 w-4 accent-primary"
26362
+ }),
26363
+ "Allow entering model IDs not listed above"
26364
+ ]
26365
+ }),
26366
+ customProviderError && /* @__PURE__ */ jsx103("p", {
26367
+ className: "text-sm text-red-500",
26368
+ children: customProviderError
26369
+ }),
26370
+ /* @__PURE__ */ jsxs91("div", {
26371
+ className: "flex gap-3 pt-2",
26372
+ children: [
26373
+ /* @__PURE__ */ jsx103("button", {
26374
+ type: "button",
26375
+ onClick: handleCloseCustomProviderModal,
26376
+ disabled: isAddingCustomProvider,
26377
+ className: "flex-1 h-11 px-4 bg-transparent border border-border text-foreground rounded-lg font-medium hover:bg-muted/50 transition-colors disabled:opacity-50",
26378
+ children: "Cancel"
26379
+ }),
26380
+ /* @__PURE__ */ jsx103("button", {
26381
+ type: "button",
26382
+ onClick: handleAddCustomProvider,
26383
+ disabled: !customProviderId.trim() || !customProviderBaseURL.trim() || isAddingCustomProvider,
26384
+ className: "flex-1 h-11 px-4 bg-foreground text-background rounded-lg font-medium hover:bg-foreground/90 transition-colors disabled:opacity-50 flex items-center justify-center gap-2",
26385
+ children: isAddingCustomProvider ? /* @__PURE__ */ jsx103(Loader216, {
26386
+ className: "w-4 h-4 animate-spin"
26387
+ }) : "Add Provider"
26388
+ })
26389
+ ]
26390
+ })
26391
+ ]
26392
+ })
26393
+ ]
26394
+ })
26395
+ }),
25909
26396
  isImportModalOpen && /* @__PURE__ */ jsx103("div", {
25910
26397
  className: "fixed inset-0 z-[9999] flex items-center justify-center bg-black/60 backdrop-blur-sm",
25911
26398
  children: /* @__PURE__ */ jsxs91("div", {
@@ -26633,6 +27120,7 @@ var OnboardingModal = memo48(function OnboardingModal2({
26633
27120
  setupWallet,
26634
27121
  importWallet,
26635
27122
  addProvider,
27123
+ addCustomProvider,
26636
27124
  removeProvider,
26637
27125
  completeOnboarding,
26638
27126
  startOAuth,
@@ -26655,6 +27143,7 @@ var OnboardingModal = memo48(function OnboardingModal2({
26655
27143
  onSetupWallet: setupWallet,
26656
27144
  onImportWallet: importWallet,
26657
27145
  onAddProvider: addProvider,
27146
+ onAddCustomProvider: addCustomProvider,
26658
27147
  onRemoveProvider: removeProvider,
26659
27148
  onStartOAuth: startOAuth,
26660
27149
  onStartOAuthManual: startOAuthManual,
@@ -27522,4 +28011,4 @@ export {
27522
28011
  API_BASE_URL
27523
28012
  };
27524
28013
 
27525
- //# debugId=A7717B48FBB0AB3064756E2164756E21
28014
+ //# debugId=AFB356A4FAF30FFC64756E2164756E21