@hermespilot/link 0.7.2 → 0.7.4-beta.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.
@@ -1316,10 +1316,10 @@ async function applyOwner(filePath, metadata) {
1316
1316
  }
1317
1317
  try {
1318
1318
  await chown(filePath, metadata.uid, metadata.gid);
1319
- } catch (error) {
1319
+ } catch {
1320
1320
  const current = await stat(filePath);
1321
- if (current.uid !== metadata.uid || current.gid !== metadata.gid) {
1322
- throw error;
1321
+ if (current.uid === metadata.uid && current.gid === metadata.gid) {
1322
+ return;
1323
1323
  }
1324
1324
  }
1325
1325
  }
@@ -2002,7 +2002,7 @@ var DEFAULT_HERMES_API_SERVER_HOST = "127.0.0.1";
2002
2002
  var DEFAULT_HERMES_API_SERVER_PORT = 8642;
2003
2003
  var PROFILE_API_SERVER_PORT_START = DEFAULT_HERMES_API_SERVER_PORT + 1;
2004
2004
  var PROFILE_API_SERVER_PORT_END = DEFAULT_HERMES_API_SERVER_PORT + 999;
2005
- var MODEL_CONFIG_RESTART_HINT = "\u6A21\u578B\u914D\u7F6E\u5DF2\u4FDD\u5B58\u3002\u5EFA\u8BAE\u91CD\u8F7D Hermes Gateway\uFF0C\u6B63\u5728\u8FD0\u884C\u4E2D\u7684 Run \u4E0D\u4F1A\u88AB\u4E2D\u65AD\uFF0C\u65B0\u7684 Run \u4F1A\u8BFB\u53D6\u6700\u65B0\u914D\u7F6E\u3002";
2005
+ var MODEL_CONFIG_APPLIED_HINT = "\u6A21\u578B\u914D\u7F6E\u5DF2\u4FDD\u5B58\u3002\u65B0\u7684 Run \u4F1A\u76F4\u63A5\u8BFB\u53D6\u6700\u65B0\u914D\u7F6E\uFF0C\u65E0\u9700\u91CD\u8F7D Hermes Gateway\u3002";
2006
2006
  var MODEL_DEFAULTS_APPLIED_HINT = "\u9ED8\u8BA4\u6A21\u578B\u8BBE\u7F6E\u5DF2\u4FDD\u5B58\u3002\u65B0\u7684 Run \u4F1A\u76F4\u63A5\u8BFB\u53D6\u6700\u65B0\u914D\u7F6E\uFF0C\u65E0\u9700\u91CD\u8F7D Hermes Gateway\u3002";
2007
2007
  var PROFILE_PERMISSIONS_RESTART_HINT = "\u6743\u9650\u914D\u7F6E\u5DF2\u4FDD\u5B58\u3002\u540E\u7EED\u4EE5\u8BE5 Profile \u53D1\u8D77\u7684\u65B0 Run \u4F1A\u8BFB\u53D6\u6700\u65B0\u914D\u7F6E\uFF1B\u5982\u679C\u8BE5 Profile \u7684 Gateway \u5DF2\u7ECF\u5728\u8FD0\u884C\uFF0C\u9700\u8981\u91CD\u8F7D\u5BF9\u5E94 Gateway\u3002";
2008
2008
  var PROFILE_TOOL_CONFIG_RESTART_HINT = "\u5DE5\u5177\u540E\u7AEF\u914D\u7F6E\u5DF2\u4FDD\u5B58\u3002\u540E\u7EED\u4EE5\u8BE5 Profile \u53D1\u8D77\u7684\u65B0 Run \u4F1A\u8BFB\u53D6\u6700\u65B0\u914D\u7F6E\uFF1B\u5982\u679C\u8BE5 Profile \u7684 Gateway \u5DF2\u7ECF\u5728\u8FD0\u884C\uFF0C\u9700\u8981\u91CD\u8F7D\u5BF9\u5E94 Gateway\u3002";
