@hermespilot/link 0.7.4 → 0.7.5

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.
@@ -2618,16 +2618,20 @@ async function saveHermesModelConfig(input, profileName = "default", configPath
2618
2618
  const shouldUpdateReasoningEffort = input.reasoningEffort !== void 0;
2619
2619
  const { document, config, existingRaw } = await readHermesConfigDocument(configPath);
2620
2620
  const providers = ensureProvidersRecordWithLegacyMigration(config);
2621
- const originalModelId = input.originalModelId?.trim() || normalized.id;
2621
+ const originalModelId = input.originalModelId?.trim();
2622
2622
  const originalProvider = input.originalProvider?.trim();
2623
2623
  const originalBaseUrl = input.originalBaseUrl?.trim();
2624
2624
  const originalApiMode = input.originalApiMode?.trim();
2625
- const existingProviderKey = findProviderConfigKeyByModelIdentity(providers, {
2626
- id: originalModelId,
2625
+ const originalIdentity = {
2626
+ id: originalModelId || normalized.id,
2627
2627
  provider: originalProvider,
2628
2628
  baseUrl: originalBaseUrl,
2629
2629
  apiMode: originalApiMode
2630
- }) ?? (originalProvider && originalBaseUrl ? findProviderConfigKeyByEndpoint(providers, {
2630
+ };
2631
+ const hasOriginalIdentity = Boolean(
2632
+ originalModelId || originalProvider || originalBaseUrl || originalApiMode
2633
+ );
2634
+ const existingProviderKey = (hasOriginalIdentity ? findProviderConfigKeyByModelIdentity(providers, originalIdentity) : null) ?? (originalProvider && originalBaseUrl ? findProviderConfigKeyByEndpoint(providers, {
2631
2635
  provider: originalProvider,
2632
2636
  baseUrl: originalBaseUrl,
2633
2637
  apiMode: originalApiMode
@@ -2647,7 +2651,7 @@ async function saveHermesModelConfig(input, profileName = "default", configPath
2647
2651
  await writeHermesEnvValue(profileName, keyEnv, normalized.apiKey);
2648
2652
  }
2649
2653
  writeProviderEndpointConfig(entry, normalized, keyEnv);
2650
- ensureEntryModelConfig(entry, originalModelId, normalized.id);
2654
+ ensureEntryModelConfig(entry, originalIdentity.id, normalized.id);
2651
2655
  writeEntryModelContextLength(entry, normalized.id, normalized.contextLength);
2652
2656
  writeEntryModelSupportsVision(entry, normalized.id, input.supportsVision);
2653
2657
  if (shouldUpdateReasoningEffort) {
@@ -2662,15 +2666,29 @@ async function saveHermesModelConfig(input, profileName = "default", configPath
2662
2666
  const currentDefaultConfig = readModelConfig(modelConfig);
2663
2667
  const currentDefault = currentDefaultConfig.model;
2664
2668
  const currentDefaultReasoningEffort = readProfileReasoningEffort(config);
2665
- if (normalized.setDefault || !currentDefault || currentDefault === originalModelId) {
2666
- if (normalized.setDefault && currentDefault && currentDefault !== normalized.id && currentDefault !== originalModelId) {
2669
+ const currentDefaultMatchesOriginal = hasOriginalIdentity && modelConfigMatchesModelIdentity(currentDefaultConfig, originalIdentity);
2670
+ const currentDefaultMatchesNext = modelConfigMatchesModelIdentity(
2671
+ currentDefaultConfig,
2672
+ {
2673
+ id: normalized.id,
2674
+ provider: providerKey,
2675
+ baseUrl: normalized.baseUrl,
2676
+ apiMode: inferApiMode(
2677
+ providerKey,
2678
+ normalized.baseUrl,
2679
+ normalized.apiMode
2680
+ )
2681
+ }
2682
+ );
2683
+ if (normalized.setDefault || !currentDefault || currentDefaultMatchesOriginal) {
2684
+ if (normalized.setDefault && currentDefault && !currentDefaultMatchesNext && !currentDefaultMatchesOriginal) {
2667
2685
  retainModelDefaultAsProvider(providers, {
2668
2686
  ...currentDefaultConfig,
2669
2687
  ...currentDefaultReasoningEffort ? { reasoningEffort: currentDefaultReasoningEffort } : {}
2670
2688
  });
2671
2689
  }
2672
- const defaultKeyEnv = keyEnv ?? (currentDefault === originalModelId ? currentDefaultConfig.keyEnv : void 0);
2673
- const defaultApiKey = normalized.apiKey ?? (!defaultKeyEnv && currentDefault === originalModelId ? currentDefaultConfig.apiKey : void 0);
2690
+ const defaultKeyEnv = keyEnv ?? (currentDefaultMatchesOriginal ? currentDefaultConfig.keyEnv : void 0);
2691
+ const defaultApiKey = normalized.apiKey ?? (!defaultKeyEnv && currentDefaultMatchesOriginal ? currentDefaultConfig.apiKey : void 0);
2674
2692
  writeDefaultModelConfig(modelConfig, {
2675
2693
  ...normalized,
2676
2694
  provider: providerKey,
@@ -2858,7 +2876,12 @@ async function saveHermesModelDefaults(input, profileName = "default", configPat
2858
2876
  const modelConfig = ensureRecord(config, "model");
2859
2877
  const currentDefaultConfig = readModelConfig(modelConfig);
2860
2878
  const currentDefaultReasoningEffort = readProfileReasoningEffort(config);
2861
- if (currentDefaultConfig.model && currentDefaultConfig.model !== selected.id) {
2879
+ if (currentDefaultConfig.model && !modelConfigMatchesModelIdentity(currentDefaultConfig, {
2880
+ id: selected.id,
2881
+ provider: selected.provider,
2882
+ baseUrl: selected.baseUrl,
2883
+ apiMode: selected.apiMode
2884
+ })) {
2862
2885
  retainModelDefaultAsProvider(providers, {
2863
2886
  ...currentDefaultConfig,
2864
2887
  ...currentDefaultReasoningEffort ? { reasoningEffort: currentDefaultReasoningEffort } : {}
@@ -3472,6 +3495,11 @@ function providerConfigToLegacyEntry(providerKey, entry) {
3472
3495
  }
3473
3496
  function ensureProvidersRecordWithLegacyMigration(config) {
3474
3497
  const providers = ensureRecord(config, "providers");
3498
+ for (const [key, rawEntry] of Object.entries(providers)) {
3499
+ const entry = toRecord(rawEntry);
3500
+ normalizeProviderCredentialReference(entry);
3501
+ providers[key] = entry;
3502
+ }
3475
3503
  const customProviders = Array.isArray(config.custom_providers) ? config.custom_providers : [];
3476
3504
  for (const rawEntry of customProviders) {
3477
3505
  const entry = toRecord(rawEntry);
@@ -3609,7 +3637,7 @@ function legacyEntryToProviderConfig(entry, baseUrl) {
3609
3637
  const apiKey = readString2(entry.api_key);
3610
3638
  const keyEnv = readString2(entry.key_env) ?? parseEnvReference(apiKey);
3611
3639
  if (keyEnv) {
3612
- next.key_env = keyEnv;
3640
+ next.api_key = `\${${keyEnv}}`;
3613
3641
  } else if (apiKey) {
3614
3642
  next.api_key = apiKey;
3615
3643
  }
@@ -3643,6 +3671,15 @@ function legacyEntryToProviderConfig(entry, baseUrl) {
3643
3671
  }
3644
3672
  return next;
3645
3673
  }
3674
+ function normalizeProviderCredentialReference(entry) {
3675
+ const apiKey = readString2(entry.api_key);
3676
+ const keyEnv = readString2(entry.key_env) ?? parseEnvReference(apiKey);
3677
+ if (!keyEnv) {
3678
+ return;
3679
+ }
3680
+ entry.api_key = `\${${keyEnv}}`;
3681
+ delete entry.key_env;
3682
+ }
3646
3683
  function normalizeEntryModelsMap(entry) {
3647
3684
  const models = entry.models;
3648
3685
  if (typeof models === "object" && models !== null && !Array.isArray(models)) {
@@ -3780,8 +3817,8 @@ function writeProviderEndpointConfig(entry, input, keyEnv) {
3780
3817
  delete entry.contextLength;
3781
3818
  }
3782
3819
  if (keyEnv) {
3783
- entry.key_env = keyEnv;
3784
- delete entry.api_key;
3820
+ entry.api_key = `\${${keyEnv}}`;
3821
+ delete entry.key_env;
3785
3822
  } else {
3786
3823
  delete entry.key_env;
3787
3824
  }
@@ -4041,22 +4078,18 @@ function managedModelPreferenceScore(model) {
4041
4078
  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
4079
  }
4043
4080
  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) {
4081
+ if (!input.defaultModel) {
4049
4082
  return false;
4050
4083
  }
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;
4084
+ return modelConfigMatchesModelIdentity(
4085
+ { ...input.modelConfig, model: input.defaultModel },
4086
+ {
4087
+ id: input.id,
4088
+ provider: input.provider,
4089
+ baseUrl: input.baseUrl,
4090
+ apiMode: input.apiMode
4091
+ }
4092
+ );
4060
4093
  }
4061
4094
  function providerEntryMatchesManagedModel(entry, model) {
4062
4095
  const providerName = readString2(entry.name) ?? readString2(entry.provider_name) ?? readString2(entry.provider_key) ?? "Custom Provider";
@@ -4266,6 +4299,29 @@ function resolveCompressionModel(config, models) {
4266
4299
  function findManagedModelById(models, id) {
4267
4300
  return models.find((model) => model.id === id);
4268
4301
  }
4302
+ function modelConfigMatchesModelIdentity(modelConfig, input) {
4303
+ if (modelConfig.model !== input.id) {
4304
+ return false;
4305
+ }
4306
+ const provider = input.provider?.trim();
4307
+ const configProvider = modelConfig.provider?.trim();
4308
+ if (provider && configProvider && configProvider !== provider) {
4309
+ return false;
4310
+ }
4311
+ const baseUrl = input.baseUrl?.trim();
4312
+ if (baseUrl !== void 0 && normalizeBaseUrl(modelConfig.baseUrl ?? "") !== normalizeBaseUrl(baseUrl)) {
4313
+ return false;
4314
+ }
4315
+ const apiMode = input.apiMode?.trim();
4316
+ if (apiMode) {
4317
+ const providerForApiMode = configProvider ?? provider ?? "default";
4318
+ const configBaseUrl = modelConfig.baseUrl ?? baseUrl ?? "";
4319
+ if (inferApiMode(providerForApiMode, configBaseUrl, modelConfig.apiMode) !== apiMode) {
4320
+ return false;
4321
+ }
4322
+ }
4323
+ return true;
4324
+ }
4269
4325
  function findManagedModel(models, input) {
4270
4326
  const provider = input.provider?.trim();
4271
4327
  const baseUrl = input.baseUrl?.trim();
@@ -4430,7 +4486,7 @@ function retainModelDefaultAsProvider(providers, model) {
4430
4486
  writeEntryModelSupportsVision(entry, id, model.supportsVision);
4431
4487
  }
4432
4488
  if (model.keyEnv) {
4433
- entry.key_env = model.keyEnv;
4489
+ entry.api_key = `\${${model.keyEnv}}`;
4434
4490
  } else if (model.apiKey) {
4435
4491
  entry.api_key = model.apiKey;
4436
4492
  }
@@ -6454,7 +6510,7 @@ function isConversationMissingError(error) {
6454
6510
  }
6455
6511
 
6456
6512
  // src/constants.ts
6457
- var LINK_VERSION = "0.7.4";
6513
+ var LINK_VERSION = "0.7.5";
6458
6514
  var LINK_COMMAND = "hermeslink";
6459
6515
  var LINK_DEFAULT_PORT = 52379;
6460
6516
  var LINK_RUNTIME_DIR_NAME = ".hermeslink";
@@ -24497,11 +24553,15 @@ function registerConversationRoutes(router, options) {
24497
24553
  router.get("/api/v1/conversations", async (ctx) => {
24498
24554
  const auth = await authenticateRequest(ctx, paths);
24499
24555
  const language = readPreferredLanguage(ctx);
24500
- await prepareConversationListRead(conversations, logger, auth);
24556
+ const cursor = readConversationListCursor(ctx.query);
24557
+ const forceSync = readConversationListForce(ctx.query);
24558
+ await prepareConversationListRead(conversations, logger, auth, {
24559
+ syncHermesSessions: forceSync || !cursor
24560
+ });
24501
24561
  ctx.set("cache-control", "no-store");
24502
24562
  const result = await conversations.listConversationPage({
24503
24563
  limit: readLimit(ctx.query.limit),
24504
- cursor: readQueryString(ctx.query.cursor) ?? readQueryString(ctx.query.after) ?? readQueryString(ctx.query.page_cursor)
24564
+ cursor
24505
24565
  });
24506
24566
  const localized = localizeConversationListPage(result, language);
24507
24567
  ctx.body = {
@@ -25014,7 +25074,15 @@ function registerConversationRoutes(router, options) {
25014
25074
  }
25015
25075
  );
25016
25076
  }
25017
- async function prepareConversationListRead(conversations, logger, auth) {
25077
+ async function prepareConversationListRead(conversations, logger, auth, options = {}) {
25078
+ if (options.syncHermesSessions) {
25079
+ await conversations.syncHermesSessions().catch((error) => {
25080
+ void logger.warn("hermes_session_sync_failed", {
25081
+ source: "conversation_list_read",
25082
+ error: error instanceof Error ? error.message : String(error)
25083
+ });
25084
+ });
25085
+ }
25018
25086
  await conversations.backfillCronOwnership({
25019
25087
  accountId: auth.accountId,
25020
25088
  appInstanceId: auth.appInstanceId
@@ -25030,6 +25098,13 @@ async function prepareConversationListRead(conversations, logger, auth) {
25030
25098
  });
25031
25099
  });
25032
25100
  }
25101
+ function readConversationListCursor(query) {
25102
+ return readQueryString(query.cursor) ?? readQueryString(query.after) ?? readQueryString(query.page_cursor);
25103
+ }
25104
+ function readConversationListForce(query) {
25105
+ const raw = Array.isArray(query.force) ? query.force[0] : query.force;
25106
+ return readBoolean3(raw) === true;
25107
+ }
25033
25108
  function localizeConversationListPage(page, language) {
25034
25109
  return {
25035
25110
  ...page,
@@ -25783,7 +25858,9 @@ function registerModelConfigRoutes(router, options) {
25783
25858
  const body = await readJsonBody(ctx.req);
25784
25859
  try {
25785
25860
  const result = await saveHermesModelConfig(readModelConfigInput(body));
25786
- ctx.body = shouldReloadGatewayAfterModelConfigChange(body) ? await reloadGatewayAfterModelConfigChange(result, {
25861
+ ctx.body = shouldReloadGatewayAfterModelConfigChange(body, {
25862
+ defaultReload: true
25863
+ }) ? await reloadGatewayAfterModelConfigChange(result, {
25787
25864
  paths,
25788
25865
  logger
25789
25866
  }) : markModelConfigAppliedWithoutGatewayReload(result);
@@ -25829,7 +25906,9 @@ function registerModelConfigRoutes(router, options) {
25829
25906
  readModelConfigInput(body),
25830
25907
  ctx.params.name
25831
25908
  );
25832
- ctx.body = shouldReloadGatewayAfterModelConfigChange(body) ? await reloadGatewayAfterProfileModelConfigChange(result, {
25909
+ ctx.body = shouldReloadGatewayAfterModelConfigChange(body, {
25910
+ defaultReload: true
25911
+ }) ? await reloadGatewayAfterProfileModelConfigChange(result, {
25833
25912
  paths,
25834
25913
  logger,
25835
25914
  profileName: ctx.params.name
@@ -25982,15 +26061,20 @@ function readModelConfigImportInput(body) {
25982
26061
  setDefault: readBoolean3(body.set_default ?? body.setDefault)
25983
26062
  };
25984
26063
  }
25985
- function shouldReloadGatewayAfterModelConfigChange(body) {
25986
- const explicit = readBoolean3(body.reload_gateway ?? body.reloadGateway) ?? (readBoolean3(body.skip_gateway_reload ?? body.skipGatewayReload) === true ? false : void 0);
25987
- return explicit ?? false;
26064
+ function shouldReloadGatewayAfterModelConfigChange(body, options = {}) {
26065
+ if (readBoolean3(body.skip_gateway_reload ?? body.skipGatewayReload) === true) {
26066
+ return false;
26067
+ }
26068
+ if (readBoolean3(body.reload_gateway ?? body.reloadGateway) === true) {
26069
+ return true;
26070
+ }
26071
+ return options.defaultReload === true;
25988
26072
  }
25989
26073
  function markModelConfigAppliedWithoutGatewayReload(result) {
25990
26074
  return {
25991
26075
  ...result,
25992
26076
  requiresGatewayReload: false,
25993
- restartHint: "\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"
26077
+ restartHint: "\u6A21\u578B\u914D\u7F6E\u5DF2\u4FDD\u5B58\uFF0C\u5DF2\u6309\u8BF7\u6C42\u8DF3\u8FC7 Hermes Gateway \u91CD\u8F7D\u3002\u82E5\u521A\u4FEE\u6539 API Key\u3001Base URL \u6216 API Mode\uFF0C\u8BF7\u624B\u52A8\u91CD\u8F7D Gateway \u540E\u518D\u5F00\u59CB\u65B0\u7684 Run\u3002"
25994
26078
  };
25995
26079
  }
25996
26080
  function toModelConfigHttpError(error) {
package/dist/cli/index.js CHANGED
@@ -53,7 +53,7 @@ import {
53
53
  stopDaemonProcess,
54
54
  summarizeUsageProbeEnsure,
55
55
  translate
56
- } from "../chunk-3A6ENKXD.js";
56
+ } from "../chunk-MVKOUAVA.js";
57
57
 
58
58
  // src/cli/index.ts
59
59
  import { Command } from "commander";
package/dist/http/app.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createApp
3
- } from "../chunk-3A6ENKXD.js";
3
+ } from "../chunk-MVKOUAVA.js";
4
4
  export {
5
5
  createApp
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hermespilot/link",
3
- "version": "0.7.4",
3
+ "version": "0.7.5",
4
4
  "private": false,
5
5
  "description": "Hermes Link companion service and CLI for connecting hermes-agent through HermesPilot",
6
6
  "license": "MIT",