@hermespilot/link 0.4.4 → 0.4.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.
|
@@ -3602,17 +3602,6 @@ function isEnvValueConfigured(value) {
|
|
|
3602
3602
|
}
|
|
3603
3603
|
async function readHermesApiServerEnvOverrides(profileName) {
|
|
3604
3604
|
const values = await readHermesEnvFile(profileName);
|
|
3605
|
-
for (const key of [
|
|
3606
|
-
"API_SERVER_ENABLED",
|
|
3607
|
-
"API_SERVER_HOST",
|
|
3608
|
-
"API_SERVER_PORT",
|
|
3609
|
-
"API_SERVER_KEY"
|
|
3610
|
-
]) {
|
|
3611
|
-
const value = process.env[key];
|
|
3612
|
-
if (typeof value === "string" && value.trim()) {
|
|
3613
|
-
values[key] = value;
|
|
3614
|
-
}
|
|
3615
|
-
}
|
|
3616
3605
|
const port = Number.parseInt(values.API_SERVER_PORT ?? "", 10);
|
|
3617
3606
|
return {
|
|
3618
3607
|
enabled: parseEnvBoolean(values.API_SERVER_ENABLED),
|
|
@@ -3996,7 +3985,7 @@ import os2 from "os";
|
|
|
3996
3985
|
import path5 from "path";
|
|
3997
3986
|
|
|
3998
3987
|
// src/constants.ts
|
|
3999
|
-
var LINK_VERSION = "0.4.
|
|
3988
|
+
var LINK_VERSION = "0.4.5";
|
|
4000
3989
|
var LINK_COMMAND = "hermeslink";
|
|
4001
3990
|
var LINK_DEFAULT_PORT = 52379;
|
|
4002
3991
|
var LINK_RUNTIME_DIR_NAME = ".hermeslink";
|
|
@@ -4496,6 +4485,7 @@ var DASHBOARD_STATUS_URL = "http://127.0.0.1:9119/api/status";
|
|
|
4496
4485
|
var DASHBOARD_STATUS_TIMEOUT_MS = 1500;
|
|
4497
4486
|
var DEFAULT_VERSION_CACHE_TTL_MS = 6e4;
|
|
4498
4487
|
var MAX_VERSION_LOG_OUTPUT_LENGTH = 1200;
|
|
4488
|
+
var HERMES_GATEWAY_ENV_BLOCKLIST_PREFIXES = ["API_SERVER_"];
|
|
4499
4489
|
var gatewayStartInFlightByProfile = /* @__PURE__ */ new Map();
|
|
4500
4490
|
var hermesVersionCache = /* @__PURE__ */ new Map();
|
|
4501
4491
|
var hermesVersionInFlight = /* @__PURE__ */ new Map();
|
|
@@ -4720,7 +4710,7 @@ async function startHermesGateway(paths, profileName, logger) {
|
|
|
4720
4710
|
try {
|
|
4721
4711
|
const child = spawn(resolveHermesBin(), gatewayRunArgs(profileName), {
|
|
4722
4712
|
detached: true,
|
|
4723
|
-
env:
|
|
4713
|
+
env: buildHermesGatewayChildEnv(),
|
|
4724
4714
|
stdio: ["ignore", "pipe", "pipe"],
|
|
4725
4715
|
windowsHide: true
|
|
4726
4716
|
});
|
|
@@ -4786,6 +4776,7 @@ async function restartHermesGatewayServiceIfAvailable(options) {
|
|
|
4786
4776
|
}
|
|
4787
4777
|
try {
|
|
4788
4778
|
await execFileAsync2(resolveHermesBin(), ["gateway", "restart"], {
|
|
4779
|
+
env: buildHermesGatewayChildEnv(),
|
|
4789
4780
|
timeout: options.timeoutMs ?? DEFAULT_START_TIMEOUT_MS,
|
|
4790
4781
|
windowsHide: true
|
|
4791
4782
|
});
|
|
@@ -4840,6 +4831,17 @@ function gatewayRunArgs(profileName) {
|
|
|
4840
4831
|
function formatHermesGatewayRunCommand(profileName) {
|
|
4841
4832
|
return `${resolveHermesBin()} ${gatewayRunArgs(profileName).join(" ")}`;
|
|
4842
4833
|
}
|
|
4834
|
+
function buildHermesGatewayChildEnv() {
|
|
4835
|
+
const env = { ...process.env };
|
|
4836
|
+
for (const key of Object.keys(env)) {
|
|
4837
|
+
if (HERMES_GATEWAY_ENV_BLOCKLIST_PREFIXES.some(
|
|
4838
|
+
(prefix) => key.startsWith(prefix)
|
|
4839
|
+
)) {
|
|
4840
|
+
delete env[key];
|
|
4841
|
+
}
|
|
4842
|
+
}
|
|
4843
|
+
return env;
|
|
4844
|
+
}
|
|
4843
4845
|
async function fileExists(filePath) {
|
|
4844
4846
|
return access(filePath).then(() => true).catch(() => false);
|
|
4845
4847
|
}
|
|
@@ -4875,6 +4877,10 @@ async function readHermesApiServerHealth(config, fetcher = fetch) {
|
|
|
4875
4877
|
}
|
|
4876
4878
|
const issue = describeDetailedHealthIssue(record);
|
|
4877
4879
|
const terminal = isTerminalDetailedHealth(record);
|
|
4880
|
+
const authIssue = issue ? null : await probeHermesApiServerAuth(resolvedConfig, fetcher);
|
|
4881
|
+
if (authIssue) {
|
|
4882
|
+
return authIssue;
|
|
4883
|
+
}
|
|
4878
4884
|
return {
|
|
4879
4885
|
healthy: !issue,
|
|
4880
4886
|
terminal,
|
|
@@ -4911,20 +4917,9 @@ async function readHermesApiServerHealth(config, fetcher = fetch) {
|
|
|
4911
4917
|
};
|
|
4912
4918
|
}
|
|
4913
4919
|
if (resolvedConfig.key) {
|
|
4914
|
-
const
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
method: "GET",
|
|
4918
|
-
headers: authHeaders(resolvedConfig)
|
|
4919
|
-
},
|
|
4920
|
-
fetcher
|
|
4921
|
-
);
|
|
4922
|
-
if (authProbe?.status === 401) {
|
|
4923
|
-
return {
|
|
4924
|
-
healthy: false,
|
|
4925
|
-
authInvalid: true,
|
|
4926
|
-
issue: "Hermes API Server \u8FD4\u56DE 401\uFF0C\u5F53\u524D\u7AEF\u53E3\u53EF\u80FD\u5C5E\u4E8E\u53E6\u4E00\u4E2A Profile\uFF0C\u6216 API Server key \u5DF2\u53D8\u66F4\u3002"
|
|
4927
|
-
};
|
|
4920
|
+
const authIssue = await probeHermesApiServerAuth(resolvedConfig, fetcher);
|
|
4921
|
+
if (authIssue) {
|
|
4922
|
+
return authIssue;
|
|
4928
4923
|
}
|
|
4929
4924
|
}
|
|
4930
4925
|
return { healthy: true };
|
|
@@ -4941,6 +4936,39 @@ async function readHermesApiServerHealth(config, fetcher = fetch) {
|
|
|
4941
4936
|
issue: describePortHealthFailure(resolvedConfig.port)
|
|
4942
4937
|
};
|
|
4943
4938
|
}
|
|
4939
|
+
async function probeHermesApiServerAuth(config, fetcher) {
|
|
4940
|
+
if (!config.key) {
|
|
4941
|
+
return null;
|
|
4942
|
+
}
|
|
4943
|
+
const response = await fetchWithTimeout(
|
|
4944
|
+
`http://127.0.0.1:${config.port}/v1/models`,
|
|
4945
|
+
{
|
|
4946
|
+
method: "GET",
|
|
4947
|
+
headers: authHeaders(config)
|
|
4948
|
+
},
|
|
4949
|
+
fetcher
|
|
4950
|
+
);
|
|
4951
|
+
if (!response) {
|
|
4952
|
+
return {
|
|
4953
|
+
healthy: false,
|
|
4954
|
+
issue: "Hermes API Server \u9274\u6743\u63A2\u6D4B\u65E0\u54CD\u5E94\uFF1A/v1/models \u6CA1\u6709\u5728\u8D85\u65F6\u65F6\u95F4\u5185\u8FD4\u56DE\u3002"
|
|
4955
|
+
};
|
|
4956
|
+
}
|
|
4957
|
+
if (response?.status === 401) {
|
|
4958
|
+
return {
|
|
4959
|
+
healthy: false,
|
|
4960
|
+
authInvalid: true,
|
|
4961
|
+
issue: "Hermes API Server \u8FD4\u56DE 401\uFF0C\u5F53\u524D\u7AEF\u53E3\u53EF\u80FD\u5C5E\u4E8E\u53E6\u4E00\u4E2A Profile\uFF0C\u6216 API Server key \u5DF2\u53D8\u66F4\u3002"
|
|
4962
|
+
};
|
|
4963
|
+
}
|
|
4964
|
+
if (response && !response.ok) {
|
|
4965
|
+
return {
|
|
4966
|
+
healthy: false,
|
|
4967
|
+
issue: `Hermes API Server \u9274\u6743\u63A2\u6D4B\u5931\u8D25\uFF1A/v1/models \u8FD4\u56DE HTTP ${response.status}\u3002`
|
|
4968
|
+
};
|
|
4969
|
+
}
|
|
4970
|
+
return null;
|
|
4971
|
+
}
|
|
4944
4972
|
function fetchWithTimeout(input, init, fetcher) {
|
|
4945
4973
|
const controller = new AbortController();
|
|
4946
4974
|
const timer = setTimeout(() => controller.abort(), HEALTH_TIMEOUT_MS);
|
|
@@ -8979,24 +9007,15 @@ var ConversationQueryCoordinator = class {
|
|
|
8979
9007
|
async listConversationPage(options = {}) {
|
|
8980
9008
|
const limit = normalizeConversationListPageLimit(options.limit);
|
|
8981
9009
|
const cursor = decodeConversationListCursor(options.cursor);
|
|
8982
|
-
|
|
9010
|
+
return this.listIndexedConversationPage({
|
|
8983
9011
|
limit,
|
|
8984
|
-
cursor
|
|
8985
|
-
|
|
8986
|
-
|
|
8987
|
-
return this.listConversationPageFromStore({ limit, cursor });
|
|
8988
|
-
}
|
|
8989
|
-
const summaries = await this.summarizeIndexedConversations(
|
|
8990
|
-
indexedPage.records
|
|
8991
|
-
);
|
|
8992
|
-
return {
|
|
8993
|
-
conversations: summaries,
|
|
8994
|
-
page: {
|
|
9012
|
+
cursor,
|
|
9013
|
+
fallback: () => this.listConversationPageFromStore({ limit, cursor }),
|
|
9014
|
+
listPage: (pageCursor) => listConversationStatsPage(this.deps.paths, {
|
|
8995
9015
|
limit,
|
|
8996
|
-
|
|
8997
|
-
|
|
8998
|
-
|
|
8999
|
-
};
|
|
9016
|
+
cursor: pageCursor
|
|
9017
|
+
})
|
|
9018
|
+
});
|
|
9000
9019
|
}
|
|
9001
9020
|
async searchConversationPage(options = {}) {
|
|
9002
9021
|
const query = normalizeConversationSearchQuery(options.query);
|
|
@@ -9005,20 +9024,72 @@ var ConversationQueryCoordinator = class {
|
|
|
9005
9024
|
}
|
|
9006
9025
|
const limit = normalizeConversationListPageLimit(options.limit);
|
|
9007
9026
|
const cursor = decodeConversationListCursor(options.cursor);
|
|
9008
|
-
|
|
9027
|
+
return this.listIndexedConversationPage({
|
|
9009
9028
|
limit,
|
|
9010
9029
|
cursor,
|
|
9011
|
-
|
|
9030
|
+
listPage: (pageCursor) => searchConversationStatsPage(this.deps.paths, {
|
|
9031
|
+
limit,
|
|
9032
|
+
cursor: pageCursor,
|
|
9033
|
+
query
|
|
9034
|
+
})
|
|
9012
9035
|
});
|
|
9013
|
-
|
|
9014
|
-
|
|
9015
|
-
|
|
9036
|
+
}
|
|
9037
|
+
async listIndexedConversationPage(input) {
|
|
9038
|
+
const collected = [];
|
|
9039
|
+
const seenConversationIds = /* @__PURE__ */ new Set();
|
|
9040
|
+
let scanCursor = input.cursor;
|
|
9041
|
+
let usedIndex = false;
|
|
9042
|
+
while (collected.length <= input.limit) {
|
|
9043
|
+
const indexedPage = await input.listPage(scanCursor);
|
|
9044
|
+
if (indexedPage.records.length === 0) {
|
|
9045
|
+
if (!usedIndex && input.fallback) {
|
|
9046
|
+
return input.fallback();
|
|
9047
|
+
}
|
|
9048
|
+
return this.buildConversationListPage({
|
|
9049
|
+
limit: input.limit,
|
|
9050
|
+
conversations: collected,
|
|
9051
|
+
hasMore: false
|
|
9052
|
+
});
|
|
9053
|
+
}
|
|
9054
|
+
usedIndex = true;
|
|
9055
|
+
const summaries = await this.summarizeIndexedConversations(
|
|
9056
|
+
indexedPage.records
|
|
9057
|
+
);
|
|
9058
|
+
for (const summary of summaries) {
|
|
9059
|
+
if (!seenConversationIds.add(summary.id)) {
|
|
9060
|
+
continue;
|
|
9061
|
+
}
|
|
9062
|
+
collected.push(summary);
|
|
9063
|
+
if (collected.length > input.limit) {
|
|
9064
|
+
break;
|
|
9065
|
+
}
|
|
9066
|
+
}
|
|
9067
|
+
if (collected.length > input.limit) {
|
|
9068
|
+
break;
|
|
9069
|
+
}
|
|
9070
|
+
if (!indexedPage.hasMore) {
|
|
9071
|
+
return this.buildConversationListPage({
|
|
9072
|
+
limit: input.limit,
|
|
9073
|
+
conversations: collected,
|
|
9074
|
+
hasMore: false
|
|
9075
|
+
});
|
|
9076
|
+
}
|
|
9077
|
+
scanCursor = conversationListCursorFromRecord(indexedPage.records.at(-1));
|
|
9078
|
+
}
|
|
9079
|
+
return this.buildConversationListPage({
|
|
9080
|
+
limit: input.limit,
|
|
9081
|
+
conversations: collected,
|
|
9082
|
+
hasMore: true
|
|
9083
|
+
});
|
|
9084
|
+
}
|
|
9085
|
+
buildConversationListPage(input) {
|
|
9086
|
+
const conversations = input.conversations.slice(0, input.limit);
|
|
9016
9087
|
return {
|
|
9017
|
-
conversations
|
|
9088
|
+
conversations,
|
|
9018
9089
|
page: {
|
|
9019
|
-
limit,
|
|
9020
|
-
has_more:
|
|
9021
|
-
next_cursor:
|
|
9090
|
+
limit: input.limit,
|
|
9091
|
+
has_more: input.hasMore,
|
|
9092
|
+
next_cursor: input.hasMore && conversations.length > 0 ? encodeConversationListCursorFromSummary(conversations.at(-1)) : null
|
|
9022
9093
|
}
|
|
9023
9094
|
};
|
|
9024
9095
|
}
|
|
@@ -9177,6 +9248,12 @@ function encodeConversationListCursor(record) {
|
|
|
9177
9248
|
"utf8"
|
|
9178
9249
|
).toString("base64url");
|
|
9179
9250
|
}
|
|
9251
|
+
function conversationListCursorFromRecord(record) {
|
|
9252
|
+
return {
|
|
9253
|
+
updatedAt: record.updatedAt,
|
|
9254
|
+
conversationId: record.conversationId
|
|
9255
|
+
};
|
|
9256
|
+
}
|
|
9180
9257
|
function encodeConversationListCursorFromSummary(summary) {
|
|
9181
9258
|
return encodeConversationListCursor({
|
|
9182
9259
|
conversationId: summary.id,
|
|
@@ -20659,6 +20736,8 @@ import { mkdir as mkdir12, rm as rm9, writeFile as writeFile3 } from "fs/promise
|
|
|
20659
20736
|
|
|
20660
20737
|
// src/relay/control-client.ts
|
|
20661
20738
|
import WebSocket from "ws";
|
|
20739
|
+
var RELAY_SSE_BATCH_FLUSH_INTERVAL_MS = 50;
|
|
20740
|
+
var RELAY_SSE_BATCH_FLUSH_BYTES = 2 * 1024;
|
|
20662
20741
|
function connectRelayControl(options) {
|
|
20663
20742
|
const wsUrl = new URL(`${options.relayBaseUrl.replace(/\/+$/u, "")}/api/v1/relay/link/connect`);
|
|
20664
20743
|
wsUrl.protocol = wsUrl.protocol === "https:" ? "wss:" : "ws:";
|
|
@@ -20794,6 +20873,7 @@ async function handleFrame(socket, raw, localPort, abortControllers) {
|
|
|
20794
20873
|
}
|
|
20795
20874
|
const abortController = new AbortController();
|
|
20796
20875
|
abortControllers.set(frame.id, abortController);
|
|
20876
|
+
let sseBatcher = null;
|
|
20797
20877
|
try {
|
|
20798
20878
|
const response = await fetch(`http://127.0.0.1:${localPort}${frame.path}`, {
|
|
20799
20879
|
method: frame.method,
|
|
@@ -20805,14 +20885,16 @@ async function handleFrame(socket, raw, localPort, abortControllers) {
|
|
|
20805
20885
|
const contentType = response.headers.get("content-type") ?? "";
|
|
20806
20886
|
if (response.body && contentType.includes("text/event-stream")) {
|
|
20807
20887
|
socket.send(JSON.stringify({ type: "http.stream.start", id: frame.id, status: response.status, headers }));
|
|
20888
|
+
sseBatcher = createRelayStreamChunkBatcher(socket, frame.id);
|
|
20808
20889
|
const reader = response.body.getReader();
|
|
20809
20890
|
while (true) {
|
|
20810
20891
|
const next = await reader.read();
|
|
20811
20892
|
if (next.done) {
|
|
20812
20893
|
break;
|
|
20813
20894
|
}
|
|
20814
|
-
|
|
20895
|
+
sseBatcher.push(next.value);
|
|
20815
20896
|
}
|
|
20897
|
+
sseBatcher.flush();
|
|
20816
20898
|
socket.send(JSON.stringify({ type: "http.stream.end", id: frame.id }));
|
|
20817
20899
|
return;
|
|
20818
20900
|
}
|
|
@@ -20822,15 +20904,73 @@ async function handleFrame(socket, raw, localPort, abortControllers) {
|
|
|
20822
20904
|
if (abortController.signal.aborted || isAbortError2(error)) {
|
|
20823
20905
|
return;
|
|
20824
20906
|
}
|
|
20907
|
+
sseBatcher?.flush();
|
|
20825
20908
|
const message = error instanceof Error ? error.message : "Relay request failed";
|
|
20826
20909
|
socket.send(JSON.stringify({ type: "http.error", id: frame.id, status: 502, message }));
|
|
20827
20910
|
} finally {
|
|
20911
|
+
sseBatcher?.dispose();
|
|
20828
20912
|
abortControllers.delete(frame.id);
|
|
20829
20913
|
}
|
|
20830
20914
|
}
|
|
20831
20915
|
function isAbortError2(error) {
|
|
20832
20916
|
return typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
|
|
20833
20917
|
}
|
|
20918
|
+
function createRelayStreamChunkBatcher(socket, id) {
|
|
20919
|
+
let chunks = [];
|
|
20920
|
+
let totalBytes = 0;
|
|
20921
|
+
let flushTimer = null;
|
|
20922
|
+
const clearFlushTimer = () => {
|
|
20923
|
+
if (flushTimer == null) {
|
|
20924
|
+
return;
|
|
20925
|
+
}
|
|
20926
|
+
clearTimeout(flushTimer);
|
|
20927
|
+
flushTimer = null;
|
|
20928
|
+
};
|
|
20929
|
+
const flush = () => {
|
|
20930
|
+
clearFlushTimer();
|
|
20931
|
+
if (totalBytes <= 0) {
|
|
20932
|
+
return;
|
|
20933
|
+
}
|
|
20934
|
+
const bodyBase64 = Buffer.concat(chunks, totalBytes).toString("base64");
|
|
20935
|
+
chunks = [];
|
|
20936
|
+
totalBytes = 0;
|
|
20937
|
+
if (socket.readyState !== WebSocket.OPEN) {
|
|
20938
|
+
return;
|
|
20939
|
+
}
|
|
20940
|
+
socket.send(JSON.stringify({ type: "http.stream.chunk", id, bodyBase64 }));
|
|
20941
|
+
};
|
|
20942
|
+
const scheduleFlush = () => {
|
|
20943
|
+
if (flushTimer != null) {
|
|
20944
|
+
return;
|
|
20945
|
+
}
|
|
20946
|
+
flushTimer = setTimeout(() => {
|
|
20947
|
+
flushTimer = null;
|
|
20948
|
+
flush();
|
|
20949
|
+
}, RELAY_SSE_BATCH_FLUSH_INTERVAL_MS);
|
|
20950
|
+
flushTimer.unref?.();
|
|
20951
|
+
};
|
|
20952
|
+
return {
|
|
20953
|
+
push(chunk) {
|
|
20954
|
+
if (chunk.byteLength <= 0) {
|
|
20955
|
+
return;
|
|
20956
|
+
}
|
|
20957
|
+
const buffer = Buffer.from(chunk);
|
|
20958
|
+
chunks.push(buffer);
|
|
20959
|
+
totalBytes += buffer.byteLength;
|
|
20960
|
+
if (totalBytes >= RELAY_SSE_BATCH_FLUSH_BYTES) {
|
|
20961
|
+
flush();
|
|
20962
|
+
return;
|
|
20963
|
+
}
|
|
20964
|
+
scheduleFlush();
|
|
20965
|
+
},
|
|
20966
|
+
flush,
|
|
20967
|
+
dispose() {
|
|
20968
|
+
clearFlushTimer();
|
|
20969
|
+
chunks = [];
|
|
20970
|
+
totalBytes = 0;
|
|
20971
|
+
}
|
|
20972
|
+
};
|
|
20973
|
+
}
|
|
20834
20974
|
|
|
20835
20975
|
// src/runtime/system-info.ts
|
|
20836
20976
|
import { execFileSync } from "child_process";
|
package/dist/cli/index.js
CHANGED
package/dist/http/app.js
CHANGED