@ottocode/web-sdk 0.1.248 → 0.1.249

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 +597 -85
  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 +597 -85
  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)"
@@ -5133,6 +5172,13 @@ import {
5133
5172
  import { ChevronDown, Search } from "lucide-react";
5134
5173
  import Fuse2 from "fuse.js";
5135
5174
  import { jsx as jsx16, jsxs as jsxs10 } from "react/jsx-runtime";
5175
+ function formatTokenCount(tokens) {
5176
+ if (tokens >= 1e6)
5177
+ return `${(tokens / 1e6).toFixed(1)}M`;
5178
+ if (tokens >= 1000)
5179
+ return `${Math.round(tokens / 1000)}K`;
5180
+ return String(tokens);
5181
+ }
5136
5182
  var UnifiedModelSelector = forwardRef6(function UnifiedModelSelector2({ provider, model, onChange, disabled = false }, ref) {
5137
5183
  const { data: allModels } = useAllModels();
5138
5184
  const { data: currentProviderModels, isLoading: isCurrentProviderLoading } = useModels(provider);
@@ -5233,6 +5279,8 @@ var UnifiedModelSelector = forwardRef6(function UnifiedModelSelector2({ provider
5233
5279
  modelLabel: modelItem.label,
5234
5280
  toolCall: modelItem.toolCall,
5235
5281
  reasoningText: modelItem.reasoningText,
5282
+ contextWindow: modelItem.contextWindow,
5283
+ maxOutputTokens: modelItem.maxOutputTokens,
5236
5284
  available: modelItem.available,
5237
5285
  unavailableReason: modelItem.unavailableReason
5238
5286
  });
@@ -5292,6 +5340,8 @@ var UnifiedModelSelector = forwardRef6(function UnifiedModelSelector2({ provider
5292
5340
  label: item.modelLabel,
5293
5341
  toolCall: item.toolCall,
5294
5342
  reasoningText: item.reasoningText,
5343
+ contextWindow: item.contextWindow,
5344
+ maxOutputTokens: item.maxOutputTokens,
5295
5345
  available: item.available,
5296
5346
  unavailableReason: item.unavailableReason
5297
5347
  });
@@ -5310,6 +5360,8 @@ var UnifiedModelSelector = forwardRef6(function UnifiedModelSelector2({ provider
5310
5360
  modelLabel: modelItem.label,
5311
5361
  toolCall: modelItem.toolCall,
5312
5362
  reasoningText: modelItem.reasoningText,
5363
+ contextWindow: modelItem.contextWindow,
5364
+ maxOutputTokens: modelItem.maxOutputTokens,
5313
5365
  available: modelItem.available,
5314
5366
  unavailableReason: modelItem.unavailableReason
5315
5367
  });
@@ -5476,9 +5528,25 @@ var UnifiedModelSelector = forwardRef6(function UnifiedModelSelector2({ provider
5476
5528
  className: "truncate",
5477
5529
  children: modelItem.label
5478
5530
  }),
5479
- (!isAvailable || modelItem.toolCall || modelItem.reasoningText) && /* @__PURE__ */ jsxs10("div", {
5531
+ (!isAvailable || modelItem.toolCall || modelItem.reasoningText || modelItem.contextWindow || modelItem.maxOutputTokens) && /* @__PURE__ */ jsxs10("div", {
5480
5532
  className: "flex gap-1 ml-2 flex-shrink-0",
5481
5533
  children: [
5534
+ modelItem.contextWindow && /* @__PURE__ */ jsxs10("span", {
5535
+ className: "text-[10px] px-1.5 py-0.5 bg-blue-600/20 text-blue-400 rounded",
5536
+ children: [
5537
+ formatTokenCount(modelItem.contextWindow),
5538
+ " ",
5539
+ "ctx"
5540
+ ]
5541
+ }),
5542
+ modelItem.maxOutputTokens && /* @__PURE__ */ jsxs10("span", {
5543
+ className: "text-[10px] px-1.5 py-0.5 bg-cyan-600/20 text-cyan-400 rounded",
5544
+ children: [
5545
+ formatTokenCount(modelItem.maxOutputTokens),
5546
+ " ",
5547
+ "out"
5548
+ ]
5549
+ }),
5482
5550
  !isAvailable && /* @__PURE__ */ jsx16("span", {
5483
5551
  className: "text-[10px] px-1.5 py-0.5 bg-red-600/20 text-red-400 rounded",
5484
5552
  children: "Unavailable"
@@ -5905,6 +5973,7 @@ var ChatInputContainer = memo5(forwardRef8(function ChatInputContainer2({ sessio
5905
5973
  removeResearchContext(sessionId, contextId);
5906
5974
  }, [sessionId, removeResearchContext]);
5907
5975
  const providerAuthType = allModels?.[provider]?.authType;
5976
+ const isCustomProvider = allModels?.[provider]?.label?.includes("(custom)") ?? false;
5908
5977
  useEffect12(() => {
5909
5978
  if (session) {
5910
5979
  setAgent(session.agent);
@@ -6149,6 +6218,7 @@ ${content}` : content;
6149
6218
  attachmentEnabled: modelSupportsAttachment,
6150
6219
  modelName: model,
6151
6220
  providerName: provider,
6221
+ isCustomProvider,
6152
6222
  authType: providerAuthType,
6153
6223
  isFreeModel: modelIsFree,
6154
6224
  researchContexts,
@@ -6345,6 +6415,7 @@ var NewSessionLanding = memo6(forwardRef9(function NewSessionLanding2({ onSessio
6345
6415
  const modelSupportsReasoning = allModels?.[provider]?.models?.find((m) => m.id === model)?.reasoningText;
6346
6416
  const modelIsFree = allModels?.[provider]?.models?.find((m) => m.id === model)?.free;
6347
6417
  const providerAuthType = allModels?.[provider]?.authType;
6418
+ const isCustomProvider = allModels?.[provider]?.label?.includes("(custom)") ?? false;
6348
6419
  const {
6349
6420
  images,
6350
6421
  documents,
@@ -6512,6 +6583,7 @@ var NewSessionLanding = memo6(forwardRef9(function NewSessionLanding2({ onSessio
6512
6583
  attachmentEnabled: modelSupportsAttachment,
6513
6584
  modelName: model,
6514
6585
  providerName: provider,
6586
+ isCustomProvider,
6515
6587
  authType: providerAuthType,
6516
6588
  isFreeModel: modelIsFree,
6517
6589
  onConfigClick: handleToggleConfig,
@@ -21274,11 +21346,37 @@ function useAuthStatus() {
21274
21346
  setLoading(false);
21275
21347
  }
21276
21348
  }, [fetchAuthStatus, setLoading, setError]);
21349
+ const addCustomProvider = useCallback26(async (data) => {
21350
+ setLoading(true);
21351
+ setError(null);
21352
+ try {
21353
+ const result = await apiClient.updateProviderSettings(data.id, {
21354
+ enabled: true,
21355
+ custom: true,
21356
+ label: data.label,
21357
+ baseURL: data.baseURL,
21358
+ ...data.apiKey ? { apiKey: data.apiKey } : {},
21359
+ compatibility: data.compatibility,
21360
+ models: data.models,
21361
+ allowAnyModel: data.allowAnyModel,
21362
+ scope: "local"
21363
+ });
21364
+ await fetchAuthStatus();
21365
+ return result;
21366
+ } catch (err) {
21367
+ const message = err instanceof Error ? err.message : "Failed to add custom provider";
21368
+ setError(message);
21369
+ throw err;
21370
+ } finally {
21371
+ setLoading(false);
21372
+ }
21373
+ }, [fetchAuthStatus, setLoading, setError]);
21277
21374
  const removeProvider = useCallback26(async (provider) => {
21278
21375
  setLoading(true);
21279
21376
  setError(null);
21280
21377
  try {
21281
- const result = await apiClient.removeProvider(provider);
21378
+ const isCustomProvider = useOnboardingStore.getState().authStatus?.providers[provider]?.custom;
21379
+ const result = isCustomProvider ? await apiClient.deleteProviderSettings(provider) : await apiClient.removeProvider(provider);
21282
21380
  await fetchAuthStatus();
21283
21381
  return result;
21284
21382
  } catch (err) {
@@ -21433,6 +21531,7 @@ function useAuthStatus() {
21433
21531
  setupWallet,
21434
21532
  importWallet,
21435
21533
  addProvider,
21534
+ addCustomProvider,
21436
21535
  removeProvider,
21437
21536
  completeOnboarding,
21438
21537
  startOAuth,
@@ -25159,15 +25258,32 @@ import {
25159
25258
  Key as Key2,
25160
25259
  ExternalLink as ExternalLink10,
25161
25260
  ArrowRight as ArrowRight2,
25162
- RefreshCw as RefreshCw11
25261
+ RefreshCw as RefreshCw11,
25262
+ Plus as Plus8
25163
25263
  } from "lucide-react";
25164
25264
  import { QRCodeSVG as QRCodeSVG3 } from "qrcode.react";
25165
25265
  import { jsx as jsx103, jsxs as jsxs91, Fragment as Fragment39 } from "react/jsx-runtime";
25266
+ var CUSTOM_PROVIDER_COMPATIBILITY_OPTIONS = [
25267
+ { value: "openai-compatible", label: "OpenAI-compatible" },
25268
+ { value: "openai", label: "OpenAI" },
25269
+ { value: "anthropic", label: "Anthropic" },
25270
+ { value: "google", label: "Google" },
25271
+ { value: "openrouter", label: "OpenRouter" },
25272
+ { value: "ollama", label: "Ollama" }
25273
+ ];
25274
+ function formatTokenCount2(tokens) {
25275
+ if (tokens >= 1e6)
25276
+ return `${(tokens / 1e6).toFixed(1)}M`;
25277
+ if (tokens >= 1000)
25278
+ return `${Math.round(tokens / 1000)}K`;
25279
+ return String(tokens);
25280
+ }
25166
25281
  var ProviderSetupStep = memo46(function ProviderSetupStep2({
25167
25282
  authStatus,
25168
25283
  onSetupWallet,
25169
25284
  onImportWallet,
25170
25285
  onAddProvider,
25286
+ onAddCustomProvider,
25171
25287
  onRemoveProvider,
25172
25288
  onStartOAuth,
25173
25289
  onStartOAuthManual,
@@ -25192,6 +25308,19 @@ var ProviderSetupStep = memo46(function ProviderSetupStep2({
25192
25308
  const [importWalletError, setImportWalletError] = useState44(null);
25193
25309
  const [addingProvider, setAddingProvider] = useState44(null);
25194
25310
  const [apiKeyInput, setApiKeyInput] = useState44("");
25311
+ const [isCustomProviderModalOpen, setIsCustomProviderModalOpen] = useState44(false);
25312
+ const [customProviderId, setCustomProviderId] = useState44("");
25313
+ const [customProviderLabel, setCustomProviderLabel] = useState44("");
25314
+ const [customProviderBaseURL, setCustomProviderBaseURL] = useState44("");
25315
+ const [customProviderApiKey, setCustomProviderApiKey] = useState44("");
25316
+ const [customProviderModels, setCustomProviderModels] = useState44("");
25317
+ const [customProviderCompatibility, setCustomProviderCompatibility] = useState44("openai-compatible");
25318
+ const [customProviderAllowAnyModel, setCustomProviderAllowAnyModel] = useState44(true);
25319
+ const [isAddingCustomProvider, setIsAddingCustomProvider] = useState44(false);
25320
+ const [customProviderError, setCustomProviderError] = useState44(null);
25321
+ const [isDiscoveringCustomModels, setIsDiscoveringCustomModels] = useState44(false);
25322
+ const [discoveredCustomModels, setDiscoveredCustomModels] = useState44([]);
25323
+ const [customProviderDiscoveryMessage, setCustomProviderDiscoveryMessage] = useState44(null);
25195
25324
  const [removingProvider, setRemovingProvider] = useState44(null);
25196
25325
  const [confirmingDelete, setConfirmingDelete] = useState44(null);
25197
25326
  const [oauthSession, setOauthSession] = useState44(null);
@@ -25316,6 +25445,83 @@ var ProviderSetupStep = memo46(function ProviderSetupStep2({
25316
25445
  setApiKeyInput("");
25317
25446
  } catch {}
25318
25447
  };
25448
+ const resetCustomProviderForm = () => {
25449
+ setCustomProviderId("");
25450
+ setCustomProviderLabel("");
25451
+ setCustomProviderBaseURL("");
25452
+ setCustomProviderApiKey("");
25453
+ setCustomProviderModels("");
25454
+ setCustomProviderCompatibility("openai-compatible");
25455
+ setCustomProviderAllowAnyModel(true);
25456
+ setCustomProviderError(null);
25457
+ setDiscoveredCustomModels([]);
25458
+ setCustomProviderDiscoveryMessage(null);
25459
+ };
25460
+ const handleCloseCustomProviderModal = () => {
25461
+ if (isAddingCustomProvider)
25462
+ return;
25463
+ setIsCustomProviderModalOpen(false);
25464
+ resetCustomProviderForm();
25465
+ };
25466
+ const handleDiscoverCustomProviderModels = async () => {
25467
+ const baseURL = customProviderBaseURL.trim();
25468
+ if (!baseURL) {
25469
+ setCustomProviderDiscoveryMessage("Enter a base URL first.");
25470
+ return;
25471
+ }
25472
+ setIsDiscoveringCustomModels(true);
25473
+ setCustomProviderDiscoveryMessage(null);
25474
+ try {
25475
+ const result = await apiClient.discoverProviderModels({
25476
+ compatibility: customProviderCompatibility,
25477
+ baseURL,
25478
+ apiKey: customProviderApiKey.trim() || undefined
25479
+ });
25480
+ if (result.baseURL)
25481
+ setCustomProviderBaseURL(result.baseURL);
25482
+ setDiscoveredCustomModels(result.models);
25483
+ if (result.models.length > 0) {
25484
+ setCustomProviderModels(result.models.map((model) => model.id).join(`
25485
+ `));
25486
+ }
25487
+ setCustomProviderDiscoveryMessage(result.message || (result.models.length > 0 ? `Fetched ${result.models.length} model${result.models.length === 1 ? "" : "s"}.` : "No models found."));
25488
+ } catch (err) {
25489
+ setDiscoveredCustomModels([]);
25490
+ setCustomProviderDiscoveryMessage(err instanceof Error ? err.message : "Failed to fetch models");
25491
+ } finally {
25492
+ setIsDiscoveringCustomModels(false);
25493
+ }
25494
+ };
25495
+ const handleAddCustomProvider = async () => {
25496
+ const id = customProviderId.trim();
25497
+ const label = customProviderLabel.trim() || id;
25498
+ const baseURL = customProviderBaseURL.trim();
25499
+ const apiKey = customProviderApiKey.trim();
25500
+ const models = customProviderModels.split(/[\n,]/).map((model) => model.trim()).filter(Boolean);
25501
+ if (!id || !baseURL) {
25502
+ setCustomProviderError("Provider ID and base URL are required.");
25503
+ return;
25504
+ }
25505
+ setIsAddingCustomProvider(true);
25506
+ setCustomProviderError(null);
25507
+ try {
25508
+ await onAddCustomProvider({
25509
+ id,
25510
+ label,
25511
+ baseURL,
25512
+ apiKey: apiKey || undefined,
25513
+ compatibility: customProviderCompatibility,
25514
+ models,
25515
+ allowAnyModel: customProviderAllowAnyModel
25516
+ });
25517
+ setIsCustomProviderModalOpen(false);
25518
+ resetCustomProviderForm();
25519
+ } catch (err) {
25520
+ setCustomProviderError(err instanceof Error ? err.message : "Failed to add custom provider");
25521
+ } finally {
25522
+ setIsAddingCustomProvider(false);
25523
+ }
25524
+ };
25319
25525
  const handleRemoveProvider = async (providerId) => {
25320
25526
  if (confirmingDelete === providerId) {
25321
25527
  setRemovingProvider(providerId);
@@ -25762,112 +25968,154 @@ var ProviderSetupStep = memo46(function ProviderSetupStep2({
25762
25968
  className: "font-semibold text-foreground mb-4",
25763
25969
  children: "Add Providers"
25764
25970
  }),
25765
- /* @__PURE__ */ jsx103("div", {
25971
+ /* @__PURE__ */ jsxs91("div", {
25766
25972
  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") {
25973
+ children: [
25974
+ unconfiguredProviders.map(([id, info]) => /* @__PURE__ */ jsx103("div", {
25975
+ children: addingProvider === id ? /* @__PURE__ */ jsxs91("div", {
25976
+ className: "flex items-center gap-2 p-3 bg-card border border-ring rounded-xl overflow-hidden",
25977
+ children: [
25978
+ /* @__PURE__ */ jsx103("div", {
25979
+ className: "shrink-0",
25980
+ children: /* @__PURE__ */ jsx103(ProviderLogo, {
25981
+ provider: id,
25982
+ size: 18
25983
+ })
25984
+ }),
25985
+ /* @__PURE__ */ jsx103("input", {
25986
+ ref: apiKeyInputRef,
25987
+ type: "password",
25988
+ value: apiKeyInput,
25989
+ onChange: (e) => setApiKeyInput(e.target.value),
25990
+ placeholder: `${info.label} API key...`,
25991
+ className: "flex-1 min-w-0 bg-transparent text-sm outline-none placeholder:text-muted-foreground text-foreground",
25992
+ onKeyDown: (e) => {
25993
+ if (e.key === "Enter")
25994
+ handleAddProvider(id);
25995
+ if (e.key === "Escape") {
25996
+ setAddingProvider(null);
25997
+ setApiKeyInput("");
25998
+ }
25999
+ }
26000
+ }),
26001
+ /* @__PURE__ */ jsx103("button", {
26002
+ type: "button",
26003
+ onClick: () => handleAddProvider(id),
26004
+ disabled: !apiKeyInput.trim(),
26005
+ className: "shrink-0 px-3 py-1.5 bg-primary text-primary-foreground text-sm font-medium rounded-lg disabled:opacity-50",
26006
+ children: "Add"
26007
+ }),
26008
+ /* @__PURE__ */ jsx103("button", {
26009
+ type: "button",
26010
+ onClick: () => {
25789
26011
  setAddingProvider(null);
25790
26012
  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"
26013
+ },
26014
+ className: "shrink-0 p-1.5 text-muted-foreground hover:text-foreground",
26015
+ children: /* @__PURE__ */ jsx103(X18, {
26016
+ className: "w-4 h-4"
26017
+ })
25810
26018
  })
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",
26019
+ ]
26020
+ }) : /* @__PURE__ */ jsxs91("div", {
26021
+ className: "flex items-center justify-between p-3 bg-card border border-border hover:border-border/80 rounded-xl transition-colors gap-2",
26022
+ children: [
26023
+ /* @__PURE__ */ jsxs91("div", {
26024
+ className: "flex items-center gap-3 min-w-0",
26025
+ children: [
26026
+ /* @__PURE__ */ jsx103(ProviderLogo, {
26027
+ provider: id,
26028
+ size: 20
26029
+ }),
26030
+ /* @__PURE__ */ jsxs91("div", {
26031
+ className: "min-w-0",
26032
+ children: [
26033
+ /* @__PURE__ */ jsx103("div", {
26034
+ className: "font-medium text-foreground truncate",
26035
+ children: info.label
26036
+ }),
26037
+ /* @__PURE__ */ jsxs91("div", {
26038
+ className: "text-xs text-muted-foreground",
26039
+ children: [
26040
+ info.modelCount,
26041
+ " models"
26042
+ ]
26043
+ })
26044
+ ]
26045
+ })
26046
+ ]
26047
+ }),
26048
+ /* @__PURE__ */ jsxs91("div", {
26049
+ className: "flex items-center gap-1",
26050
+ children: [
26051
+ id !== "copilot" && /* @__PURE__ */ jsxs91("button", {
26052
+ type: "button",
26053
+ onClick: () => setAddingProvider(id),
26054
+ 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",
26055
+ children: [
26056
+ /* @__PURE__ */ jsx103(Key2, {
26057
+ className: "w-3.5 h-3.5"
26058
+ }),
26059
+ "API"
26060
+ ]
26061
+ }),
26062
+ info.supportsOAuth && /* @__PURE__ */ jsxs91("button", {
26063
+ type: "button",
26064
+ onClick: () => handleStartOAuth(id, id === "anthropic" ? "max" : undefined),
26065
+ 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",
26066
+ children: [
26067
+ /* @__PURE__ */ jsx103(ExternalLink10, {
26068
+ className: "w-3.5 h-3.5"
26069
+ }),
26070
+ id === "anthropic" ? "Pro" : id === "copilot" ? "Login" : "OAuth"
26071
+ ]
26072
+ })
26073
+ ]
26074
+ })
26075
+ ]
26076
+ })
26077
+ }, id)),
26078
+ /* @__PURE__ */ jsxs91("button", {
26079
+ type: "button",
26080
+ onClick: () => setIsCustomProviderModalOpen(true),
26081
+ 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
26082
  children: [
25816
26083
  /* @__PURE__ */ jsxs91("div", {
25817
26084
  className: "flex items-center gap-3 min-w-0",
25818
26085
  children: [
25819
- /* @__PURE__ */ jsx103(ProviderLogo, {
25820
- provider: id,
25821
- size: 20
26086
+ /* @__PURE__ */ jsx103("span", {
26087
+ 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",
26088
+ children: /* @__PURE__ */ jsx103(Plus8, {
26089
+ className: "w-3.5 h-3.5"
26090
+ })
25822
26091
  }),
25823
26092
  /* @__PURE__ */ jsxs91("div", {
25824
26093
  className: "min-w-0",
25825
26094
  children: [
25826
26095
  /* @__PURE__ */ jsx103("div", {
25827
26096
  className: "font-medium text-foreground truncate",
25828
- children: info.label
26097
+ children: "Custom Provider"
25829
26098
  }),
25830
- /* @__PURE__ */ jsxs91("div", {
25831
- className: "text-xs text-muted-foreground",
25832
- children: [
25833
- info.modelCount,
25834
- " models"
25835
- ]
26099
+ /* @__PURE__ */ jsx103("div", {
26100
+ className: "text-xs text-muted-foreground group-hover:text-foreground/70 transition-colors",
26101
+ children: "OpenAI-compatible or Ollama endpoint"
25836
26102
  })
25837
26103
  ]
25838
26104
  })
25839
26105
  ]
25840
26106
  }),
25841
- /* @__PURE__ */ jsxs91("div", {
25842
- className: "flex items-center gap-1",
26107
+ /* @__PURE__ */ jsxs91("span", {
26108
+ 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
26109
  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
- ]
26110
+ /* @__PURE__ */ jsx103(Key2, {
26111
+ className: "w-3.5 h-3.5"
25854
26112
  }),
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
- })
26113
+ "Add"
25866
26114
  ]
25867
26115
  })
25868
26116
  ]
25869
26117
  })
25870
- }, id))
26118
+ ]
25871
26119
  })
25872
26120
  ]
25873
26121
  })
@@ -25906,6 +26154,268 @@ var ProviderSetupStep = memo46(function ProviderSetupStep2({
25906
26154
  ]
25907
26155
  })
25908
26156
  }),
26157
+ isCustomProviderModalOpen && /* @__PURE__ */ jsx103("div", {
26158
+ className: "fixed inset-0 z-[9999] flex items-center justify-center bg-black/60 backdrop-blur-sm",
26159
+ children: /* @__PURE__ */ jsxs91("div", {
26160
+ className: "bg-background border border-border rounded-xl w-full max-w-2xl mx-6 shadow-2xl max-h-[90vh] overflow-y-auto",
26161
+ children: [
26162
+ /* @__PURE__ */ jsxs91("div", {
26163
+ className: "flex items-center gap-3 p-6 border-b border-border",
26164
+ children: [
26165
+ /* @__PURE__ */ jsx103("span", {
26166
+ className: "inline-flex items-center justify-center w-6 h-6 rounded-full bg-muted text-muted-foreground",
26167
+ children: /* @__PURE__ */ jsx103(Plus8, {
26168
+ className: "w-4 h-4"
26169
+ })
26170
+ }),
26171
+ /* @__PURE__ */ jsx103("h3", {
26172
+ className: "text-lg font-semibold",
26173
+ children: "Add Custom Provider"
26174
+ })
26175
+ ]
26176
+ }),
26177
+ /* @__PURE__ */ jsxs91("div", {
26178
+ className: "p-6 space-y-4",
26179
+ children: [
26180
+ /* @__PURE__ */ jsxs91("div", {
26181
+ className: "grid grid-cols-1 sm:grid-cols-2 gap-4",
26182
+ children: [
26183
+ /* @__PURE__ */ jsxs91("label", {
26184
+ className: "space-y-2",
26185
+ children: [
26186
+ /* @__PURE__ */ jsx103("span", {
26187
+ className: "text-sm font-medium text-foreground",
26188
+ children: "Provider ID"
26189
+ }),
26190
+ /* @__PURE__ */ jsx103("input", {
26191
+ type: "text",
26192
+ value: customProviderId,
26193
+ onChange: (e) => setCustomProviderId(e.target.value),
26194
+ placeholder: "my-provider",
26195
+ 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"
26196
+ })
26197
+ ]
26198
+ }),
26199
+ /* @__PURE__ */ jsxs91("label", {
26200
+ className: "space-y-2",
26201
+ children: [
26202
+ /* @__PURE__ */ jsx103("span", {
26203
+ className: "text-sm font-medium text-foreground",
26204
+ children: "Display Name"
26205
+ }),
26206
+ /* @__PURE__ */ jsx103("input", {
26207
+ type: "text",
26208
+ value: customProviderLabel,
26209
+ onChange: (e) => setCustomProviderLabel(e.target.value),
26210
+ placeholder: "My Provider",
26211
+ 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"
26212
+ })
26213
+ ]
26214
+ })
26215
+ ]
26216
+ }),
26217
+ /* @__PURE__ */ jsxs91("div", {
26218
+ className: "flex items-center justify-between gap-3 p-3 bg-muted/30 border border-border rounded-lg",
26219
+ children: [
26220
+ /* @__PURE__ */ jsxs91("div", {
26221
+ className: "min-w-0",
26222
+ children: [
26223
+ /* @__PURE__ */ jsx103("div", {
26224
+ className: "text-sm font-medium text-foreground",
26225
+ children: "Fetch models from provider"
26226
+ }),
26227
+ /* @__PURE__ */ jsx103("div", {
26228
+ className: "text-xs text-muted-foreground",
26229
+ children: "For Ollama, this reads /api/tags and /api/show to include context windows."
26230
+ })
26231
+ ]
26232
+ }),
26233
+ /* @__PURE__ */ jsxs91("button", {
26234
+ type: "button",
26235
+ onClick: handleDiscoverCustomProviderModels,
26236
+ disabled: !customProviderBaseURL.trim() || isDiscoveringCustomModels,
26237
+ 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",
26238
+ children: [
26239
+ isDiscoveringCustomModels && /* @__PURE__ */ jsx103(Loader216, {
26240
+ className: "w-3.5 h-3.5 animate-spin"
26241
+ }),
26242
+ "Fetch Models"
26243
+ ]
26244
+ })
26245
+ ]
26246
+ }),
26247
+ customProviderDiscoveryMessage && /* @__PURE__ */ jsx103("p", {
26248
+ className: "text-xs text-muted-foreground",
26249
+ children: customProviderDiscoveryMessage
26250
+ }),
26251
+ discoveredCustomModels.length > 0 && /* @__PURE__ */ jsx103("div", {
26252
+ className: "space-y-2 max-h-44 overflow-y-auto border border-border rounded-lg p-2",
26253
+ children: discoveredCustomModels.map((model) => /* @__PURE__ */ jsxs91("div", {
26254
+ className: "flex items-center justify-between gap-3 p-2 bg-card rounded-md",
26255
+ children: [
26256
+ /* @__PURE__ */ jsxs91("div", {
26257
+ className: "min-w-0",
26258
+ children: [
26259
+ /* @__PURE__ */ jsx103("div", {
26260
+ className: "text-sm font-medium text-foreground truncate",
26261
+ children: model.label
26262
+ }),
26263
+ /* @__PURE__ */ jsx103("div", {
26264
+ className: "text-xs text-muted-foreground font-mono truncate",
26265
+ children: model.id
26266
+ })
26267
+ ]
26268
+ }),
26269
+ /* @__PURE__ */ jsxs91("div", {
26270
+ className: "flex flex-wrap justify-end gap-1 shrink-0",
26271
+ children: [
26272
+ model.contextWindow && /* @__PURE__ */ jsxs91("span", {
26273
+ className: "text-[10px] px-1.5 py-0.5 bg-blue-600/20 text-blue-400 rounded",
26274
+ children: [
26275
+ formatTokenCount2(model.contextWindow),
26276
+ " ctx"
26277
+ ]
26278
+ }),
26279
+ model.maxOutputTokens && /* @__PURE__ */ jsxs91("span", {
26280
+ className: "text-[10px] px-1.5 py-0.5 bg-cyan-600/20 text-cyan-400 rounded",
26281
+ children: [
26282
+ formatTokenCount2(model.maxOutputTokens),
26283
+ " out"
26284
+ ]
26285
+ }),
26286
+ model.toolCall && /* @__PURE__ */ jsx103("span", {
26287
+ className: "text-[10px] px-1.5 py-0.5 bg-green-600/20 text-green-400 rounded",
26288
+ children: "Tools"
26289
+ }),
26290
+ model.reasoningText && /* @__PURE__ */ jsx103("span", {
26291
+ className: "text-[10px] px-1.5 py-0.5 bg-purple-600/20 text-purple-400 rounded",
26292
+ children: "Reasoning"
26293
+ }),
26294
+ model.vision && /* @__PURE__ */ jsx103("span", {
26295
+ className: "text-[10px] px-1.5 py-0.5 bg-orange-600/20 text-orange-400 rounded",
26296
+ children: "Vision"
26297
+ })
26298
+ ]
26299
+ })
26300
+ ]
26301
+ }, model.id))
26302
+ }),
26303
+ /* @__PURE__ */ jsxs91("label", {
26304
+ className: "space-y-2 block",
26305
+ children: [
26306
+ /* @__PURE__ */ jsx103("span", {
26307
+ className: "text-sm font-medium text-foreground",
26308
+ children: "Base URL"
26309
+ }),
26310
+ /* @__PURE__ */ jsx103("input", {
26311
+ type: "url",
26312
+ value: customProviderBaseURL,
26313
+ onChange: (e) => setCustomProviderBaseURL(e.target.value),
26314
+ placeholder: "https://api.example.com/v1",
26315
+ 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"
26316
+ })
26317
+ ]
26318
+ }),
26319
+ /* @__PURE__ */ jsxs91("div", {
26320
+ className: "grid grid-cols-1 sm:grid-cols-2 gap-4",
26321
+ children: [
26322
+ /* @__PURE__ */ jsxs91("label", {
26323
+ className: "space-y-2",
26324
+ children: [
26325
+ /* @__PURE__ */ jsx103("span", {
26326
+ className: "text-sm font-medium text-foreground",
26327
+ children: "API Key"
26328
+ }),
26329
+ /* @__PURE__ */ jsx103("input", {
26330
+ type: "password",
26331
+ value: customProviderApiKey,
26332
+ onChange: (e) => setCustomProviderApiKey(e.target.value),
26333
+ placeholder: "Optional",
26334
+ 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"
26335
+ })
26336
+ ]
26337
+ }),
26338
+ /* @__PURE__ */ jsxs91("label", {
26339
+ className: "space-y-2",
26340
+ children: [
26341
+ /* @__PURE__ */ jsx103("span", {
26342
+ className: "text-sm font-medium text-foreground",
26343
+ children: "Compatibility"
26344
+ }),
26345
+ /* @__PURE__ */ jsx103("select", {
26346
+ value: customProviderCompatibility,
26347
+ onChange: (e) => setCustomProviderCompatibility(e.target.value),
26348
+ 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",
26349
+ children: CUSTOM_PROVIDER_COMPATIBILITY_OPTIONS.map((option) => /* @__PURE__ */ jsx103("option", {
26350
+ value: option.value,
26351
+ children: option.label
26352
+ }, option.value))
26353
+ })
26354
+ ]
26355
+ })
26356
+ ]
26357
+ }),
26358
+ /* @__PURE__ */ jsxs91("label", {
26359
+ className: "space-y-2 block",
26360
+ children: [
26361
+ /* @__PURE__ */ jsx103("span", {
26362
+ className: "text-sm font-medium text-foreground",
26363
+ children: "Models"
26364
+ }),
26365
+ /* @__PURE__ */ jsx103("textarea", {
26366
+ value: customProviderModels,
26367
+ onChange: (e) => setCustomProviderModels(e.target.value),
26368
+ placeholder: "gpt-4o, claude-sonnet-4-5, llama3.3",
26369
+ 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"
26370
+ }),
26371
+ /* @__PURE__ */ jsx103("span", {
26372
+ className: "text-xs text-muted-foreground",
26373
+ children: "Comma or newline separated. Leave blank to allow any model."
26374
+ })
26375
+ ]
26376
+ }),
26377
+ /* @__PURE__ */ jsxs91("label", {
26378
+ className: "flex items-center gap-3 text-sm text-muted-foreground",
26379
+ children: [
26380
+ /* @__PURE__ */ jsx103("input", {
26381
+ type: "checkbox",
26382
+ checked: customProviderAllowAnyModel,
26383
+ onChange: (e) => setCustomProviderAllowAnyModel(e.target.checked),
26384
+ className: "h-4 w-4 accent-primary"
26385
+ }),
26386
+ "Allow entering model IDs not listed above"
26387
+ ]
26388
+ }),
26389
+ customProviderError && /* @__PURE__ */ jsx103("p", {
26390
+ className: "text-sm text-red-500",
26391
+ children: customProviderError
26392
+ }),
26393
+ /* @__PURE__ */ jsxs91("div", {
26394
+ className: "flex gap-3 pt-2",
26395
+ children: [
26396
+ /* @__PURE__ */ jsx103("button", {
26397
+ type: "button",
26398
+ onClick: handleCloseCustomProviderModal,
26399
+ disabled: isAddingCustomProvider,
26400
+ 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",
26401
+ children: "Cancel"
26402
+ }),
26403
+ /* @__PURE__ */ jsx103("button", {
26404
+ type: "button",
26405
+ onClick: handleAddCustomProvider,
26406
+ disabled: !customProviderId.trim() || !customProviderBaseURL.trim() || isAddingCustomProvider,
26407
+ 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",
26408
+ children: isAddingCustomProvider ? /* @__PURE__ */ jsx103(Loader216, {
26409
+ className: "w-4 h-4 animate-spin"
26410
+ }) : "Add Provider"
26411
+ })
26412
+ ]
26413
+ })
26414
+ ]
26415
+ })
26416
+ ]
26417
+ })
26418
+ }),
25909
26419
  isImportModalOpen && /* @__PURE__ */ jsx103("div", {
25910
26420
  className: "fixed inset-0 z-[9999] flex items-center justify-center bg-black/60 backdrop-blur-sm",
25911
26421
  children: /* @__PURE__ */ jsxs91("div", {
@@ -26633,6 +27143,7 @@ var OnboardingModal = memo48(function OnboardingModal2({
26633
27143
  setupWallet,
26634
27144
  importWallet,
26635
27145
  addProvider,
27146
+ addCustomProvider,
26636
27147
  removeProvider,
26637
27148
  completeOnboarding,
26638
27149
  startOAuth,
@@ -26655,6 +27166,7 @@ var OnboardingModal = memo48(function OnboardingModal2({
26655
27166
  onSetupWallet: setupWallet,
26656
27167
  onImportWallet: importWallet,
26657
27168
  onAddProvider: addProvider,
27169
+ onAddCustomProvider: addCustomProvider,
26658
27170
  onRemoveProvider: removeProvider,
26659
27171
  onStartOAuth: startOAuth,
26660
27172
  onStartOAuthManual: startOAuthManual,
@@ -27522,4 +28034,4 @@ export {
27522
28034
  API_BASE_URL
27523
28035
  };
27524
28036
 
27525
- //# debugId=A7717B48FBB0AB3064756E2164756E21
28037
+ //# debugId=513BAF52FD41B05764756E2164756E21