@a1hvdy/cc-openclaw 0.6.0 → 0.7.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/dist/src/engines/persistent-session.js +13 -0
- package/dist/src/engines/persistent-session.js.map +1 -1
- package/dist/src/lib/config.d.ts +2 -0
- package/dist/src/lib/config.js +19 -0
- package/dist/src/lib/config.js.map +1 -1
- package/dist/src/lib/trajectory.d.ts +1 -1
- package/dist/src/lib/trajectory.js.map +1 -1
- package/dist/src/lib/vendor-paths.d.ts +6 -4
- package/dist/src/lib/vendor-paths.js +21 -14
- package/dist/src/lib/vendor-paths.js.map +1 -1
- package/dist/src/openai-compat/openai-compat.d.ts +7 -1
- package/dist/src/openai-compat/openai-compat.js +35 -4
- package/dist/src/openai-compat/openai-compat.js.map +1 -1
- package/dist/src/openai-compat/sse-translator.d.ts +23 -3
- package/dist/src/openai-compat/sse-translator.js +45 -6
- package/dist/src/openai-compat/sse-translator.js.map +1 -1
- package/dist/src/types.d.ts +9 -0
- package/package.json +2 -3
- package/vendor/base-oneshot-session.d.ts +0 -87
- package/vendor/base-oneshot-session.js +0 -227
- package/vendor/base-oneshot-session.js.map +0 -1
- package/vendor/circuit-breaker.d.ts +0 -21
- package/vendor/circuit-breaker.js +0 -47
- package/vendor/circuit-breaker.js.map +0 -1
- package/vendor/consensus.d.ts +0 -20
- package/vendor/consensus.js +0 -52
- package/vendor/consensus.js.map +0 -1
- package/vendor/constants.d.ts +0 -130
- package/vendor/constants.js +0 -139
- package/vendor/constants.js.map +0 -1
- package/vendor/council.d.ts +0 -67
- package/vendor/council.js +0 -913
- package/vendor/council.js.map +0 -1
- package/vendor/embedded-server.d.ts +0 -25
- package/vendor/embedded-server.js +0 -373
- package/vendor/embedded-server.js.map +0 -1
- package/vendor/inbox-manager.d.ts +0 -38
- package/vendor/inbox-manager.js +0 -111
- package/vendor/inbox-manager.js.map +0 -1
- package/vendor/index.d.ts +0 -63
- package/vendor/index.js +0 -705
- package/vendor/index.js.map +0 -1
- package/vendor/logger.d.ts +0 -16
- package/vendor/logger.js +0 -44
- package/vendor/logger.js.map +0 -1
- package/vendor/models.d.ts +0 -69
- package/vendor/models.js +0 -289
- package/vendor/models.js.map +0 -1
- package/vendor/openai-compat.d.ts +0 -197
- package/vendor/openai-compat.js +0 -765
- package/vendor/openai-compat.js.map +0 -1
- package/vendor/persistent-codex-session.d.ts +0 -16
- package/vendor/persistent-codex-session.js +0 -105
- package/vendor/persistent-codex-session.js.map +0 -1
- package/vendor/persistent-cursor-session.d.ts +0 -21
- package/vendor/persistent-cursor-session.js +0 -241
- package/vendor/persistent-cursor-session.js.map +0 -1
- package/vendor/persistent-custom-session.d.ts +0 -78
- package/vendor/persistent-custom-session.js +0 -937
- package/vendor/persistent-custom-session.js.map +0 -1
- package/vendor/persistent-gemini-session.d.ts +0 -21
- package/vendor/persistent-gemini-session.js +0 -216
- package/vendor/persistent-gemini-session.js.map +0 -1
- package/vendor/persistent-session.d.ts +0 -74
- package/vendor/persistent-session.js +0 -698
- package/vendor/persistent-session.js.map +0 -1
- package/vendor/proxy/anthropic-adapter.d.ts +0 -136
- package/vendor/proxy/anthropic-adapter.js +0 -392
- package/vendor/proxy/anthropic-adapter.js.map +0 -1
- package/vendor/proxy/handler.d.ts +0 -39
- package/vendor/proxy/handler.js +0 -323
- package/vendor/proxy/handler.js.map +0 -1
- package/vendor/proxy/schema-cleaner.d.ts +0 -11
- package/vendor/proxy/schema-cleaner.js +0 -34
- package/vendor/proxy/schema-cleaner.js.map +0 -1
- package/vendor/proxy/thought-cache.d.ts +0 -19
- package/vendor/proxy/thought-cache.js +0 -53
- package/vendor/proxy/thought-cache.js.map +0 -1
- package/vendor/session-manager.d.ts +0 -211
- package/vendor/session-manager.js +0 -1345
- package/vendor/session-manager.js.map +0 -1
- package/vendor/skill-resolver.js +0 -107
- package/vendor/types.d.ts +0 -466
- package/vendor/types.js +0 -8
- package/vendor/types.js.map +0 -1
- package/vendor/validation.d.ts +0 -31
- package/vendor/validation.js +0 -104
- package/vendor/validation.js.map +0 -1
|
@@ -1,937 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Persistent Custom Session — generic wrapper for any coding agent CLI
|
|
3
|
-
*
|
|
4
|
-
* Supports two operating modes based on CustomEngineConfig.persistent:
|
|
5
|
-
*
|
|
6
|
-
* persistent=true — long-running subprocess with stream-json I/O over
|
|
7
|
-
* stdin/stdout (like Claude Code). Started once, messages
|
|
8
|
-
* sent as JSON lines on stdin.
|
|
9
|
-
*
|
|
10
|
-
* persistent=false — one-shot per send (like Gemini/Codex). Each send()
|
|
11
|
-
* spawns a new process with the message as a CLI argument.
|
|
12
|
-
*
|
|
13
|
-
* The config maps OpenClaw session concepts (permission modes, models, etc.)
|
|
14
|
-
* to the target CLI's flags, so any coding agent with a CLI can be integrated
|
|
15
|
-
* without writing engine-specific code.
|
|
16
|
-
*/
|
|
17
|
-
import { spawn } from 'node:child_process';
|
|
18
|
-
import { EventEmitter } from 'node:events';
|
|
19
|
-
import * as readline from 'node:readline';
|
|
20
|
-
import * as fs from 'node:fs';
|
|
21
|
-
import * as path from 'node:path';
|
|
22
|
-
import { getModelPricing as _getModelPricingBase, } from './types.js';
|
|
23
|
-
import { resolveAlias, estimateTokens } from './models.js';
|
|
24
|
-
import { CONTEXT_HIGH_THRESHOLD, MAX_HISTORY_ITEMS, DEFAULT_HISTORY_LIMIT, SESSION_READY_TIMEOUT_MS, SESSION_READY_FALLBACK_MS, TURN_TIMEOUT_MS, COMPACT_TIMEOUT_MS, STOP_SIGKILL_DELAY_MS, SESSION_EVENT, } from './constants.js';
|
|
25
|
-
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
26
|
-
function getModelPricing(model, engineConfig) {
|
|
27
|
-
// Try the model registry first; fall back to engine-level pricing
|
|
28
|
-
const base = _getModelPricingBase(model, 'claude-sonnet-4-6');
|
|
29
|
-
if (base.input === 0 && base.output === 0 && engineConfig.pricing) {
|
|
30
|
-
return engineConfig.pricing;
|
|
31
|
-
}
|
|
32
|
-
return base;
|
|
33
|
-
}
|
|
34
|
-
function resolveBin(engineConfig) {
|
|
35
|
-
if (engineConfig.binEnv) {
|
|
36
|
-
const envVal = process.env[engineConfig.binEnv];
|
|
37
|
-
if (envVal)
|
|
38
|
-
return envVal;
|
|
39
|
-
}
|
|
40
|
-
return engineConfig.bin;
|
|
41
|
-
}
|
|
42
|
-
/** Build sanitizer function from config patterns + common defaults */
|
|
43
|
-
function buildSanitizer(engineConfig) {
|
|
44
|
-
const patterns = [
|
|
45
|
-
// Always sanitize Bearer tokens and common API key patterns
|
|
46
|
-
{ re: /Bearer [a-zA-Z0-9_-]+/g, replacement: 'Bearer ***' },
|
|
47
|
-
{ re: /sk-[a-zA-Z0-9_-]{10,}/g, replacement: 'sk-***' },
|
|
48
|
-
];
|
|
49
|
-
if (engineConfig.sanitizePatterns) {
|
|
50
|
-
for (const p of engineConfig.sanitizePatterns) {
|
|
51
|
-
try {
|
|
52
|
-
patterns.push({ re: new RegExp(p, 'g'), replacement: '***' });
|
|
53
|
-
}
|
|
54
|
-
catch {
|
|
55
|
-
// Invalid regex — skip silently
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return (text) => {
|
|
60
|
-
let result = text;
|
|
61
|
-
for (const { re, replacement } of patterns) {
|
|
62
|
-
re.lastIndex = 0;
|
|
63
|
-
result = result.replace(re, replacement);
|
|
64
|
-
}
|
|
65
|
-
return result;
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
// ─── PersistentCustomSession ───────────────────────────────────────────────
|
|
69
|
-
export class PersistentCustomSession extends EventEmitter {
|
|
70
|
-
options;
|
|
71
|
-
engineConfig;
|
|
72
|
-
engineBin;
|
|
73
|
-
sanitize;
|
|
74
|
-
// Persistent mode state
|
|
75
|
-
proc = null;
|
|
76
|
-
_rl = null;
|
|
77
|
-
_streamCallbacks = null;
|
|
78
|
-
_contextHighFired = false;
|
|
79
|
-
// One-shot mode state
|
|
80
|
-
currentProc = null;
|
|
81
|
-
_currentRl = null;
|
|
82
|
-
// Shared state
|
|
83
|
-
_isReady = false;
|
|
84
|
-
_isPaused = false;
|
|
85
|
-
_isBusy = false;
|
|
86
|
-
currentRequestId = 0;
|
|
87
|
-
_startTime = null;
|
|
88
|
-
_history = [];
|
|
89
|
-
sessionId;
|
|
90
|
-
_stats = {
|
|
91
|
-
turns: 0,
|
|
92
|
-
toolCalls: 0,
|
|
93
|
-
toolErrors: 0,
|
|
94
|
-
tokensIn: 0,
|
|
95
|
-
tokensOut: 0,
|
|
96
|
-
cachedTokens: 0,
|
|
97
|
-
costUsd: 0,
|
|
98
|
-
lastActivity: null,
|
|
99
|
-
};
|
|
100
|
-
constructor(config) {
|
|
101
|
-
super();
|
|
102
|
-
if (!config.customEngine) {
|
|
103
|
-
throw new Error('CustomEngineConfig is required for custom engine sessions');
|
|
104
|
-
}
|
|
105
|
-
this.engineConfig = config.customEngine;
|
|
106
|
-
this.engineBin = resolveBin(this.engineConfig);
|
|
107
|
-
this.sanitize = buildSanitizer(this.engineConfig);
|
|
108
|
-
this.options = {
|
|
109
|
-
...config,
|
|
110
|
-
permissionMode: config.permissionMode || 'acceptEdits',
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
get pid() {
|
|
114
|
-
return this.proc?.pid ?? this.currentProc?.pid ?? undefined;
|
|
115
|
-
}
|
|
116
|
-
get isReady() {
|
|
117
|
-
return this._isReady;
|
|
118
|
-
}
|
|
119
|
-
get isPaused() {
|
|
120
|
-
return this._isPaused;
|
|
121
|
-
}
|
|
122
|
-
get isBusy() {
|
|
123
|
-
return this._isBusy;
|
|
124
|
-
}
|
|
125
|
-
// ─── Start ───────────────────────────────────────────────────────────────
|
|
126
|
-
async start() {
|
|
127
|
-
// Normalize CWD
|
|
128
|
-
if (this.options.cwd) {
|
|
129
|
-
this.options.cwd = path.resolve(this.options.cwd);
|
|
130
|
-
if (!fs.existsSync(this.options.cwd)) {
|
|
131
|
-
fs.mkdirSync(this.options.cwd, { recursive: true });
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
if (this.engineConfig.persistent) {
|
|
135
|
-
return this._startPersistent();
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
return this._startOneShot();
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
// ── Persistent mode start ───────────────────────────────────────────────
|
|
142
|
-
async _startPersistent() {
|
|
143
|
-
const a = this.engineConfig.args;
|
|
144
|
-
const args = [];
|
|
145
|
-
// Print mode
|
|
146
|
-
if (a.print)
|
|
147
|
-
args.push(a.print);
|
|
148
|
-
// I/O format
|
|
149
|
-
if (a.inputFormat && a.inputFormatValue)
|
|
150
|
-
args.push(a.inputFormat, a.inputFormatValue);
|
|
151
|
-
if (a.outputFormat && a.outputFormatValue)
|
|
152
|
-
args.push(a.outputFormat, a.outputFormatValue);
|
|
153
|
-
// Streaming flags
|
|
154
|
-
if (a.replayUserMessages)
|
|
155
|
-
args.push(a.replayUserMessages);
|
|
156
|
-
if (a.verbose)
|
|
157
|
-
args.push(a.verbose);
|
|
158
|
-
if (a.includePartialMessages)
|
|
159
|
-
args.push(a.includePartialMessages);
|
|
160
|
-
// Permission mode
|
|
161
|
-
this._appendPermissionArgs(args);
|
|
162
|
-
// Model
|
|
163
|
-
if (this.options.model && a.model) {
|
|
164
|
-
args.push(a.model, this.options.model);
|
|
165
|
-
}
|
|
166
|
-
// Resume
|
|
167
|
-
if (this.options.resumeSessionId && a.resume) {
|
|
168
|
-
args.push(a.resume, this.options.resumeSessionId);
|
|
169
|
-
}
|
|
170
|
-
// System prompts
|
|
171
|
-
if (this.options.systemPrompt && a.systemPrompt) {
|
|
172
|
-
args.push(a.systemPrompt, this.options.systemPrompt);
|
|
173
|
-
}
|
|
174
|
-
if (this.options.appendSystemPrompt && a.appendSystemPrompt) {
|
|
175
|
-
args.push(a.appendSystemPrompt, this.options.appendSystemPrompt);
|
|
176
|
-
}
|
|
177
|
-
// Limits
|
|
178
|
-
if (this.options.maxTurns && a.maxTurns) {
|
|
179
|
-
args.push(a.maxTurns, String(this.options.maxTurns));
|
|
180
|
-
}
|
|
181
|
-
// Skip permissions
|
|
182
|
-
if (this.options.dangerouslySkipPermissions && a.skipPermissions) {
|
|
183
|
-
args.push(a.skipPermissions);
|
|
184
|
-
}
|
|
185
|
-
// Effort
|
|
186
|
-
if (this.options.effort && this.options.effort !== 'auto' && a.effort) {
|
|
187
|
-
args.push(a.effort, this.options.effort);
|
|
188
|
-
}
|
|
189
|
-
// Extra static args
|
|
190
|
-
if (a.extra?.length)
|
|
191
|
-
args.push(...a.extra);
|
|
192
|
-
// Spawn environment
|
|
193
|
-
const spawnEnv = {
|
|
194
|
-
...process.env,
|
|
195
|
-
PATH: process.env.PATH || '/usr/local/bin:/usr/bin:/bin',
|
|
196
|
-
...this.engineConfig.env,
|
|
197
|
-
};
|
|
198
|
-
this.proc = spawn(this.engineBin, args, {
|
|
199
|
-
cwd: this.options.cwd,
|
|
200
|
-
env: spawnEnv,
|
|
201
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
202
|
-
detached: true,
|
|
203
|
-
});
|
|
204
|
-
this.proc.unref();
|
|
205
|
-
// Parse stdout line-by-line
|
|
206
|
-
this._rl = readline.createInterface({ input: this.proc.stdout, crlfDelay: Infinity });
|
|
207
|
-
this._rl.on('line', (line) => {
|
|
208
|
-
if (!line.trim())
|
|
209
|
-
return;
|
|
210
|
-
try {
|
|
211
|
-
const event = JSON.parse(line);
|
|
212
|
-
this._handlePersistentEvent(event);
|
|
213
|
-
}
|
|
214
|
-
catch {
|
|
215
|
-
this.emit(SESSION_EVENT.LOG, `[${this.engineConfig.name}-stdout] ${line}`);
|
|
216
|
-
}
|
|
217
|
-
});
|
|
218
|
-
this.proc.stderr?.on('data', (data) => {
|
|
219
|
-
this.emit(SESSION_EVENT.LOG, `[${this.engineConfig.name}-stderr] ${this.sanitize(data.toString())}`);
|
|
220
|
-
});
|
|
221
|
-
this.proc.on('close', (code) => {
|
|
222
|
-
this._isReady = false;
|
|
223
|
-
this.emit(SESSION_EVENT.CLOSE, code);
|
|
224
|
-
});
|
|
225
|
-
this.proc.on('error', (err) => {
|
|
226
|
-
this.emit(SESSION_EVENT.ERROR, err);
|
|
227
|
-
});
|
|
228
|
-
// Wait for ready
|
|
229
|
-
return new Promise((resolve, reject) => {
|
|
230
|
-
const timeout = setTimeout(() => reject(new Error(`Timeout waiting for ${this.engineConfig.name} session ready`)), SESSION_READY_TIMEOUT_MS);
|
|
231
|
-
this.once(SESSION_EVENT.READY, () => {
|
|
232
|
-
clearTimeout(timeout);
|
|
233
|
-
resolve(this);
|
|
234
|
-
});
|
|
235
|
-
this.once(SESSION_EVENT.ERROR, (err) => {
|
|
236
|
-
clearTimeout(timeout);
|
|
237
|
-
reject(err);
|
|
238
|
-
});
|
|
239
|
-
const onCloseBeforeReady = (code) => {
|
|
240
|
-
if (!this._isReady) {
|
|
241
|
-
clearTimeout(timeout);
|
|
242
|
-
reject(new Error(`${this.engineConfig.name} process exited prematurely with code ${code}. Session failed to start.`));
|
|
243
|
-
}
|
|
244
|
-
};
|
|
245
|
-
this.once(SESSION_EVENT.CLOSE, onCloseBeforeReady);
|
|
246
|
-
const onInit = () => {
|
|
247
|
-
if (!this._isReady) {
|
|
248
|
-
this._isReady = true;
|
|
249
|
-
this.removeListener(SESSION_EVENT.CLOSE, onCloseBeforeReady);
|
|
250
|
-
this.emit(SESSION_EVENT.READY);
|
|
251
|
-
}
|
|
252
|
-
};
|
|
253
|
-
this.once(SESSION_EVENT.INIT, onInit);
|
|
254
|
-
// Fallback: mark ready after 2s if no init event
|
|
255
|
-
setTimeout(() => {
|
|
256
|
-
this.removeListener(SESSION_EVENT.INIT, onInit);
|
|
257
|
-
if (this.proc?.killed || this.proc?.exitCode !== null) {
|
|
258
|
-
clearTimeout(timeout);
|
|
259
|
-
this.removeListener(SESSION_EVENT.CLOSE, onCloseBeforeReady);
|
|
260
|
-
reject(new Error(`${this.engineConfig.name} CLI crashed on startup. Fallback timer aborted.`));
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
if (!this._isReady) {
|
|
264
|
-
this._isReady = true;
|
|
265
|
-
this.removeListener(SESSION_EVENT.CLOSE, onCloseBeforeReady);
|
|
266
|
-
this.emit(SESSION_EVENT.READY);
|
|
267
|
-
}
|
|
268
|
-
}, SESSION_READY_FALLBACK_MS);
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
// ── One-shot mode start ─────────────────────────────────────────────────
|
|
272
|
-
async _startOneShot() {
|
|
273
|
-
const eName = this.engineConfig.name;
|
|
274
|
-
this.sessionId = `${eName}-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
275
|
-
this._startTime = new Date().toISOString();
|
|
276
|
-
this._isReady = true;
|
|
277
|
-
this.emit(SESSION_EVENT.READY);
|
|
278
|
-
this.emit(SESSION_EVENT.INIT, { type: 'system', subtype: 'init', session_id: this.sessionId });
|
|
279
|
-
return this;
|
|
280
|
-
}
|
|
281
|
-
// ─── Send ────────────────────────────────────────────────────────────────
|
|
282
|
-
async send(message, options = {}) {
|
|
283
|
-
if (!this._isReady)
|
|
284
|
-
throw new Error('Session not ready. Call start() first.');
|
|
285
|
-
if (this.engineConfig.persistent) {
|
|
286
|
-
return this._sendPersistent(message, options);
|
|
287
|
-
}
|
|
288
|
-
else {
|
|
289
|
-
return this._sendOneShot(message, options);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
// ── Persistent mode send ────────────────────────────────────────────────
|
|
293
|
-
async _sendPersistent(message, options) {
|
|
294
|
-
if (!this.proc)
|
|
295
|
-
throw new Error('Session not ready. Call start() first.');
|
|
296
|
-
const requestId = ++this.currentRequestId;
|
|
297
|
-
let finalMessage = typeof message === 'string' ? message : message;
|
|
298
|
-
if (typeof finalMessage === 'string') {
|
|
299
|
-
if (options.effort === 'high' || options.effort === 'max') {
|
|
300
|
-
finalMessage = `ultrathink\n\n${finalMessage}`;
|
|
301
|
-
}
|
|
302
|
-
if (options.plan) {
|
|
303
|
-
finalMessage = `/plan ${finalMessage}`;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
const payload = {
|
|
307
|
-
type: 'user',
|
|
308
|
-
message: {
|
|
309
|
-
role: 'user',
|
|
310
|
-
content: typeof finalMessage === 'string' ? [{ type: 'text', text: finalMessage }] : finalMessage,
|
|
311
|
-
},
|
|
312
|
-
};
|
|
313
|
-
this.proc.stdin.write(JSON.stringify(payload) + '\n');
|
|
314
|
-
if (options.callbacks)
|
|
315
|
-
this._streamCallbacks = options.callbacks;
|
|
316
|
-
if (options.waitForComplete) {
|
|
317
|
-
this._isBusy = true;
|
|
318
|
-
try {
|
|
319
|
-
return await this._waitForTurnComplete(options.timeout || TURN_TIMEOUT_MS);
|
|
320
|
-
}
|
|
321
|
-
finally {
|
|
322
|
-
this._isBusy = false;
|
|
323
|
-
if (options.callbacks)
|
|
324
|
-
this._streamCallbacks = null;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
return { requestId, sent: true };
|
|
328
|
-
}
|
|
329
|
-
// ── One-shot mode send ──────────────────────────────────────────────────
|
|
330
|
-
async _sendOneShot(message, options) {
|
|
331
|
-
const requestId = ++this.currentRequestId;
|
|
332
|
-
const textMessage = typeof message === 'string' ? message : JSON.stringify(message);
|
|
333
|
-
if (!options.waitForComplete) {
|
|
334
|
-
this._runOneShot(textMessage, options).catch((err) => this.emit(SESSION_EVENT.ERROR, err));
|
|
335
|
-
return { requestId, sent: true };
|
|
336
|
-
}
|
|
337
|
-
this._isBusy = true;
|
|
338
|
-
try {
|
|
339
|
-
return await this._runOneShot(textMessage, options);
|
|
340
|
-
}
|
|
341
|
-
finally {
|
|
342
|
-
this._isBusy = false;
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
async _runOneShot(message, options) {
|
|
346
|
-
const a = this.engineConfig.args;
|
|
347
|
-
const args = [];
|
|
348
|
-
// Print mode + message
|
|
349
|
-
if (a.print)
|
|
350
|
-
args.push(a.print);
|
|
351
|
-
args.push(message);
|
|
352
|
-
// Output format
|
|
353
|
-
if (a.outputFormat && a.outputFormatValue)
|
|
354
|
-
args.push(a.outputFormat, a.outputFormatValue);
|
|
355
|
-
// Permission mode
|
|
356
|
-
this._appendPermissionArgs(args);
|
|
357
|
-
// Skip permissions
|
|
358
|
-
if (this.options.dangerouslySkipPermissions && a.skipPermissions) {
|
|
359
|
-
args.push(a.skipPermissions);
|
|
360
|
-
}
|
|
361
|
-
// Model
|
|
362
|
-
if (this.options.model && a.model)
|
|
363
|
-
args.push(a.model, this.options.model);
|
|
364
|
-
// System prompts
|
|
365
|
-
if (this.options.systemPrompt && a.systemPrompt) {
|
|
366
|
-
args.push(a.systemPrompt, this.options.systemPrompt);
|
|
367
|
-
}
|
|
368
|
-
// Max turns
|
|
369
|
-
if (this.options.maxTurns && a.maxTurns) {
|
|
370
|
-
args.push(a.maxTurns, String(this.options.maxTurns));
|
|
371
|
-
}
|
|
372
|
-
// Effort
|
|
373
|
-
if (this.options.effort && this.options.effort !== 'auto' && a.effort) {
|
|
374
|
-
args.push(a.effort, this.options.effort);
|
|
375
|
-
}
|
|
376
|
-
// Workspace
|
|
377
|
-
if (this.options.cwd && a.workspace) {
|
|
378
|
-
args.push(a.workspace, this.options.cwd);
|
|
379
|
-
}
|
|
380
|
-
// Extra static args
|
|
381
|
-
if (a.extra?.length)
|
|
382
|
-
args.push(...a.extra);
|
|
383
|
-
const timeout = options.timeout || 300_000;
|
|
384
|
-
const spawnEnv = {
|
|
385
|
-
...process.env,
|
|
386
|
-
...this.engineConfig.env,
|
|
387
|
-
};
|
|
388
|
-
return new Promise((resolve, reject) => {
|
|
389
|
-
const resultText = { value: '' };
|
|
390
|
-
let stderr = '';
|
|
391
|
-
let settled = false;
|
|
392
|
-
let gotUsageFromEvents = false;
|
|
393
|
-
const proc = spawn(this.engineBin, args, {
|
|
394
|
-
cwd: this.options.cwd,
|
|
395
|
-
env: spawnEnv,
|
|
396
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
397
|
-
});
|
|
398
|
-
this.currentProc = proc;
|
|
399
|
-
const timer = setTimeout(() => {
|
|
400
|
-
if (!settled) {
|
|
401
|
-
settled = true;
|
|
402
|
-
proc.kill('SIGTERM');
|
|
403
|
-
reject(new Error(`Timeout waiting for ${this.engineConfig.name} response`));
|
|
404
|
-
}
|
|
405
|
-
}, timeout);
|
|
406
|
-
const rl = readline.createInterface({ input: proc.stdout, crlfDelay: Infinity });
|
|
407
|
-
this._currentRl = rl;
|
|
408
|
-
rl.on('line', (line) => {
|
|
409
|
-
if (!line.trim())
|
|
410
|
-
return;
|
|
411
|
-
try {
|
|
412
|
-
const event = JSON.parse(line);
|
|
413
|
-
this._handleOneShotEvent(event, options, resultText, () => {
|
|
414
|
-
gotUsageFromEvents = true;
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
|
-
catch {
|
|
418
|
-
// Non-JSON line — treat as plain text
|
|
419
|
-
resultText.value += line + '\n';
|
|
420
|
-
try {
|
|
421
|
-
options.callbacks?.onText?.(line + '\n');
|
|
422
|
-
}
|
|
423
|
-
catch {
|
|
424
|
-
/* ignore callback errors */
|
|
425
|
-
}
|
|
426
|
-
this.emit(SESSION_EVENT.TEXT, line + '\n');
|
|
427
|
-
}
|
|
428
|
-
});
|
|
429
|
-
proc.stderr?.on('data', (data) => {
|
|
430
|
-
const sanitized = this.sanitize(data.toString());
|
|
431
|
-
stderr += sanitized;
|
|
432
|
-
this.emit(SESSION_EVENT.LOG, `[${this.engineConfig.name}-stderr] ${sanitized}`);
|
|
433
|
-
});
|
|
434
|
-
proc.on('close', (code) => {
|
|
435
|
-
clearTimeout(timer);
|
|
436
|
-
this.currentProc = null;
|
|
437
|
-
if (this._currentRl) {
|
|
438
|
-
this._currentRl.close();
|
|
439
|
-
this._currentRl = null;
|
|
440
|
-
}
|
|
441
|
-
if (settled)
|
|
442
|
-
return;
|
|
443
|
-
settled = true;
|
|
444
|
-
const now = new Date().toISOString();
|
|
445
|
-
this._stats.turns++;
|
|
446
|
-
this._stats.lastActivity = now;
|
|
447
|
-
if (!gotUsageFromEvents && resultText.value.length > 0) {
|
|
448
|
-
this._stats.tokensIn += estimateTokens(message);
|
|
449
|
-
this._stats.tokensOut += estimateTokens(resultText.value);
|
|
450
|
-
this._updateCost();
|
|
451
|
-
}
|
|
452
|
-
this._history.push({ time: now, type: 'result', event: { text: resultText.value, code } });
|
|
453
|
-
if (this._history.length > MAX_HISTORY_ITEMS)
|
|
454
|
-
this._history.shift();
|
|
455
|
-
const event = {
|
|
456
|
-
type: 'result',
|
|
457
|
-
result: resultText.value,
|
|
458
|
-
stop_reason: code === 0 ? 'end_turn' : 'error',
|
|
459
|
-
};
|
|
460
|
-
this.emit(SESSION_EVENT.RESULT, event);
|
|
461
|
-
this.emit(SESSION_EVENT.TURN_COMPLETE, event);
|
|
462
|
-
if (code !== 0 && !resultText.value) {
|
|
463
|
-
reject(new Error(stderr || `${this.engineConfig.name} exited with code ${code}`));
|
|
464
|
-
}
|
|
465
|
-
else if (code !== 0) {
|
|
466
|
-
reject(new Error(stderr || `${this.engineConfig.name} exited with code ${code}`));
|
|
467
|
-
}
|
|
468
|
-
else {
|
|
469
|
-
resolve({ text: resultText.value, event });
|
|
470
|
-
}
|
|
471
|
-
});
|
|
472
|
-
proc.on('error', (err) => {
|
|
473
|
-
clearTimeout(timer);
|
|
474
|
-
if (!settled) {
|
|
475
|
-
settled = true;
|
|
476
|
-
reject(err);
|
|
477
|
-
}
|
|
478
|
-
});
|
|
479
|
-
});
|
|
480
|
-
}
|
|
481
|
-
// ─── Event Handling (Persistent mode) ───────────────────────────────────
|
|
482
|
-
_handlePersistentEvent(event) {
|
|
483
|
-
const type = event.type;
|
|
484
|
-
this._stats.lastActivity = new Date().toISOString();
|
|
485
|
-
this._history.push({ time: this._stats.lastActivity, type, event });
|
|
486
|
-
if (this._history.length > MAX_HISTORY_ITEMS)
|
|
487
|
-
this._history.shift();
|
|
488
|
-
switch (type) {
|
|
489
|
-
case 'system':
|
|
490
|
-
if (event.subtype === 'init') {
|
|
491
|
-
this.sessionId = event.session_id;
|
|
492
|
-
this._startTime = new Date().toISOString();
|
|
493
|
-
this.emit(SESSION_EVENT.INIT, event);
|
|
494
|
-
}
|
|
495
|
-
this.emit(SESSION_EVENT.SYSTEM, event);
|
|
496
|
-
break;
|
|
497
|
-
case 'stream_event': {
|
|
498
|
-
const inner = event.event;
|
|
499
|
-
if (!inner)
|
|
500
|
-
break;
|
|
501
|
-
const innerType = inner.type;
|
|
502
|
-
if (innerType === 'content_block_start') {
|
|
503
|
-
const block = inner.content_block;
|
|
504
|
-
if (block?.type === 'tool_use') {
|
|
505
|
-
this._stats.toolCalls++;
|
|
506
|
-
const toolEvent = { tool: { name: block.name, input: {} } };
|
|
507
|
-
try {
|
|
508
|
-
this._streamCallbacks?.onToolUse?.(toolEvent);
|
|
509
|
-
}
|
|
510
|
-
catch {
|
|
511
|
-
/* ignore */
|
|
512
|
-
}
|
|
513
|
-
this.emit(SESSION_EVENT.TOOL_USE, toolEvent);
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
else if (innerType === 'content_block_delta') {
|
|
517
|
-
const delta = inner.delta;
|
|
518
|
-
if (delta?.type === 'text_delta' && delta.text) {
|
|
519
|
-
try {
|
|
520
|
-
this._streamCallbacks?.onText?.(delta.text);
|
|
521
|
-
}
|
|
522
|
-
catch {
|
|
523
|
-
/* ignore */
|
|
524
|
-
}
|
|
525
|
-
this.emit(SESSION_EVENT.TEXT, delta.text);
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
else if (innerType === 'message_delta') {
|
|
529
|
-
const usage = inner.usage;
|
|
530
|
-
if (usage) {
|
|
531
|
-
this._stats.tokensIn += usage.input_tokens || 0;
|
|
532
|
-
this._stats.tokensOut += usage.output_tokens || 0;
|
|
533
|
-
this._stats.cachedTokens += usage.cache_read_input_tokens || 0;
|
|
534
|
-
this._updateCost();
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
this.emit(SESSION_EVENT.STREAM_EVENT, event);
|
|
538
|
-
break;
|
|
539
|
-
}
|
|
540
|
-
case 'user':
|
|
541
|
-
this._stats.turns++;
|
|
542
|
-
this.emit(SESSION_EVENT.USER_ECHO, event);
|
|
543
|
-
break;
|
|
544
|
-
case 'assistant':
|
|
545
|
-
this.emit(SESSION_EVENT.ASSISTANT, event);
|
|
546
|
-
if (event.message?.content && Array.isArray(event.message.content)) {
|
|
547
|
-
for (const block of event.message.content) {
|
|
548
|
-
if (block.type === 'tool_use') {
|
|
549
|
-
this._stats.toolCalls++;
|
|
550
|
-
const toolEvent = {
|
|
551
|
-
tool: {
|
|
552
|
-
name: block.name,
|
|
553
|
-
input: block.input || {},
|
|
554
|
-
},
|
|
555
|
-
};
|
|
556
|
-
try {
|
|
557
|
-
this._streamCallbacks?.onToolUse?.(toolEvent);
|
|
558
|
-
}
|
|
559
|
-
catch {
|
|
560
|
-
/* ignore */
|
|
561
|
-
}
|
|
562
|
-
this.emit(SESSION_EVENT.TOOL_USE, toolEvent);
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
break;
|
|
567
|
-
case 'tool_use':
|
|
568
|
-
this._stats.toolCalls++;
|
|
569
|
-
try {
|
|
570
|
-
this._streamCallbacks?.onToolUse?.(event);
|
|
571
|
-
}
|
|
572
|
-
catch {
|
|
573
|
-
/* ignore */
|
|
574
|
-
}
|
|
575
|
-
this.emit(SESSION_EVENT.TOOL_USE, event);
|
|
576
|
-
break;
|
|
577
|
-
case 'tool_result':
|
|
578
|
-
try {
|
|
579
|
-
this._streamCallbacks?.onToolResult?.(event);
|
|
580
|
-
}
|
|
581
|
-
catch {
|
|
582
|
-
/* ignore */
|
|
583
|
-
}
|
|
584
|
-
if (event.is_error || event.error) {
|
|
585
|
-
this._stats.toolErrors++;
|
|
586
|
-
}
|
|
587
|
-
this.emit(SESSION_EVENT.TOOL_RESULT, event);
|
|
588
|
-
break;
|
|
589
|
-
case 'error':
|
|
590
|
-
this.emit(SESSION_EVENT.ERROR, new Error(String(event.error) || JSON.stringify(event)));
|
|
591
|
-
break;
|
|
592
|
-
case 'result': {
|
|
593
|
-
const usage = event.usage;
|
|
594
|
-
if (usage) {
|
|
595
|
-
this._stats.tokensIn += usage.input_tokens || 0;
|
|
596
|
-
this._stats.tokensOut += usage.output_tokens || 0;
|
|
597
|
-
this._stats.cachedTokens += usage.cache_read_input_tokens || 0;
|
|
598
|
-
this._updateCost();
|
|
599
|
-
}
|
|
600
|
-
this.emit(SESSION_EVENT.RESULT, event);
|
|
601
|
-
this.emit(SESSION_EVENT.TURN_COMPLETE, event);
|
|
602
|
-
const totalTokens = this._stats.tokensIn + this._stats.tokensOut;
|
|
603
|
-
if (totalTokens > CONTEXT_HIGH_THRESHOLD && !this._contextHighFired) {
|
|
604
|
-
this._contextHighFired = true;
|
|
605
|
-
}
|
|
606
|
-
break;
|
|
607
|
-
}
|
|
608
|
-
default:
|
|
609
|
-
this.emit(SESSION_EVENT.EVENT, event);
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
// ─── Event Handling (One-shot mode) ─────────────────────────────────────
|
|
613
|
-
_handleOneShotEvent(event, options, resultText, markUsageReceived) {
|
|
614
|
-
const type = event.type;
|
|
615
|
-
switch (type) {
|
|
616
|
-
case 'system':
|
|
617
|
-
if (event.session_id)
|
|
618
|
-
this.sessionId = String(event.session_id);
|
|
619
|
-
break;
|
|
620
|
-
case 'user':
|
|
621
|
-
// Echo of user prompt — skip
|
|
622
|
-
break;
|
|
623
|
-
case 'assistant': {
|
|
624
|
-
const msg = event.message;
|
|
625
|
-
if (!msg)
|
|
626
|
-
break;
|
|
627
|
-
const contentArr = msg.content;
|
|
628
|
-
if (contentArr) {
|
|
629
|
-
for (const block of contentArr) {
|
|
630
|
-
if (block.type === 'text' && block.text) {
|
|
631
|
-
resultText.value += block.text;
|
|
632
|
-
try {
|
|
633
|
-
options.callbacks?.onText?.(block.text);
|
|
634
|
-
}
|
|
635
|
-
catch {
|
|
636
|
-
/* ignore */
|
|
637
|
-
}
|
|
638
|
-
this.emit(SESSION_EVENT.TEXT, block.text);
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
break;
|
|
643
|
-
}
|
|
644
|
-
case 'message': {
|
|
645
|
-
if (event.role === 'user')
|
|
646
|
-
break;
|
|
647
|
-
const text = event.content || '';
|
|
648
|
-
if (text) {
|
|
649
|
-
resultText.value += text;
|
|
650
|
-
try {
|
|
651
|
-
options.callbacks?.onText?.(text);
|
|
652
|
-
}
|
|
653
|
-
catch {
|
|
654
|
-
/* ignore */
|
|
655
|
-
}
|
|
656
|
-
this.emit(SESSION_EVENT.TEXT, text);
|
|
657
|
-
}
|
|
658
|
-
break;
|
|
659
|
-
}
|
|
660
|
-
case 'tool_use':
|
|
661
|
-
this._stats.toolCalls++;
|
|
662
|
-
try {
|
|
663
|
-
options.callbacks?.onToolUse?.(event);
|
|
664
|
-
}
|
|
665
|
-
catch {
|
|
666
|
-
/* ignore */
|
|
667
|
-
}
|
|
668
|
-
this.emit(SESSION_EVENT.TOOL_USE, event);
|
|
669
|
-
break;
|
|
670
|
-
case 'tool_result':
|
|
671
|
-
try {
|
|
672
|
-
options.callbacks?.onToolResult?.(event);
|
|
673
|
-
}
|
|
674
|
-
catch {
|
|
675
|
-
/* ignore */
|
|
676
|
-
}
|
|
677
|
-
if (event.is_error)
|
|
678
|
-
this._stats.toolErrors++;
|
|
679
|
-
this.emit(SESSION_EVENT.TOOL_RESULT, event);
|
|
680
|
-
break;
|
|
681
|
-
case 'result': {
|
|
682
|
-
const usage = event.usage;
|
|
683
|
-
if (usage) {
|
|
684
|
-
this._stats.tokensIn += usage.input_tokens || usage.inputTokens || usage.prompt_tokens || 0;
|
|
685
|
-
this._stats.tokensOut += usage.output_tokens || usage.outputTokens || usage.completion_tokens || 0;
|
|
686
|
-
const cached = usage.cache_read_input_tokens || usage.cached_tokens || 0;
|
|
687
|
-
if (cached)
|
|
688
|
-
this._stats.cachedTokens += cached;
|
|
689
|
-
this._updateCost();
|
|
690
|
-
markUsageReceived();
|
|
691
|
-
}
|
|
692
|
-
const resultStr = event.result;
|
|
693
|
-
if (resultStr && !resultText.value)
|
|
694
|
-
resultText.value = resultStr;
|
|
695
|
-
break;
|
|
696
|
-
}
|
|
697
|
-
case 'error':
|
|
698
|
-
this.emit(SESSION_EVENT.LOG, `[${this.engineConfig.name}-error] ${event.error || JSON.stringify(event)}`);
|
|
699
|
-
break;
|
|
700
|
-
default:
|
|
701
|
-
break;
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
// ─── Wait for Turn Complete (Persistent mode) ───────────────────────────
|
|
705
|
-
_waitForTurnComplete(timeout) {
|
|
706
|
-
return new Promise((resolve, reject) => {
|
|
707
|
-
let settled = false;
|
|
708
|
-
let streamedText = '';
|
|
709
|
-
let allAssistantText = '';
|
|
710
|
-
const toolNames = [];
|
|
711
|
-
const onText = (chunk) => {
|
|
712
|
-
streamedText += chunk;
|
|
713
|
-
};
|
|
714
|
-
this.on(SESSION_EVENT.TEXT, onText);
|
|
715
|
-
const onAssistant = (event) => {
|
|
716
|
-
if (event.message?.content && Array.isArray(event.message.content)) {
|
|
717
|
-
for (const block of event.message.content) {
|
|
718
|
-
if (block.type === 'text' && block.text)
|
|
719
|
-
allAssistantText += block.text + '\n';
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
};
|
|
723
|
-
this.on(SESSION_EVENT.ASSISTANT, onAssistant);
|
|
724
|
-
const onToolUse = (event) => {
|
|
725
|
-
const tool = event.tool;
|
|
726
|
-
toolNames.push(tool?.name || event.name || 'unknown');
|
|
727
|
-
};
|
|
728
|
-
this.on(SESSION_EVENT.TOOL_USE, onToolUse);
|
|
729
|
-
const cleanup = () => {
|
|
730
|
-
clearTimeout(timer);
|
|
731
|
-
this.removeListener(SESSION_EVENT.TEXT, onText);
|
|
732
|
-
this.removeListener(SESSION_EVENT.ASSISTANT, onAssistant);
|
|
733
|
-
this.removeListener(SESSION_EVENT.TOOL_USE, onToolUse);
|
|
734
|
-
this.removeListener(SESSION_EVENT.TURN_COMPLETE, onTurnComplete);
|
|
735
|
-
this.removeListener(SESSION_EVENT.ERROR, onError);
|
|
736
|
-
this.removeListener(SESSION_EVENT.CLOSE, onClose);
|
|
737
|
-
};
|
|
738
|
-
const timer = setTimeout(() => {
|
|
739
|
-
if (settled)
|
|
740
|
-
return;
|
|
741
|
-
settled = true;
|
|
742
|
-
cleanup();
|
|
743
|
-
reject(new Error('Timeout waiting for response'));
|
|
744
|
-
}, timeout);
|
|
745
|
-
const onTurnComplete = (event) => {
|
|
746
|
-
if (settled)
|
|
747
|
-
return;
|
|
748
|
-
settled = true;
|
|
749
|
-
cleanup();
|
|
750
|
-
let text = event.result || streamedText || allAssistantText.trim() || '';
|
|
751
|
-
if (!text && toolNames.length > 0) {
|
|
752
|
-
const unique = [...new Set(toolNames)];
|
|
753
|
-
text = `[Agent completed ${toolNames.length} tool calls: ${unique.join(', ')}]`;
|
|
754
|
-
}
|
|
755
|
-
resolve({ text, event });
|
|
756
|
-
};
|
|
757
|
-
const onError = (err) => {
|
|
758
|
-
if (settled)
|
|
759
|
-
return;
|
|
760
|
-
settled = true;
|
|
761
|
-
cleanup();
|
|
762
|
-
reject(err);
|
|
763
|
-
};
|
|
764
|
-
const onClose = (code) => {
|
|
765
|
-
if (settled)
|
|
766
|
-
return;
|
|
767
|
-
settled = true;
|
|
768
|
-
cleanup();
|
|
769
|
-
const text = streamedText || allAssistantText.trim() || '';
|
|
770
|
-
resolve({
|
|
771
|
-
text,
|
|
772
|
-
event: {
|
|
773
|
-
type: 'result',
|
|
774
|
-
result: text,
|
|
775
|
-
stop_reason: 'process_exit',
|
|
776
|
-
exit_code: code,
|
|
777
|
-
},
|
|
778
|
-
});
|
|
779
|
-
};
|
|
780
|
-
this.once(SESSION_EVENT.TURN_COMPLETE, onTurnComplete);
|
|
781
|
-
this.once(SESSION_EVENT.ERROR, onError);
|
|
782
|
-
this.once(SESSION_EVENT.CLOSE, onClose);
|
|
783
|
-
});
|
|
784
|
-
}
|
|
785
|
-
// ─── Utilities ───────────────────────────────────────────────────────────
|
|
786
|
-
getStats() {
|
|
787
|
-
const ctxWindow = this.engineConfig.contextWindow ?? 200_000;
|
|
788
|
-
return {
|
|
789
|
-
turns: this._stats.turns,
|
|
790
|
-
toolCalls: this._stats.toolCalls,
|
|
791
|
-
toolErrors: this._stats.toolErrors,
|
|
792
|
-
tokensIn: this._stats.tokensIn,
|
|
793
|
-
tokensOut: this._stats.tokensOut,
|
|
794
|
-
cachedTokens: this._stats.cachedTokens,
|
|
795
|
-
costUsd: Math.round(this._stats.costUsd * 10000) / 10000,
|
|
796
|
-
isReady: this._isReady,
|
|
797
|
-
startTime: this._startTime,
|
|
798
|
-
lastActivity: this._stats.lastActivity,
|
|
799
|
-
contextPercent: this.engineConfig.persistent
|
|
800
|
-
? Math.min(100, Math.round(((this._stats.tokensIn + this._stats.tokensOut) / ctxWindow) * 100))
|
|
801
|
-
: 0,
|
|
802
|
-
sessionId: this.sessionId,
|
|
803
|
-
uptime: this._startTime ? Math.round((Date.now() - new Date(this._startTime).getTime()) / 1000) : 0,
|
|
804
|
-
};
|
|
805
|
-
}
|
|
806
|
-
getHistory(limit = DEFAULT_HISTORY_LIMIT) {
|
|
807
|
-
return this._history.slice(-limit);
|
|
808
|
-
}
|
|
809
|
-
async compact(summary) {
|
|
810
|
-
if (!this.engineConfig.persistent) {
|
|
811
|
-
const event = {
|
|
812
|
-
type: 'result',
|
|
813
|
-
result: `${this.engineConfig.name} engine does not support compaction (one-shot mode)`,
|
|
814
|
-
};
|
|
815
|
-
return { text: event.result, event };
|
|
816
|
-
}
|
|
817
|
-
const msg = summary ? `/compact ${summary}` : '/compact';
|
|
818
|
-
return this.send(msg, { waitForComplete: true, timeout: COMPACT_TIMEOUT_MS });
|
|
819
|
-
}
|
|
820
|
-
getEffort() {
|
|
821
|
-
return this.options.effort || 'auto';
|
|
822
|
-
}
|
|
823
|
-
setEffort(level) {
|
|
824
|
-
this.options.effort = level;
|
|
825
|
-
}
|
|
826
|
-
getCost() {
|
|
827
|
-
const pricing = getModelPricing(this.options.model, this.engineConfig);
|
|
828
|
-
const cachedPrice = pricing.cached ?? 0;
|
|
829
|
-
const nonCachedIn = Math.max(0, this._stats.tokensIn - this._stats.cachedTokens);
|
|
830
|
-
return {
|
|
831
|
-
model: this.options.model || this.engineConfig.name,
|
|
832
|
-
tokensIn: this._stats.tokensIn,
|
|
833
|
-
tokensOut: this._stats.tokensOut,
|
|
834
|
-
cachedTokens: this._stats.cachedTokens,
|
|
835
|
-
pricing: { inputPer1M: pricing.input, outputPer1M: pricing.output, cachedPer1M: cachedPrice || undefined },
|
|
836
|
-
breakdown: {
|
|
837
|
-
inputCost: (nonCachedIn / 1_000_000) * pricing.input,
|
|
838
|
-
cachedCost: (this._stats.cachedTokens / 1_000_000) * cachedPrice,
|
|
839
|
-
outputCost: (this._stats.tokensOut / 1_000_000) * pricing.output,
|
|
840
|
-
},
|
|
841
|
-
totalUsd: this._stats.costUsd,
|
|
842
|
-
};
|
|
843
|
-
}
|
|
844
|
-
resolveModel(alias) {
|
|
845
|
-
return resolveAlias(alias);
|
|
846
|
-
}
|
|
847
|
-
pause() {
|
|
848
|
-
this._isPaused = true;
|
|
849
|
-
this.emit(SESSION_EVENT.PAUSED, { sessionId: this.sessionId });
|
|
850
|
-
}
|
|
851
|
-
resume() {
|
|
852
|
-
this._isPaused = false;
|
|
853
|
-
this.emit(SESSION_EVENT.RESUMED, { sessionId: this.sessionId });
|
|
854
|
-
}
|
|
855
|
-
stop() {
|
|
856
|
-
// Persistent mode cleanup
|
|
857
|
-
if (this._rl) {
|
|
858
|
-
this._rl.close();
|
|
859
|
-
this._rl = null;
|
|
860
|
-
}
|
|
861
|
-
if (this.proc) {
|
|
862
|
-
const pid = this.proc.pid;
|
|
863
|
-
this.proc.stdin?.end();
|
|
864
|
-
this.proc.stdout?.destroy();
|
|
865
|
-
this.proc.stderr?.destroy();
|
|
866
|
-
try {
|
|
867
|
-
process.kill(-pid, 'SIGTERM');
|
|
868
|
-
}
|
|
869
|
-
catch (err) {
|
|
870
|
-
if (err.code !== 'ESRCH') {
|
|
871
|
-
try {
|
|
872
|
-
this.proc.kill('SIGTERM');
|
|
873
|
-
}
|
|
874
|
-
catch {
|
|
875
|
-
/* ESRCH expected */
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
const p = this.proc;
|
|
880
|
-
setTimeout(() => {
|
|
881
|
-
try {
|
|
882
|
-
process.kill(-pid, 'SIGKILL');
|
|
883
|
-
}
|
|
884
|
-
catch {
|
|
885
|
-
/* ESRCH expected */
|
|
886
|
-
}
|
|
887
|
-
try {
|
|
888
|
-
p.kill('SIGKILL');
|
|
889
|
-
}
|
|
890
|
-
catch {
|
|
891
|
-
/* ESRCH expected */
|
|
892
|
-
}
|
|
893
|
-
}, STOP_SIGKILL_DELAY_MS);
|
|
894
|
-
this.proc = null;
|
|
895
|
-
}
|
|
896
|
-
// One-shot mode cleanup
|
|
897
|
-
if (this._currentRl) {
|
|
898
|
-
this._currentRl.close();
|
|
899
|
-
this._currentRl = null;
|
|
900
|
-
}
|
|
901
|
-
if (this.currentProc) {
|
|
902
|
-
this.currentProc.stdin?.end();
|
|
903
|
-
this.currentProc.stdout?.destroy();
|
|
904
|
-
this.currentProc.stderr?.destroy();
|
|
905
|
-
try {
|
|
906
|
-
this.currentProc.kill('SIGTERM');
|
|
907
|
-
}
|
|
908
|
-
catch {
|
|
909
|
-
/* ignore */
|
|
910
|
-
}
|
|
911
|
-
this.currentProc = null;
|
|
912
|
-
}
|
|
913
|
-
this._isReady = false;
|
|
914
|
-
this._isPaused = false;
|
|
915
|
-
this.emit(SESSION_EVENT.CLOSE, 143);
|
|
916
|
-
}
|
|
917
|
-
// ─── Private ─────────────────────────────────────────────────────────────
|
|
918
|
-
_appendPermissionArgs(args) {
|
|
919
|
-
const a = this.engineConfig.args;
|
|
920
|
-
const mode = this.options.permissionMode;
|
|
921
|
-
if (!mode || !a.permissionMode)
|
|
922
|
-
return;
|
|
923
|
-
// Map mode name if the engine uses different names
|
|
924
|
-
const mapped = this.engineConfig.permissionModes?.[mode] ?? mode;
|
|
925
|
-
args.push(a.permissionMode, mapped);
|
|
926
|
-
}
|
|
927
|
-
_updateCost() {
|
|
928
|
-
const pricing = getModelPricing(this.options.model, this.engineConfig);
|
|
929
|
-
const cachedPrice = pricing.cached ?? 0;
|
|
930
|
-
const nonCachedIn = Math.max(0, this._stats.tokensIn - this._stats.cachedTokens);
|
|
931
|
-
this._stats.costUsd =
|
|
932
|
-
(nonCachedIn / 1_000_000) * pricing.input +
|
|
933
|
-
(this._stats.cachedTokens / 1_000_000) * cachedPrice +
|
|
934
|
-
(this._stats.tokensOut / 1_000_000) * pricing.output;
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
//# sourceMappingURL=persistent-custom-session.js.map
|