@agent-spaces/server 0.3.67 → 0.4.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.
- package/dist/adapters/agent-runtime.js +4 -0
- package/dist/adapters/codex-function-tool-bridge.js +9 -1
- package/dist/adapters/git.js +13 -2
- package/dist/adapters/hermes-runtime.js +711 -108
- package/dist/adapters/langchain-runtime.js +384 -21
- package/dist/adapters/oh-my-pi-runtime.js +858 -0
- package/dist/adapters/open-agent-sdk-runtime.js +202 -5
- package/dist/adapters/open-agent-sdk-runtime.test.js +35 -0
- package/dist/agents/agent-message-parts.js +52 -2
- package/dist/agents/issue-task-controller.js +4 -2
- package/dist/agents/title-generator-agent.js +120 -0
- package/dist/app.js +59 -6
- package/dist/routes/agent-sse.js +70 -7
- package/dist/routes/channel.js +23 -2
- package/dist/routes/chat-run.js +191 -0
- package/dist/routes/chat.js +142 -0
- package/dist/routes/command.js +16 -0
- package/dist/routes/data.js +189 -0
- package/dist/routes/git.js +10 -1
- package/dist/routes/import.js +199 -0
- package/dist/routes/issue.js +16 -4
- package/dist/routes/plugin.js +69 -0
- package/dist/routes/version.js +2 -1
- package/dist/routes/workflow-hook.js +71 -0
- package/dist/routes/workflow.js +282 -7
- package/dist/routes/workspace.js +13 -4
- package/dist/services/agent.js +123 -36
- package/dist/services/ai-text.js +185 -0
- package/dist/services/builtin-tools/index.js +1 -0
- package/dist/services/builtin-tools/workflow-editor-tools.js +509 -0
- package/dist/services/builtin-tools/workflow-exec-tools.js +320 -0
- package/dist/services/chat.js +134 -0
- package/dist/services/command-process-manager.js +16 -0
- package/dist/services/execution-manager.js +1346 -0
- package/dist/services/generated-title.js +59 -0
- package/dist/services/gitignore.js +22 -18
- package/dist/services/interaction-manager.js +114 -0
- package/dist/services/issue-retry.js +25 -0
- package/dist/services/output-style.js +8 -1
- package/dist/services/plugin.js +257 -0
- package/dist/services/prompt-template.js +8 -1
- package/dist/services/pty.js +20 -0
- package/dist/services/search.js +16 -6
- package/dist/services/skill.js +2 -0
- package/dist/services/version.js +17 -7
- package/dist/services/workflow-command-runner.js +5 -4
- package/dist/services/workflow-trigger-service.js +137 -0
- package/dist/services/workflow.js +199 -36
- package/dist/storage/agent-store.js +8 -0
- package/dist/storage/chat-store.js +151 -0
- package/dist/storage/database-store.js +6 -0
- package/dist/storage/json-store.js +2 -1
- package/dist/storage/kanban-store.js +6 -0
- package/dist/storage/workflow-store.js +386 -22
- package/dist/ws/agent-prompt.js +6 -2
- package/dist/ws/agent-runner.js +29 -18
- package/dist/ws/chat-handler.js +89 -0
- package/dist/ws/connection-manager.js +49 -2
- package/dist/ws/execution-channels.js +88 -0
- package/dist/ws/handler.js +32 -5
- package/dist/ws/message-parts.js +130 -12
- package/dist/ws/terminal-handler.js +26 -0
- package/package.json +12 -1
- package/public/avatars/user.jpg +0 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
import { copyFileSync, existsSync, mkdirSync, readdirSync, rmSync, statSync } from 'node:fs';
|
|
3
|
-
import { basename, extname, join } from 'node:path';
|
|
2
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { basename, delimiter, extname, join } from 'node:path';
|
|
4
4
|
import { appendOutputStyleToPrompt, summarizeResult } from './agent-runtime-types.js';
|
|
5
|
+
import { startCodexFunctionToolBridge } from './codex-function-tool-bridge.js';
|
|
5
6
|
/**
|
|
6
7
|
* Runtime backed by the external Hermes CLI.
|
|
7
8
|
*
|
|
8
9
|
* Hermes does not currently expose a structured JS SDK here, so this adapter
|
|
9
|
-
*
|
|
10
|
+
* classifies CLI text output before forwarding it to the UI.
|
|
10
11
|
*/
|
|
11
12
|
export class HermesRuntime {
|
|
12
13
|
config;
|
|
@@ -18,115 +19,145 @@ export class HermesRuntime {
|
|
|
18
19
|
const output = [];
|
|
19
20
|
const cwd = workingDir || process.cwd();
|
|
20
21
|
const startTime = Date.now();
|
|
21
|
-
const d = (message) => console.log(`[hermes] ${message}`);
|
|
22
|
+
const d = (message) => console.log(`[hermes] ${truncateConsoleMessage(message)}`);
|
|
22
23
|
const agentDir = options?.configDir;
|
|
23
24
|
const hermesHome = agentDir ? join(agentDir, '.hermes') : undefined;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
25
|
+
let functionToolBridge;
|
|
26
|
+
try {
|
|
27
|
+
functionToolBridge = await startCodexFunctionToolBridge(options?.functionTools, d);
|
|
28
|
+
const mcpServers = withFunctionToolBridge(options?.mcpServers, functionToolBridge);
|
|
29
|
+
if (hermesHome)
|
|
30
|
+
prepareHermesHome(hermesHome, this.config, agentDir, options?.skills, mcpServers, d);
|
|
31
|
+
const args = buildHermesArgs(appendOutputStyleToPrompt(prompt, options?.outputStyle), this.config, options);
|
|
32
|
+
const cliProvider = getHermesCliProvider(this.config.provider);
|
|
33
|
+
d(`starting | cwd=${cwd} model=${this.config.model ?? 'profile-default'} provider=${cliProvider ?? 'profile-default'} requestedProvider=${this.config.provider ?? 'profile-default'} hermesHome=${hermesHome ?? 'default'} mcpServers=${Object.keys(mcpServers ?? {}).join(',') || '-'} functionToolBridge=${functionToolBridge?.url ?? '-'} skills=${options?.skills?.join(',') || '-'}`);
|
|
34
|
+
return await new Promise((resolve) => {
|
|
35
|
+
let settled = false;
|
|
36
|
+
let stdoutBuffer = '';
|
|
37
|
+
let stderrBuffer = '';
|
|
38
|
+
let sessionId = options?.resumeSessionId;
|
|
39
|
+
let emittedSessionId = sessionId;
|
|
40
|
+
const lineParser = createHermesLineParser(output, options);
|
|
41
|
+
const finish = (result) => {
|
|
42
|
+
if (settled)
|
|
43
|
+
return;
|
|
44
|
+
settled = true;
|
|
45
|
+
this.child = null;
|
|
46
|
+
resolve(result);
|
|
47
|
+
};
|
|
48
|
+
try {
|
|
49
|
+
this.child = spawn(resolveHermesCommand(), args, {
|
|
50
|
+
cwd,
|
|
51
|
+
env: buildEnv(this.config, hermesHome),
|
|
52
|
+
windowsHide: true,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
57
|
+
d(`failed to start | ${message}`);
|
|
58
|
+
finish({
|
|
59
|
+
success: false,
|
|
60
|
+
summary: 'Hermes execution failed',
|
|
61
|
+
artifacts: [],
|
|
62
|
+
error: message,
|
|
63
|
+
output,
|
|
64
|
+
sessionId,
|
|
65
|
+
});
|
|
36
66
|
return;
|
|
37
|
-
|
|
38
|
-
this.child
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
67
|
+
}
|
|
68
|
+
this.child.stdout.setEncoding('utf-8');
|
|
69
|
+
this.child.stderr.setEncoding('utf-8');
|
|
70
|
+
this.child.stdout.on('data', (chunk) => {
|
|
71
|
+
stdoutBuffer = consumeLines(stdoutBuffer + chunk, (line) => {
|
|
72
|
+
if (!line)
|
|
73
|
+
return;
|
|
74
|
+
d(`stdout | ${line}`);
|
|
75
|
+
const parsedSessionId = extractSessionId(line);
|
|
76
|
+
if (parsedSessionId && parsedSessionId !== emittedSessionId) {
|
|
77
|
+
sessionId = parsedSessionId;
|
|
78
|
+
emittedSessionId = parsedSessionId;
|
|
79
|
+
options?.onEvent?.({ type: 'session', sessionId });
|
|
80
|
+
}
|
|
81
|
+
lineParser.handleLine(line, 'stdout');
|
|
82
|
+
});
|
|
46
83
|
});
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
artifacts: [],
|
|
55
|
-
error: message,
|
|
56
|
-
output,
|
|
57
|
-
sessionId,
|
|
84
|
+
this.child.stderr.on('data', (chunk) => {
|
|
85
|
+
stderrBuffer = consumeLines(stderrBuffer + chunk, (line) => {
|
|
86
|
+
if (!line)
|
|
87
|
+
return;
|
|
88
|
+
d(`stderr | ${line}`);
|
|
89
|
+
lineParser.handleLine(line, 'stderr');
|
|
90
|
+
});
|
|
58
91
|
});
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
sessionId
|
|
71
|
-
|
|
72
|
-
options?.onEvent?.({ type: 'session', sessionId });
|
|
73
|
-
}
|
|
74
|
-
options?.onEvent?.({ type: 'output', line });
|
|
92
|
+
this.child.on('error', (err) => {
|
|
93
|
+
const message = err.message.includes('ENOENT')
|
|
94
|
+
? 'Hermes CLI was not found. Install Hermes and ensure the `hermes` command is available on PATH.'
|
|
95
|
+
: err.message;
|
|
96
|
+
d(`failed ${Date.now() - startTime}ms | ${message}`);
|
|
97
|
+
finish({
|
|
98
|
+
success: false,
|
|
99
|
+
summary: 'Hermes execution failed',
|
|
100
|
+
artifacts: [],
|
|
101
|
+
error: message,
|
|
102
|
+
output,
|
|
103
|
+
sessionId,
|
|
104
|
+
});
|
|
75
105
|
});
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
106
|
+
this.child.on('close', (code, signal) => {
|
|
107
|
+
stdoutBuffer = flushLine(stdoutBuffer, (line) => lineParser.handleLine(line, 'stdout'));
|
|
108
|
+
stderrBuffer = flushLine(stderrBuffer, (line) => lineParser.handleLine(line, 'stderr'));
|
|
109
|
+
const elapsed = Date.now() - startTime;
|
|
110
|
+
if (code === 0) {
|
|
111
|
+
const text = lastMeaningfulLine(output);
|
|
112
|
+
d(`done ${elapsed}ms`);
|
|
113
|
+
finish({
|
|
114
|
+
success: true,
|
|
115
|
+
summary: summarizeResult(text),
|
|
116
|
+
artifacts: [],
|
|
117
|
+
output,
|
|
118
|
+
sessionId,
|
|
119
|
+
});
|
|
80
120
|
return;
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
this.child.on('error', (err) => {
|
|
87
|
-
const message = err.message.includes('ENOENT')
|
|
88
|
-
? 'Hermes CLI was not found. Install Hermes and ensure the `hermes` command is available on PATH.'
|
|
89
|
-
: err.message;
|
|
90
|
-
d(`failed ${Date.now() - startTime}ms | ${message}`);
|
|
91
|
-
finish({
|
|
92
|
-
success: false,
|
|
93
|
-
summary: 'Hermes execution failed',
|
|
94
|
-
artifacts: [],
|
|
95
|
-
error: message,
|
|
96
|
-
output,
|
|
97
|
-
sessionId,
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
this.child.on('close', (code, signal) => {
|
|
101
|
-
stdoutBuffer = flushLine(stdoutBuffer, output, options);
|
|
102
|
-
stderrBuffer = flushLine(stderrBuffer, output, options, '[stderr] ');
|
|
103
|
-
const elapsed = Date.now() - startTime;
|
|
104
|
-
if (code === 0) {
|
|
105
|
-
const text = lastMeaningfulLine(output);
|
|
106
|
-
d(`done ${elapsed}ms`);
|
|
121
|
+
}
|
|
122
|
+
const error = signal
|
|
123
|
+
? `Hermes execution stopped by signal ${signal}`
|
|
124
|
+
: `Hermes execution failed with exit code ${code ?? 'unknown'}`;
|
|
125
|
+
d(`failed ${elapsed}ms | ${error}`);
|
|
107
126
|
finish({
|
|
108
|
-
success:
|
|
109
|
-
summary:
|
|
127
|
+
success: false,
|
|
128
|
+
summary: 'Hermes execution failed',
|
|
110
129
|
artifacts: [],
|
|
130
|
+
error,
|
|
111
131
|
output,
|
|
112
132
|
sessionId,
|
|
113
133
|
});
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
const error = signal
|
|
117
|
-
? `Hermes execution stopped by signal ${signal}`
|
|
118
|
-
: `Hermes execution failed with exit code ${code ?? 'unknown'}`;
|
|
119
|
-
d(`failed ${elapsed}ms | ${error}`);
|
|
120
|
-
finish({
|
|
121
|
-
success: false,
|
|
122
|
-
summary: 'Hermes execution failed',
|
|
123
|
-
artifacts: [],
|
|
124
|
-
error,
|
|
125
|
-
output,
|
|
126
|
-
sessionId,
|
|
127
134
|
});
|
|
128
135
|
});
|
|
129
|
-
}
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
const elapsed = Date.now() - startTime;
|
|
139
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
140
|
+
d(`failed ${elapsed}ms | ${message}`);
|
|
141
|
+
if (err instanceof Error && err.stack)
|
|
142
|
+
console.error(err.stack);
|
|
143
|
+
return {
|
|
144
|
+
success: false,
|
|
145
|
+
summary: 'Hermes execution failed',
|
|
146
|
+
artifacts: [],
|
|
147
|
+
error: message,
|
|
148
|
+
output,
|
|
149
|
+
sessionId: options?.resumeSessionId,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
finally {
|
|
153
|
+
try {
|
|
154
|
+
await functionToolBridge?.close();
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
158
|
+
d(`function tool bridge close failed | ${message}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
130
161
|
}
|
|
131
162
|
stop() {
|
|
132
163
|
this.child?.kill();
|
|
@@ -138,14 +169,29 @@ function buildHermesArgs(prompt, config, options) {
|
|
|
138
169
|
args.push('-s', skill);
|
|
139
170
|
if (config.model)
|
|
140
171
|
args.push('--model', config.model);
|
|
141
|
-
|
|
142
|
-
|
|
172
|
+
const provider = getHermesCliProvider(config.provider);
|
|
173
|
+
if (provider)
|
|
174
|
+
args.push('--provider', provider);
|
|
143
175
|
return args;
|
|
144
176
|
}
|
|
177
|
+
function getHermesCliProvider(provider) {
|
|
178
|
+
if (!provider || isAgentSpacesProtocolProvider(provider))
|
|
179
|
+
return undefined;
|
|
180
|
+
return String(provider);
|
|
181
|
+
}
|
|
182
|
+
function isAgentSpacesProtocolProvider(provider) {
|
|
183
|
+
return provider === 'anthropic-messages'
|
|
184
|
+
|| provider === 'openai-chat-completions'
|
|
185
|
+
|| provider === 'openai-responses'
|
|
186
|
+
|| provider === 'openai-responses-to-anthropic-messages'
|
|
187
|
+
|| provider === 'openai-chat-completions-to-anthropic-messages'
|
|
188
|
+
|| provider === 'gemini-generate-content';
|
|
189
|
+
}
|
|
145
190
|
function buildEnv(config, hermesHome) {
|
|
146
191
|
const apiKey = config.apiKey || process.env.HERMES_API_KEY || process.env.OPENAI_API_KEY || process.env.ANTHROPIC_API_KEY;
|
|
147
192
|
return {
|
|
148
193
|
...process.env,
|
|
194
|
+
AGENT_SPACES_HERMES_API_KEY: apiKey,
|
|
149
195
|
HERMES_HOME: hermesHome || process.env.HERMES_HOME,
|
|
150
196
|
HERMES_API_KEY: apiKey,
|
|
151
197
|
HERMES_BASE_URL: config.baseURL || process.env.HERMES_BASE_URL,
|
|
@@ -155,8 +201,44 @@ function buildEnv(config, hermesHome) {
|
|
|
155
201
|
NO_COLOR: process.env.NO_COLOR || '1',
|
|
156
202
|
};
|
|
157
203
|
}
|
|
158
|
-
function
|
|
204
|
+
function resolveHermesCommand() {
|
|
205
|
+
const configured = process.env.HERMES_CLI_PATH?.trim();
|
|
206
|
+
if (configured)
|
|
207
|
+
return configured;
|
|
208
|
+
if (process.platform !== 'win32')
|
|
209
|
+
return 'hermes';
|
|
210
|
+
const pathCommand = resolveWindowsPathCommand('hermes.exe');
|
|
211
|
+
if (pathCommand)
|
|
212
|
+
return pathCommand;
|
|
213
|
+
const candidates = [
|
|
214
|
+
process.env.LOCALAPPDATA
|
|
215
|
+
? join(process.env.LOCALAPPDATA, 'hermes', 'hermes-agent', 'venv', 'Scripts', 'hermes.exe')
|
|
216
|
+
: undefined,
|
|
217
|
+
process.env.USERPROFILE
|
|
218
|
+
? join(process.env.USERPROFILE, 'AppData', 'Local', 'hermes', 'hermes-agent', 'venv', 'Scripts', 'hermes.exe')
|
|
219
|
+
: undefined,
|
|
220
|
+
].filter((candidate) => Boolean(candidate));
|
|
221
|
+
return candidates.find((candidate) => existsSync(candidate)) ?? 'hermes';
|
|
222
|
+
}
|
|
223
|
+
function resolveWindowsPathCommand(command) {
|
|
224
|
+
const pathValue = getPathEnvValue();
|
|
225
|
+
if (!pathValue)
|
|
226
|
+
return undefined;
|
|
227
|
+
for (const dir of pathValue.split(delimiter)) {
|
|
228
|
+
if (!dir.trim())
|
|
229
|
+
continue;
|
|
230
|
+
const candidate = join(dir.trim(), command);
|
|
231
|
+
if (existsSync(candidate))
|
|
232
|
+
return candidate;
|
|
233
|
+
}
|
|
234
|
+
return undefined;
|
|
235
|
+
}
|
|
236
|
+
function getPathEnvValue() {
|
|
237
|
+
return Object.entries(process.env).find(([key]) => key.toLowerCase() === 'path')?.[1];
|
|
238
|
+
}
|
|
239
|
+
function prepareHermesHome(hermesHome, config, agentDir, requestedSkills, mcpServers, log) {
|
|
159
240
|
mkdirSync(hermesHome, { recursive: true });
|
|
241
|
+
writeManagedHermesConfig(hermesHome, config, mcpServers, log);
|
|
160
242
|
const skillsDir = join(hermesHome, 'skills');
|
|
161
243
|
rmSync(skillsDir, { recursive: true, force: true });
|
|
162
244
|
mkdirSync(skillsDir, { recursive: true });
|
|
@@ -173,6 +255,273 @@ function prepareHermesHome(hermesHome, agentDir) {
|
|
|
173
255
|
continue;
|
|
174
256
|
copyFileSync(sourceFile, join(skillsDir, basename(file)));
|
|
175
257
|
}
|
|
258
|
+
for (const skill of normalizeSkillNames(requestedSkills)) {
|
|
259
|
+
const sourceFile = join(sourceSkillsDir, `${skill}.md`);
|
|
260
|
+
const targetFile = join(skillsDir, `${skill}.md`);
|
|
261
|
+
if (existsSync(targetFile) || !existsSync(sourceFile) || !statSync(sourceFile).isFile())
|
|
262
|
+
continue;
|
|
263
|
+
copyFileSync(sourceFile, targetFile);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
function writeManagedHermesConfig(hermesHome, config, mcpServers, log) {
|
|
267
|
+
const normalizedMcpServers = normalizeHermesMcpServers(mcpServers);
|
|
268
|
+
const hasModelConfig = Boolean(config.baseURL && config.model && isAgentSpacesProtocolProvider(config.provider));
|
|
269
|
+
if (!hasModelConfig && !normalizedMcpServers)
|
|
270
|
+
return;
|
|
271
|
+
const configPath = join(hermesHome, 'config.yaml');
|
|
272
|
+
if (existsSync(configPath)) {
|
|
273
|
+
const content = readFileSync(configPath, 'utf-8');
|
|
274
|
+
if (!isWritableAgentSpacesHermesConfigContent(content)) {
|
|
275
|
+
if (!normalizedMcpServers)
|
|
276
|
+
return;
|
|
277
|
+
const nextContent = mergeManagedHermesAgentSpacesConfig(content, normalizedMcpServers);
|
|
278
|
+
if (nextContent !== content) {
|
|
279
|
+
writeFileSync(configPath, nextContent, 'utf-8');
|
|
280
|
+
log?.(`merged Hermes MCP config | path=${configPath} servers=${Object.keys(normalizedMcpServers ?? {}).join(',') || '-'}`);
|
|
281
|
+
}
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
const content = buildManagedHermesConfig(config, normalizedMcpServers);
|
|
286
|
+
writeFileSync(configPath, content, 'utf-8');
|
|
287
|
+
if (normalizedMcpServers) {
|
|
288
|
+
log?.(`wrote Hermes MCP config | path=${configPath} servers=${Object.keys(normalizedMcpServers).join(',')}`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
function buildManagedHermesConfig(config, normalizedMcpServers) {
|
|
292
|
+
const hasModelConfig = Boolean(config.baseURL && config.model && isAgentSpacesProtocolProvider(config.provider));
|
|
293
|
+
const lines = [
|
|
294
|
+
'# Managed by Agent Spaces for this agent profile.',
|
|
295
|
+
];
|
|
296
|
+
if (hasModelConfig) {
|
|
297
|
+
const apiMode = getHermesApiMode(config.provider, config.baseURL);
|
|
298
|
+
lines.push('model:', ` default: ${yamlString(config.model)}`, ' provider: custom', ` base_url: ${yamlString(config.baseURL)}`, ' api_key: ${AGENT_SPACES_HERMES_API_KEY}');
|
|
299
|
+
if (apiMode)
|
|
300
|
+
lines.push(` api_mode: ${apiMode}`);
|
|
301
|
+
}
|
|
302
|
+
if (normalizedMcpServers) {
|
|
303
|
+
if (lines.length > 1)
|
|
304
|
+
lines.push('');
|
|
305
|
+
lines.push('mcp_servers:');
|
|
306
|
+
lines.push(...yamlMappingLines(normalizedMcpServers, 2));
|
|
307
|
+
}
|
|
308
|
+
return `${lines.join('\n')}\n`;
|
|
309
|
+
}
|
|
310
|
+
const AGENT_SPACES_MCP_START = ' # Agent Spaces managed MCP servers start';
|
|
311
|
+
const AGENT_SPACES_MCP_END = ' # Agent Spaces managed MCP servers end';
|
|
312
|
+
function mergeManagedHermesAgentSpacesConfig(content, normalizedMcpServers) {
|
|
313
|
+
return mergeManagedHermesMcpServers(content, normalizedMcpServers);
|
|
314
|
+
}
|
|
315
|
+
function mergeManagedHermesMcpServers(content, normalizedMcpServers) {
|
|
316
|
+
const lines = content.replace(/\r\n/g, '\n').split('\n');
|
|
317
|
+
if (lines.at(-1) === '')
|
|
318
|
+
lines.pop();
|
|
319
|
+
const mcpStart = lines.findIndex((line) => /^mcp_servers\s*:\s*(?:#.*)?$/.test(line));
|
|
320
|
+
const managedBlock = normalizedMcpServers
|
|
321
|
+
? [AGENT_SPACES_MCP_START, ...yamlMappingLines(normalizedMcpServers, 2), AGENT_SPACES_MCP_END]
|
|
322
|
+
: [];
|
|
323
|
+
if (mcpStart === -1) {
|
|
324
|
+
if (!managedBlock.length)
|
|
325
|
+
return content;
|
|
326
|
+
const nextLines = [...lines];
|
|
327
|
+
if (nextLines.length && nextLines.at(-1)?.trim())
|
|
328
|
+
nextLines.push('');
|
|
329
|
+
nextLines.push('mcp_servers:', ...managedBlock);
|
|
330
|
+
return `${nextLines.join('\n')}\n`;
|
|
331
|
+
}
|
|
332
|
+
const mcpEnd = findNextTopLevelYamlKey(lines, mcpStart + 1);
|
|
333
|
+
const before = lines.slice(0, mcpStart + 1);
|
|
334
|
+
const mcpBody = removeManagedHermesMcpEntries(lines.slice(mcpStart + 1, mcpEnd), Object.keys(normalizedMcpServers ?? {}));
|
|
335
|
+
const after = lines.slice(mcpEnd);
|
|
336
|
+
const nextLines = [
|
|
337
|
+
...before,
|
|
338
|
+
...managedBlock,
|
|
339
|
+
...mcpBody,
|
|
340
|
+
...after,
|
|
341
|
+
];
|
|
342
|
+
return `${nextLines.join('\n')}\n`;
|
|
343
|
+
}
|
|
344
|
+
function findNextTopLevelYamlKey(lines, startIndex) {
|
|
345
|
+
for (let index = startIndex; index < lines.length; index += 1) {
|
|
346
|
+
const line = lines[index];
|
|
347
|
+
if (!line.trim() || line.trimStart().startsWith('#'))
|
|
348
|
+
continue;
|
|
349
|
+
if (/^\S[^:]*:\s*/.test(line))
|
|
350
|
+
return index;
|
|
351
|
+
}
|
|
352
|
+
return lines.length;
|
|
353
|
+
}
|
|
354
|
+
function removeManagedHermesMcpEntries(lines, managedServerNames) {
|
|
355
|
+
const managedNameKeys = new Set(managedServerNames.flatMap((name) => [yamlKey(name), yamlString(name)]));
|
|
356
|
+
const result = [];
|
|
357
|
+
for (let index = 0; index < lines.length;) {
|
|
358
|
+
const line = lines[index];
|
|
359
|
+
if (line === AGENT_SPACES_MCP_START) {
|
|
360
|
+
index += 1;
|
|
361
|
+
while (index < lines.length && lines[index] !== AGENT_SPACES_MCP_END)
|
|
362
|
+
index += 1;
|
|
363
|
+
if (index < lines.length)
|
|
364
|
+
index += 1;
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
const entryKey = readIndentedYamlKey(line, 2);
|
|
368
|
+
if (entryKey && managedNameKeys.has(entryKey)) {
|
|
369
|
+
index += 1;
|
|
370
|
+
while (index < lines.length && !readIndentedYamlKey(lines[index], 2))
|
|
371
|
+
index += 1;
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
result.push(line);
|
|
375
|
+
index += 1;
|
|
376
|
+
}
|
|
377
|
+
return result;
|
|
378
|
+
}
|
|
379
|
+
function readIndentedYamlKey(line, indent) {
|
|
380
|
+
if (!line.startsWith(' '.repeat(indent)) || line.startsWith(' '.repeat(indent + 1)))
|
|
381
|
+
return undefined;
|
|
382
|
+
const match = line.slice(indent).match(/^((?:"(?:\\.|[^"])*")|[A-Za-z0-9_-]+)\s*:/);
|
|
383
|
+
return match?.[1];
|
|
384
|
+
}
|
|
385
|
+
function withFunctionToolBridge(mcpServers, bridge) {
|
|
386
|
+
if (!bridge)
|
|
387
|
+
return mcpServers;
|
|
388
|
+
return {
|
|
389
|
+
...(mcpServers ?? {}),
|
|
390
|
+
[bridge.name]: { url: bridge.url },
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
function normalizeHermesMcpServers(servers) {
|
|
394
|
+
if (!servers || Object.keys(servers).length === 0)
|
|
395
|
+
return undefined;
|
|
396
|
+
const normalized = Object.fromEntries(Object.entries(servers).flatMap(([name, server]) => {
|
|
397
|
+
const normalizedServer = normalizeHermesMcpServer(server);
|
|
398
|
+
return normalizedServer ? [[name, normalizedServer]] : [];
|
|
399
|
+
}));
|
|
400
|
+
return Object.keys(normalized).length > 0 ? normalized : undefined;
|
|
401
|
+
}
|
|
402
|
+
function normalizeHermesMcpServer(server) {
|
|
403
|
+
if (!server || typeof server !== 'object' || Array.isArray(server))
|
|
404
|
+
return null;
|
|
405
|
+
const record = server;
|
|
406
|
+
if (typeof record.command === 'string') {
|
|
407
|
+
return removeUndefined({
|
|
408
|
+
command: record.command,
|
|
409
|
+
args: Array.isArray(record.args) ? record.args.map(String) : undefined,
|
|
410
|
+
env: isRecord(record.env) ? record.env : undefined,
|
|
411
|
+
ssl_verify: isBooleanOrString(record.ssl_verify) ? record.ssl_verify : undefined,
|
|
412
|
+
client_cert: isStringOrStringArray(record.client_cert) ? record.client_cert : undefined,
|
|
413
|
+
client_key: typeof record.client_key === 'string' ? record.client_key : undefined,
|
|
414
|
+
enabled: typeof record.enabled === 'boolean' ? record.enabled : true,
|
|
415
|
+
timeout: typeof record.timeout === 'number' ? record.timeout : undefined,
|
|
416
|
+
connect_timeout: typeof record.connect_timeout === 'number' ? record.connect_timeout : undefined,
|
|
417
|
+
supports_parallel_tool_calls: typeof record.supports_parallel_tool_calls === 'boolean'
|
|
418
|
+
? record.supports_parallel_tool_calls
|
|
419
|
+
: undefined,
|
|
420
|
+
tools: isRecord(record.tools) ? record.tools : undefined,
|
|
421
|
+
sampling: isRecord(record.sampling) ? record.sampling : undefined,
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
if (typeof record.url === 'string') {
|
|
425
|
+
return removeUndefined({
|
|
426
|
+
url: record.url,
|
|
427
|
+
headers: isRecord(record.headers) ? record.headers : undefined,
|
|
428
|
+
ssl_verify: isBooleanOrString(record.ssl_verify) ? record.ssl_verify : undefined,
|
|
429
|
+
client_cert: isStringOrStringArray(record.client_cert) ? record.client_cert : undefined,
|
|
430
|
+
client_key: typeof record.client_key === 'string' ? record.client_key : undefined,
|
|
431
|
+
enabled: typeof record.enabled === 'boolean' ? record.enabled : true,
|
|
432
|
+
timeout: typeof record.timeout === 'number' ? record.timeout : undefined,
|
|
433
|
+
connect_timeout: typeof record.connect_timeout === 'number' ? record.connect_timeout : undefined,
|
|
434
|
+
supports_parallel_tool_calls: typeof record.supports_parallel_tool_calls === 'boolean'
|
|
435
|
+
? record.supports_parallel_tool_calls
|
|
436
|
+
: undefined,
|
|
437
|
+
tools: isRecord(record.tools) ? record.tools : undefined,
|
|
438
|
+
auth: typeof record.auth === 'string' ? record.auth : undefined,
|
|
439
|
+
sampling: isRecord(record.sampling) ? record.sampling : undefined,
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
return null;
|
|
443
|
+
}
|
|
444
|
+
function yamlMappingLines(value, indent) {
|
|
445
|
+
return Object.entries(value).flatMap(([key, child]) => yamlEntryLines(key, child, indent));
|
|
446
|
+
}
|
|
447
|
+
function yamlEntryLines(key, value, indent) {
|
|
448
|
+
const prefix = ' '.repeat(indent);
|
|
449
|
+
if (Array.isArray(value)) {
|
|
450
|
+
if (value.length === 0)
|
|
451
|
+
return [`${prefix}${yamlKey(key)}: []`];
|
|
452
|
+
return [
|
|
453
|
+
`${prefix}${yamlKey(key)}:`,
|
|
454
|
+
...value.map((item) => `${' '.repeat(indent + 2)}- ${yamlInlineValue(item)}`),
|
|
455
|
+
];
|
|
456
|
+
}
|
|
457
|
+
if (isRecord(value)) {
|
|
458
|
+
const entries = yamlMappingLines(value, indent + 2);
|
|
459
|
+
return entries.length ? [`${prefix}${yamlKey(key)}:`, ...entries] : [`${prefix}${yamlKey(key)}: {}`];
|
|
460
|
+
}
|
|
461
|
+
return [`${prefix}${yamlKey(key)}: ${yamlInlineValue(value)}`];
|
|
462
|
+
}
|
|
463
|
+
function yamlInlineValue(value) {
|
|
464
|
+
if (typeof value === 'string')
|
|
465
|
+
return yamlString(value);
|
|
466
|
+
if (typeof value === 'number' || typeof value === 'boolean')
|
|
467
|
+
return String(value);
|
|
468
|
+
if (value === null)
|
|
469
|
+
return 'null';
|
|
470
|
+
return yamlString(JSON.stringify(value));
|
|
471
|
+
}
|
|
472
|
+
function yamlKey(value) {
|
|
473
|
+
return /^[A-Za-z0-9_-]+$/.test(value) ? value : yamlString(value);
|
|
474
|
+
}
|
|
475
|
+
function removeUndefined(record) {
|
|
476
|
+
return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined));
|
|
477
|
+
}
|
|
478
|
+
function isRecord(value) {
|
|
479
|
+
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
480
|
+
}
|
|
481
|
+
function isBooleanOrString(value) {
|
|
482
|
+
return typeof value === 'boolean' || typeof value === 'string';
|
|
483
|
+
}
|
|
484
|
+
function isStringOrStringArray(value) {
|
|
485
|
+
return typeof value === 'string' || (Array.isArray(value) && value.every((item) => typeof item === 'string'));
|
|
486
|
+
}
|
|
487
|
+
function isWritableAgentSpacesHermesConfigContent(content) {
|
|
488
|
+
return content.startsWith('# Managed by Agent Spaces for this agent profile.')
|
|
489
|
+
|| content.includes('api_key: ${AGENT_SPACES_HERMES_API_KEY}');
|
|
490
|
+
}
|
|
491
|
+
function getHermesApiMode(provider, baseURL) {
|
|
492
|
+
switch (provider) {
|
|
493
|
+
case 'anthropic-messages':
|
|
494
|
+
return isAnthropicCompatibleBaseURL(baseURL) ? 'anthropic_messages' : 'chat_completions';
|
|
495
|
+
case 'openai-chat-completions':
|
|
496
|
+
case 'openai-chat-completions-to-anthropic-messages':
|
|
497
|
+
return 'chat_completions';
|
|
498
|
+
case 'openai-responses':
|
|
499
|
+
case 'openai-responses-to-anthropic-messages':
|
|
500
|
+
return 'codex_responses';
|
|
501
|
+
default:
|
|
502
|
+
return undefined;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
function isAnthropicCompatibleBaseURL(baseURL) {
|
|
506
|
+
if (!baseURL)
|
|
507
|
+
return false;
|
|
508
|
+
try {
|
|
509
|
+
const url = new URL(baseURL);
|
|
510
|
+
return url.hostname.toLowerCase().endsWith('anthropic.com')
|
|
511
|
+
|| url.pathname.toLowerCase().split('/').includes('anthropic');
|
|
512
|
+
}
|
|
513
|
+
catch {
|
|
514
|
+
return false;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
function yamlString(value) {
|
|
518
|
+
return JSON.stringify(value);
|
|
519
|
+
}
|
|
520
|
+
function truncateConsoleMessage(message) {
|
|
521
|
+
const maxLength = 600;
|
|
522
|
+
if (message.length <= maxLength)
|
|
523
|
+
return message;
|
|
524
|
+
return `${message.slice(0, maxLength)}... [truncated ${message.length - maxLength} chars]`;
|
|
176
525
|
}
|
|
177
526
|
function consumeLines(buffer, onLine) {
|
|
178
527
|
const lines = buffer.split(/\r?\n/);
|
|
@@ -181,13 +530,10 @@ function consumeLines(buffer, onLine) {
|
|
|
181
530
|
onLine(stripAnsi(line).trimEnd());
|
|
182
531
|
return remainder;
|
|
183
532
|
}
|
|
184
|
-
function flushLine(buffer,
|
|
533
|
+
function flushLine(buffer, onLine) {
|
|
185
534
|
const line = stripAnsi(buffer).trimEnd();
|
|
186
|
-
if (line)
|
|
187
|
-
|
|
188
|
-
output.push(formatted);
|
|
189
|
-
options?.onEvent?.({ type: 'output', line: formatted });
|
|
190
|
-
}
|
|
535
|
+
if (line)
|
|
536
|
+
onLine(line);
|
|
191
537
|
return '';
|
|
192
538
|
}
|
|
193
539
|
function extractSessionId(line) {
|
|
@@ -202,9 +548,266 @@ function normalizeSkillNames(skills) {
|
|
|
202
548
|
.filter(Boolean);
|
|
203
549
|
}
|
|
204
550
|
function lastMeaningfulLine(output) {
|
|
205
|
-
return [...output].reverse().find((line) => line.trim() && !line
|
|
551
|
+
return [...output].reverse().find((line) => line.trim() && !isDiagnosticOutputLine(line)) ?? '';
|
|
206
552
|
}
|
|
207
553
|
function stripAnsi(value) {
|
|
208
554
|
return value.replace(/\u001b\[[0-9;]*m/g, '');
|
|
209
555
|
}
|
|
556
|
+
function createHermesLineParser(output, options) {
|
|
557
|
+
let inEchoedQuery = false;
|
|
558
|
+
let inRuntimeConfig = false;
|
|
559
|
+
let inUserMessageEcho = false;
|
|
560
|
+
let inVerboseArgsBlock = false;
|
|
561
|
+
let inVerboseResultBlock = false;
|
|
562
|
+
let inVerboseThinkingBlock = false;
|
|
563
|
+
let inCapturedReasoningBlock = false;
|
|
564
|
+
let toolUseIndex = 0;
|
|
565
|
+
let emittedUsage = false;
|
|
566
|
+
const emitOutput = (line) => {
|
|
567
|
+
output.push(line);
|
|
568
|
+
options?.onEvent?.({ type: 'output', line });
|
|
569
|
+
};
|
|
570
|
+
const emitReasoning = (text) => {
|
|
571
|
+
const normalized = text.trim();
|
|
572
|
+
if (!normalized)
|
|
573
|
+
return;
|
|
574
|
+
options?.onEvent?.({ type: 'reasoning', text: normalized, status: 'completed' });
|
|
575
|
+
};
|
|
576
|
+
const emitUsage = (usageLine) => {
|
|
577
|
+
if (emittedUsage)
|
|
578
|
+
return;
|
|
579
|
+
emittedUsage = true;
|
|
580
|
+
emitOutput(usageLine);
|
|
581
|
+
};
|
|
582
|
+
const emitToolUse = (tool) => {
|
|
583
|
+
toolUseIndex += 1;
|
|
584
|
+
const id = `hermes-tool-${toolUseIndex}`;
|
|
585
|
+
const line = tool.rawInput ? `Tool: ${tool.name} ${tool.rawInput}` : `Tool: ${tool.name}`;
|
|
586
|
+
options?.onEvent?.({
|
|
587
|
+
type: 'tool_use',
|
|
588
|
+
id,
|
|
589
|
+
name: tool.name,
|
|
590
|
+
input: tool.input,
|
|
591
|
+
line,
|
|
592
|
+
});
|
|
593
|
+
};
|
|
594
|
+
return {
|
|
595
|
+
handleLine(line, source) {
|
|
596
|
+
const normalized = line.trim();
|
|
597
|
+
if (!normalized)
|
|
598
|
+
return;
|
|
599
|
+
if (inCapturedReasoningBlock) {
|
|
600
|
+
if (source === 'stderr' && !isHermesLogLine(normalized)) {
|
|
601
|
+
emitReasoning(normalized);
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
if (source === 'stderr')
|
|
605
|
+
inCapturedReasoningBlock = false;
|
|
606
|
+
}
|
|
607
|
+
const capturedReasoning = parseCapturedReasoningStart(normalized);
|
|
608
|
+
if (capturedReasoning !== undefined) {
|
|
609
|
+
if (capturedReasoning)
|
|
610
|
+
emitReasoning(capturedReasoning);
|
|
611
|
+
inCapturedReasoningBlock = true;
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
const toolUse = parseHermesToolUse(normalized);
|
|
615
|
+
if (toolUse) {
|
|
616
|
+
emitToolUse(toolUse);
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
const usageLine = parseHermesUsageLine(normalized);
|
|
620
|
+
if (usageLine) {
|
|
621
|
+
emitUsage(usageLine);
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
const reasoning = parseHermesReasoningLine(normalized);
|
|
625
|
+
if (reasoning) {
|
|
626
|
+
emitReasoning(reasoning);
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
if (source === 'stderr')
|
|
630
|
+
return;
|
|
631
|
+
if (inVerboseThinkingBlock) {
|
|
632
|
+
if (!isHermesThinkingContinuationLine(normalized)) {
|
|
633
|
+
inVerboseThinkingBlock = false;
|
|
634
|
+
}
|
|
635
|
+
else {
|
|
636
|
+
emitReasoning(normalized);
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
if (inVerboseResultBlock) {
|
|
641
|
+
if (isHermesVerboseBlockBoundaryLine(normalized))
|
|
642
|
+
inVerboseResultBlock = false;
|
|
643
|
+
else {
|
|
644
|
+
if (normalized.endsWith('}'))
|
|
645
|
+
inVerboseResultBlock = false;
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
if (inVerboseArgsBlock) {
|
|
650
|
+
if (normalized === '}')
|
|
651
|
+
inVerboseArgsBlock = false;
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
if (inUserMessageEcho)
|
|
655
|
+
return;
|
|
656
|
+
if (/^\[thinking\]/i.test(normalized)) {
|
|
657
|
+
emitReasoning(normalized.replace(/^\[thinking\]\s*/i, ''));
|
|
658
|
+
inVerboseThinkingBlock = true;
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
if (/^Args:\s*\{$/i.test(normalized)) {
|
|
662
|
+
inVerboseArgsBlock = true;
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
if (/^Result:\s*/i.test(normalized)) {
|
|
666
|
+
if (!normalized.endsWith('}'))
|
|
667
|
+
inVerboseResultBlock = true;
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
if (/^Query:\s*/i.test(normalized)) {
|
|
671
|
+
inEchoedQuery = true;
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
if (inEchoedQuery && isHermesRuntimeBoundaryLine(normalized)) {
|
|
675
|
+
inEchoedQuery = false;
|
|
676
|
+
}
|
|
677
|
+
if (/^Agent runtime configuration:\s*$/i.test(normalized)) {
|
|
678
|
+
inEchoedQuery = false;
|
|
679
|
+
inRuntimeConfig = true;
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
if (inEchoedQuery)
|
|
683
|
+
return;
|
|
684
|
+
if (inRuntimeConfig) {
|
|
685
|
+
if (/^User message:\s*$/i.test(normalized)) {
|
|
686
|
+
inRuntimeConfig = false;
|
|
687
|
+
inUserMessageEcho = true;
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
if (isHermesRuntimeConfigLine(normalized))
|
|
691
|
+
return;
|
|
692
|
+
inRuntimeConfig = false;
|
|
693
|
+
}
|
|
694
|
+
if (isHermesNoiseLine(normalized))
|
|
695
|
+
return;
|
|
696
|
+
emitOutput(normalized);
|
|
697
|
+
},
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
function parseHermesToolUse(line) {
|
|
701
|
+
const match = line.match(/\bTool call:\s*([A-Za-z_][\w.-]*)\s+with args:\s*(.+)$/i);
|
|
702
|
+
if (!match)
|
|
703
|
+
return null;
|
|
704
|
+
const name = match[1];
|
|
705
|
+
const rawInput = match[2].replace(/\.\.\.$/, '').trim();
|
|
706
|
+
return {
|
|
707
|
+
name,
|
|
708
|
+
rawInput,
|
|
709
|
+
input: parseJsonObject(rawInput) ?? rawInput,
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
function parseHermesReasoningLine(line) {
|
|
713
|
+
return line.match(/\b(?:Reasoning|Thinking):\s*(.+)$/i)?.[1]?.trim();
|
|
714
|
+
}
|
|
715
|
+
function parseCapturedReasoningStart(line) {
|
|
716
|
+
const match = line.match(/\bCaptured reasoning \(\d+ chars\):\s*(.*)$/i);
|
|
717
|
+
if (!match)
|
|
718
|
+
return undefined;
|
|
719
|
+
return match[1].trim();
|
|
720
|
+
}
|
|
721
|
+
function isHermesLogLine(line) {
|
|
722
|
+
return /^\d{2}:\d{2}:\d{2}\s+-\s+/.test(line);
|
|
723
|
+
}
|
|
724
|
+
function parseHermesUsageLine(line) {
|
|
725
|
+
const apiCallMatch = line.match(/\bAPI call #\d+:.*?\bin=([\d,]+)\s+out=([\d,]+)\s+total=([\d,]+)/i);
|
|
726
|
+
if (apiCallMatch) {
|
|
727
|
+
const cache = line.match(/\bcache=([\d,]+)\/[\d,]+/i)?.[1];
|
|
728
|
+
return formatUsageLine({
|
|
729
|
+
input: apiCallMatch[1],
|
|
730
|
+
output: apiCallMatch[2],
|
|
731
|
+
total: apiCallMatch[3],
|
|
732
|
+
cached: cache,
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
const tokenUsageMatch = line.match(/\bToken usage:\s*prompt=([\d,]+),\s*completion=([\d,]+),\s*total=([\d,]+)/i);
|
|
736
|
+
if (tokenUsageMatch) {
|
|
737
|
+
return formatUsageLine({
|
|
738
|
+
input: tokenUsageMatch[1],
|
|
739
|
+
output: tokenUsageMatch[2],
|
|
740
|
+
total: tokenUsageMatch[3],
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
return undefined;
|
|
744
|
+
}
|
|
745
|
+
function formatUsageLine(usage) {
|
|
746
|
+
const parts = [
|
|
747
|
+
`[Usage] total: ${usage.total}`,
|
|
748
|
+
`input: ${usage.input}`,
|
|
749
|
+
`output: ${usage.output}`,
|
|
750
|
+
];
|
|
751
|
+
if (usage.cached)
|
|
752
|
+
parts.push(`cached: ${usage.cached}`);
|
|
753
|
+
return parts.join(' ');
|
|
754
|
+
}
|
|
755
|
+
function parseJsonObject(value) {
|
|
756
|
+
if (!value.startsWith('{') && !value.startsWith('['))
|
|
757
|
+
return undefined;
|
|
758
|
+
try {
|
|
759
|
+
return JSON.parse(value);
|
|
760
|
+
}
|
|
761
|
+
catch {
|
|
762
|
+
return undefined;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
function isHermesNoiseLine(line) {
|
|
766
|
+
return /^\u{1f916}\s*AI Agent initialized\b/iu.test(line)
|
|
767
|
+
|| /^\u{1f517}\s*Using custom base URL:/iu.test(line)
|
|
768
|
+
|| /^\u{1f511}\s*Using API key:/iu.test(line)
|
|
769
|
+
|| /^\u{1f511}\s*Using token:/iu.test(line)
|
|
770
|
+
|| isHermesRuntimeBoundaryLine(line)
|
|
771
|
+
|| isHermesVerboseUiLine(line);
|
|
772
|
+
}
|
|
773
|
+
function isHermesRuntimeBoundaryLine(line) {
|
|
774
|
+
return /^Initializing agent\b/i.test(line)
|
|
775
|
+
|| /^\u{1f916}\s*AI Agent initialized\b/iu.test(line);
|
|
776
|
+
}
|
|
777
|
+
function isHermesRuntimeConfigLine(line) {
|
|
778
|
+
return /^-\s+/.test(line)
|
|
779
|
+
|| /^(skills|directory,|paths\.|tools you have,|are|built-in runtime tools|internals, previous sessions, or filesystem settings\.)$/i.test(line)
|
|
780
|
+
|| /^For Bash commands that create or modify files under the current working\b/i.test(line)
|
|
781
|
+
|| /^When asked what MCP servers, skills, runtime tools, or Agent Spaces channel\b/i.test(line)
|
|
782
|
+
|| /^Important distinction: MCP servers configured for this agent are only the names\b/i.test(line)
|
|
783
|
+
|| /^in "MCP servers configured for this agent"\./i.test(line)
|
|
784
|
+
|| /^Do not infer availability from provider-side function names, hidden runtime\b/i.test(line);
|
|
785
|
+
}
|
|
786
|
+
function isHermesVerboseUiLine(line) {
|
|
787
|
+
return /^[\u{2705}\u{1f6e0}\u{fe0f}\u{26a0}\u{1f512}\u{1f4be}\u{1f4ca}\u{1f4ac}\u{1f389}\u{1f4de}]/u.test(line)
|
|
788
|
+
|| /^[\u{2500}\u{256d}\u{2570}\u{2502}]/u.test(line)
|
|
789
|
+
|| /^\[thinking\]/i.test(line)
|
|
790
|
+
|| /^┊\s*/u.test(line)
|
|
791
|
+
|| /^(Tool \d+:|Args:|Result:|Resume this session with:|Session:|Duration:|Messages:)/i.test(line)
|
|
792
|
+
|| /^hermes\s+--resume\b/i.test(line)
|
|
793
|
+
|| /^\{".*"\}?$/.test(line);
|
|
794
|
+
}
|
|
795
|
+
function isHermesVerboseBlockBoundaryLine(line) {
|
|
796
|
+
return /^\[thinking\]/i.test(line)
|
|
797
|
+
|| /^[\u{2705}\u{1f389}\u{1f4de}]/u.test(line)
|
|
798
|
+
|| /^[\u{2500}\u{256d}\u{2570}\u{2502}]/u.test(line)
|
|
799
|
+
|| /^(Tool \d+:|Args:|Result:|Resume this session with:|Session:|Duration:|Messages:)/i.test(line)
|
|
800
|
+
|| /^hermes\s+--resume\b/i.test(line);
|
|
801
|
+
}
|
|
802
|
+
function isHermesThinkingContinuationLine(line) {
|
|
803
|
+
return !isHermesVerboseBlockBoundaryLine(line)
|
|
804
|
+
&& !/^\[Usage\]/i.test(line)
|
|
805
|
+
&& !/^Tool:\s*/i.test(line);
|
|
806
|
+
}
|
|
807
|
+
function isDiagnosticOutputLine(line) {
|
|
808
|
+
return line.startsWith('[stderr]')
|
|
809
|
+
|| /^\[Usage\]/i.test(line)
|
|
810
|
+
|| /^Tool:\s*/i.test(line)
|
|
811
|
+
|| isHermesNoiseLine(line);
|
|
812
|
+
}
|
|
210
813
|
//# sourceMappingURL=hermes-runtime.js.map
|