@hermespilot/link 0.4.7 → 0.4.9
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.
|
@@ -2110,13 +2110,13 @@ async function ensureHermesApiServerConfigUnlocked(profileName = "default", conf
|
|
|
2110
2110
|
const envHost = envOverrides.host?.trim() ? envOverrides.host : null;
|
|
2111
2111
|
const configPort = readApiServerPort(configOnly.port);
|
|
2112
2112
|
const envPort = readApiServerPort(envOverrides.port);
|
|
2113
|
-
const desiredHost =
|
|
2113
|
+
const desiredHost = envHost ?? configHost ?? DEFAULT_HERMES_API_SERVER_HOST;
|
|
2114
2114
|
const desiredPort = await resolveDesiredApiServerPort({
|
|
2115
2115
|
profileName,
|
|
2116
2116
|
configPort,
|
|
2117
2117
|
envPort
|
|
2118
2118
|
});
|
|
2119
|
-
const desiredKey =
|
|
2119
|
+
const desiredKey = envKey ?? configKey ?? randomBytes(32).toString("base64url");
|
|
2120
2120
|
let changed = false;
|
|
2121
2121
|
let enabledAdded = false;
|
|
2122
2122
|
let hostAdded = false;
|
|
@@ -2210,7 +2210,7 @@ async function repairHermesApiServerConfigUnlocked(profileName = "default", conf
|
|
|
2210
2210
|
await readHermesApiServerEnvOverrides(profileName),
|
|
2211
2211
|
true
|
|
2212
2212
|
);
|
|
2213
|
-
const freshPort = await nextProfileApiServerPort(profileName);
|
|
2213
|
+
const freshPort = profileName === "default" ? DEFAULT_HERMES_API_SERVER_PORT : await nextProfileApiServerPort(profileName);
|
|
2214
2214
|
const freshKey = randomBytes(32).toString("base64url");
|
|
2215
2215
|
apiServer.enabled = true;
|
|
2216
2216
|
extra.host = DEFAULT_HERMES_API_SERVER_HOST;
|
|
@@ -2239,7 +2239,7 @@ async function repairHermesApiServerConfigUnlocked(profileName = "default", conf
|
|
|
2239
2239
|
hostAdded: previous.host !== DEFAULT_HERMES_API_SERVER_HOST,
|
|
2240
2240
|
portAdded: previous.port !== freshPort,
|
|
2241
2241
|
backupPath,
|
|
2242
|
-
notice: "\u5DF2\u4E3A Hermes API Server \u91CD\u65B0\u5206\u914D\u672C\u673A\u7AEF\u53E3\u5E76\u8F6E\u6362 key\uFF0C\u7528\u4E8E\u4FEE\u590D\u65E7 Gateway \u6216 key \u4E0D\u4E00\u81F4\u5BFC\u81F4\u7684 401\u3002"
|
|
2242
|
+
notice: profileName === "default" ? "\u5DF2\u4E3A Hermes API Server \u4FDD\u6301\u9ED8\u8BA4\u7AEF\u53E3\u5E76\u8F6E\u6362 key\uFF0C\u7528\u4E8E\u4FEE\u590D\u65E7 Gateway \u6216 key \u4E0D\u4E00\u81F4\u5BFC\u81F4\u7684 401\u3002" : "\u5DF2\u4E3A Hermes API Server \u91CD\u65B0\u5206\u914D\u672C\u673A\u7AEF\u53E3\u5E76\u8F6E\u6362 key\uFF0C\u7528\u4E8E\u4FEE\u590D\u65E7 Gateway \u6216 key \u4E0D\u4E00\u81F4\u5BFC\u81F4\u7684 401\u3002"
|
|
2243
2243
|
};
|
|
2244
2244
|
}
|
|
2245
2245
|
async function readHermesConfigDocument(configPath) {
|
|
@@ -3579,16 +3579,14 @@ function readApiServerPort(value) {
|
|
|
3579
3579
|
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
|
|
3580
3580
|
}
|
|
3581
3581
|
async function resolveDesiredApiServerPort(input) {
|
|
3582
|
+
const effectivePort = input.envPort ?? input.configPort;
|
|
3582
3583
|
if (shouldAssignDedicatedProfileApiServerPort(
|
|
3583
3584
|
input.profileName,
|
|
3584
|
-
|
|
3585
|
+
effectivePort ?? void 0
|
|
3585
3586
|
)) {
|
|
3586
|
-
if (input.profileName !== "default" && input.envPort !== null && input.envPort !== DEFAULT_HERMES_API_SERVER_PORT) {
|
|
3587
|
-
return input.envPort;
|
|
3588
|
-
}
|
|
3589
3587
|
return nextProfileApiServerPort(input.profileName);
|
|
3590
3588
|
}
|
|
3591
|
-
return
|
|
3589
|
+
return effectivePort ?? DEFAULT_HERMES_API_SERVER_PORT;
|
|
3592
3590
|
}
|
|
3593
3591
|
async function nextProfileApiServerPort(profileName) {
|
|
3594
3592
|
const usedPorts = await readConfiguredApiServerPorts(profileName);
|
|
@@ -3877,6 +3875,9 @@ function buildNotice(flags) {
|
|
|
3877
3875
|
if (flags.keyAdded) {
|
|
3878
3876
|
fields.push("key");
|
|
3879
3877
|
}
|
|
3878
|
+
if (fields.length === 0) {
|
|
3879
|
+
return "\u5DF2\u540C\u6B65 Hermes API Server config.yaml \u4E0E Profile .env \u7684\u6709\u6548\u914D\u7F6E\u3002";
|
|
3880
|
+
}
|
|
3880
3881
|
return `\u5DF2\u4E3A Hermes API Server \u81EA\u52A8\u8865\u5145 ${fields.join("\u3001")}\uFF1B\u672A\u8986\u76D6\u5DF2\u6709 port/host/key\u3002`;
|
|
3881
3882
|
}
|
|
3882
3883
|
function toRecord(value) {
|
|
@@ -4117,7 +4118,8 @@ function isConversationMissingError(error) {
|
|
|
4117
4118
|
|
|
4118
4119
|
// src/hermes/gateway.ts
|
|
4119
4120
|
import { execFile as execFile2, spawn } from "child_process";
|
|
4120
|
-
import {
|
|
4121
|
+
import { constants as fsConstants } from "fs";
|
|
4122
|
+
import { access, readFile as readFile5, realpath, stat as stat4 } from "fs/promises";
|
|
4121
4123
|
import path7 from "path";
|
|
4122
4124
|
import { setTimeout as delay } from "timers/promises";
|
|
4123
4125
|
import { promisify as promisify2 } from "util";
|
|
@@ -4132,7 +4134,7 @@ import os2 from "os";
|
|
|
4132
4134
|
import path5 from "path";
|
|
4133
4135
|
|
|
4134
4136
|
// src/constants.ts
|
|
4135
|
-
var LINK_VERSION = "0.4.
|
|
4137
|
+
var LINK_VERSION = "0.4.9";
|
|
4136
4138
|
var LINK_COMMAND = "hermeslink";
|
|
4137
4139
|
var LINK_DEFAULT_PORT = 52379;
|
|
4138
4140
|
var LINK_RUNTIME_DIR_NAME = ".hermeslink";
|
|
@@ -4631,6 +4633,8 @@ var PROFILE_NAME_PATTERN = /^[a-zA-Z0-9._-]{1,64}$/u;
|
|
|
4631
4633
|
var DASHBOARD_STATUS_URL = "http://127.0.0.1:9119/api/status";
|
|
4632
4634
|
var DASHBOARD_STATUS_TIMEOUT_MS = 1500;
|
|
4633
4635
|
var DEFAULT_VERSION_CACHE_TTL_MS = 6e4;
|
|
4636
|
+
var VERSION_METADATA_TIMEOUT_MS = 3e3;
|
|
4637
|
+
var VERSION_COMMAND_TIMEOUT_MS = 5e3;
|
|
4634
4638
|
var MAX_VERSION_LOG_OUTPUT_LENGTH = 1200;
|
|
4635
4639
|
var HERMES_GATEWAY_ENV_BLOCKLIST_PREFIXES = ["API_SERVER_"];
|
|
4636
4640
|
var gatewayStartInFlightByProfile = /* @__PURE__ */ new Map();
|
|
@@ -4826,9 +4830,26 @@ async function readHermesVersion(options = {}) {
|
|
|
4826
4830
|
}
|
|
4827
4831
|
async function execHermesVersion(hermesBin, logger) {
|
|
4828
4832
|
const failures = [];
|
|
4833
|
+
try {
|
|
4834
|
+
const metadataVersion = await readHermesPythonMetadataVersion(
|
|
4835
|
+
hermesBin,
|
|
4836
|
+
VERSION_METADATA_TIMEOUT_MS
|
|
4837
|
+
);
|
|
4838
|
+
if (metadataVersion) {
|
|
4839
|
+
return metadataVersion;
|
|
4840
|
+
}
|
|
4841
|
+
} catch (error) {
|
|
4842
|
+
const failure = describeVersionMetadataFailure(hermesBin, error);
|
|
4843
|
+
failures.push(failure);
|
|
4844
|
+
void logger?.debug("hermes_version_metadata_probe_failed", failure.fields);
|
|
4845
|
+
}
|
|
4829
4846
|
for (const args of [["--version"], ["version"]]) {
|
|
4830
4847
|
try {
|
|
4831
|
-
return await spawnHermesVersionCommand(
|
|
4848
|
+
return await spawnHermesVersionCommand(
|
|
4849
|
+
hermesBin,
|
|
4850
|
+
args,
|
|
4851
|
+
VERSION_COMMAND_TIMEOUT_MS
|
|
4852
|
+
);
|
|
4832
4853
|
} catch (error) {
|
|
4833
4854
|
const failure = describeVersionCommandFailure(hermesBin, args, error);
|
|
4834
4855
|
failures.push(failure);
|
|
@@ -4844,6 +4865,109 @@ async function execHermesVersion(hermesBin, logger) {
|
|
|
4844
4865
|
});
|
|
4845
4866
|
throw new Error(summary);
|
|
4846
4867
|
}
|
|
4868
|
+
async function readHermesPythonMetadataVersion(hermesBin, timeoutMs) {
|
|
4869
|
+
const hermesPath = await resolveExecutablePath(hermesBin);
|
|
4870
|
+
if (!hermesPath) {
|
|
4871
|
+
return null;
|
|
4872
|
+
}
|
|
4873
|
+
const firstLine = await readFirstLine(hermesPath);
|
|
4874
|
+
const interpreter = parsePythonShebang(firstLine);
|
|
4875
|
+
if (!interpreter) {
|
|
4876
|
+
return null;
|
|
4877
|
+
}
|
|
4878
|
+
return await spawnHermesMetadataCommand(interpreter, timeoutMs);
|
|
4879
|
+
}
|
|
4880
|
+
async function spawnHermesMetadataCommand(interpreter, timeoutMs) {
|
|
4881
|
+
const script = [
|
|
4882
|
+
"import importlib.metadata as metadata",
|
|
4883
|
+
"try:",
|
|
4884
|
+
' version = metadata.version("hermes-agent")',
|
|
4885
|
+
' release_date = ""',
|
|
4886
|
+
"except Exception:",
|
|
4887
|
+
" import hermes_cli",
|
|
4888
|
+
' version = getattr(hermes_cli, "__version__", "")',
|
|
4889
|
+
' release_date = getattr(hermes_cli, "__release_date__", "")',
|
|
4890
|
+
"if not version:",
|
|
4891
|
+
' raise SystemExit("Hermes Agent version metadata not found")',
|
|
4892
|
+
'suffix = f" ({release_date})" if release_date else ""',
|
|
4893
|
+
'print(f"Hermes Agent v{version}{suffix}")',
|
|
4894
|
+
""
|
|
4895
|
+
].join("\n");
|
|
4896
|
+
return await new Promise((resolve, reject) => {
|
|
4897
|
+
const child = spawn(
|
|
4898
|
+
interpreter.command,
|
|
4899
|
+
[...interpreter.args, "-c", script],
|
|
4900
|
+
{
|
|
4901
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
4902
|
+
windowsHide: true,
|
|
4903
|
+
env: { ...process.env, HERMES_NONINTERACTIVE: "1" }
|
|
4904
|
+
}
|
|
4905
|
+
);
|
|
4906
|
+
let stdout = "";
|
|
4907
|
+
let stderr = "";
|
|
4908
|
+
let settled = false;
|
|
4909
|
+
const timer = setTimeout(() => {
|
|
4910
|
+
finish(() => {
|
|
4911
|
+
reject(
|
|
4912
|
+
createVersionCommandError(
|
|
4913
|
+
`Hermes Python metadata probe timed out after ${timeoutMs}ms`,
|
|
4914
|
+
{ stdout, stderr, killed: true }
|
|
4915
|
+
)
|
|
4916
|
+
);
|
|
4917
|
+
}, true);
|
|
4918
|
+
}, timeoutMs);
|
|
4919
|
+
const finish = (callback, kill = false) => {
|
|
4920
|
+
if (settled) {
|
|
4921
|
+
return;
|
|
4922
|
+
}
|
|
4923
|
+
settled = true;
|
|
4924
|
+
clearTimeout(timer);
|
|
4925
|
+
if (kill && child.pid && !child.killed) {
|
|
4926
|
+
child.kill();
|
|
4927
|
+
}
|
|
4928
|
+
callback();
|
|
4929
|
+
};
|
|
4930
|
+
child.stdout?.on("data", (chunk) => {
|
|
4931
|
+
stdout += chunk.toString();
|
|
4932
|
+
});
|
|
4933
|
+
child.stderr?.on("data", (chunk) => {
|
|
4934
|
+
stderr += chunk.toString();
|
|
4935
|
+
});
|
|
4936
|
+
child.once("error", (error) => {
|
|
4937
|
+
finish(() => {
|
|
4938
|
+
reject(
|
|
4939
|
+
createVersionCommandError(error.message, {
|
|
4940
|
+
stdout,
|
|
4941
|
+
stderr,
|
|
4942
|
+
cause: error
|
|
4943
|
+
})
|
|
4944
|
+
);
|
|
4945
|
+
});
|
|
4946
|
+
});
|
|
4947
|
+
child.once("close", (code, signal) => {
|
|
4948
|
+
finish(() => {
|
|
4949
|
+
const raw = `${stdout}
|
|
4950
|
+
${stderr}`.trim();
|
|
4951
|
+
if (code === 0 && parseHermesVersion(raw)) {
|
|
4952
|
+
resolve({ stdout: raw });
|
|
4953
|
+
return;
|
|
4954
|
+
}
|
|
4955
|
+
reject(
|
|
4956
|
+
createVersionCommandError(
|
|
4957
|
+
`Hermes Python metadata probe exited with code ${code ?? "null"}`,
|
|
4958
|
+
{
|
|
4959
|
+
stdout,
|
|
4960
|
+
stderr,
|
|
4961
|
+
code,
|
|
4962
|
+
signal,
|
|
4963
|
+
killed: child.killed
|
|
4964
|
+
}
|
|
4965
|
+
)
|
|
4966
|
+
);
|
|
4967
|
+
});
|
|
4968
|
+
});
|
|
4969
|
+
});
|
|
4970
|
+
}
|
|
4847
4971
|
async function spawnHermesVersionCommand(hermesBin, args, timeoutMs) {
|
|
4848
4972
|
return await new Promise((resolve, reject) => {
|
|
4849
4973
|
const child = spawn(hermesBin, args, {
|
|
@@ -5424,6 +5548,20 @@ function createVersionCommandError(message, details) {
|
|
|
5424
5548
|
}
|
|
5425
5549
|
return error;
|
|
5426
5550
|
}
|
|
5551
|
+
function describeVersionMetadataFailure(hermesBin, error) {
|
|
5552
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5553
|
+
const details = readExecErrorDetails(error, message);
|
|
5554
|
+
return {
|
|
5555
|
+
summary: `${hermesBin} Python metadata probe failed: ${message}`,
|
|
5556
|
+
fields: {
|
|
5557
|
+
hermes_bin: hermesBin,
|
|
5558
|
+
command: "python-metadata",
|
|
5559
|
+
cwd: process.cwd(),
|
|
5560
|
+
error: message,
|
|
5561
|
+
...details
|
|
5562
|
+
}
|
|
5563
|
+
};
|
|
5564
|
+
}
|
|
5427
5565
|
function describeVersionCommandFailure(hermesBin, args, error) {
|
|
5428
5566
|
const message = error instanceof Error ? error.message : String(error);
|
|
5429
5567
|
const details = readExecErrorDetails(error, message);
|
|
@@ -5473,6 +5611,91 @@ function readExecErrorDetails(error, message) {
|
|
|
5473
5611
|
function truncateVersionLogOutput(value) {
|
|
5474
5612
|
return value.length > MAX_VERSION_LOG_OUTPUT_LENGTH ? `${value.slice(0, MAX_VERSION_LOG_OUTPUT_LENGTH)}...` : value;
|
|
5475
5613
|
}
|
|
5614
|
+
async function resolveExecutablePath(command) {
|
|
5615
|
+
const candidates = executablePathCandidates(command);
|
|
5616
|
+
for (const candidate of candidates) {
|
|
5617
|
+
try {
|
|
5618
|
+
await access(candidate, fsConstants.X_OK);
|
|
5619
|
+
return await realpath(candidate).catch(() => candidate);
|
|
5620
|
+
} catch {
|
|
5621
|
+
}
|
|
5622
|
+
}
|
|
5623
|
+
return null;
|
|
5624
|
+
}
|
|
5625
|
+
function executablePathCandidates(command) {
|
|
5626
|
+
const normalized = command.trim();
|
|
5627
|
+
if (!normalized) {
|
|
5628
|
+
return [];
|
|
5629
|
+
}
|
|
5630
|
+
if (/[\\/]/u.test(normalized)) {
|
|
5631
|
+
return withWindowsExecutableExtensions(normalized);
|
|
5632
|
+
}
|
|
5633
|
+
const pathEntries = (process.env.PATH ?? "").split(path7.delimiter).filter(Boolean);
|
|
5634
|
+
return pathEntries.flatMap(
|
|
5635
|
+
(entry) => withWindowsExecutableExtensions(path7.join(entry, normalized))
|
|
5636
|
+
);
|
|
5637
|
+
}
|
|
5638
|
+
function withWindowsExecutableExtensions(filePath) {
|
|
5639
|
+
if (process.platform !== "win32" || path7.extname(filePath)) {
|
|
5640
|
+
return [filePath];
|
|
5641
|
+
}
|
|
5642
|
+
const extensions = (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean);
|
|
5643
|
+
return [filePath, ...extensions.map((extension) => `${filePath}${extension}`)];
|
|
5644
|
+
}
|
|
5645
|
+
async function readFirstLine(filePath) {
|
|
5646
|
+
const raw = await readFile5(filePath, "utf8");
|
|
5647
|
+
return raw.split(/\r?\n/u, 1)[0] ?? "";
|
|
5648
|
+
}
|
|
5649
|
+
function parsePythonShebang(line) {
|
|
5650
|
+
if (!line.startsWith("#!")) {
|
|
5651
|
+
return null;
|
|
5652
|
+
}
|
|
5653
|
+
const parts = splitShellWords(line.slice(2).trim());
|
|
5654
|
+
if (parts.length === 0) {
|
|
5655
|
+
return null;
|
|
5656
|
+
}
|
|
5657
|
+
const commandName = path7.basename(parts[0] ?? "").toLowerCase();
|
|
5658
|
+
if (commandName === "env") {
|
|
5659
|
+
const args = expandEnvShebangArgs(parts.slice(1));
|
|
5660
|
+
const pythonIndex = args.findIndex((part) => isPythonCommandName(part));
|
|
5661
|
+
if (pythonIndex < 0) {
|
|
5662
|
+
return null;
|
|
5663
|
+
}
|
|
5664
|
+
return {
|
|
5665
|
+
command: parts[0],
|
|
5666
|
+
args
|
|
5667
|
+
};
|
|
5668
|
+
}
|
|
5669
|
+
if (!isPythonCommandName(commandName)) {
|
|
5670
|
+
return null;
|
|
5671
|
+
}
|
|
5672
|
+
return {
|
|
5673
|
+
command: parts[0],
|
|
5674
|
+
args: parts.slice(1)
|
|
5675
|
+
};
|
|
5676
|
+
}
|
|
5677
|
+
function isPythonCommandName(command) {
|
|
5678
|
+
return /^python(?:\d+(?:\.\d+)?)?$/iu.test(path7.basename(command));
|
|
5679
|
+
}
|
|
5680
|
+
function expandEnvShebangArgs(args) {
|
|
5681
|
+
const expanded = [];
|
|
5682
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
5683
|
+
const arg = args[index];
|
|
5684
|
+
if (arg === "-S" && args[index + 1]) {
|
|
5685
|
+
expanded.push(...splitShellWords(args[index + 1]));
|
|
5686
|
+
index += 1;
|
|
5687
|
+
continue;
|
|
5688
|
+
}
|
|
5689
|
+
if (arg !== "-S") {
|
|
5690
|
+
expanded.push(arg);
|
|
5691
|
+
}
|
|
5692
|
+
}
|
|
5693
|
+
return expanded;
|
|
5694
|
+
}
|
|
5695
|
+
function splitShellWords(value) {
|
|
5696
|
+
const matches = value.matchAll(/"([^"]*)"|'([^']*)'|(\S+)/gu);
|
|
5697
|
+
return [...matches].map((match) => match[1] ?? match[2] ?? match[3] ?? "");
|
|
5698
|
+
}
|
|
5476
5699
|
function compareSemver(left, right) {
|
|
5477
5700
|
const leftParts = left.split(".").map((part) => Number.parseInt(part, 10));
|
|
5478
5701
|
const rightParts = right.split(".").map((part) => Number.parseInt(part, 10));
|
|
@@ -12677,7 +12900,7 @@ async function resolveHermesPythonCommand() {
|
|
|
12677
12900
|
if (explicit) {
|
|
12678
12901
|
return { command: explicit, args: [] };
|
|
12679
12902
|
}
|
|
12680
|
-
const hermesBin = await
|
|
12903
|
+
const hermesBin = await resolveExecutablePath2(resolveHermesBin());
|
|
12681
12904
|
if (hermesBin) {
|
|
12682
12905
|
const shebang = await readShebang(hermesBin);
|
|
12683
12906
|
const command = shebangToPythonCommand(shebang);
|
|
@@ -12690,7 +12913,7 @@ async function resolveHermesPythonCommand() {
|
|
|
12690
12913
|
args: []
|
|
12691
12914
|
};
|
|
12692
12915
|
}
|
|
12693
|
-
async function
|
|
12916
|
+
async function resolveExecutablePath2(command) {
|
|
12694
12917
|
if (path17.isAbsolute(command)) {
|
|
12695
12918
|
return await isExecutableFile(command) ? command : null;
|
|
12696
12919
|
}
|
|
@@ -12907,8 +13130,15 @@ function normalizeHermesResponseEvent(event) {
|
|
|
12907
13130
|
return normalizeResponseCompleted(event);
|
|
12908
13131
|
case "response.failed":
|
|
12909
13132
|
return normalizeResponseFailed(event);
|
|
13133
|
+
case "response.output_text.done": {
|
|
13134
|
+
const delta = readDelta(event.payload);
|
|
13135
|
+
return delta ? {
|
|
13136
|
+
...event,
|
|
13137
|
+
payloadType: "message.delta",
|
|
13138
|
+
payload: { type: "message.delta", delta }
|
|
13139
|
+
} : null;
|
|
13140
|
+
}
|
|
12910
13141
|
case "response.created":
|
|
12911
|
-
case "response.output_text.done":
|
|
12912
13142
|
return null;
|
|
12913
13143
|
default:
|
|
12914
13144
|
return null;
|
|
@@ -12938,6 +13168,14 @@ function normalizeResponseOutputItemAdded(event) {
|
|
|
12938
13168
|
}
|
|
12939
13169
|
function normalizeResponseOutputItemDone(event) {
|
|
12940
13170
|
const item = toRecord10(event.payload.item);
|
|
13171
|
+
if (readString12(item, "type") === "message") {
|
|
13172
|
+
const delta = extractResponseAssistantText({ output: [item] });
|
|
13173
|
+
return delta ? {
|
|
13174
|
+
...event,
|
|
13175
|
+
payloadType: "message.delta",
|
|
13176
|
+
payload: { type: "message.delta", delta }
|
|
13177
|
+
} : null;
|
|
13178
|
+
}
|
|
12941
13179
|
if (readString12(item, "type") !== "function_call_output") {
|
|
12942
13180
|
return null;
|
|
12943
13181
|
}
|
|
@@ -13012,6 +13250,30 @@ function readErrorMessage2(payload) {
|
|
|
13012
13250
|
function readDelta(payload) {
|
|
13013
13251
|
return readText2(payload, "delta") ?? readText2(payload, "text") ?? readText2(payload, "content");
|
|
13014
13252
|
}
|
|
13253
|
+
function extractResponseAssistantText(value) {
|
|
13254
|
+
if (typeof value === "string") {
|
|
13255
|
+
return value.trim().length > 0 ? value : null;
|
|
13256
|
+
}
|
|
13257
|
+
const payload = toRecord10(value);
|
|
13258
|
+
const response = toRecord10(payload.response ?? value);
|
|
13259
|
+
const directText = readText2(response, "output_text");
|
|
13260
|
+
if (directText?.trim()) {
|
|
13261
|
+
return directText;
|
|
13262
|
+
}
|
|
13263
|
+
const output = response.output;
|
|
13264
|
+
if (Array.isArray(output)) {
|
|
13265
|
+
const messages = output.map(readResponseOutputItemText).filter((text) => Boolean(text?.trim()));
|
|
13266
|
+
if (messages.length > 0) {
|
|
13267
|
+
return messages.join("\n\n");
|
|
13268
|
+
}
|
|
13269
|
+
}
|
|
13270
|
+
const choicesText = readAssistantTextFromChoices(response);
|
|
13271
|
+
if (choicesText) {
|
|
13272
|
+
return choicesText;
|
|
13273
|
+
}
|
|
13274
|
+
const fallbackText = readResponseMessageText(response);
|
|
13275
|
+
return fallbackText?.trim() ? fallbackText : null;
|
|
13276
|
+
}
|
|
13015
13277
|
function emptyHermesResponseMessage() {
|
|
13016
13278
|
return "Hermes \u6CA1\u6709\u8FD4\u56DE\u6709\u6548\u5185\u5BB9\u3002\u8BF7\u5148\u770B Gateway / \u6A21\u578B\u914D\u7F6E\u662F\u5426\u6B63\u5E38\uFF0C\u6216\u67E5\u770B\u4E0B\u65B9\u5931\u8D25\u8BE6\u60C5\u3002";
|
|
13017
13279
|
}
|
|
@@ -13081,6 +13343,17 @@ function readFirstChoice(payload) {
|
|
|
13081
13343
|
}
|
|
13082
13344
|
return toRecord10(choices[0]);
|
|
13083
13345
|
}
|
|
13346
|
+
function readAssistantTextFromChoices(payload) {
|
|
13347
|
+
const choices = payload.choices;
|
|
13348
|
+
if (!Array.isArray(choices)) {
|
|
13349
|
+
return null;
|
|
13350
|
+
}
|
|
13351
|
+
const messages = choices.map(toRecord10).map((choice) => toRecord10(choice.message ?? choice.delta)).filter((message) => {
|
|
13352
|
+
const role = readString12(message, "role");
|
|
13353
|
+
return !role || role === "assistant";
|
|
13354
|
+
}).map(readResponseMessageText).filter((text) => Boolean(text?.trim()));
|
|
13355
|
+
return messages.length > 0 ? messages.join("\n\n") : null;
|
|
13356
|
+
}
|
|
13084
13357
|
function readInteger2(payload, key) {
|
|
13085
13358
|
const value = payload[key];
|
|
13086
13359
|
if (typeof value === "number" && Number.isFinite(value)) {
|
|
@@ -13100,6 +13373,50 @@ function readText2(payload, key) {
|
|
|
13100
13373
|
const value = payload[key];
|
|
13101
13374
|
return typeof value === "string" && value.length > 0 ? value : null;
|
|
13102
13375
|
}
|
|
13376
|
+
function readResponseOutputItemText(value) {
|
|
13377
|
+
if (typeof value === "string") {
|
|
13378
|
+
return value;
|
|
13379
|
+
}
|
|
13380
|
+
const item = toRecord10(value);
|
|
13381
|
+
const type = readString12(item, "type");
|
|
13382
|
+
const role = readString12(item, "role");
|
|
13383
|
+
if (type && type !== "message" && type !== "output_text" && type !== "text") {
|
|
13384
|
+
return null;
|
|
13385
|
+
}
|
|
13386
|
+
if (role && role !== "assistant") {
|
|
13387
|
+
return null;
|
|
13388
|
+
}
|
|
13389
|
+
return readResponseMessageText(item);
|
|
13390
|
+
}
|
|
13391
|
+
function readResponseMessageText(payload) {
|
|
13392
|
+
const contentText = readResponseContentText(payload.content);
|
|
13393
|
+
return contentText ?? readText2(payload, "output_text") ?? readText2(payload, "text") ?? readText2(payload, "content") ?? readText2(payload, "refusal");
|
|
13394
|
+
}
|
|
13395
|
+
function readResponseContentText(value) {
|
|
13396
|
+
if (typeof value === "string") {
|
|
13397
|
+
return value;
|
|
13398
|
+
}
|
|
13399
|
+
if (!Array.isArray(value)) {
|
|
13400
|
+
const record = toRecord10(value);
|
|
13401
|
+
return readText2(record, "text") ?? readText2(record, "content") ?? readText2(record, "output_text") ?? readText2(record, "refusal");
|
|
13402
|
+
}
|
|
13403
|
+
const chunks = value.map((partValue) => {
|
|
13404
|
+
if (typeof partValue === "string") {
|
|
13405
|
+
return partValue;
|
|
13406
|
+
}
|
|
13407
|
+
const part = toRecord10(partValue);
|
|
13408
|
+
const type = readString12(part, "type");
|
|
13409
|
+
if (type && !isVisibleResponseTextPart(type)) {
|
|
13410
|
+
return null;
|
|
13411
|
+
}
|
|
13412
|
+
return readText2(part, "text") ?? readText2(part, "content") ?? readText2(part, "output_text") ?? readText2(part, "refusal");
|
|
13413
|
+
}).filter((text2) => Boolean(text2));
|
|
13414
|
+
const text = chunks.join("");
|
|
13415
|
+
return text.trim() ? text : null;
|
|
13416
|
+
}
|
|
13417
|
+
function isVisibleResponseTextPart(type) {
|
|
13418
|
+
return type === "output_text" || type === "text" || type === "message_text" || type === "refusal";
|
|
13419
|
+
}
|
|
13103
13420
|
function readResponseItemOutput(value) {
|
|
13104
13421
|
if (typeof value === "string") {
|
|
13105
13422
|
return value;
|
|
@@ -13280,6 +13597,11 @@ var ConversationRunLifecycle = class {
|
|
|
13280
13597
|
error: error instanceof Error ? error.message : String(error)
|
|
13281
13598
|
});
|
|
13282
13599
|
});
|
|
13600
|
+
await this.appendAssistantTextFromCompletedResponse(
|
|
13601
|
+
conversationId,
|
|
13602
|
+
runId,
|
|
13603
|
+
event
|
|
13604
|
+
);
|
|
13283
13605
|
await this.importMediaReferencesForEvent(conversationId, runId, event);
|
|
13284
13606
|
if (!await this.runHasAssistantOutput(conversationId, runId)) {
|
|
13285
13607
|
await this.failRun(
|
|
@@ -13325,7 +13647,7 @@ var ConversationRunLifecycle = class {
|
|
|
13325
13647
|
conversationId,
|
|
13326
13648
|
runId,
|
|
13327
13649
|
await this.buildEmptyHermesResponseMessage({
|
|
13328
|
-
source: "
|
|
13650
|
+
source: "stream-ended-without-terminal-event"
|
|
13329
13651
|
})
|
|
13330
13652
|
);
|
|
13331
13653
|
}
|
|
@@ -13465,6 +13787,31 @@ ${attachmentLines.join("\n")}`
|
|
|
13465
13787
|
}
|
|
13466
13788
|
return messageText(assistant).length > 0 || (assistant.agent_events?.length ?? 0) > 0 || (assistant.approvals?.length ?? 0) > 0 || assistant.parts.some((part) => part.type !== "text");
|
|
13467
13789
|
}
|
|
13790
|
+
async appendAssistantTextFromCompletedResponse(conversationId, runId, event) {
|
|
13791
|
+
const terminalText = extractResponseAssistantText(event.payload);
|
|
13792
|
+
if (!terminalText?.trim()) {
|
|
13793
|
+
return;
|
|
13794
|
+
}
|
|
13795
|
+
await this.deps.withConversationLock(conversationId, async () => {
|
|
13796
|
+
const snapshot = await this.deps.readSnapshot(conversationId);
|
|
13797
|
+
const run = snapshot.runs.find((item) => item.id === runId);
|
|
13798
|
+
const assistant = snapshot.messages.find(
|
|
13799
|
+
(item) => item.id === run?.assistant_message_id
|
|
13800
|
+
);
|
|
13801
|
+
if (!assistant) {
|
|
13802
|
+
return;
|
|
13803
|
+
}
|
|
13804
|
+
const currentText = assistant.parts.find((part) => part.type === "text")?.text ?? "";
|
|
13805
|
+
const delta = normalizeTerminalResponseTextDelta(
|
|
13806
|
+
currentText,
|
|
13807
|
+
terminalText
|
|
13808
|
+
);
|
|
13809
|
+
if (!delta) {
|
|
13810
|
+
return;
|
|
13811
|
+
}
|
|
13812
|
+
await this.appendAssistantDelta(conversationId, runId, delta, event);
|
|
13813
|
+
});
|
|
13814
|
+
}
|
|
13468
13815
|
async buildEmptyHermesResponseMessage(input) {
|
|
13469
13816
|
const runtime = await readCurrentConversationRuntime(this.deps.paths).catch(
|
|
13470
13817
|
() => null
|
|
@@ -13485,6 +13832,14 @@ ${attachmentLines.join("\n")}`
|
|
|
13485
13832
|
);
|
|
13486
13833
|
}
|
|
13487
13834
|
}
|
|
13835
|
+
if (input?.source === "stream-ended-without-terminal-event") {
|
|
13836
|
+
details.unshift(
|
|
13837
|
+
"Hermes \u7684\u8FD0\u884C\u4E8B\u4EF6\u6D41\u63D0\u524D\u7ED3\u675F\uFF0CLink \u6CA1\u6709\u6536\u5230\u5B8C\u6210\u6216\u5931\u8D25\u4E8B\u4EF6\u3002"
|
|
13838
|
+
);
|
|
13839
|
+
details.push(
|
|
13840
|
+
"\u8FD9\u66F4\u50CF\u662F Gateway \u6216 provider \u7684\u6D41\u5F0F\u4E8B\u4EF6\u4E2D\u65AD\uFF0C\u4E0D\u662F\u6A21\u578B\u660E\u786E\u5B8C\u6210\u4E86\u7A7A\u56DE\u590D\u3002"
|
|
13841
|
+
);
|
|
13842
|
+
}
|
|
13488
13843
|
return details.length > 0 ? `Hermes \u6CA1\u6709\u8FD4\u56DE\u6709\u6548\u5185\u5BB9\u3002
|
|
13489
13844
|
${details.join("\n")}` : emptyHermesResponseMessage();
|
|
13490
13845
|
}
|
|
@@ -14073,6 +14428,29 @@ function isVoicePart(part) {
|
|
|
14073
14428
|
function normalizeToolName(value) {
|
|
14074
14429
|
return value.trim().toLowerCase().replace(/[\s-]+/gu, "_");
|
|
14075
14430
|
}
|
|
14431
|
+
function normalizeTerminalResponseTextDelta(currentText, terminalText) {
|
|
14432
|
+
if (!terminalText.trim()) {
|
|
14433
|
+
return "";
|
|
14434
|
+
}
|
|
14435
|
+
if (!currentText) {
|
|
14436
|
+
return terminalText;
|
|
14437
|
+
}
|
|
14438
|
+
if (terminalText === currentText) {
|
|
14439
|
+
return "";
|
|
14440
|
+
}
|
|
14441
|
+
if (terminalText.startsWith(currentText)) {
|
|
14442
|
+
return terminalText.slice(currentText.length);
|
|
14443
|
+
}
|
|
14444
|
+
const normalizedCurrent = currentText.trim();
|
|
14445
|
+
const normalizedTerminal = terminalText.trim();
|
|
14446
|
+
if (!normalizedCurrent || normalizedTerminal === normalizedCurrent) {
|
|
14447
|
+
return "";
|
|
14448
|
+
}
|
|
14449
|
+
if (normalizedTerminal.startsWith(normalizedCurrent)) {
|
|
14450
|
+
return normalizedTerminal.slice(normalizedCurrent.length);
|
|
14451
|
+
}
|
|
14452
|
+
return "";
|
|
14453
|
+
}
|
|
14076
14454
|
function appendTextBlock2(message, delta, updatedAt) {
|
|
14077
14455
|
if (!delta) {
|
|
14078
14456
|
return;
|
package/dist/cli/index.js
CHANGED
package/dist/http/app.js
CHANGED