@offerpilot/axiomruntime 0.0.1
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/README.md +185 -0
- package/dist/cli/commands/add.js +29 -0
- package/dist/cli/commands/context.js +29 -0
- package/dist/cli/commands/doctor.js +62 -0
- package/dist/cli/commands/edit.js +85 -0
- package/dist/cli/commands/help.js +16 -0
- package/dist/cli/commands/list.js +25 -0
- package/dist/cli/commands/log.js +63 -0
- package/dist/cli/commands/memory.js +241 -0
- package/dist/cli/commands/report.js +35 -0
- package/dist/cli/commands/session.js +74 -0
- package/dist/cli/commands/setup.js +86 -0
- package/dist/cli/commands/status.js +50 -0
- package/dist/cli/commands/telegram.js +701 -0
- package/dist/cli/commands/use.js +108 -0
- package/dist/cli/commands/version.js +12 -0
- package/dist/cli/index.js +276 -0
- package/dist/cli/output/table.js +22 -0
- package/dist/cli/prompts/prompt.js +37 -0
- package/dist/cli/registry.js +16 -0
- package/dist/core/config/cache-store.js +193 -0
- package/dist/core/config/json-store.js +114 -0
- package/dist/core/config/paths.js +85 -0
- package/dist/core/config/providers-store.js +89 -0
- package/dist/core/config/schema.js +60 -0
- package/dist/core/config/session-store.js +30 -0
- package/dist/core/config/usage-store.js +18 -0
- package/dist/core/context/context-service.js +186 -0
- package/dist/core/integrations/integration-state.js +105 -0
- package/dist/core/logs/log-service.js +56 -0
- package/dist/core/memory/embedding-check.js +121 -0
- package/dist/core/memory/memory-config.js +122 -0
- package/dist/core/models/model-discovery.js +430 -0
- package/dist/core/models/model-filter.js +13 -0
- package/dist/core/providers/provider-service.js +212 -0
- package/dist/core/reports/report-service.js +166 -0
- package/dist/core/runner/command-resolver.js +60 -0
- package/dist/core/runner/engine-registry.js +93 -0
- package/dist/core/runner/fallback.js +114 -0
- package/dist/core/runner/openai-usage-http.js +82 -0
- package/dist/core/runner/openai-usage-proxy.js +1 -0
- package/dist/core/runner/openai-usage-recording.js +172 -0
- package/dist/core/runner/openai-usage-responses.js +469 -0
- package/dist/core/runner/openai-usage-server.js +319 -0
- package/dist/core/runner/openai-usage-types.js +1 -0
- package/dist/core/runner/tool-runner.js +138 -0
- package/dist/core/sessions/session-service.js +47 -0
- package/dist/core/status/doctor-service.js +391 -0
- package/dist/core/status/status-service.js +60 -0
- package/dist/core/types.js +1 -0
- package/dist/core/usage/pricing.js +113 -0
- package/dist/core/usage/usage-service.js +30 -0
- package/dist/core/utils/is-record.js +3 -0
- package/dist/server/index.js +28 -0
- package/dist/server/runtime-server.js +430 -0
- package/dist/telegram/bot-registry.js +80 -0
- package/dist/telegram/bot.js +128 -0
- package/dist/telegram/config.js +235 -0
- package/dist/telegram/engine/claude-engine.js +240 -0
- package/dist/telegram/engine/codex-engine.js +437 -0
- package/dist/telegram/engine/engine-utils.js +67 -0
- package/dist/telegram/engine/process-utils.js +132 -0
- package/dist/telegram/engine/registry.js +31 -0
- package/dist/telegram/engine/types.js +1 -0
- package/dist/telegram/handler-registry.js +28 -0
- package/dist/telegram/handlers/callback.js +311 -0
- package/dist/telegram/handlers/command.js +272 -0
- package/dist/telegram/handlers/document.js +108 -0
- package/dist/telegram/handlers/memory.js +305 -0
- package/dist/telegram/handlers/message.js +701 -0
- package/dist/telegram/handlers/provider.js +332 -0
- package/dist/telegram/handlers/setup.js +527 -0
- package/dist/telegram/handlers/usage.js +124 -0
- package/dist/telegram/index.js +93 -0
- package/dist/telegram/interaction/approval.js +108 -0
- package/dist/telegram/interaction/command-menu.js +253 -0
- package/dist/telegram/interaction/formatter.js +487 -0
- package/dist/telegram/interaction/keyboards.js +145 -0
- package/dist/telegram/interaction/progress-reporter.js +160 -0
- package/dist/telegram/interaction/prompt-middleware.js +168 -0
- package/dist/telegram/interaction/result-store.js +41 -0
- package/dist/telegram/interaction/token-budget.js +21 -0
- package/dist/telegram/interaction/tool-name.js +41 -0
- package/dist/telegram/lifecycle-registry.js +47 -0
- package/dist/telegram/log.js +46 -0
- package/dist/telegram/memory/memory-inject.js +52 -0
- package/dist/telegram/memory/memory-service.js +413 -0
- package/dist/telegram/memory/memory-store.js +216 -0
- package/dist/telegram/memory/types.js +1 -0
- package/dist/telegram/network-retry.js +22 -0
- package/dist/telegram/network.js +53 -0
- package/dist/telegram/session/manager.js +229 -0
- package/dist/telegram/session/store.js +363 -0
- package/dist/telegram/session/types.js +1 -0
- package/dist/telegram/supervisor.js +57 -0
- package/dist/telegram/templates/messages.js +1 -0
- package/docs/README.md +98 -0
- package/docs/USAGE.html +853 -0
- package/package.json +57 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import { getTelegramBotConfigPath, getTelegramBotDbPath, getTelegramBotDir, getTelegramConfigPath, getTelegramDbPath } from "../core/config/paths.js";
|
|
4
|
+
import { readJsonFile, updateJsonFile, writeJsonFile } from "../core/config/json-store.js";
|
|
5
|
+
import { readBoolean, readNumber, readOptionalString, readString } from "../core/config/schema.js";
|
|
6
|
+
const VALID_ENGINES = new Set(["claude", "codex"]);
|
|
7
|
+
const VALID_PERMISSION_MODES = new Set(["readOnly", "default", "acceptEdits", "bypassPermissions"]);
|
|
8
|
+
export async function loadTelegramConfig() {
|
|
9
|
+
const raw = await readJsonFile(getTelegramConfigPath(), getDefaultTelegramConfig());
|
|
10
|
+
return normalizeTelegramConfig(raw);
|
|
11
|
+
}
|
|
12
|
+
export async function saveTelegramConfig(config) {
|
|
13
|
+
const normalized = normalizeTelegramConfig(config);
|
|
14
|
+
await writeJsonFile(getTelegramConfigPath(), normalized);
|
|
15
|
+
await chmodConfigFile();
|
|
16
|
+
return normalized;
|
|
17
|
+
}
|
|
18
|
+
export async function updateTelegramConfig(patch) {
|
|
19
|
+
const next = await updateJsonFile(getTelegramConfigPath(), getDefaultTelegramConfig(), (current) => normalizeTelegramConfig({ ...normalizeTelegramConfig(current), ...patch }));
|
|
20
|
+
await chmodConfigFile();
|
|
21
|
+
return normalizeTelegramConfig(next);
|
|
22
|
+
}
|
|
23
|
+
export async function setTelegramConfigValue(key, value) {
|
|
24
|
+
if (!isTelegramConfigKey(key)) {
|
|
25
|
+
throw new Error(`Unknown telegram config key: ${key}`);
|
|
26
|
+
}
|
|
27
|
+
const parsed = parseTelegramConfigValue(key, value);
|
|
28
|
+
return updateTelegramConfig({ [key]: parsed });
|
|
29
|
+
}
|
|
30
|
+
export function normalizeTelegramConfig(value) {
|
|
31
|
+
const defaults = getDefaultTelegramConfig();
|
|
32
|
+
const input = isConfigRecord(value) ? value : {};
|
|
33
|
+
const defaultEngine = readString(input, "defaultEngine", { fallback: defaults.defaultEngine });
|
|
34
|
+
const defaultPermissionMode = normalizePermissionMode(readString(input, "defaultPermissionMode", { fallback: defaults.defaultPermissionMode }));
|
|
35
|
+
const sessionTimeoutHours = readNumber(input, "sessionTimeoutHours", defaults.sessionTimeoutHours);
|
|
36
|
+
const maxOutputLength = readNumber(input, "maxOutputLength", defaults.maxOutputLength);
|
|
37
|
+
const allowedUserIds = normalizeAllowedUserIds(input.allowedUserIds);
|
|
38
|
+
const provider = readOptionalString(input, "provider");
|
|
39
|
+
const model = readOptionalString(input, "model");
|
|
40
|
+
const proxyUrl = readOptionalString(input, "proxyUrl");
|
|
41
|
+
const dbPath = readString(input, "dbPath", { fallback: defaults.dbPath }) || defaults.dbPath;
|
|
42
|
+
return {
|
|
43
|
+
token: readString(input, "token", { fallback: defaults.token }),
|
|
44
|
+
allowAllUsers: readBoolean(input.allowAllUsers ?? defaults.allowAllUsers),
|
|
45
|
+
allowedUserIds,
|
|
46
|
+
defaultEngine: VALID_ENGINES.has(defaultEngine) ? defaultEngine : defaults.defaultEngine,
|
|
47
|
+
defaultCwd: String(input.defaultCwd ?? defaults.defaultCwd).trim() || defaults.defaultCwd,
|
|
48
|
+
defaultPermissionMode,
|
|
49
|
+
sessionTimeoutHours: Number.isFinite(sessionTimeoutHours) && sessionTimeoutHours > 0 ? sessionTimeoutHours : defaults.sessionTimeoutHours,
|
|
50
|
+
maxOutputLength: Number.isFinite(maxOutputLength) && maxOutputLength >= 500 ? maxOutputLength : defaults.maxOutputLength,
|
|
51
|
+
...(provider ? { provider } : {}),
|
|
52
|
+
...(model ? { model } : {}),
|
|
53
|
+
...(proxyUrl ? { proxyUrl } : {}),
|
|
54
|
+
dbPath
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export function parseTelegramConfigValue(key, rawValue) {
|
|
58
|
+
const value = rawValue.trim();
|
|
59
|
+
if (key === "allowedUserIds") {
|
|
60
|
+
return normalizeAllowedUserIds(value);
|
|
61
|
+
}
|
|
62
|
+
if (key === "allowAllUsers") {
|
|
63
|
+
return parseBoolean(value);
|
|
64
|
+
}
|
|
65
|
+
if (key === "sessionTimeoutHours" || key === "maxOutputLength") {
|
|
66
|
+
const parsed = Number(value);
|
|
67
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
68
|
+
throw new Error(`${key} must be a positive number.`);
|
|
69
|
+
}
|
|
70
|
+
return parsed;
|
|
71
|
+
}
|
|
72
|
+
if (key === "defaultEngine") {
|
|
73
|
+
if (!VALID_ENGINES.has(value)) {
|
|
74
|
+
throw new Error("defaultEngine must be claude or codex.");
|
|
75
|
+
}
|
|
76
|
+
return value;
|
|
77
|
+
}
|
|
78
|
+
if (key === "defaultPermissionMode") {
|
|
79
|
+
return normalizePermissionMode(value);
|
|
80
|
+
}
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
export function normalizePermissionMode(value) {
|
|
84
|
+
if (value === "auto-edit")
|
|
85
|
+
return "acceptEdits";
|
|
86
|
+
if (value === "full-auto")
|
|
87
|
+
return "bypassPermissions";
|
|
88
|
+
if (VALID_PERMISSION_MODES.has(value))
|
|
89
|
+
return value;
|
|
90
|
+
return getDefaultTelegramConfig().defaultPermissionMode;
|
|
91
|
+
}
|
|
92
|
+
export function describeTelegramConfig(config) {
|
|
93
|
+
return [
|
|
94
|
+
`token: ${config.token ? maskToken(config.token) : "(empty)"}`,
|
|
95
|
+
`allowAllUsers: ${config.allowAllUsers ? "true" : "false"}`,
|
|
96
|
+
`allowedUserIds: ${config.allowedUserIds.length ? config.allowedUserIds.join(", ") : "(empty)"}`,
|
|
97
|
+
`defaultEngine: ${config.defaultEngine}`,
|
|
98
|
+
`defaultCwd: ${config.defaultCwd}`,
|
|
99
|
+
`defaultPermissionMode: ${config.defaultPermissionMode}`,
|
|
100
|
+
`sessionTimeoutHours: ${config.sessionTimeoutHours}`,
|
|
101
|
+
`maxOutputLength: ${config.maxOutputLength}`,
|
|
102
|
+
`provider: ${config.provider ?? "(auto)"}`,
|
|
103
|
+
`model: ${config.model ?? "(auto)"}`,
|
|
104
|
+
`proxyUrl: ${config.proxyUrl ?? "(none)"}`,
|
|
105
|
+
`dbPath: ${config.dbPath}`
|
|
106
|
+
];
|
|
107
|
+
}
|
|
108
|
+
function normalizeAllowedUserIds(value) {
|
|
109
|
+
const items = Array.isArray(value)
|
|
110
|
+
? value
|
|
111
|
+
: typeof value === "string"
|
|
112
|
+
? value.split(/[,\s]+/).filter(Boolean)
|
|
113
|
+
: [];
|
|
114
|
+
const seen = new Set();
|
|
115
|
+
const ids = [];
|
|
116
|
+
for (const item of items) {
|
|
117
|
+
const parsed = Number(item);
|
|
118
|
+
if (Number.isInteger(parsed) && parsed > 0 && !seen.has(parsed)) {
|
|
119
|
+
seen.add(parsed);
|
|
120
|
+
ids.push(parsed);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return ids;
|
|
124
|
+
}
|
|
125
|
+
function getDefaultTelegramConfig() {
|
|
126
|
+
return {
|
|
127
|
+
token: "",
|
|
128
|
+
allowAllUsers: false,
|
|
129
|
+
allowedUserIds: [],
|
|
130
|
+
defaultEngine: "claude",
|
|
131
|
+
defaultCwd: os.homedir(),
|
|
132
|
+
defaultPermissionMode: "bypassPermissions",
|
|
133
|
+
sessionTimeoutHours: 24,
|
|
134
|
+
maxOutputLength: 4000,
|
|
135
|
+
dbPath: getTelegramDbPath()
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function isTelegramConfigKey(key) {
|
|
139
|
+
return [
|
|
140
|
+
"token",
|
|
141
|
+
"allowAllUsers",
|
|
142
|
+
"allowedUserIds",
|
|
143
|
+
"defaultEngine",
|
|
144
|
+
"defaultCwd",
|
|
145
|
+
"defaultPermissionMode",
|
|
146
|
+
"sessionTimeoutHours",
|
|
147
|
+
"maxOutputLength",
|
|
148
|
+
"provider",
|
|
149
|
+
"model",
|
|
150
|
+
"proxyUrl",
|
|
151
|
+
"dbPath"
|
|
152
|
+
].includes(key);
|
|
153
|
+
}
|
|
154
|
+
function parseBoolean(value) {
|
|
155
|
+
return readBoolean(value);
|
|
156
|
+
}
|
|
157
|
+
function isConfigRecord(value) {
|
|
158
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
159
|
+
}
|
|
160
|
+
async function chmodConfigFile() {
|
|
161
|
+
try {
|
|
162
|
+
await fs.chmod(getTelegramConfigPath(), 0o600);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
// Best-effort on filesystems that may not support chmod.
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function maskToken(token) {
|
|
169
|
+
if (token.length <= 12)
|
|
170
|
+
return "****";
|
|
171
|
+
return `${token.slice(0, 6)}****${token.slice(-4)}`;
|
|
172
|
+
}
|
|
173
|
+
export async function loadBotConfig(name) {
|
|
174
|
+
const configPath = getTelegramBotConfigPath(name);
|
|
175
|
+
const defaults = getDefaultBotConfig(name);
|
|
176
|
+
const raw = await readJsonFile(configPath, defaults);
|
|
177
|
+
return normalizeTelegramConfig(raw);
|
|
178
|
+
}
|
|
179
|
+
export async function saveBotConfig(name, config) {
|
|
180
|
+
const configPath = getTelegramBotConfigPath(name);
|
|
181
|
+
const dir = getTelegramBotDir();
|
|
182
|
+
await fs.mkdir(dir, { recursive: true });
|
|
183
|
+
const withDb = { ...config, dbPath: config.dbPath || getTelegramBotDbPath(name) };
|
|
184
|
+
const normalized = normalizeTelegramConfig(withDb);
|
|
185
|
+
if (normalized.dbPath === getTelegramDbPath()) {
|
|
186
|
+
normalized.dbPath = getTelegramBotDbPath(name);
|
|
187
|
+
}
|
|
188
|
+
await writeJsonFile(configPath, normalized);
|
|
189
|
+
try {
|
|
190
|
+
await fs.chmod(configPath, 0o600);
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
// Best-effort on filesystems that may not support chmod.
|
|
194
|
+
}
|
|
195
|
+
return normalized;
|
|
196
|
+
}
|
|
197
|
+
export async function updateBotConfig(name, patch) {
|
|
198
|
+
const configPath = getTelegramBotConfigPath(name);
|
|
199
|
+
const defaults = getDefaultBotConfig(name);
|
|
200
|
+
await fs.mkdir(getTelegramBotDir(), { recursive: true });
|
|
201
|
+
const next = await updateJsonFile(configPath, defaults, (current) => {
|
|
202
|
+
const normalized = normalizeTelegramConfig({ ...normalizeTelegramConfig(current), ...patch });
|
|
203
|
+
if (normalized.dbPath === getTelegramDbPath()) {
|
|
204
|
+
normalized.dbPath = getTelegramBotDbPath(name);
|
|
205
|
+
}
|
|
206
|
+
return normalized;
|
|
207
|
+
});
|
|
208
|
+
try {
|
|
209
|
+
await fs.chmod(configPath, 0o600);
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
// Best-effort on filesystems that may not support chmod.
|
|
213
|
+
}
|
|
214
|
+
return normalizeTelegramConfig(next);
|
|
215
|
+
}
|
|
216
|
+
export async function setBotConfigValue(name, key, value) {
|
|
217
|
+
if (!isTelegramConfigKey(key)) {
|
|
218
|
+
throw new Error(`Unknown telegram config key: ${key}`);
|
|
219
|
+
}
|
|
220
|
+
const parsed = parseTelegramConfigValue(key, value);
|
|
221
|
+
return updateBotConfig(name, { [key]: parsed });
|
|
222
|
+
}
|
|
223
|
+
function getDefaultBotConfig(name) {
|
|
224
|
+
return {
|
|
225
|
+
token: "",
|
|
226
|
+
allowAllUsers: false,
|
|
227
|
+
allowedUserIds: [],
|
|
228
|
+
defaultEngine: "claude",
|
|
229
|
+
defaultCwd: os.homedir(),
|
|
230
|
+
defaultPermissionMode: "bypassPermissions",
|
|
231
|
+
sessionTimeoutHours: 24,
|
|
232
|
+
maxOutputLength: 4000,
|
|
233
|
+
dbPath: getTelegramBotDbPath(name)
|
|
234
|
+
};
|
|
235
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { cleanupRuntime, createCommandPermissionRequest, formatEngineTimeout, prepareEngineRuntime, summarizeRuntime } from "./engine-utils.js";
|
|
2
|
+
import { createJsonLineAccumulator, extractJsonLines, spawnCapture, stripAnsi, terminateProcessTree } from "./process-utils.js";
|
|
3
|
+
import { requiresDangerousConfirmation } from "../interaction/approval.js";
|
|
4
|
+
import { writeTelegramLog } from "../log.js";
|
|
5
|
+
import { isRecord } from "../../core/utils/is-record.js";
|
|
6
|
+
export class ClaudeEngine {
|
|
7
|
+
name = "claude";
|
|
8
|
+
processes = new Map();
|
|
9
|
+
controllers = new Map();
|
|
10
|
+
async execute(params) {
|
|
11
|
+
const permissionMode = params.permissionMode ?? "default";
|
|
12
|
+
const runtime = await prepareEngineRuntime("claude", params.providerName, params.modelName, permissionMode);
|
|
13
|
+
const controller = new AbortController();
|
|
14
|
+
this.controllers.set(params.chatId, controller);
|
|
15
|
+
const signal = params.signal ?? controller.signal;
|
|
16
|
+
const startTime = Date.now();
|
|
17
|
+
if (permissionMode === "bypassPermissions" && requiresDangerousConfirmation(params.prompt)) {
|
|
18
|
+
const approved = await params.onPermission(createCommandPermissionRequest("Claude prompt", params.prompt));
|
|
19
|
+
if (!approved) {
|
|
20
|
+
cleanupRuntime(runtime);
|
|
21
|
+
throw new Error("Dangerous command denied.");
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const args = buildClaudeArgs({
|
|
25
|
+
prompt: params.prompt,
|
|
26
|
+
model: runtime.model,
|
|
27
|
+
settingsPath: runtime.claudeSettingsPath,
|
|
28
|
+
permissionMode,
|
|
29
|
+
resume: params.resume,
|
|
30
|
+
allowedTools: params.allowedTools
|
|
31
|
+
});
|
|
32
|
+
await params.onProgress?.({ phase: "thinking", message: "Claude started" });
|
|
33
|
+
await writeTelegramLog({
|
|
34
|
+
action: "claude_start",
|
|
35
|
+
message: `Starting Claude for Telegram chat ${params.chatId}`,
|
|
36
|
+
metadata: { ...summarizeRuntime(runtime.provider, runtime.model, "claude"), cwd: params.cwd }
|
|
37
|
+
});
|
|
38
|
+
try {
|
|
39
|
+
const progressParser = createJsonLineAccumulator((event) => handleClaudeProgressEvent(event, params.onProgress));
|
|
40
|
+
const result = await spawnCapture({
|
|
41
|
+
command: runtime.command,
|
|
42
|
+
args,
|
|
43
|
+
cwd: params.cwd,
|
|
44
|
+
env: {
|
|
45
|
+
...runtime.env,
|
|
46
|
+
CLAUDE_CODE_HEADLESS: "1"
|
|
47
|
+
},
|
|
48
|
+
signal,
|
|
49
|
+
timeoutMs: params.timeoutMs,
|
|
50
|
+
onStdout: (chunk) => progressParser.push(chunk)
|
|
51
|
+
}, (child) => this.processes.set(params.chatId, child));
|
|
52
|
+
progressParser.end();
|
|
53
|
+
if (result.timedOut) {
|
|
54
|
+
throw new Error(formatEngineTimeout("Claude Code", params.timeoutMs, runtime.provider.name, runtime.model));
|
|
55
|
+
}
|
|
56
|
+
if (result.aborted) {
|
|
57
|
+
throw new Error("任务已停止。");
|
|
58
|
+
}
|
|
59
|
+
if (result.code !== 0) {
|
|
60
|
+
throw new Error(formatClaudeFailure(result.stdout, result.stderr, result.code));
|
|
61
|
+
}
|
|
62
|
+
const parsed = parseClaudeOutput(result.stdout, Date.now() - startTime);
|
|
63
|
+
await params.onProgress?.({ phase: "done" });
|
|
64
|
+
return {
|
|
65
|
+
...parsed,
|
|
66
|
+
provider: runtime.provider.name,
|
|
67
|
+
model: runtime.model
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
this.processes.delete(params.chatId);
|
|
72
|
+
this.controllers.delete(params.chatId);
|
|
73
|
+
cleanupRuntime(runtime);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
abort(chatId) {
|
|
77
|
+
if (!Number.isFinite(chatId)) {
|
|
78
|
+
for (const id of new Set([...this.controllers.keys(), ...this.processes.keys()])) {
|
|
79
|
+
this.abort(id);
|
|
80
|
+
}
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
this.controllers.get(chatId)?.abort();
|
|
84
|
+
const child = this.processes.get(chatId);
|
|
85
|
+
if (child)
|
|
86
|
+
terminateProcessTree(child, "SIGTERM");
|
|
87
|
+
this.controllers.delete(chatId);
|
|
88
|
+
this.processes.delete(chatId);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export function buildClaudeArgs(options) {
|
|
92
|
+
const permissionMode = options.permissionMode === "readOnly" ? "default" : options.permissionMode;
|
|
93
|
+
const args = [
|
|
94
|
+
"-p",
|
|
95
|
+
options.prompt,
|
|
96
|
+
"--output-format",
|
|
97
|
+
"stream-json",
|
|
98
|
+
"--verbose",
|
|
99
|
+
"--model",
|
|
100
|
+
options.model,
|
|
101
|
+
"--settings",
|
|
102
|
+
options.settingsPath ?? "",
|
|
103
|
+
"--permission-mode",
|
|
104
|
+
permissionMode,
|
|
105
|
+
"--max-budget-usd",
|
|
106
|
+
"20"
|
|
107
|
+
].filter(Boolean);
|
|
108
|
+
if (options.resume) {
|
|
109
|
+
args.push("--resume", options.resume);
|
|
110
|
+
}
|
|
111
|
+
if (options.allowedTools?.length) {
|
|
112
|
+
args.push("--allowedTools", options.allowedTools.join(","));
|
|
113
|
+
}
|
|
114
|
+
if (options.permissionMode === "bypassPermissions") {
|
|
115
|
+
args.push("--dangerously-skip-permissions");
|
|
116
|
+
}
|
|
117
|
+
if (options.permissionMode === "readOnly") {
|
|
118
|
+
args.push("--allowedTools", "Read,Grep,Glob,WebFetch,WebSearch");
|
|
119
|
+
}
|
|
120
|
+
return args;
|
|
121
|
+
}
|
|
122
|
+
export function parseClaudeOutput(raw, duration) {
|
|
123
|
+
const events = extractJsonLines(raw);
|
|
124
|
+
let response = "";
|
|
125
|
+
let sessionId;
|
|
126
|
+
let cost;
|
|
127
|
+
const toolCalls = [];
|
|
128
|
+
for (const event of events) {
|
|
129
|
+
if (!isRecord(event))
|
|
130
|
+
continue;
|
|
131
|
+
if (typeof event.session_id === "string")
|
|
132
|
+
sessionId = event.session_id;
|
|
133
|
+
if (typeof event.sessionId === "string")
|
|
134
|
+
sessionId = event.sessionId;
|
|
135
|
+
if (typeof event.total_cost_usd === "number")
|
|
136
|
+
cost = event.total_cost_usd;
|
|
137
|
+
if (typeof event.cost_usd === "number")
|
|
138
|
+
cost = event.cost_usd;
|
|
139
|
+
if (event.type === "result" && typeof event.result === "string") {
|
|
140
|
+
response = event.result;
|
|
141
|
+
}
|
|
142
|
+
collectTextFromMessage(event, (text) => {
|
|
143
|
+
if (!response.includes(text))
|
|
144
|
+
response += `${response ? "\n" : ""}${text}`;
|
|
145
|
+
});
|
|
146
|
+
collectToolCalls(event, toolCalls);
|
|
147
|
+
}
|
|
148
|
+
if (!response.trim()) {
|
|
149
|
+
response = stripAnsi(raw).trim();
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
sessionId,
|
|
153
|
+
response,
|
|
154
|
+
toolCalls,
|
|
155
|
+
filesChanged: extractFilesChanged(toolCalls),
|
|
156
|
+
cost,
|
|
157
|
+
duration
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
export function formatClaudeFailure(stdout, stderr = "", code) {
|
|
161
|
+
let terminalResult = "";
|
|
162
|
+
for (const event of extractJsonLines(stdout)) {
|
|
163
|
+
if (!isRecord(event) || event.type !== "result")
|
|
164
|
+
continue;
|
|
165
|
+
if (typeof event.result === "string" && event.result.trim()) {
|
|
166
|
+
terminalResult = event.result.trim();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const stderrText = stripAnsi(stderr).trim();
|
|
170
|
+
const plainStdout = stripAnsi(stdout)
|
|
171
|
+
.split(/\r?\n/)
|
|
172
|
+
.map((line) => line.trim())
|
|
173
|
+
.filter((line) => line && !line.startsWith("{"))
|
|
174
|
+
.slice(-6)
|
|
175
|
+
.join("\n");
|
|
176
|
+
const diagnostics = [stderrText, terminalResult, plainStdout]
|
|
177
|
+
.filter((value, index, values) => value && values.indexOf(value) === index);
|
|
178
|
+
return diagnostics.join("\n") || `Claude exited with code ${code ?? "unknown"}.`;
|
|
179
|
+
}
|
|
180
|
+
function handleClaudeProgressEvent(event, onProgress) {
|
|
181
|
+
if (!onProgress)
|
|
182
|
+
return;
|
|
183
|
+
if (!isRecord(event) || event.type !== "assistant")
|
|
184
|
+
return;
|
|
185
|
+
const tool = findFirstToolName(event);
|
|
186
|
+
if (tool) {
|
|
187
|
+
void onProgress({ phase: "tool_calling", tool });
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
const responseText = readMessageText(event);
|
|
191
|
+
void onProgress({ phase: "writing", responseText: responseText || undefined });
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function readMessageText(event) {
|
|
195
|
+
const parts = [];
|
|
196
|
+
collectTextFromMessage(event, (text) => {
|
|
197
|
+
if (text.trim())
|
|
198
|
+
parts.push(text.trim());
|
|
199
|
+
});
|
|
200
|
+
return parts.join("\n");
|
|
201
|
+
}
|
|
202
|
+
function collectTextFromMessage(event, add) {
|
|
203
|
+
const message = isRecord(event.message) ? event.message : event;
|
|
204
|
+
const content = Array.isArray(message.content) ? message.content : [];
|
|
205
|
+
for (const item of content) {
|
|
206
|
+
if (isRecord(item) && item.type === "text" && typeof item.text === "string") {
|
|
207
|
+
add(item.text);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function collectToolCalls(event, calls) {
|
|
212
|
+
const message = isRecord(event.message) ? event.message : event;
|
|
213
|
+
const content = Array.isArray(message.content) ? message.content : [];
|
|
214
|
+
for (const item of content) {
|
|
215
|
+
if (isRecord(item) && item.type === "tool_use") {
|
|
216
|
+
calls.push({
|
|
217
|
+
tool: String(item.name ?? "tool"),
|
|
218
|
+
input: isRecord(item.input) ? item.input : {},
|
|
219
|
+
output: "",
|
|
220
|
+
status: "success"
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function findFirstToolName(event) {
|
|
226
|
+
const message = isRecord(event.message) ? event.message : event;
|
|
227
|
+
const content = Array.isArray(message.content) ? message.content : [];
|
|
228
|
+
const tool = content.find((item) => isRecord(item) && item.type === "tool_use");
|
|
229
|
+
return isRecord(tool) ? String(tool.name ?? "") || undefined : undefined;
|
|
230
|
+
}
|
|
231
|
+
function extractFilesChanged(calls) {
|
|
232
|
+
const files = new Set();
|
|
233
|
+
for (const call of calls) {
|
|
234
|
+
const filePath = call.input.file_path ?? call.input.path;
|
|
235
|
+
if (typeof filePath === "string" && ["Write", "Edit", "MultiEdit", "NotebookEdit"].includes(call.tool)) {
|
|
236
|
+
files.add(filePath);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return [...files];
|
|
240
|
+
}
|