@@ -2386,6 +2386,7 @@ async function listHermesModelConfigs(profileName = "default", configPath = reso
2386
2386
  configPath,
2387
2387
  defaultModel,
2388
2388
  defaultReasoningEffort,
2389
+ imageInputMode: readProfileImageInputMode(config),
2389
2390
  compressionModelId,
2390
2391
  compressionModel: resolveCompressionModel(config, models),
2391
2392
  models
@@ -2396,7 +2397,7 @@ async function listHermesModelConfigCatalog(input) {
2396
2397
  const targetModels = targetProfileName ? await listHermesModelConfigs(targetProfileName).then((result) => result.models).catch(() => []) : [];
2397
2398
  const targetKeys = new Set(
2398
2399
  targetModels.map(
2399
- (model) => modelConfigKey(model.provider, model.baseUrl, model.id)
2400
+ (model) => modelConfigKey(model.provider, model.baseUrl, model.id, model.apiMode)
2400
2401
  )
2401
2402
  );
2402
2403
  const items = /* @__PURE__ */ new Map();
@@ -2410,7 +2411,12 @@ async function listHermesModelConfigCatalog(input) {
2410
2411
  continue;
2411
2412
  }
2412
2413
  for (const model of listed.models) {
2413
- const key = modelConfigKey(model.provider, model.baseUrl, model.id);
2414
+ const key = modelConfigKey(
2415
+ model.provider,
2416
+ model.baseUrl,
2417
+ model.id,
2418
+ model.apiMode
2419
+ );
2414
2420
  const existing = items.get(key);
2415
2421
  if (existing) {
2416
2422
  if (!existing.sourceProfiles.some(
@@ -2449,6 +2455,7 @@ async function listHermesModelConfigCatalog(input) {
2449
2455
  ...model.reasoningEffort ? { reasoningEffort: model.reasoningEffort } : {},
2450
2456
  reasoningSupport: model.reasoningSupport,
2451
2457
  supportedReasoningEfforts: model.supportedReasoningEfforts,
2458
+ supportsVision: model.supportsVision,
2452
2459
  sourceProfiles: [
2453
2460
  {
2454
2461
  name: profileName,
@@ -2480,12 +2487,24 @@ async function importHermesModelConfig(input, targetProfileName = "default", tar
2480
2487
  });
2481
2488
  const { document, config, existingRaw } = await readHermesConfigDocument(targetConfigPath);
2482
2489
  const targetEnv = await readHermesEnvFile(targetProfileName);
2483
- const customProviders = ensureCustomProvidersList(config);
2484
- const targetEntryIndex = findCustomProviderIndexByEndpoint(customProviders, {
2490
+ const providers = ensureProvidersRecordWithLegacyMigration(config);
2491
+ const targetProviderKey = findProviderConfigKeyByVisibleEndpoint(providers, {
2492
+ providerName: source.model.providerName,
2493
+ baseUrl: source.model.baseUrl,
2494
+ apiMode: source.model.apiMode
2495
+ }) ?? findProviderConfigKeyByEndpoint(providers, {
2485
2496
  provider: source.model.provider,
2486
- baseUrl: source.model.baseUrl
2487
- });
2488
- const entry = targetEntryIndex >= 0 ? toRecord(customProviders[targetEntryIndex]) : {};
2497
+ baseUrl: source.model.baseUrl,
2498
+ apiMode: source.model.apiMode
2499
+ }) ?? uniqueProviderConfigKey(
2500
+ providerConfigKeyBase(
2501
+ source.model.provider,
2502
+ source.model.providerName,
2503
+ source.model.baseUrl
2504
+ ),
2505
+ providers
2506
+ );
2507
+ const entry = toRecord(providers[targetProviderKey]);
2489
2508
  const entryHadModels = readEntryModelIds(entry).length > 0;
2490
2509
  const existingKeyEnv = readString2(entry.key_env) ?? parseEnvReference(readString2(entry.api_key));
2491
2510
  const existingInlineApiKey = readInlineApiKey(readString2(entry.api_key));
@@ -2498,14 +2517,26 @@ async function importHermesModelConfig(input, targetProfileName = "default", tar
2498
2517
  if (sourceApiKey && keyEnv && !existingInlineApiKey && (!existingKeyEnv || !targetEnv[keyEnv]?.trim())) {
2499
2518
  await writeHermesEnvValue(targetProfileName, keyEnv, sourceApiKey);
2500
2519
  }
2501
- entry.name = readString2(entry.name) ?? readString2(entry.provider_name) ?? source.model.providerName;
2502
- entry.provider_key = source.model.provider;
2503
- entry.base_url = source.model.baseUrl;
2520
+ writeProviderEndpointConfig(
2521
+ entry,
2522
+ {
2523
+ id: source.model.id,
2524
+ provider: targetProviderKey,
2525
+ providerName: readString2(entry.name) ?? readString2(entry.provider_name) ?? source.model.providerName,
2526
+ baseUrl: source.model.baseUrl,
2527
+ apiMode: source.model.apiMode,
2528
+ contextLength: source.model.contextLength,
2529
+ keyEnv,
2530
+ setDefault: input.setDefault,
2531
+ ...source.model.reasoningEffort ? { reasoningEffort: source.model.reasoningEffort } : {},
2532
+ supportsVision: source.model.supportsVision
2533
+ },
2534
+ keyEnv
2535
+ );
2536
+ ensureEntryModelConfig(entry, source.model.id, source.model.id);
2504
2537
  if (!entryHadModels) {
2505
- entry.model = source.model.id;
2538
+ entry.default_model = source.model.id;
2506
2539
  }
2507
- entry.api_mode = source.model.apiMode;
2508
- addEntryModel(entry, source.model.id);
2509
2540
  if (source.model.contextLength) {
2510
2541
  writeEntryModelContextLength(
2511
2542
  entry,
@@ -2515,43 +2546,39 @@ async function importHermesModelConfig(input, targetProfileName = "default", tar
2515
2546
  } else if (!entryHadModels) {
2516
2547
  delete entry.context_length;
2517
2548
  }
2518
- if (keyEnv) {
2519
- entry.key_env = keyEnv;
2549
+ if (!keyEnv && !existingInlineApiKey) {
2520
2550
  delete entry.api_key;
2521
- } else {
2522
- delete entry.key_env;
2523
- if (!existingInlineApiKey) {
2524
- delete entry.api_key;
2525
- }
2526
2551
  }
2527
2552
  writeEntryModelReasoningEffort(
2528
2553
  entry,
2529
2554
  source.model.id,
2530
2555
  source.model.reasoningEffort
2531
2556
  );
2532
- if (targetEntryIndex >= 0) {
2533
- customProviders[targetEntryIndex] = entry;
2534
- } else {
2535
- customProviders.push(entry);
2536
- }
2557
+ writeEntryModelSupportsVision(
2558
+ entry,
2559
+ source.model.id,
2560
+ source.model.supportsVision
2561
+ );
2562
+ providers[targetProviderKey] = entry;
2537
2563
  const modelConfig = ensureRecord(config, "model");
2538
2564
  const currentDefaultConfig = readModelConfig(modelConfig);
2539
2565
  const currentDefaultReasoningEffort = readProfileReasoningEffort(config);
2540
2566
  if (input.setDefault || !currentDefaultConfig.model) {
2541
2567
  if (input.setDefault && currentDefaultConfig.model && currentDefaultConfig.model !== source.model.id) {
2542
- retainModelDefaultAsCustomProvider(customProviders, {
2568
+ retainModelDefaultAsProvider(providers, {
2543
2569
  ...currentDefaultConfig,
2544
2570
  ...currentDefaultReasoningEffort ? { reasoningEffort: currentDefaultReasoningEffort } : {}
2545
2571
  });
2546
2572
  }
2547
2573
  writeDefaultModelConfig(modelConfig, {
2548
2574
  id: source.model.id,
2549
- provider: source.model.provider,
2575
+ provider: targetProviderKey,
2550
2576
  baseUrl: source.model.baseUrl,
2551
2577
  apiMode: source.model.apiMode,
2552
2578
  contextLength: source.model.contextLength,
2553
2579
  keyEnv
2554
2580
  });
2581
+ writeDefaultModelSupportsVision(modelConfig, source.model.supportsVision);
2555
2582
  if (source.model.reasoningEffort) {
2556
2583
  writeProfileReasoningEffort(config, source.model.reasoningEffort);
2557
2584
  }
@@ -2563,9 +2590,17 @@ async function importHermesModelConfig(input, targetProfileName = "default", tar
2563
2590
  existingRaw
2564
2591
  });
2565
2592
  const listed = await listHermesModelConfigs(targetProfileName, targetConfigPath);
2566
- const importedModel = listed.models.find(
2567
- (model) => model.id === source.model.id && model.provider === source.model.provider && model.baseUrl === source.model.baseUrl
2568
- ) ?? listed.models.find((model) => model.id === source.model.id);
2593
+ const importedModel = findManagedModel(listed.models, {
2594
+ id: source.model.id,
2595
+ provider: targetProviderKey,
2596
+ baseUrl: source.model.baseUrl,
2597
+ apiMode: source.model.apiMode
2598
+ }) ?? findManagedModelByVisibleIdentity(listed.models, {
2599
+ id: source.model.id,
2600
+ providerName: source.model.providerName,
2601
+ baseUrl: source.model.baseUrl,
2602
+ apiMode: source.model.apiMode
2603
+ });
2569
2604
  if (!importedModel) {
2570
2605
  throw new Error("imported model is missing from config");
2571
2606
  }
@@ -2574,44 +2609,47 @@ async function importHermesModelConfig(input, targetProfileName = "default", tar
2574
2609
  model: importedModel,
2575
2610
  sourceProfileName,
2576
2611
  backupPath,
2577
- requiresGatewayReload: true,
2578
- restartHint: MODEL_CONFIG_RESTART_HINT
2612
+ requiresGatewayReload: false,
2613
+ restartHint: MODEL_CONFIG_APPLIED_HINT
2579
2614
  };
2580
2615
  }
2581
2616
  async function saveHermesModelConfig(input, profileName = "default", configPath = resolveHermesConfigPath(profileName)) {
2582
2617
  const normalized = normalizeModelConfigInput(input);
2583
2618
  const shouldUpdateReasoningEffort = input.reasoningEffort !== void 0;
2584
2619
  const { document, config, existingRaw } = await readHermesConfigDocument(configPath);
2585
- const customProviders = ensureCustomProvidersList(config);
2620
+ const providers = ensureProvidersRecordWithLegacyMigration(config);
2586
2621
  const originalModelId = input.originalModelId?.trim() || normalized.id;
2587
- const index = findCustomProviderIndex(customProviders, originalModelId);
2588
- const entry = index >= 0 ? toRecord(customProviders[index]) : {};
2622
+ const originalProvider = input.originalProvider?.trim();
2623
+ const originalBaseUrl = input.originalBaseUrl?.trim();
2624
+ const originalApiMode = input.originalApiMode?.trim();
2625
+ const existingProviderKey = findProviderConfigKeyByModelIdentity(providers, {
2626
+ id: originalModelId,
2627
+ provider: originalProvider,
2628
+ baseUrl: originalBaseUrl,
2629
+ apiMode: originalApiMode
2630
+ }) ?? (originalProvider && originalBaseUrl ? findProviderConfigKeyByEndpoint(providers, {
2631
+ provider: originalProvider,
2632
+ baseUrl: originalBaseUrl,
2633
+ apiMode: originalApiMode
2634
+ }) : null) ?? findProviderConfigKeyByVisibleEndpoint(providers, normalized) ?? findProviderConfigKeyByEndpoint(providers, normalized);
2635
+ const providerKey = existingProviderKey ?? uniqueProviderConfigKey(
2636
+ providerConfigKeyBase(
2637
+ normalized.provider === "custom" ? void 0 : normalized.provider,
2638
+ normalized.providerName,
2639
+ normalized.baseUrl
2640
+ ),
2641
+ providers
2642
+ );
2643
+ const entry = toRecord(providers[providerKey]);
2589
2644
  const existingKeyEnv = readString2(entry.key_env) ?? parseEnvReference(readString2(entry.api_key));
2590
2645
  const keyEnv = normalized.keyEnv ?? existingKeyEnv ?? (normalized.apiKey ? buildApiKeyEnvName(normalized.providerName, normalized.id) : void 0);
2591
2646
  if (normalized.apiKey && keyEnv) {
2592
2647
  await writeHermesEnvValue(profileName, keyEnv, normalized.apiKey);
2593
2648
  }
2594
- entry.name = normalized.providerName;
2595
- entry.provider_key = normalized.provider;
2596
- entry.base_url = normalized.baseUrl;
2597
- entry.model = normalized.id;
2598
- if (normalized.apiMode) {
2599
- entry.api_mode = normalized.apiMode;
2600
- } else {
2601
- delete entry.api_mode;
2602
- }
2603
- if (normalized.contextLength) {
2604
- entry.context_length = normalized.contextLength;
2605
- } else {
2606
- delete entry.context_length;
2607
- }
2608
- if (keyEnv) {
2609
- entry.key_env = keyEnv;
2610
- delete entry.api_key;
2611
- } else {
2612
- delete entry.key_env;
2613
- }
2614
- updateEntryModels(entry, originalModelId, normalized.id);
2649
+ writeProviderEndpointConfig(entry, normalized, keyEnv);
2650
+ ensureEntryModelConfig(entry, originalModelId, normalized.id);
2651
+ writeEntryModelContextLength(entry, normalized.id, normalized.contextLength);
2652
+ writeEntryModelSupportsVision(entry, normalized.id, input.supportsVision);
2615
2653
  if (shouldUpdateReasoningEffort) {
2616
2654
  writeEntryModelReasoningEffort(
2617
2655
  entry,
@@ -2619,18 +2657,14 @@ async function saveHermesModelConfig(input, profileName = "default", configPath
2619
2657
  normalized.reasoningEffort
2620
2658
  );
2621
2659
  }
2622
- if (index >= 0) {
2623
- customProviders[index] = entry;
2624
- } else {
2625
- customProviders.push(entry);
2626
- }
2660
+ providers[providerKey] = entry;
2627
2661
  const modelConfig = ensureRecord(config, "model");
2628
2662
  const currentDefaultConfig = readModelConfig(modelConfig);
2629
2663
  const currentDefault = currentDefaultConfig.model;
2630
2664
  const currentDefaultReasoningEffort = readProfileReasoningEffort(config);
2631
2665
  if (normalized.setDefault || !currentDefault || currentDefault === originalModelId) {
2632
2666
  if (normalized.setDefault && currentDefault && currentDefault !== normalized.id && currentDefault !== originalModelId) {
2633
- retainModelDefaultAsCustomProvider(customProviders, {
2667
+ retainModelDefaultAsProvider(providers, {
2634
2668
  ...currentDefaultConfig,
2635
2669
  ...currentDefaultReasoningEffort ? { reasoningEffort: currentDefaultReasoningEffort } : {}
2636
2670
  });
@@ -2639,9 +2673,13 @@ async function saveHermesModelConfig(input, profileName = "default", configPath
2639
2673
  const defaultApiKey = normalized.apiKey ?? (!defaultKeyEnv && currentDefault === originalModelId ? currentDefaultConfig.apiKey : void 0);
2640
2674
  writeDefaultModelConfig(modelConfig, {
2641
2675
  ...normalized,
2676
+ provider: providerKey,
2642
2677
  apiKey: defaultApiKey,
2643
2678
  keyEnv: defaultKeyEnv
2644
2679
  });
2680
+ if (input.supportsVision !== void 0) {
2681
+ writeDefaultModelSupportsVision(modelConfig, input.supportsVision);
2682
+ }
2645
2683
  if (shouldUpdateReasoningEffort && normalized.reasoningEffort) {
2646
2684
  writeProfileReasoningEffort(config, normalized.reasoningEffort);
2647
2685
  }
@@ -2653,7 +2691,22 @@ async function saveHermesModelConfig(input, profileName = "default", configPath
2653
2691
  existingRaw
2654
2692
  });
2655
2693
  const listed = await listHermesModelConfigs(profileName, configPath);
2656
- const savedModel = listed.models.find((model) => model.id === normalized.id);
2694
+ const normalizedApiMode = inferApiMode(
2695
+ providerKey,
2696
+ normalized.baseUrl,
2697
+ normalized.apiMode
2698
+ );
2699
+ const savedModel = findManagedModel(listed.models, {
2700
+ id: normalized.id,
2701
+ provider: providerKey,
2702
+ baseUrl: normalized.baseUrl,
2703
+ apiMode: normalizedApiMode
2704
+ }) ?? findManagedModelByVisibleIdentity(listed.models, {
2705
+ id: normalized.id,
2706
+ providerName: normalized.providerName,
2707
+ baseUrl: normalized.baseUrl,
2708
+ apiMode: normalizedApiMode
2709
+ });
2657
2710
  if (!savedModel) {
2658
2711
  throw new Error("saved model is missing from config");
2659
2712
  }
@@ -2661,12 +2714,18 @@ async function saveHermesModelConfig(input, profileName = "default", configPath
2661
2714
  ...listed,
2662
2715
  model: savedModel,
2663
2716
  backupPath,
2664
- requiresGatewayReload: true,
2665
- restartHint: MODEL_CONFIG_RESTART_HINT
2717
+ requiresGatewayReload: false,
2718
+ restartHint: MODEL_CONFIG_APPLIED_HINT
2666
2719
  };
2667
2720
  }
2668
- async function deleteHermesModelConfig(modelId, profileName = "default", configPath = resolveHermesConfigPath(profileName)) {
2669
- const id = modelId.trim();
2721
+ async function deleteHermesModelConfig(input, profileName = "default", configPath = resolveHermesConfigPath(profileName)) {
2722
+ const deleteInput = typeof input === "string" ? { id: input } : {
2723
+ id: input.id,
2724
+ provider: input.provider,
2725
+ baseUrl: input.baseUrl,
2726
+ apiMode: input.apiMode
2727
+ };
2728
+ const id = deleteInput.id.trim();
2670
2729
  if (!id) {
2671
2730
  throw new Error("model id is required");
2672
2731
  }
@@ -2680,7 +2739,7 @@ async function deleteHermesModelConfig(modelId, profileName = "default", configP
2680
2739
  readProfileReasoningEffort(config),
2681
2740
  authBackedProviders
2682
2741
  );
2683
- const deletingModel = findManagedModelById(existingModels, id);
2742
+ const deletingModel = findManagedModel(existingModels, deleteInput) ?? (hasModelIdentitySelector(deleteInput) ? void 0 : findManagedModelById(existingModels, id));
2684
2743
  if (!deletingModel) {
2685
2744
  throw new Error(`model "${id}" is not configured`);
2686
2745
  }
@@ -2694,9 +2753,19 @@ async function deleteHermesModelConfig(modelId, profileName = "default", configP
2694
2753
  "\u81F3\u5C11\u9700\u8981\u4FDD\u7559\u4E00\u4E2A\u6A21\u578B\uFF0C\u907F\u514D Hermes Agent \u6CA1\u6709\u53EF\u7528\u9ED8\u8BA4\u6A21\u578B\u3002"
2695
2754
  );
2696
2755
  }
2697
- const customProviders = ensureCustomProvidersList(config);
2698
- const nextProviders = customProviders.map((entry) => removeModelFromCustomProvider(toRecord(entry), id)).filter((entry) => entry !== null);
2699
- config.custom_providers = nextProviders;
2756
+ const providers = ensureProvidersRecordWithLegacyMigration(config);
2757
+ for (const [key, rawEntry] of Object.entries(providers)) {
2758
+ const entry = providerConfigToLegacyEntry(key, toRecord(rawEntry));
2759
+ if (!entry || !providerEntryMatchesManagedModel(entry, deletingModel)) {
2760
+ continue;
2761
+ }
2762
+ const nextEntry = removeModelFromProvider(toRecord(rawEntry), id);
2763
+ if (nextEntry) {
2764
+ providers[key] = nextEntry;
2765
+ } else {
2766
+ delete providers[key];
2767
+ }
2768
+ }
2700
2769
  const modelConfig = ensureRecord(config, "model");
2701
2770
  const currentDefault = readModelConfig(modelConfig).model;
2702
2771
  if (currentDefault === id) {
@@ -2716,6 +2785,7 @@ async function deleteHermesModelConfig(modelId, profileName = "default", configP
2716
2785
  contextLength: nextDefault.contextLength,
2717
2786
  keyEnv: nextDefault.keyEnv
2718
2787
  });
2788
+ writeDefaultModelSupportsVision(modelConfig, nextDefault.supportsVision);
2719
2789
  if (nextDefault.reasoningEffort) {
2720
2790
  writeProfileReasoningEffort(config, nextDefault.reasoningEffort);
2721
2791
  }
@@ -2729,6 +2799,7 @@ async function deleteHermesModelConfig(modelId, profileName = "default", configP
2729
2799
  delete modelConfig.key_env;
2730
2800
  delete modelConfig.api_mode;
2731
2801
  delete modelConfig.context_length;
2802
+ delete modelConfig.supports_vision;
2732
2803
  }
2733
2804
  }
2734
2805
  const backupPath = await writeHermesConfigDocument({
@@ -2741,22 +2812,26 @@ async function deleteHermesModelConfig(modelId, profileName = "default", configP
2741
2812
  return {
2742
2813
  ...listed,
2743
2814
  backupPath,
2744
- requiresGatewayReload: true,
2745
- restartHint: MODEL_CONFIG_RESTART_HINT
2815
+ requiresGatewayReload: false,
2816
+ restartHint: MODEL_CONFIG_APPLIED_HINT
2746
2817
  };
2747
2818
  }
2748
2819
  async function saveHermesModelDefaults(input, profileName = "default", configPath = resolveHermesConfigPath(profileName)) {
2749
2820
  const taskModelId = input.taskModelId?.trim();
2750
2821
  const compressionModelId = input.compressionModelId?.trim();
2822
+ const imageInputMode = input.imageInputMode ? normalizeImageInputMode(input.imageInputMode) : void 0;
2751
2823
  const reasoningEffort = input.reasoningEffort ? normalizeReasoningEffort(input.reasoningEffort) : void 0;
2752
2824
  if (input.reasoningEffort && !reasoningEffort) {
2753
2825
  throw new Error(
2754
2826
  "reasoningEffort must be none, minimal, low, medium, high or xhigh"
2755
2827
  );
2756
2828
  }
2757
- if (!taskModelId && !compressionModelId && !reasoningEffort) {
2829
+ if (input.imageInputMode && !imageInputMode) {
2830
+ throw new Error("imageInputMode must be auto, native or text");
2831
+ }
2832
+ if (!taskModelId && !compressionModelId && !reasoningEffort && !imageInputMode) {
2758
2833
  throw new Error(
2759
- "taskModelId, compressionModelId or reasoningEffort is required"
2834
+ "taskModelId, compressionModelId, reasoningEffort or imageInputMode is required"
2760
2835
  );
2761
2836
  }
2762
2837
  const { document, config, existingRaw } = await readHermesConfigDocument(configPath);
@@ -2773,17 +2848,18 @@ async function saveHermesModelDefaults(input, profileName = "default", configPat
2773
2848
  const selected = findManagedModel(models, {
2774
2849
  id: taskModelId,
2775
2850
  provider: input.taskModelProvider,
2776
- baseUrl: input.taskModelBaseUrl
2851
+ baseUrl: input.taskModelBaseUrl,
2852
+ apiMode: input.taskModelApiMode
2777
2853
  });
2778
2854
  if (!selected) {
2779
2855
  throw new Error(`model "${taskModelId}" is not configured`);
2780
2856
  }
2781
- const customProviders = ensureCustomProvidersList(config);
2857
+ const providers = ensureProvidersRecordWithLegacyMigration(config);
2782
2858
  const modelConfig = ensureRecord(config, "model");
2783
2859
  const currentDefaultConfig = readModelConfig(modelConfig);
2784
2860
  const currentDefaultReasoningEffort = readProfileReasoningEffort(config);
2785
2861
  if (currentDefaultConfig.model && currentDefaultConfig.model !== selected.id) {
2786
- retainModelDefaultAsCustomProvider(customProviders, {
2862
+ retainModelDefaultAsProvider(providers, {
2787
2863
  ...currentDefaultConfig,
2788
2864
  ...currentDefaultReasoningEffort ? { reasoningEffort: currentDefaultReasoningEffort } : {}
2789
2865
  });
@@ -2796,6 +2872,7 @@ async function saveHermesModelDefaults(input, profileName = "default", configPat
2796
2872
  contextLength: selected.contextLength,
2797
2873
  keyEnv: selected.keyEnv
2798
2874
  });
2875
+ writeDefaultModelSupportsVision(modelConfig, selected.supportsVision);
2799
2876
  if (selected.reasoningEffort) {
2800
2877
  writeProfileReasoningEffort(config, selected.reasoningEffort);
2801
2878
  }
@@ -2803,6 +2880,9 @@ async function saveHermesModelDefaults(input, profileName = "default", configPat
2803
2880
  if (reasoningEffort) {
2804
2881
  writeProfileReasoningEffort(config, reasoningEffort);
2805
2882
  }
2883
+ if (imageInputMode) {
2884
+ writeProfileImageInputMode(config, imageInputMode);
2885
+ }
2806
2886
  if (compressionModelId) {
2807
2887
  const models = readManagedModelConfigs(
2808
2888
  config,
@@ -2814,7 +2894,8 @@ async function saveHermesModelDefaults(input, profileName = "default", configPat
2814
2894
  const selected = findManagedModel(models, {
2815
2895
  id: compressionModelId,
2816
2896
  provider: input.compressionModelProvider,
2817
- baseUrl: input.compressionModelBaseUrl
2897
+ baseUrl: input.compressionModelBaseUrl,
2898
+ apiMode: input.compressionModelApiMode
2818
2899
  });
2819
2900
  if (!selected) {
2820
2901
  throw new Error(`model "${compressionModelId}" is not configured`);
@@ -2992,6 +3073,15 @@ async function saveHermesProfileToolConfig(profileName, toolKey, input, configPa
2992
3073
  "PARALLEL_API_KEY"
2993
3074
  ]);
2994
3075
  break;
3076
+ case "vision":
3077
+ configTouched = applyVisionToolConfig(config, input.values);
3078
+ await writeToolConfigEnvValues(profileName, input.values, [
3079
+ "OPENROUTER_API_KEY",
3080
+ "OPENAI_API_KEY",
3081
+ "ANTHROPIC_API_KEY",
3082
+ "AUXILIARY_VISION_API_KEY"
3083
+ ]);
3084
+ break;
2995
3085
  case "image_gen":
2996
3086
  configTouched = applyImageGenToolConfig(config, input.values);
2997
3087
  await writeToolConfigEnvValues(profileName, input.values, [
@@ -3307,12 +3397,411 @@ async function writeHermesConfigDocument(input) {
3307
3397
  );
3308
3398
  return backupPath;
3309
3399
  }
3400
+ function readCompatibleModelProviderEntries(config) {
3401
+ const entries = [];
3402
+ const seenProviderKeys = /* @__PURE__ */ new Set();
3403
+ const seenEndpointModels = /* @__PURE__ */ new Set();
3404
+ const seenVisibleModels = /* @__PURE__ */ new Set();
3405
+ const append = (entry) => {
3406
+ const provider = readString2(entry.provider_key) ?? readString2(entry.provider) ?? "custom";
3407
+ const providerName = readString2(entry.name) ?? readString2(entry.provider_name) ?? readString2(entry.provider_key) ?? "Custom Provider";
3408
+ const baseUrl = readString2(entry.base_url) ?? readString2(entry.url) ?? readString2(entry.api) ?? "";
3409
+ const apiMode = inferApiMode(provider, baseUrl, readString2(entry.api_mode));
3410
+ const providerKey = provider.trim().toLowerCase();
3411
+ const dedupeProviderKey = providerKey && providerKey !== "custom";
3412
+ const modelIds = readEntryModelIds(entry);
3413
+ const endpointModels = modelIds.map(
3414
+ (modelId) => modelConfigKey(provider, baseUrl, modelId, apiMode)
3415
+ );
3416
+ const visibleModels = modelIds.map(
3417
+ (modelId) => modelVisibleKey({ providerName, baseUrl, id: modelId, apiMode })
3418
+ );
3419
+ if (dedupeProviderKey && seenProviderKeys.has(providerKey)) {
3420
+ return;
3421
+ }
3422
+ if (endpointModels.length > 0 && endpointModels.every((key) => seenEndpointModels.has(key))) {
3423
+ return;
3424
+ }
3425
+ if (visibleModels.length > 0 && visibleModels.every((key) => seenVisibleModels.has(key))) {
3426
+ return;
3427
+ }
3428
+ entries.push(entry);
3429
+ if (dedupeProviderKey) {
3430
+ seenProviderKeys.add(providerKey);
3431
+ }
3432
+ for (const key of endpointModels) {
3433
+ seenEndpointModels.add(key);
3434
+ }
3435
+ for (const key of visibleModels) {
3436
+ seenVisibleModels.add(key);
3437
+ }
3438
+ };
3439
+ const customProviders = Array.isArray(config.custom_providers) ? config.custom_providers : [];
3440
+ for (const rawEntry of customProviders) {
3441
+ append(toRecord(rawEntry));
3442
+ }
3443
+ const providers = toRecord(config.providers);
3444
+ for (const [key, rawEntry] of Object.entries(providers)) {
3445
+ const entry = providerConfigToLegacyEntry(key, toRecord(rawEntry));
3446
+ if (entry) {
3447
+ append(entry);
3448
+ }
3449
+ }
3450
+ return entries;
3451
+ }
3452
+ function providerConfigToLegacyEntry(providerKey, entry) {
3453
+ const baseUrl = readString2(entry.base_url) ?? readString2(entry.url) ?? readString2(entry.api);
3454
+ if (!baseUrl) {
3455
+ return null;
3456
+ }
3457
+ const normalized = {
3458
+ ...entry,
3459
+ name: readString2(entry.name) ?? providerKey,
3460
+ provider_key: providerKey,
3461
+ base_url: baseUrl
3462
+ };
3463
+ const apiMode = readString2(entry.api_mode) ?? readString2(entry.transport);
3464
+ if (apiMode) {
3465
+ normalized.api_mode = apiMode;
3466
+ }
3467
+ const defaultModel = readString2(entry.model) ?? readString2(entry.default_model);
3468
+ if (defaultModel) {
3469
+ normalized.model = defaultModel;
3470
+ }
3471
+ return normalized;
3472
+ }
3473
+ function ensureProvidersRecordWithLegacyMigration(config) {
3474
+ const providers = ensureRecord(config, "providers");
3475
+ const customProviders = Array.isArray(config.custom_providers) ? config.custom_providers : [];
3476
+ for (const rawEntry of customProviders) {
3477
+ const entry = toRecord(rawEntry);
3478
+ const baseUrl = readString2(entry.base_url) ?? readString2(entry.url) ?? readString2(entry.api);
3479
+ if (!baseUrl) {
3480
+ continue;
3481
+ }
3482
+ const providerKey = findProviderConfigKeyForLegacyMigration(providers, entry, baseUrl) ?? buildProviderConfigKey(entry, providers);
3483
+ const nextConfig = legacyEntryToProviderConfig(entry, baseUrl);
3484
+ providers[providerKey] = providers[providerKey] ? mergeProviderConfig(toRecord(providers[providerKey]), nextConfig) : nextConfig;
3485
+ }
3486
+ delete config.custom_providers;
3487
+ const providerAliases = mergeDuplicateProviderConfigs(providers);
3488
+ rewriteProviderReferences(config, providerAliases);
3489
+ return providers;
3490
+ }
3491
+ function mergeDuplicateProviderConfigs(providers) {
3492
+ const groupKeys = /* @__PURE__ */ new Map();
3493
+ const aliases = /* @__PURE__ */ new Map();
3494
+ for (const [key, rawEntry] of Object.entries(providers)) {
3495
+ const entry = toRecord(rawEntry);
3496
+ const legacyEntry = providerConfigToLegacyEntry(key, entry);
3497
+ if (!legacyEntry) {
3498
+ continue;
3499
+ }
3500
+ const groupKey = providerConfigGroupKey(legacyEntry);
3501
+ const existingKey = groupKeys.get(groupKey);
3502
+ if (!existingKey) {
3503
+ groupKeys.set(groupKey, key);
3504
+ providers[key] = entry;
3505
+ continue;
3506
+ }
3507
+ providers[existingKey] = mergeProviderConfig(
3508
+ toRecord(providers[existingKey]),
3509
+ entry
3510
+ );
3511
+ delete providers[key];
3512
+ aliases.set(key, existingKey);
3513
+ }
3514
+ return aliases;
3515
+ }
3516
+ function rewriteProviderReferences(config, aliases) {
3517
+ if (aliases.size === 0) {
3518
+ return;
3519
+ }
3520
+ const rewriteProvider = (record) => {
3521
+ const provider = readString2(record.provider);
3522
+ if (provider && aliases.has(provider)) {
3523
+ record.provider = aliases.get(provider);
3524
+ }
3525
+ };
3526
+ rewriteProvider(toRecord(config.model));
3527
+ const auxiliary = toRecord(config.auxiliary);
3528
+ rewriteProvider(toRecord(auxiliary.compression));
3529
+ rewriteProvider(toRecord(config.fallback_model));
3530
+ if (Array.isArray(config.fallback_model)) {
3531
+ for (const entry of config.fallback_model) {
3532
+ rewriteProvider(toRecord(entry));
3533
+ }
3534
+ }
3535
+ if (Array.isArray(config.fallback_providers)) {
3536
+ for (const entry of config.fallback_providers) {
3537
+ rewriteProvider(toRecord(entry));
3538
+ }
3539
+ }
3540
+ }
3541
+ function findProviderConfigKeyForLegacyMigration(providers, entry, baseUrl) {
3542
+ const provider = readString2(entry.provider_key) ?? readString2(entry.provider) ?? "custom";
3543
+ const apiMode = readString2(entry.api_mode) ?? readString2(entry.transport);
3544
+ if (provider !== "custom" && Object.prototype.hasOwnProperty.call(providers, provider)) {
3545
+ const existingEntry = providerConfigToLegacyEntry(
3546
+ provider,
3547
+ toRecord(providers[provider])
3548
+ );
3549
+ if (existingEntry) {
3550
+ const existingBaseUrl = readString2(existingEntry.base_url) ?? readString2(existingEntry.url) ?? readString2(existingEntry.api) ?? "";
3551
+ const existingApiMode = inferApiMode(
3552
+ provider,
3553
+ existingBaseUrl,
3554
+ readString2(existingEntry.api_mode)
3555
+ );
3556
+ if (normalizeBaseUrl(existingBaseUrl) === normalizeBaseUrl(baseUrl) && existingApiMode === inferApiMode(provider, baseUrl, apiMode) && providerCredentialIdentity(existingEntry) === providerCredentialIdentity(entry)) {
3557
+ return provider;
3558
+ }
3559
+ }
3560
+ }
3561
+ return findProviderConfigKeyByEndpoint(providers, {
3562
+ provider,
3563
+ baseUrl,
3564
+ apiMode
3565
+ }) ?? findProviderConfigKeyByVisibleEndpoint(providers, {
3566
+ providerName: readString2(entry.name) ?? readString2(entry.provider_name) ?? readString2(entry.provider_key) ?? provider,
3567
+ baseUrl,
3568
+ apiMode,
3569
+ keyEnv: readString2(entry.key_env) ?? parseEnvReference(readString2(entry.api_key))
3570
+ });
3571
+ }
3572
+ function mergeProviderConfig(existing, next) {
3573
+ const merged = { ...existing };
3574
+ for (const [key, value] of Object.entries(next)) {
3575
+ if (key === "models") {
3576
+ continue;
3577
+ }
3578
+ if (merged[key] === void 0 || merged[key] === null) {
3579
+ merged[key] = value;
3580
+ }
3581
+ }
3582
+ const models = mergeEntryModelsMap(existing, next);
3583
+ if (Object.keys(models).length > 0) {
3584
+ merged.models = models;
3585
+ }
3586
+ const defaultModel = readString2(existing.default_model) ?? readString2(existing.model) ?? readString2(next.default_model) ?? readString2(next.model);
3587
+ if (defaultModel) {
3588
+ merged.default_model = defaultModel;
3589
+ delete merged.model;
3590
+ }
3591
+ return merged;
3592
+ }
3593
+ function mergeEntryModelsMap(existing, next) {
3594
+ const merged = { ...normalizeEntryModelsMap(existing) };
3595
+ for (const [id, rawNextModelConfig] of Object.entries(
3596
+ normalizeEntryModelsMap(next)
3597
+ )) {
3598
+ const existingModelConfig = toRecord(merged[id]);
3599
+ const nextModelConfig = toRecord(rawNextModelConfig);
3600
+ merged[id] = { ...nextModelConfig, ...existingModelConfig };
3601
+ }
3602
+ return merged;
3603
+ }
3604
+ function legacyEntryToProviderConfig(entry, baseUrl) {
3605
+ const next = {
3606
+ name: readString2(entry.name) ?? readString2(entry.provider_name) ?? readString2(entry.provider_key) ?? "Custom Provider",
3607
+ api: baseUrl
3608
+ };
3609
+ const apiKey = readString2(entry.api_key);
3610
+ const keyEnv = readString2(entry.key_env) ?? parseEnvReference(apiKey);
3611
+ if (keyEnv) {
3612
+ next.key_env = keyEnv;
3613
+ } else if (apiKey) {
3614
+ next.api_key = apiKey;
3615
+ }
3616
+ const transport = readString2(entry.api_mode) ?? readString2(entry.transport);
3617
+ if (transport) {
3618
+ next.transport = transport;
3619
+ }
3620
+ const defaultModel = readString2(entry.model) ?? readString2(entry.default_model);
3621
+ if (defaultModel) {
3622
+ next.default_model = defaultModel;
3623
+ }
3624
+ const models = normalizeEntryModelsMap(entry);
3625
+ if (Object.keys(models).length > 0) {
3626
+ next.models = models;
3627
+ }
3628
+ const contextLength = readPositiveInteger(entry.context_length);
3629
+ if (contextLength) {
3630
+ next.context_length = contextLength;
3631
+ }
3632
+ const reasoningEffort = normalizeReasoningEffort(
3633
+ entry.reasoning_effort ?? entry.reasoningEffort
3634
+ );
3635
+ if (reasoningEffort) {
3636
+ next.reasoning_effort = reasoningEffort;
3637
+ }
3638
+ const supportsVision = readCapabilityBoolean(
3639
+ entry.supports_vision ?? entry.supportsVision
3640
+ );
3641
+ if (supportsVision !== void 0) {
3642
+ next.supports_vision = supportsVision;
3643
+ }
3644
+ return next;
3645
+ }
3646
+ function normalizeEntryModelsMap(entry) {
3647
+ const models = entry.models;
3648
+ if (typeof models === "object" && models !== null && !Array.isArray(models)) {
3649
+ return { ...models };
3650
+ }
3651
+ return Object.fromEntries(readEntryModelIds(entry).map((id) => [id, {}]));
3652
+ }
3653
+ function buildProviderConfigKey(entry, existing) {
3654
+ const rawProviderKey = readString2(entry.provider_key);
3655
+ const rawName = readString2(entry.name) ?? readString2(entry.provider_name);
3656
+ const rawBaseUrl = readString2(entry.base_url) ?? readString2(entry.url) ?? readString2(entry.api);
3657
+ const base = providerConfigKeyBase(
3658
+ rawProviderKey && rawProviderKey !== "custom" ? rawProviderKey : void 0,
3659
+ rawName,
3660
+ rawBaseUrl
3661
+ );
3662
+ return uniqueProviderConfigKey(base, existing);
3663
+ }
3664
+ function providerConfigKeyBase(provider, providerName, baseUrl) {
3665
+ const fromProvider = provider?.trim();
3666
+ const fromName = providerName?.trim();
3667
+ const host = baseUrl ? UriHostname.safe(baseUrl) : "";
3668
+ return slugifyProviderKey(fromProvider || fromName || host || "provider");
3669
+ }
3670
+ function uniqueProviderConfigKey(base, existing) {
3671
+ let key = base || "provider";
3672
+ let index = 2;
3673
+ while (Object.prototype.hasOwnProperty.call(existing, key)) {
3674
+ key = `${base}-${index}`;
3675
+ index += 1;
3676
+ }
3677
+ return key;
3678
+ }
3679
+ function slugifyProviderKey(value) {
3680
+ const slug = value.trim().toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-+|-+$/gu, "");
3681
+ return slug || "provider";
3682
+ }
3683
+ function findProviderConfigKeyByModelIdentity(providers, input) {
3684
+ const provider = input.provider?.trim();
3685
+ const baseUrl = input.baseUrl?.trim();
3686
+ const apiMode = input.apiMode?.trim();
3687
+ for (const [key, rawEntry] of Object.entries(providers)) {
3688
+ const entry = providerConfigToLegacyEntry(key, toRecord(rawEntry));
3689
+ if (!entry || !readEntryModelIds(entry).includes(input.id)) {
3690
+ continue;
3691
+ }
3692
+ const entryProvider = readString2(entry.provider_key) ?? readString2(entry.provider) ?? "custom";
3693
+ const entryBaseUrl = readString2(entry.base_url) ?? readString2(entry.url) ?? readString2(entry.api) ?? "";
3694
+ const entryApiMode = inferApiMode(
3695
+ entryProvider,
3696
+ entryBaseUrl,
3697
+ readString2(entry.api_mode)
3698
+ );
3699
+ if (provider && provider !== key && provider !== entryProvider) {
3700
+ continue;
3701
+ }
3702
+ if (baseUrl !== void 0 && normalizeBaseUrl(baseUrl) !== normalizeBaseUrl(entryBaseUrl)) {
3703
+ continue;
3704
+ }
3705
+ if (apiMode && apiMode !== entryApiMode) {
3706
+ continue;
3707
+ }
3708
+ return key;
3709
+ }
3710
+ return null;
3711
+ }
3712
+ function findProviderConfigKeyByVisibleEndpoint(providers, endpoint) {
3713
+ const targetProviderName = endpoint.providerName?.trim().toLowerCase();
3714
+ if (!targetProviderName) {
3715
+ return null;
3716
+ }
3717
+ const targetBaseUrl = normalizeBaseUrl(endpoint.baseUrl);
3718
+ const targetApiMode = endpoint.apiMode?.trim();
3719
+ const targetKeyEnv = endpoint.keyEnv?.trim().toLowerCase();
3720
+ for (const [key, rawEntry] of Object.entries(providers)) {
3721
+ const entry = providerConfigToLegacyEntry(key, toRecord(rawEntry));
3722
+ if (!entry) {
3723
+ continue;
3724
+ }
3725
+ const provider = readString2(entry.provider_key) ?? readString2(entry.provider) ?? "custom";
3726
+ const providerName = readString2(entry.name) ?? readString2(entry.provider_name) ?? readString2(entry.provider_key) ?? key;
3727
+ const baseUrl = readString2(entry.base_url) ?? readString2(entry.url) ?? readString2(entry.api) ?? "";
3728
+ const apiMode = inferApiMode(provider, baseUrl, readString2(entry.api_mode));
3729
+ const keyEnv = (readString2(entry.key_env) ?? parseEnvReference(readString2(entry.api_key)))?.toLowerCase();
3730
+ if (providerName.trim().toLowerCase() !== targetProviderName) {
3731
+ continue;
3732
+ }
3733
+ if (normalizeBaseUrl(baseUrl) !== targetBaseUrl) {
3734
+ continue;
3735
+ }
3736
+ if (targetApiMode && targetApiMode !== apiMode) {
3737
+ continue;
3738
+ }
3739
+ if (targetKeyEnv && targetKeyEnv !== keyEnv) {
3740
+ continue;
3741
+ }
3742
+ return key;
3743
+ }
3744
+ return null;
3745
+ }
3746
+ function findProviderConfigKeyByEndpoint(providers, endpoint) {
3747
+ const targetProvider = endpoint.provider.trim().toLowerCase();
3748
+ const targetBaseUrl = normalizeBaseUrl(endpoint.baseUrl);
3749
+ const targetApiMode = endpoint.apiMode?.trim();
3750
+ for (const [key, rawEntry] of Object.entries(providers)) {
3751
+ const entry = providerConfigToLegacyEntry(key, toRecord(rawEntry));
3752
+ if (!entry) {
3753
+ continue;
3754
+ }
3755
+ const provider = readString2(entry.provider_key) ?? readString2(entry.provider) ?? "custom";
3756
+ const baseUrl = readString2(entry.base_url) ?? readString2(entry.url) ?? readString2(entry.api) ?? "";
3757
+ const apiMode = inferApiMode(provider, baseUrl, readString2(entry.api_mode));
3758
+ if (targetApiMode && targetApiMode !== apiMode) {
3759
+ continue;
3760
+ }
3761
+ if ((key.toLowerCase() === targetProvider || provider.trim().toLowerCase() === targetProvider) && normalizeBaseUrl(baseUrl) === targetBaseUrl) {
3762
+ return key;
3763
+ }
3764
+ }
3765
+ return null;
3766
+ }
3767
+ function writeProviderEndpointConfig(entry, input, keyEnv) {
3768
+ entry.name = input.providerName;
3769
+ entry.api = input.baseUrl;
3770
+ delete entry.base_url;
3771
+ delete entry.url;
3772
+ entry.default_model = input.id;
3773
+ delete entry.model;
3774
+ entry.transport = inferApiMode(input.provider, input.baseUrl, input.apiMode);
3775
+ delete entry.api_mode;
3776
+ if (input.contextLength) {
3777
+ entry.context_length = input.contextLength;
3778
+ } else {
3779
+ delete entry.context_length;
3780
+ delete entry.contextLength;
3781
+ }
3782
+ if (keyEnv) {
3783
+ entry.key_env = keyEnv;
3784
+ delete entry.api_key;
3785
+ } else {
3786
+ delete entry.key_env;
3787
+ }
3788
+ }
3789
+ var UriHostname = class {
3790
+ static safe(value) {
3791
+ try {
3792
+ return new URL(value).hostname.replace(/\./gu, "-");
3793
+ } catch {
3794
+ return "";
3795
+ }
3796
+ }
3797
+ };
3310
3798
  function readManagedModelConfigs(config, env, defaultModel, defaultReasoningEffort, authBackedProviders = EMPTY_AUTH_BACKED_PROVIDER_STATE) {
3311
3799
  const models = [];
3312
3800
  const seen = /* @__PURE__ */ new Set();
3801
+ const seenVisibleModels = /* @__PURE__ */ new Map();
3313
3802
  const seenEndpoint = /* @__PURE__ */ new Map();
3314
3803
  const modelConfig = readModelConfig(config.model);
3315
- const customProviders = Array.isArray(config.custom_providers) ? config.custom_providers : [];
3804
+ const customProviders = readCompatibleModelProviderEntries(config);
3316
3805
  for (const rawEntry of customProviders) {
3317
3806
  const entry = toRecord(rawEntry);
3318
3807
  const providerName = readString2(entry.name) ?? readString2(entry.provider_name) ?? readString2(entry.provider_key) ?? "Custom Provider";
@@ -3329,21 +3818,30 @@ function readManagedModelConfigs(config, env, defaultModel, defaultReasoningEffo
3329
3818
  authBackedProviders
3330
3819
  );
3331
3820
  for (const id of readEntryModelIds(entry)) {
3332
- const key = modelConfigKey(provider, baseUrl, id);
3821
+ const key = modelConfigKey(provider, baseUrl, id, apiMode);
3333
3822
  if (seen.has(key)) {
3334
3823
  continue;
3335
3824
  }
3336
3825
  seen.add(key);
3337
- const endpointKey = modelEndpointKey(baseUrl, id);
3826
+ const endpointKey = modelEndpointKey(baseUrl, id, apiMode);
3338
3827
  const modelContextLength = readEntryModelContextLength(entry, id) ?? contextLength;
3339
3828
  const reasoningEffort = readEntryModelReasoningEffort(entry, id);
3829
+ const supportsVision = readEntryModelSupportsVision(entry, id);
3340
3830
  const reasoningMetadata = modelReasoningMetadata({
3341
3831
  provider,
3342
3832
  baseUrl,
3343
3833
  modelId: id,
3344
3834
  apiMode
3345
3835
  });
3346
- models.push({
3836
+ const isDefault = matchesDefaultModelConfig({
3837
+ id,
3838
+ provider,
3839
+ baseUrl,
3840
+ apiMode,
3841
+ defaultModel,
3842
+ modelConfig
3843
+ });
3844
+ const index = upsertManagedModelConfig(models, seenVisibleModels, {
3347
3845
  id,
3348
3846
  provider,
3349
3847
  providerName,
@@ -3354,32 +3852,40 @@ function readManagedModelConfigs(config, env, defaultModel, defaultReasoningEffo
3354
3852
  ...keyEnv ? { keyEnv } : {},
3355
3853
  credentialConfigured: credentialMetadata.credentialState === "configured",
3356
3854
  ...credentialMetadata,
3357
- isDefault: id === defaultModel,
3855
+ isDefault,
3358
3856
  ...reasoningEffort ? { reasoningEffort } : {},
3359
- ...reasoningMetadata
3857
+ ...reasoningMetadata,
3858
+ supportsVision
3360
3859
  });
3361
- seenEndpoint.set(endpointKey, models.length - 1);
3860
+ seenEndpoint.set(endpointKey, index);
3362
3861
  }
3363
3862
  }
3364
3863
  if (defaultModel) {
3365
3864
  const provider = modelConfig.provider ?? "default";
3366
3865
  const authDefinition = AUTH_BACKED_MODEL_PROVIDER_BY_KEY.get(provider);
3367
3866
  const baseUrl = modelConfig.baseUrl ?? authDefinition?.baseUrl ?? "";
3867
+ const defaultApiMode = inferApiMode(provider, baseUrl, modelConfig.apiMode);
3368
3868
  const endpointMatchIndex = seenEndpoint.get(
3369
- modelEndpointKey(baseUrl, defaultModel)
3869
+ modelEndpointKey(baseUrl, defaultModel, defaultApiMode)
3370
3870
  );
3371
3871
  if (endpointMatchIndex !== void 0) {
3372
3872
  const existing = models[endpointMatchIndex];
3373
3873
  models[endpointMatchIndex] = {
3374
3874
  ...existing,
3375
- apiMode: modelConfig.apiMode ? inferApiMode(provider, baseUrl, modelConfig.apiMode) : existing.apiMode,
3875
+ apiMode: defaultApiMode,
3376
3876
  contextLength: existing.contextLength ?? modelConfig.contextLength,
3377
3877
  keyEnv: existing.keyEnv ?? modelConfig.keyEnv,
3378
3878
  isDefault: true,
3379
- reasoningEffort: existing.reasoningEffort ?? defaultReasoningEffort ?? void 0
3879
+ reasoningEffort: existing.reasoningEffort ?? defaultReasoningEffort ?? void 0,
3880
+ supportsVision: existing.supportsVision ?? modelConfig.supportsVision ?? null
3380
3881
  };
3381
3882
  } else {
3382
- const key = modelConfigKey(provider, baseUrl, defaultModel);
3883
+ const key = modelConfigKey(
3884
+ provider,
3885
+ baseUrl,
3886
+ defaultModel,
3887
+ defaultApiMode
3888
+ );
3383
3889
  if (!seen.has(key)) {
3384
3890
  const credentialMetadata = readModelCredentialMetadata(
3385
3891
  modelConfig,
@@ -3388,8 +3894,9 @@ function readManagedModelConfigs(config, env, defaultModel, defaultReasoningEffo
3388
3894
  authBackedProviders
3389
3895
  );
3390
3896
  const reasoningEffort = modelConfig.reasoningEffort ?? defaultReasoningEffort;
3391
- const apiMode = inferApiMode(provider, baseUrl, modelConfig.apiMode);
3392
- models.unshift({
3897
+ const supportsVision = modelConfig.supportsVision ?? null;
3898
+ const apiMode = defaultApiMode;
3899
+ const index = upsertManagedModelConfig(models, seenVisibleModels, {
3393
3900
  id: defaultModel,
3394
3901
  provider,
3395
3902
  providerName: authDefinition?.providerName ?? provider,
@@ -3407,18 +3914,21 @@ function readManagedModelConfigs(config, env, defaultModel, defaultReasoningEffo
3407
3914
  baseUrl,
3408
3915
  modelId: defaultModel,
3409
3916
  apiMode
3410
- })
3917
+ }),
3918
+ supportsVision
3411
3919
  });
3412
3920
  seen.add(key);
3413
- seenEndpoint.set(modelEndpointKey(baseUrl, defaultModel), 0);
3921
+ seenEndpoint.set(modelEndpointKey(baseUrl, defaultModel, apiMode), index);
3414
3922
  }
3415
3923
  }
3416
3924
  }
3417
3925
  appendAuthBackedModelConfigs({
3418
3926
  models,
3419
3927
  seen,
3928
+ seenVisibleModels,
3420
3929
  seenEndpoint,
3421
3930
  defaultModel,
3931
+ modelConfig,
3422
3932
  defaultProvider: modelConfig.provider ?? null,
3423
3933
  defaultReasoningEffort,
3424
3934
  authBackedProviders
@@ -3437,46 +3947,132 @@ function appendAuthBackedModelConfigs(input) {
3437
3947
  definition.provider
3438
3948
  ) ? "external_auth" : "auth_store";
3439
3949
  for (const id of definition.modelIds) {
3440
- const key = modelConfigKey(definition.provider, baseUrl, id);
3441
- if (input.seen.has(key)) {
3442
- continue;
3443
- }
3444
- input.seen.add(key);
3445
3950
  const apiMode = inferApiMode(
3446
3951
  definition.provider,
3447
3952
  baseUrl,
3448
3953
  definition.apiMode
3449
3954
  );
3450
- const isDefault = id === input.defaultModel && definition.provider === input.defaultProvider;
3451
- input.models.push({
3955
+ const key = modelConfigKey(definition.provider, baseUrl, id, apiMode);
3956
+ if (input.seen.has(key)) {
3957
+ continue;
3958
+ }
3959
+ input.seen.add(key);
3960
+ const isDefault = matchesDefaultModelConfig({
3452
3961
  id,
3453
3962
  provider: definition.provider,
3454
- providerName: definition.providerName,
3455
- source: "auth_store",
3456
3963
  baseUrl,
3457
3964
  apiMode,
3458
- credentialConfigured: true,
3459
- credentialState: "configured",
3460
- credentialSource,
3461
- authType: definition.authType,
3462
- editable: false,
3463
- isReadOnly: true,
3464
- isDefault,
3465
- ...isDefault && input.defaultReasoningEffort ? { reasoningEffort: input.defaultReasoningEffort } : {},
3466
- ...modelReasoningMetadata({
3965
+ defaultModel: input.defaultModel,
3966
+ modelConfig: input.modelConfig
3967
+ });
3968
+ const index = upsertManagedModelConfig(
3969
+ input.models,
3970
+ input.seenVisibleModels,
3971
+ {
3972
+ id,
3467
3973
  provider: definition.provider,
3974
+ providerName: definition.providerName,
3975
+ source: "auth_store",
3468
3976
  baseUrl,
3469
- modelId: id,
3470
- apiMode
3471
- })
3472
- });
3473
- input.seenEndpoint.set(
3474
- modelEndpointKey(baseUrl, id),
3475
- input.models.length - 1
3977
+ apiMode,
3978
+ credentialConfigured: true,
3979
+ credentialState: "configured",
3980
+ credentialSource,
3981
+ authType: definition.authType,
3982
+ editable: false,
3983
+ isReadOnly: true,
3984
+ isDefault,
3985
+ ...isDefault && input.defaultReasoningEffort ? { reasoningEffort: input.defaultReasoningEffort } : {},
3986
+ ...modelReasoningMetadata({
3987
+ provider: definition.provider,
3988
+ baseUrl,
3989
+ modelId: id,
3990
+ apiMode
3991
+ }),
3992
+ supportsVision: null
3993
+ }
3476
3994
  );
3995
+ input.seenEndpoint.set(modelEndpointKey(baseUrl, id, apiMode), index);
3477
3996
  }
3478
3997
  }
3479
3998
  }
3999
+ function modelVisibleKey(model) {
4000
+ return [
4001
+ model.providerName.trim(),
4002
+ normalizeBaseUrl(model.baseUrl),
4003
+ model.id.trim(),
4004
+ model.apiMode.trim()
4005
+ ].map((value) => value.toLowerCase()).join("\n");
4006
+ }
4007
+ function upsertManagedModelConfig(models, seenVisibleModels, next) {
4008
+ const key = modelVisibleKey(next);
4009
+ const existingIndex = seenVisibleModels.get(key);
4010
+ if (existingIndex === void 0) {
4011
+ models.push(next);
4012
+ const index = models.length - 1;
4013
+ seenVisibleModels.set(key, index);
4014
+ return index;
4015
+ }
4016
+ models[existingIndex] = mergeManagedModelConfig(
4017
+ models[existingIndex],
4018
+ next
4019
+ );
4020
+ return existingIndex;
4021
+ }
4022
+ function mergeManagedModelConfig(existing, next) {
4023
+ const primary = managedModelPreferenceScore(next) > managedModelPreferenceScore(existing) ? next : existing;
4024
+ const secondary = primary === next ? existing : next;
4025
+ const credentialState = primary.credentialState === "configured" || secondary.credentialState !== "configured" ? primary.credentialState : secondary.credentialState;
4026
+ return {
4027
+ ...primary,
4028
+ contextLength: primary.contextLength ?? secondary.contextLength,
4029
+ keyEnv: primary.keyEnv ?? secondary.keyEnv,
4030
+ credentialConfigured: primary.credentialConfigured || secondary.credentialConfigured,
4031
+ credentialState,
4032
+ credentialSource: primary.credentialState === "configured" || secondary.credentialState !== "configured" ? primary.credentialSource : secondary.credentialSource,
4033
+ isDefault: primary.isDefault || secondary.isDefault,
4034
+ reasoningEffort: primary.reasoningEffort ?? secondary.reasoningEffort,
4035
+ reasoningSupport: primary.reasoningSupport === "unknown" ? secondary.reasoningSupport : primary.reasoningSupport,
4036
+ supportedReasoningEfforts: primary.supportedReasoningEfforts ?? secondary.supportedReasoningEfforts,
4037
+ supportsVision: primary.supportsVision ?? secondary.supportsVision
4038
+ };
4039
+ }
4040
+ function managedModelPreferenceScore(model) {
4041
+ return (model.isDefault ? 1e3 : 0) + (model.credentialState === "configured" ? 200 : 0) + (model.credentialState === "missing" ? 50 : 0) + (model.source === "auth_store" ? 100 : 0) + (model.credentialSource !== "unknown" ? 40 : 0) + (model.isReadOnly ? 10 : 0);
4042
+ }
4043
+ function matchesDefaultModelConfig(input) {
4044
+ if (!input.defaultModel || input.id !== input.defaultModel) {
4045
+ return false;
4046
+ }
4047
+ const defaultApiMode = input.modelConfig.apiMode?.trim();
4048
+ if (defaultApiMode && input.apiMode !== defaultApiMode) {
4049
+ return false;
4050
+ }
4051
+ const defaultBaseUrl = input.modelConfig.baseUrl;
4052
+ if (defaultBaseUrl?.trim()) {
4053
+ return normalizeBaseUrl(input.baseUrl) === normalizeBaseUrl(defaultBaseUrl);
4054
+ }
4055
+ const defaultProvider = input.modelConfig.provider?.trim();
4056
+ if (defaultProvider) {
4057
+ return input.provider === defaultProvider;
4058
+ }
4059
+ return true;
4060
+ }
4061
+ function providerEntryMatchesManagedModel(entry, model) {
4062
+ const providerName = readString2(entry.name) ?? readString2(entry.provider_name) ?? readString2(entry.provider_key) ?? "Custom Provider";
4063
+ const baseUrl = readString2(entry.base_url) ?? readString2(entry.url) ?? readString2(entry.api) ?? "";
4064
+ const apiMode = inferApiMode(
4065
+ readString2(entry.provider_key) ?? readString2(entry.provider) ?? "custom",
4066
+ baseUrl,
4067
+ readString2(entry.api_mode)
4068
+ );
4069
+ return modelVisibleKey({
4070
+ providerName,
4071
+ baseUrl,
4072
+ id: model.id,
4073
+ apiMode
4074
+ }) === modelVisibleKey(model);
4075
+ }
3480
4076
  function readEntryCredentialMetadata(entry, env, provider, authBackedProviders) {
3481
4077
  const apiKey = readString2(entry.api_key);
3482
4078
  const keyEnv = readString2(entry.key_env) ?? parseEnvReference(apiKey);
@@ -3673,6 +4269,7 @@ function findManagedModelById(models, id) {
3673
4269
  function findManagedModel(models, input) {
3674
4270
  const provider = input.provider?.trim();
3675
4271
  const baseUrl = input.baseUrl?.trim();
4272
+ const apiMode = input.apiMode?.trim();
3676
4273
  return models.find((model) => {
3677
4274
  if (model.id !== input.id) {
3678
4275
  return false;
@@ -3683,9 +4280,21 @@ function findManagedModel(models, input) {
3683
4280
  if (baseUrl !== void 0 && normalizeBaseUrl(model.baseUrl) !== normalizeBaseUrl(baseUrl)) {
3684
4281
  return false;
3685
4282
  }
4283
+ if (apiMode && model.apiMode !== apiMode) {
4284
+ return false;
4285
+ }
3686
4286
  return true;
3687
4287
  });
3688
4288
  }
4289
+ function findManagedModelByVisibleIdentity(models, input) {
4290
+ const key = modelVisibleKey(input);
4291
+ return models.find((model) => modelVisibleKey(model) === key);
4292
+ }
4293
+ function hasModelIdentitySelector(input) {
4294
+ return Boolean(
4295
+ input.provider?.trim() || input.baseUrl?.trim() || input.apiMode?.trim()
4296
+ );
4297
+ }
3689
4298
  function writeAuxiliaryCompressionModelConfig(config, model, env) {
3690
4299
  const auxiliary = ensureRecord(config, "auxiliary");
3691
4300
  const compression = ensureRecord(auxiliary, "compression");
@@ -3722,8 +4331,7 @@ function resolveManagedModelApiKey(config, env, model) {
3722
4331
  return defaultKey;
3723
4332
  }
3724
4333
  }
3725
- const customProviders = Array.isArray(config.custom_providers) ? config.custom_providers : [];
3726
- for (const rawEntry of customProviders) {
4334
+ for (const rawEntry of readCompatibleModelProviderEntries(config)) {
3727
4335
  const entry = toRecord(rawEntry);
3728
4336
  const provider = readString2(entry.provider_key) ?? readString2(entry.provider) ?? "custom";
3729
4337
  const baseUrl = readString2(entry.base_url) ?? readString2(entry.url) ?? readString2(entry.api) ?? "";
@@ -3774,53 +4382,59 @@ function normalizeModelConfigInput(input) {
3774
4382
  ...reasoningEffort ? { reasoningEffort } : {}
3775
4383
  };
3776
4384
  }
3777
- function ensureCustomProvidersList(config) {
3778
- const existing = config.custom_providers;
3779
- if (Array.isArray(existing)) {
3780
- return existing;
3781
- }
3782
- const next = [];
3783
- config.custom_providers = next;
3784
- return next;
3785
- }
3786
- function retainModelDefaultAsCustomProvider(entries, model) {
4385
+ function retainModelDefaultAsProvider(providers, model) {
3787
4386
  const id = model.model?.trim();
3788
4387
  if (!id) {
3789
4388
  return;
3790
4389
  }
3791
4390
  const provider = model.provider?.trim() || "default";
3792
4391
  const baseUrl = model.baseUrl?.trim() || "";
3793
- const key = modelConfigKey(provider, baseUrl, id);
3794
- const exists = entries.some((entry2) => {
3795
- const record = toRecord(entry2);
3796
- const entryProvider = readString2(record.provider_key) ?? readString2(record.provider) ?? "custom";
3797
- const entryBaseUrl = readString2(record.base_url) ?? readString2(record.url) ?? readString2(record.api) ?? "";
3798
- return readEntryModelIds(record).some(
3799
- (modelId) => modelConfigKey(entryProvider, entryBaseUrl, modelId) === key
3800
- );
3801
- });
4392
+ const apiMode = inferApiMode(provider, baseUrl, model.apiMode);
4393
+ const key = modelConfigKey(provider, baseUrl, id, apiMode);
4394
+ const exists = readCompatibleModelProviderEntries({ providers }).some(
4395
+ (entry2) => {
4396
+ const record = toRecord(entry2);
4397
+ const entryProvider = readString2(record.provider_key) ?? readString2(record.provider) ?? "custom";
4398
+ const entryBaseUrl = readString2(record.base_url) ?? readString2(record.url) ?? readString2(record.api) ?? "";
4399
+ const entryApiMode = inferApiMode(
4400
+ entryProvider,
4401
+ entryBaseUrl,
4402
+ readString2(record.api_mode)
4403
+ );
4404
+ return readEntryModelIds(record).some(
4405
+ (modelId) => modelConfigKey(entryProvider, entryBaseUrl, modelId, entryApiMode) === key
4406
+ );
4407
+ }
4408
+ );
3802
4409
  if (exists) {
3803
4410
  return;
3804
4411
  }
4412
+ const providerKey = uniqueProviderConfigKey(
4413
+ providerConfigKeyBase(provider, provider, baseUrl),
4414
+ providers
4415
+ );
3805
4416
  const entry = {
3806
4417
  name: provider,
3807
- provider_key: provider,
3808
- base_url: baseUrl,
3809
- api_mode: inferApiMode(provider, baseUrl, model.apiMode),
3810
- model: id
4418
+ api: baseUrl,
4419
+ transport: inferApiMode(provider, baseUrl, model.apiMode),
4420
+ default_model: id,
4421
+ models: { [id]: {} }
3811
4422
  };
3812
4423
  if (model.contextLength) {
3813
4424
  entry.context_length = model.contextLength;
3814
4425
  }
3815
4426
  if (model.reasoningEffort) {
3816
- entry.reasoning_effort = model.reasoningEffort;
4427
+ writeEntryModelReasoningEffort(entry, id, model.reasoningEffort);
4428
+ }
4429
+ if (model.supportsVision !== void 0) {
4430
+ writeEntryModelSupportsVision(entry, id, model.supportsVision);
3817
4431
  }
3818
4432
  if (model.keyEnv) {
3819
4433
  entry.key_env = model.keyEnv;
3820
4434
  } else if (model.apiKey) {
3821
4435
  entry.api_key = model.apiKey;
3822
4436
  }
3823
- entries.push(entry);
4437
+ providers[providerKey] = entry;
3824
4438
  }
3825
4439
  function writeDefaultModelConfig(modelConfig, model) {
3826
4440
  modelConfig.default = model.id;
@@ -3847,71 +4461,15 @@ function writeDefaultModelConfig(modelConfig, model) {
3847
4461
  delete modelConfig.context_length;
3848
4462
  }
3849
4463
  }
3850
- function findCustomProviderIndex(entries, modelId) {
3851
- return entries.findIndex(
3852
- (entry) => readEntryModelIds(toRecord(entry)).includes(modelId)
3853
- );
3854
- }
3855
- function findCustomProviderIndexByEndpoint(entries, endpoint) {
3856
- return entries.findIndex((entry) => {
3857
- const record = toRecord(entry);
3858
- const provider = readString2(record.provider_key) ?? readString2(record.provider) ?? "custom";
3859
- const baseUrl = readString2(record.base_url) ?? readString2(record.url) ?? readString2(record.api) ?? "";
3860
- return provider.trim().toLowerCase() === endpoint.provider.trim().toLowerCase() && normalizeBaseUrl(baseUrl) === normalizeBaseUrl(endpoint.baseUrl);
3861
- });
3862
- }
3863
- function updateEntryModels(entry, originalModelId, nextModelId) {
3864
- const models = entry.models;
3865
- if (Array.isArray(models)) {
3866
- const next = models.map((value) => value === originalModelId ? nextModelId : value).filter(
3867
- (value) => typeof value === "string" && value.trim().length > 0
3868
- );
3869
- if (!next.includes(nextModelId)) {
3870
- next.unshift(nextModelId);
3871
- }
3872
- entry.models = Array.from(new Set(next));
3873
- return;
3874
- }
3875
- if (typeof models === "object" && models !== null && originalModelId !== nextModelId && originalModelId in models) {
3876
- const record = models;
3877
- record[nextModelId] = record[originalModelId];
3878
- delete record[originalModelId];
3879
- }
3880
- }
3881
- function addEntryModel(entry, modelId) {
3882
- const id = modelId.trim();
3883
- if (!id) {
3884
- return;
3885
- }
3886
- const models = entry.models;
3887
- if (Array.isArray(models)) {
3888
- entry.models = Object.fromEntries(
3889
- Array.from(
3890
- /* @__PURE__ */ new Set([
3891
- ...models.filter(
3892
- (value) => typeof value === "string" && value.trim().length > 0
3893
- ),
3894
- id
3895
- ])
3896
- ).map((model) => [model, {}])
3897
- );
3898
- return;
3899
- }
3900
- if (typeof models === "object" && models !== null) {
3901
- const record = models;
3902
- record[id] = toRecord(record[id]);
4464
+ function writeDefaultModelSupportsVision(modelConfig, supportsVision) {
4465
+ if (supportsVision === void 0 || supportsVision === null) {
4466
+ delete modelConfig.supports_vision;
4467
+ delete modelConfig.supportsVision;
3903
4468
  return;
3904
4469
  }
3905
- if (readString2(entry.model) !== id && readString2(entry.default_model) !== id) {
3906
- entry.models = Object.fromEntries(
3907
- Array.from(/* @__PURE__ */ new Set([...readEntryModelIds(entry), id])).map((model) => [
3908
- model,
3909
- {}
3910
- ])
3911
- );
3912
- }
4470
+ modelConfig.supports_vision = supportsVision;
3913
4471
  }
3914
- function removeModelFromCustomProvider(entry, modelId) {
4472
+ function removeModelFromProvider(entry, modelId) {
3915
4473
  if (readString2(entry.model) === modelId || readString2(entry.default_model) === modelId) {
3916
4474
  delete entry.model;
3917
4475
  delete entry.default_model;
@@ -3924,7 +4482,7 @@ function removeModelFromCustomProvider(entry, modelId) {
3924
4482
  }
3925
4483
  const remainingModels = readEntryModelIds(entry);
3926
4484
  if (remainingModels.length > 0) {
3927
- entry.model = readString2(entry.model) ?? remainingModels[0];
4485
+ entry.default_model = readString2(entry.default_model) ?? remainingModels[0];
3928
4486
  return entry;
3929
4487
  }
3930
4488
  return null;
@@ -4017,6 +4575,63 @@ function writeEntryModelReasoningEffort(entry, modelId, reasoningEffort) {
4017
4575
  delete entry.reasoningEffort;
4018
4576
  }
4019
4577
  }
4578
+ function readEntryModelSupportsVision(entry, modelId) {
4579
+ const models = entry.models;
4580
+ if (typeof models === "object" && models !== null && !Array.isArray(models)) {
4581
+ const modelConfig = toRecord(models[modelId]);
4582
+ const modelValue = readCapabilityBoolean(
4583
+ modelConfig.supports_vision ?? modelConfig.supportsVision
4584
+ );
4585
+ if (modelValue !== void 0) {
4586
+ return modelValue;
4587
+ }
4588
+ }
4589
+ const entryValue = readCapabilityBoolean(
4590
+ entry.supports_vision ?? entry.supportsVision
4591
+ );
4592
+ return entryValue ?? null;
4593
+ }
4594
+ function writeEntryModelSupportsVision(entry, modelId, supportsVision) {
4595
+ if (supportsVision === void 0) {
4596
+ return;
4597
+ }
4598
+ const modelConfig = ensureEntryModelConfig(entry, modelId, modelId);
4599
+ if (supportsVision === null) {
4600
+ delete modelConfig.supports_vision;
4601
+ delete modelConfig.supportsVision;
4602
+ } else {
4603
+ modelConfig.supports_vision = supportsVision;
4604
+ }
4605
+ }
4606
+ function readCapabilityBoolean(value) {
4607
+ if (typeof value === "boolean") {
4608
+ return value;
4609
+ }
4610
+ if (typeof value !== "string") {
4611
+ return void 0;
4612
+ }
4613
+ const normalized = value.trim().toLowerCase();
4614
+ if (["true", "1", "yes", "y", "on"].includes(normalized)) {
4615
+ return true;
4616
+ }
4617
+ if (["false", "0", "no", "n", "off"].includes(normalized)) {
4618
+ return false;
4619
+ }
4620
+ return void 0;
4621
+ }
4622
+ function ensureEntryModelConfig(entry, originalModelId, nextModelId) {
4623
+ const modelMap = normalizeEntryModelsMap(entry);
4624
+ if (originalModelId !== nextModelId && Object.prototype.hasOwnProperty.call(modelMap, originalModelId) && !Object.prototype.hasOwnProperty.call(modelMap, nextModelId)) {
4625
+ modelMap[nextModelId] = modelMap[originalModelId];
4626
+ }
4627
+ if (originalModelId !== nextModelId) {
4628
+ delete modelMap[originalModelId];
4629
+ }
4630
+ const modelConfig = toRecord(modelMap[nextModelId]);
4631
+ modelMap[nextModelId] = modelConfig;
4632
+ entry.models = modelMap;
4633
+ return modelConfig;
4634
+ }
4020
4635
  async function readHermesModelConfigForImport(input) {
4021
4636
  const { config } = await readHermesConfigDocument(
4022
4637
  resolveHermesConfigPath(input.profileName)
@@ -4064,8 +4679,7 @@ function readInlineApiKeyForModel(config, model) {
4064
4679
  return key;
4065
4680
  }
4066
4681
  }
4067
- const customProviders = Array.isArray(config.custom_providers) ? config.custom_providers : [];
4068
- for (const rawEntry of customProviders) {
4682
+ for (const rawEntry of readCompatibleModelProviderEntries(config)) {
4069
4683
  const entry = toRecord(rawEntry);
4070
4684
  const provider = readString2(entry.provider_key) ?? readString2(entry.provider) ?? "custom";
4071
4685
  const baseUrl = readString2(entry.base_url) ?? readString2(entry.url) ?? readString2(entry.api) ?? "";
@@ -4095,11 +4709,35 @@ function compareCatalogItems(left, right) {
4095
4709
  }
4096
4710
  return left.id.localeCompare(right.id);
4097
4711
  }
4098
- function modelConfigKey(provider, baseUrl, modelId) {
4099
- return [provider, normalizeBaseUrl(baseUrl), modelId].join("\n").toLowerCase();
4712
+ function modelConfigKey(provider, baseUrl, modelId, apiMode) {
4713
+ return [provider, normalizeBaseUrl(baseUrl), modelId, apiMode].join("\n").toLowerCase();
4714
+ }
4715
+ function modelEndpointKey(baseUrl, modelId, apiMode) {
4716
+ return [normalizeBaseUrl(baseUrl), modelId, apiMode].join("\n").toLowerCase();
4717
+ }
4718
+ function providerConfigGroupKey(entry) {
4719
+ const provider = readString2(entry.provider_key) ?? readString2(entry.provider) ?? "custom";
4720
+ const providerName = readString2(entry.name) ?? readString2(entry.provider_name) ?? "";
4721
+ const baseUrl = readString2(entry.base_url) ?? readString2(entry.url) ?? readString2(entry.api) ?? "";
4722
+ const apiMode = inferApiMode(provider, baseUrl, readString2(entry.api_mode));
4723
+ return [
4724
+ providerName.trim().toLowerCase(),
4725
+ normalizeBaseUrl(baseUrl).toLowerCase(),
4726
+ apiMode.trim().toLowerCase(),
4727
+ providerCredentialIdentity(entry)
4728
+ ].join("\n");
4100
4729
  }
4101
- function modelEndpointKey(baseUrl, modelId) {
4102
- return [normalizeBaseUrl(baseUrl), modelId].join("\n").toLowerCase();
4730
+ function providerCredentialIdentity(entry) {
4731
+ const apiKey = readString2(entry.api_key);
4732
+ const keyEnv = readString2(entry.key_env) ?? parseEnvReference(apiKey);
4733
+ if (keyEnv) {
4734
+ return `env:${keyEnv.trim().toLowerCase()}`;
4735
+ }
4736
+ const inlineApiKey = readInlineApiKey(apiKey);
4737
+ if (inlineApiKey) {
4738
+ return `inline:${inlineApiKey}`;
4739
+ }
4740
+ return "";
4103
4741
  }
4104
4742
  function normalizeBaseUrl(baseUrl) {
4105
4743
  return baseUrl.trim().replace(/\/+$/u, "");
@@ -4206,9 +4844,18 @@ function readModelConfig(value) {
4206
4844
  contextLength,
4207
4845
  reasoningEffort: normalizeReasoningEffort(
4208
4846
  model.reasoning_effort ?? model.reasoningEffort
4209
- )
4847
+ ),
4848
+ supportsVision: readCapabilityBoolean(model.supports_vision ?? model.supportsVision) ?? null
4210
4849
  };
4211
4850
  }
4851
+ function readProfileImageInputMode(config) {
4852
+ const agent = toRecord(config.agent);
4853
+ return normalizeImageInputMode(agent.image_input_mode) ?? "auto";
4854
+ }
4855
+ function writeProfileImageInputMode(config, imageInputMode) {
4856
+ const agent = ensureRecord(config, "agent");
4857
+ agent.image_input_mode = imageInputMode;
4858
+ }
4212
4859
  function readProfileReasoningEffort(config) {
4213
4860
  const agent = toRecord(config.agent);
4214
4861
  return normalizeReasoningEffort(agent.reasoning_effort ?? agent.reasoningEffort) ?? null;
@@ -4224,6 +4871,13 @@ function normalizeReasoningEffort(value) {
4224
4871
  const normalized = value.trim().toLowerCase();
4225
4872
  return REASONING_EFFORTS.includes(normalized) ? normalized : void 0;
4226
4873
  }
4874
+ function normalizeImageInputMode(value) {
4875
+ if (typeof value !== "string") {
4876
+ return void 0;
4877
+ }
4878
+ const normalized = value.trim().toLowerCase();
4879
+ return normalized === "auto" || normalized === "native" || normalized === "text" ? normalized : void 0;
4880
+ }
4227
4881
  function readPositiveInteger(value) {
4228
4882
  if (typeof value === "number" && Number.isFinite(value) && value > 0) {
4229
4883
  return Math.floor(value);
@@ -4293,6 +4947,11 @@ function readToolsetConfigState(key, config, env) {
4293
4947
  requiresConfig: true,
4294
4948
  configured: isWebToolConfigured(config, env)
4295
4949
  };
4950
+ case "vision":
4951
+ return {
4952
+ requiresConfig: true,
4953
+ configured: isVisionToolConfigured(config, env)
4954
+ };
4296
4955
  case "image_gen":
4297
4956
  return {
4298
4957
  requiresConfig: true,
@@ -4348,6 +5007,34 @@ function isWebToolConfigured(config, env) {
4348
5007
  }
4349
5008
  return isEnvValueConfigured(env.FIRECRAWL_API_KEY) || isEnvValueConfigured(env.FIRECRAWL_API_URL) || isEnvValueConfigured(env.TAVILY_API_KEY) || isEnvValueConfigured(env.EXA_API_KEY) || isEnvValueConfigured(env.PARALLEL_API_KEY);
4350
5009
  }
5010
+ function isVisionToolConfigured(config, env) {
5011
+ const auxiliary = toRecord(config.auxiliary);
5012
+ const vision = toRecord(auxiliary.vision);
5013
+ const provider = readString2(vision.provider)?.toLowerCase() ?? "auto";
5014
+ const baseUrl = readString2(vision.base_url);
5015
+ const apiKey = readString2(vision.api_key);
5016
+ const keyEnv = parseEnvReference(apiKey);
5017
+ const resolvedApiKey = resolveConfiguredApiKey(apiKey, keyEnv, env);
5018
+ if (provider === "auto" && !baseUrl) {
5019
+ return true;
5020
+ }
5021
+ if (provider === "nous") {
5022
+ return true;
5023
+ }
5024
+ if (provider === "openrouter") {
5025
+ return isEnvValueConfigured(env.OPENROUTER_API_KEY);
5026
+ }
5027
+ if (provider === "openai") {
5028
+ return isEnvValueConfigured(env.OPENAI_API_KEY) || isEnvValueConfigured(resolvedApiKey);
5029
+ }
5030
+ if (provider === "anthropic") {
5031
+ return isEnvValueConfigured(env.ANTHROPIC_API_KEY);
5032
+ }
5033
+ if (provider === "custom" || baseUrl) {
5034
+ return isEnvValueConfigured(resolvedApiKey);
5035
+ }
5036
+ return true;
5037
+ }
4351
5038
  function isImageGenToolConfigured(config, env) {
4352
5039
  const imageGen = toRecord(config.image_gen);
4353
5040
  const provider = readString2(imageGen.provider)?.toLowerCase() ?? "fal";
@@ -4474,7 +5161,7 @@ function readString2(value) {
4474
5161
  }
4475
5162
  function normalizeProfileToolConfigKey(value) {
4476
5163
  const normalized = value.trim();
4477
- if (normalized === "web" || normalized === "image_gen" || normalized === "stt" || normalized === "tts" || normalized === "messaging" || normalized === "homeassistant" || normalized === "rl") {
5164
+ if (normalized === "web" || normalized === "vision" || normalized === "image_gen" || normalized === "stt" || normalized === "tts" || normalized === "messaging" || normalized === "homeassistant" || normalized === "rl") {
4478
5165
  return normalized;
4479
5166
  }
4480
5167
  throw new Error(`unsupported tool config "${value}"`);
@@ -4501,6 +5188,25 @@ function profileToolConfigFromSources(profileName, configPath, toolKey, config,
4501
5188
  configured.FIRECRAWL_API_URL = isEnvValueConfigured(env.FIRECRAWL_API_URL);
4502
5189
  break;
4503
5190
  }
5191
+ case "vision": {
5192
+ const auxiliary = toRecord(config.auxiliary);
5193
+ const section = toRecord(auxiliary.vision);
5194
+ values.provider = readString2(section.provider) ?? "auto";
5195
+ values.model = readString2(section.model) ?? env.AUXILIARY_VISION_MODEL?.trim() ?? "";
5196
+ values.baseUrl = readString2(section.base_url) ?? env.AUXILIARY_VISION_BASE_URL?.trim() ?? "";
5197
+ values.apiMode = readString2(section.api_mode) ?? "";
5198
+ configured.OPENROUTER_API_KEY = isEnvValueConfigured(env.OPENROUTER_API_KEY);
5199
+ configured.OPENAI_API_KEY = isEnvValueConfigured(env.OPENAI_API_KEY);
5200
+ configured.ANTHROPIC_API_KEY = isEnvValueConfigured(env.ANTHROPIC_API_KEY);
5201
+ configured.AUXILIARY_VISION_API_KEY = isEnvValueConfigured(env.AUXILIARY_VISION_API_KEY) || isEnvValueConfigured(
5202
+ resolveConfiguredApiKey(
5203
+ readString2(section.api_key),
5204
+ parseEnvReference(readString2(section.api_key)),
5205
+ env
5206
+ )
5207
+ );
5208
+ break;
5209
+ }
4504
5210
  case "image_gen": {
4505
5211
  const section = toRecord(config.image_gen);
4506
5212
  values.provider = readString2(section.provider) ?? "fal";
@@ -4669,6 +5375,71 @@ function applyImageGenToolConfig(config, values) {
4669
5375
  }
4670
5376
  return changed;
4671
5377
  }
5378
+ function applyVisionToolConfig(config, values) {
5379
+ let changed = false;
5380
+ const auxiliary = ensureRecord(config, "auxiliary");
5381
+ const section = ensureRecord(auxiliary, "vision");
5382
+ const provider = readToolConfigString(values.provider);
5383
+ if (provider !== void 0) {
5384
+ if (![
5385
+ "auto",
5386
+ "openrouter",
5387
+ "nous",
5388
+ "openai",
5389
+ "anthropic",
5390
+ "custom"
5391
+ ].includes(provider)) {
5392
+ throw new Error("auxiliary.vision.provider is not supported");
5393
+ }
5394
+ section.provider = provider;
5395
+ changed = true;
5396
+ if (provider === "auto") {
5397
+ delete section.base_url;
5398
+ delete section.api_key;
5399
+ delete section.api_mode;
5400
+ }
5401
+ }
5402
+ if (Object.prototype.hasOwnProperty.call(values, "model")) {
5403
+ const model = readToolConfigString(values.model);
5404
+ if (model) {
5405
+ section.model = model;
5406
+ } else {
5407
+ delete section.model;
5408
+ }
5409
+ changed = true;
5410
+ }
5411
+ if (Object.prototype.hasOwnProperty.call(values, "baseUrl")) {
5412
+ const baseUrl = readToolConfigString(values.baseUrl);
5413
+ if (baseUrl) {
5414
+ section.base_url = normalizeToolConfigHttpUrl(
5415
+ baseUrl,
5416
+ "auxiliary.vision.base_url"
5417
+ );
5418
+ section.api_key = "${AUXILIARY_VISION_API_KEY}";
5419
+ } else {
5420
+ delete section.base_url;
5421
+ if (readString2(section.api_key) === "${AUXILIARY_VISION_API_KEY}") {
5422
+ delete section.api_key;
5423
+ }
5424
+ }
5425
+ changed = true;
5426
+ }
5427
+ if (Object.prototype.hasOwnProperty.call(values, "apiMode")) {
5428
+ const apiMode = readToolConfigString(values.apiMode);
5429
+ if (apiMode) {
5430
+ section.api_mode = apiMode;
5431
+ } else {
5432
+ delete section.api_mode;
5433
+ }
5434
+ changed = true;
5435
+ }
5436
+ if (readToolConfigString(values.AUXILIARY_VISION_API_KEY) && readString2(section.base_url)) {
5437
+ section.api_key = "${AUXILIARY_VISION_API_KEY}";
5438
+ changed = true;
5439
+ }
5440
+ deleteEmptyNestedToolSection(auxiliary, "vision");
5441
+ return changed;
5442
+ }
4672
5443
  function applyOptionalNestedToolString(parent, sectionKey, targetKey, values, inputKeys) {
4673
5444
  const inputKey = inputKeys.find(
4674
5445
  (key) => Object.prototype.hasOwnProperty.call(values, key)
@@ -5621,7 +6392,7 @@ function isConversationMissingError(error) {
5621
6392
  }
5622
6393
 
5623
6394
  // src/constants.ts
5624
- var LINK_VERSION = "0.7.2";
6395
+ var LINK_VERSION = "0.7.4-beta.0";
5625
6396
  var LINK_COMMAND = "hermeslink";
5626
6397
  var LINK_DEFAULT_PORT = 52379;
5627
6398
  var LINK_RUNTIME_DIR_NAME = ".hermeslink";
@@ -9210,7 +9981,8 @@ var ConversationCommandHandlers = class {
9210
9981
  {
9211
9982
  taskModelId: configuredModel.id,
9212
9983
  taskModelProvider: configuredModel.provider,
9213
- taskModelBaseUrl: configuredModel.baseUrl
9984
+ taskModelBaseUrl: configuredModel.baseUrl,
9985
+ taskModelApiMode: configuredModel.apiMode
9214
9986
  },
9215
9987
  runtime.profileName
9216
9988
  );
@@ -13208,6 +13980,7 @@ async function syncHermesSessionsIntoConversations(paths, logger, options = {})
13208
13980
  reprojected_count: 0,
13209
13981
  skipped_existing: 0,
13210
13982
  skipped_hidden: 0,
13983
+ skipped_empty_transcript: 0,
13211
13984
  skipped_deleted: 0,
13212
13985
  skipped_over_limit: 0,
13213
13986
  errors: []
@@ -13279,11 +14052,28 @@ async function syncHermesSessionsIntoConversations(paths, logger, options = {})
13279
14052
  result.skipped_existing += 1;
13280
14053
  continue;
13281
14054
  }
14055
+ const candidateMessages = await readHermesLineageMessages(candidate).catch(
14056
+ (error) => {
14057
+ result.errors.push({
14058
+ profile: candidate.profileName,
14059
+ message: error instanceof Error ? error.message : String(error)
14060
+ });
14061
+ return null;
14062
+ }
14063
+ );
14064
+ if (!candidateMessages) {
14065
+ continue;
14066
+ }
14067
+ if (candidateMessages.length === 0) {
14068
+ result.skipped_empty_transcript += 1;
14069
+ continue;
14070
+ }
13282
14071
  const importedConversationId = await importHermesSession({
13283
14072
  paths,
13284
14073
  store,
13285
14074
  logger,
13286
- candidate
14075
+ candidate,
14076
+ messages: candidateMessages
13287
14077
  }).catch((error) => {
13288
14078
  result.errors.push({
13289
14079
  profile: candidate.profileName,
@@ -13387,7 +14177,10 @@ async function importHermesSession(input) {
13387
14177
  );
13388
14178
  const sessionId = candidate.session.id;
13389
14179
  const hermesSessionIds = lineageSessionIds(candidate);
13390
- const messages2 = await readHermesLineageMessages(candidate);
14180
+ const messages2 = input.messages ?? await readHermesLineageMessages(candidate);
14181
+ if (messages2.length === 0) {
14182
+ return null;
14183
+ }
13391
14184
  const now = (/* @__PURE__ */ new Date()).toISOString();
13392
14185
  const createdAt = isoFromHermesTime(candidate.session.started_at) ?? now;
13393
14186
  const updatedAt = isoFromHermesTime(candidate.session.last_active) ?? isoFromHermesTime(messages2.at(-1)?.timestamp) ?? createdAt;
@@ -13487,6 +14280,18 @@ async function mergeExistingHermesConversation(input) {
13487
14280
  }
13488
14281
  let changed = false;
13489
14282
  const candidateMessages = await readHermesLineageMessages(input.candidate);
14283
+ if (candidateMessages.length === 0 && isEmptyHermesImportPlaceholder(canonical)) {
14284
+ await softDeleteEmptyHermesImportPlaceholder({
14285
+ paths: input.paths,
14286
+ store: input.store,
14287
+ conversation: canonical,
14288
+ candidate: input.candidate
14289
+ });
14290
+ return true;
14291
+ }
14292
+ if (candidateMessages.length === 0) {
14293
+ return false;
14294
+ }
13490
14295
  const nextCanonical = await mergeHermesCandidateIntoConversation({
13491
14296
  ...input,
13492
14297
  existing: canonical,
@@ -13698,7 +14503,26 @@ function isSafeHermesImportConversation(item) {
13698
14503
  }
13699
14504
  return snapshot.messages.length === 0 || snapshot.messages.every((message) => isHermesImportedMessage(message));
13700
14505
  }
14506
+ function isEmptyHermesImportPlaceholder(item) {
14507
+ return item.manifest.status === "active" && item.snapshot.messages.length === 0 && item.snapshot.runs.length === 0 && item.manifest.title_source === "default" && isDefaultConversationTitle(item.manifest.title) && item.manifest.hermes_session_id !== void 0 && !item.manifest.hermes_session_id.startsWith("hp_");
14508
+ }
14509
+ async function softDeleteEmptyHermesImportPlaceholder(input) {
14510
+ await softDeleteHermesImportConversation({
14511
+ paths: input.paths,
14512
+ store: input.store,
14513
+ conversation: input.conversation,
14514
+ candidate: input.candidate
14515
+ });
14516
+ }
13701
14517
  async function softDeleteMergedHermesDuplicate(input) {
14518
+ await softDeleteHermesImportConversation({
14519
+ paths: input.paths,
14520
+ store: input.store,
14521
+ conversation: input.duplicate,
14522
+ candidate: input.candidate
14523
+ });
14524
+ }
14525
+ async function softDeleteHermesImportConversation(input) {
13702
14526
  const deletedAt = (/* @__PURE__ */ new Date()).toISOString();
13703
14527
  const emptySnapshot3 = {
13704
14528
  schema_version: 1,
@@ -13706,13 +14530,13 @@ async function softDeleteMergedHermesDuplicate(input) {
13706
14530
  runs: []
13707
14531
  };
13708
14532
  const hermesSessionIds = normalizeSessionIds([
13709
- input.duplicate.manifest.hermes_session_id,
13710
- ...input.duplicate.manifest.hermes_session_ids ?? [],
13711
- ...input.duplicate.manifest.hermes_lineage?.session_ids ?? [],
14533
+ input.conversation.manifest.hermes_session_id,
14534
+ ...input.conversation.manifest.hermes_session_ids ?? [],
14535
+ ...input.conversation.manifest.hermes_lineage?.session_ids ?? [],
13712
14536
  ...lineageSessionIds(input.candidate)
13713
14537
  ]);
13714
14538
  const nextManifest = {
13715
- ...input.duplicate.manifest,
14539
+ ...input.conversation.manifest,
13716
14540
  status: "deleted_soft",
13717
14541
  hermes_session_ids: hermesSessionIds,
13718
14542
  ...lineageManifestPatch(input.candidate),
@@ -13720,12 +14544,12 @@ async function softDeleteMergedHermesDuplicate(input) {
13720
14544
  deleted_at: deletedAt
13721
14545
  };
13722
14546
  const stats = buildConversationStats(
13723
- { ...nextManifest, stats: input.duplicate.manifest.stats },
14547
+ { ...nextManifest, stats: input.conversation.manifest.stats },
13724
14548
  emptySnapshot3
13725
14549
  );
13726
14550
  const tombstone = { ...nextManifest, stats };
13727
14551
  await input.store.writeManifest(tombstone);
13728
- await input.store.writeSnapshot(input.duplicate.conversationId, emptySnapshot3);
14552
+ await input.store.writeSnapshot(input.conversation.conversationId, emptySnapshot3);
13729
14553
  await upsertConversationStats(input.paths, toStatsIndexRecord(tombstone, stats));
13730
14554
  }
13731
14555
  function mergeHermesLineageIntoManifest(input) {
@@ -16263,6 +17087,13 @@ async function resolveHermesPythonRuntime() {
16263
17087
  const hermesBin = await resolveExecutablePath2(resolveHermesBin());
16264
17088
  let shebangRuntime = null;
16265
17089
  if (hermesBin) {
17090
+ const launcherTarget = await readHermesLauncherTarget(hermesBin);
17091
+ if (launcherTarget) {
17092
+ const launcherRuntime = await resolveHermesEntrypointRuntime(launcherTarget);
17093
+ if (launcherRuntime) {
17094
+ return launcherRuntime;
17095
+ }
17096
+ }
16266
17097
  const installRoot = await findHermesSourceRoot(hermesBin);
16267
17098
  const shebang = await readShebang(hermesBin);
16268
17099
  const command = shebangToPythonCommand(shebang);
@@ -16383,6 +17214,42 @@ async function findDevHermesAgentSource() {
16383
17214
  }
16384
17215
  return null;
16385
17216
  }
17217
+ async function readHermesLauncherTarget(filePath) {
17218
+ const raw = await readFile11(filePath, "utf8").catch(() => "");
17219
+ for (const line of raw.split(/\r?\n/u)) {
17220
+ const quoted = /^\s*exec\s+(["'])(?<target>.+?)\1\s+(?:"\$@"|'\$@')/.exec(
17221
+ line
17222
+ );
17223
+ const rawTarget = quoted?.groups?.target ?? /^\s*exec\s+(?<target>\S+)\s+(?:"\$@"|'\$@')/.exec(line)?.groups?.target;
17224
+ if (!rawTarget || !path18.isAbsolute(rawTarget)) {
17225
+ continue;
17226
+ }
17227
+ if (await isExecutableFile(rawTarget)) {
17228
+ return rawTarget;
17229
+ }
17230
+ }
17231
+ return null;
17232
+ }
17233
+ async function resolveHermesEntrypointRuntime(entrypointPath) {
17234
+ const installRoot = await findHermesSourceRoot(entrypointPath);
17235
+ const shebang = await readShebang(entrypointPath);
17236
+ const command = shebangToPythonCommand(shebang);
17237
+ if (command) {
17238
+ return {
17239
+ ...command,
17240
+ sourceRoot: await findHermesSourceRoot(command.command) ?? installRoot
17241
+ };
17242
+ }
17243
+ const installPython = installRoot ? await findHermesVenvPython(installRoot) : null;
17244
+ if (installPython) {
17245
+ return {
17246
+ command: installPython,
17247
+ args: [],
17248
+ sourceRoot: installRoot
17249
+ };
17250
+ }
17251
+ return null;
17252
+ }
16386
17253
  async function findHermesSourceRoot(executablePath) {
16387
17254
  let cursor = path18.dirname(path18.resolve(executablePath));
16388
17255
  for (let index = 0; index < 6; index += 1) {
@@ -21501,7 +22368,8 @@ var ConversationService = class {
21501
22368
  {
21502
22369
  taskModelId: configuredModel.id,
21503
22370
  taskModelProvider: configuredModel.provider,
21504
- taskModelBaseUrl: configuredModel.baseUrl
22371
+ taskModelBaseUrl: configuredModel.baseUrl,
22372
+ taskModelApiMode: configuredModel.apiMode
21505
22373
  },
21506
22374
  currentRuntime.profile.name
21507
22375
  );
@@ -24262,16 +25130,13 @@ function registerModelConfigRoutes(router, options) {
24262
25130
  router.delete("/api/v1/model-configs", async (ctx) => {
24263
25131
  await authenticateRequest(ctx, paths);
24264
25132
  const body = await readJsonBody(ctx.req);
24265
- const modelId = readString17(body, "model_id") ?? readString17(body, "modelId");
24266
- if (!modelId) {
24267
- throw new LinkHttpError(400, "model_id_required", "model_id is required");
24268
- }
25133
+ const input = readModelDeleteInput(body);
24269
25134
  try {
24270
- const result = await deleteHermesModelConfig(modelId);
24271
- ctx.body = await reloadGatewayAfterModelConfigChange(result, {
25135
+ const result = await deleteHermesModelConfig(input);
25136
+ ctx.body = shouldReloadGatewayAfterModelConfigChange(body) ? await reloadGatewayAfterModelConfigChange(result, {
24272
25137
  paths,
24273
25138
  logger
24274
- });
25139
+ }) : markModelConfigAppliedWithoutGatewayReload(result);
24275
25140
  } catch (error) {
24276
25141
  throw toModelConfigHttpError(error);
24277
25142
  }
@@ -24334,17 +25199,14 @@ function registerModelConfigRoutes(router, options) {
24334
25199
  await authenticateRequest(ctx, paths);
24335
25200
  await getHermesProfileStatus(ctx.params.name, paths);
24336
25201
  const body = await readJsonBody(ctx.req);
24337
- const modelId = readString17(body, "model_id") ?? readString17(body, "modelId");
24338
- if (!modelId) {
24339
- throw new LinkHttpError(400, "model_id_required", "model_id is required");
24340
- }
25202
+ const input = readModelDeleteInput(body);
24341
25203
  try {
24342
- const result = await deleteHermesModelConfig(modelId, ctx.params.name);
24343
- ctx.body = await reloadGatewayAfterProfileModelConfigChange(result, {
25204
+ const result = await deleteHermesModelConfig(input, ctx.params.name);
25205
+ ctx.body = shouldReloadGatewayAfterModelConfigChange(body) ? await reloadGatewayAfterProfileModelConfigChange(result, {
24344
25206
  paths,
24345
25207
  logger,
24346
25208
  profileName: ctx.params.name
24347
- });
25209
+ }) : markModelConfigAppliedWithoutGatewayReload(result);
24348
25210
  } catch (error) {
24349
25211
  throw toModelConfigHttpError(error);
24350
25212
  }
@@ -24364,6 +25226,9 @@ function readModelConfigInput(body) {
24364
25226
  return {
24365
25227
  id,
24366
25228
  originalModelId: readString17(body, "original_model_id") ?? readString17(body, "originalModelId") ?? readString17(body, "original_id") ?? void 0,
25229
+ originalProvider: readString17(body, "original_provider") ?? readString17(body, "originalProvider") ?? readString17(body, "original_provider_key") ?? readString17(body, "originalProviderKey") ?? void 0,
25230
+ originalBaseUrl: readString17(body, "original_base_url") ?? readString17(body, "originalBaseUrl") ?? void 0,
25231
+ originalApiMode: readString17(body, "original_api_mode") ?? readString17(body, "originalApiMode") ?? void 0,
24367
25232
  provider,
24368
25233
  providerName: readString17(body, "provider_name") ?? readString17(body, "providerName") ?? void 0,
24369
25234
  baseUrl,
@@ -24374,7 +25239,10 @@ function readModelConfigInput(body) {
24374
25239
  ),
24375
25240
  keyEnv: readString17(body, "key_env") ?? readString17(body, "keyEnv") ?? void 0,
24376
25241
  setDefault: readBoolean3(body.set_default ?? body.setDefault),
24377
- reasoningEffort: readString17(body, "reasoning_effort") ?? readString17(body, "reasoningEffort") ?? void 0
25242
+ reasoningEffort: readString17(body, "reasoning_effort") ?? readString17(body, "reasoningEffort") ?? void 0,
25243
+ supportsVision: readNullableBoolean(
25244
+ body.supports_vision ?? body.supportsVision
25245
+ )
24378
25246
  };
24379
25247
  }
24380
25248
  function readModelDefaultsInput(body) {
@@ -24382,12 +25250,46 @@ function readModelDefaultsInput(body) {
24382
25250
  taskModelId: readString17(body, "task_model_id") ?? readString17(body, "taskModelId") ?? readString17(body, "default_model_id") ?? readString17(body, "defaultModelId") ?? void 0,
24383
25251
  taskModelProvider: readString17(body, "task_model_provider") ?? readString17(body, "taskModelProvider") ?? readString17(body, "default_model_provider") ?? readString17(body, "defaultModelProvider") ?? void 0,
24384
25252
  taskModelBaseUrl: readString17(body, "task_model_base_url") ?? readString17(body, "taskModelBaseUrl") ?? readString17(body, "default_model_base_url") ?? readString17(body, "defaultModelBaseUrl") ?? void 0,
25253
+ taskModelApiMode: readString17(body, "task_model_api_mode") ?? readString17(body, "taskModelApiMode") ?? readString17(body, "default_model_api_mode") ?? readString17(body, "defaultModelApiMode") ?? void 0,
24385
25254
  compressionModelId: readString17(body, "compression_model_id") ?? readString17(body, "compressionModelId") ?? void 0,
24386
25255
  compressionModelProvider: readString17(body, "compression_model_provider") ?? readString17(body, "compressionModelProvider") ?? void 0,
24387
25256
  compressionModelBaseUrl: readString17(body, "compression_model_base_url") ?? readString17(body, "compressionModelBaseUrl") ?? void 0,
24388
- reasoningEffort: readString17(body, "reasoning_effort") ?? readString17(body, "reasoningEffort") ?? readString17(body, "default_reasoning_effort") ?? readString17(body, "defaultReasoningEffort") ?? void 0
25257
+ compressionModelApiMode: readString17(body, "compression_model_api_mode") ?? readString17(body, "compressionModelApiMode") ?? void 0,
25258
+ reasoningEffort: readString17(body, "reasoning_effort") ?? readString17(body, "reasoningEffort") ?? readString17(body, "default_reasoning_effort") ?? readString17(body, "defaultReasoningEffort") ?? void 0,
25259
+ imageInputMode: readString17(body, "image_input_mode") ?? readString17(body, "imageInputMode") ?? void 0
25260
+ };
25261
+ }
25262
+ function readModelDeleteInput(body) {
25263
+ const id = readString17(body, "model_id") ?? readString17(body, "modelId");
25264
+ if (!id) {
25265
+ throw new LinkHttpError(400, "model_id_required", "model_id is required");
25266
+ }
25267
+ return {
25268
+ id,
25269
+ provider: readString17(body, "provider") ?? readString17(body, "provider_key") ?? readString17(body, "providerKey") ?? void 0,
25270
+ baseUrl: readString17(body, "base_url") ?? readString17(body, "baseUrl") ?? void 0,
25271
+ apiMode: readString17(body, "api_mode") ?? readString17(body, "apiMode") ?? void 0
24389
25272
  };
24390
25273
  }
25274
+ function readNullableBoolean(value) {
25275
+ if (value === null) {
25276
+ return null;
25277
+ }
25278
+ if (typeof value === "boolean") {
25279
+ return value;
25280
+ }
25281
+ if (typeof value !== "string") {
25282
+ return void 0;
25283
+ }
25284
+ const normalized = value.trim().toLowerCase();
25285
+ if (["true", "1", "yes", "on"].includes(normalized)) {
25286
+ return true;
25287
+ }
25288
+ if (["false", "0", "no", "off"].includes(normalized)) {
25289
+ return false;
25290
+ }
25291
+ return void 0;
25292
+ }
24391
25293
  function readModelConfigImportInput(body) {
24392
25294
  const sourceProfileName = readString17(body, "source_profile") ?? readString17(body, "sourceProfile") ?? readString17(body, "source_profile_name") ?? readString17(body, "sourceProfileName");
24393
25295
  const modelId = readString17(body, "model_id") ?? readString17(body, "modelId") ?? readString17(body, "id");
@@ -24409,7 +25311,7 @@ function readModelConfigImportInput(body) {
24409
25311
  }
24410
25312
  function shouldReloadGatewayAfterModelConfigChange(body) {
24411
25313
  const explicit = readBoolean3(body.reload_gateway ?? body.reloadGateway) ?? (readBoolean3(body.skip_gateway_reload ?? body.skipGatewayReload) === true ? false : void 0);
24412
- return explicit ?? true;
25314
+ return explicit ?? false;
24413
25315
  }
24414
25316
  function markModelConfigAppliedWithoutGatewayReload(result) {
24415
25317
  return {