@khalilgharbaoui/opencode-claude-code-plugin 0.8.2 → 0.9.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 +50 -5
- package/dist/index.d.ts +32 -0
- package/dist/index.js +797 -147
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1252,11 +1252,15 @@ function envFlagEnabled(value) {
|
|
|
1252
1252
|
function isClaudeThinkingDisabled() {
|
|
1253
1253
|
return envFlagEnabled(process.env.CLAUDE_CODE_DISABLE_THINKING) || envFlagEnabled(process.env.CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING);
|
|
1254
1254
|
}
|
|
1255
|
-
function claudeSpawnEnv() {
|
|
1255
|
+
function claudeSpawnEnv(opts) {
|
|
1256
1256
|
const env = {
|
|
1257
1257
|
...process.env,
|
|
1258
1258
|
TERM: "xterm-256color"
|
|
1259
1259
|
};
|
|
1260
|
+
if (opts?.ignoreAnthropicApiKey) {
|
|
1261
|
+
delete env.ANTHROPIC_API_KEY;
|
|
1262
|
+
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
1263
|
+
}
|
|
1260
1264
|
if (!isClaudeThinkingDisabled() && process.env.CLAUDE_CODE_SHOW_THINKING_SUMMARIES === void 0) {
|
|
1261
1265
|
env.CLAUDE_CODE_SHOW_THINKING_SUMMARIES = "1";
|
|
1262
1266
|
}
|
|
@@ -1282,6 +1286,9 @@ function getActiveProcess(key) {
|
|
|
1282
1286
|
if (ap) touch(key);
|
|
1283
1287
|
return ap;
|
|
1284
1288
|
}
|
|
1289
|
+
function setActiveProcess(key, ap) {
|
|
1290
|
+
activeProcesses.set(key, ap);
|
|
1291
|
+
}
|
|
1285
1292
|
function deleteActiveProcess(key) {
|
|
1286
1293
|
const ap = activeProcesses.get(key);
|
|
1287
1294
|
if (ap) {
|
|
@@ -1301,13 +1308,13 @@ function deleteClaudeSessionId(key) {
|
|
|
1301
1308
|
if (claudeSessionId) clearLedger(claudeSessionId);
|
|
1302
1309
|
claudeSessions.delete(key);
|
|
1303
1310
|
}
|
|
1304
|
-
function spawnClaudeProcess(cliPath, cliArgs, cwd, sessionKey2, proxyServer, mcpHash, systemPromptFile) {
|
|
1311
|
+
function spawnClaudeProcess(cliPath, cliArgs, cwd, sessionKey2, proxyServer, mcpHash, systemPromptFile, ignoreAnthropicApiKey) {
|
|
1305
1312
|
evictIfNeeded();
|
|
1306
1313
|
log.info("spawning new claude process", { cliPath, cliArgs, cwd, sessionKey: sessionKey2 });
|
|
1307
1314
|
const proc = spawn(cliPath, cliArgs, {
|
|
1308
1315
|
cwd,
|
|
1309
1316
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1310
|
-
env: claudeSpawnEnv(),
|
|
1317
|
+
env: claudeSpawnEnv({ ignoreAnthropicApiKey }),
|
|
1311
1318
|
shell: process.platform === "win32"
|
|
1312
1319
|
});
|
|
1313
1320
|
const lineEmitter = new EventEmitter();
|
|
@@ -1425,12 +1432,564 @@ function sessionKey(cwd, modelId) {
|
|
|
1425
1432
|
return `${cwd}::${modelId}`;
|
|
1426
1433
|
}
|
|
1427
1434
|
|
|
1428
|
-
// src/
|
|
1429
|
-
import {
|
|
1435
|
+
// src/claude-session-wrapper.ts
|
|
1436
|
+
import { EventEmitter as EventEmitter2 } from "events";
|
|
1437
|
+
import { unlink as unlink2 } from "fs/promises";
|
|
1438
|
+
|
|
1439
|
+
// src/claude-session-bun.ts
|
|
1440
|
+
import * as os3 from "os";
|
|
1430
1441
|
import * as fs3 from "fs";
|
|
1431
1442
|
import * as path3 from "path";
|
|
1443
|
+
import { execFileSync } from "child_process";
|
|
1444
|
+
import { randomUUID } from "crypto";
|
|
1445
|
+
function resolveClaude(cmd = "claude") {
|
|
1446
|
+
if (path3.isAbsolute(cmd) && fs3.existsSync(cmd)) return cmd;
|
|
1447
|
+
const viaBun = Bun.which(cmd);
|
|
1448
|
+
if (viaBun) return viaBun;
|
|
1449
|
+
const isWin = os3.platform() === "win32";
|
|
1450
|
+
try {
|
|
1451
|
+
const out = execFileSync(isWin ? "where" : "which", [cmd], {
|
|
1452
|
+
encoding: "utf8",
|
|
1453
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1454
|
+
});
|
|
1455
|
+
const first = out.split(/\r?\n/).map((l) => l.trim()).filter(Boolean).find((p) => fs3.existsSync(p));
|
|
1456
|
+
if (first) return first;
|
|
1457
|
+
} catch {
|
|
1458
|
+
}
|
|
1459
|
+
throw new Error(`Could not resolve command on PATH: ${cmd}`);
|
|
1460
|
+
}
|
|
1461
|
+
function encodeCwd(cwd) {
|
|
1462
|
+
return path3.resolve(cwd).replace(/[^a-zA-Z0-9]/g, "-");
|
|
1463
|
+
}
|
|
1464
|
+
var TERMINAL_STOP = /* @__PURE__ */ new Set(["end_turn", "stop_sequence", "max_tokens"]);
|
|
1465
|
+
var delay = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
1466
|
+
function resolveConfigDir(configDir) {
|
|
1467
|
+
const value = configDir ?? process.env.CLAUDE_CONFIG_DIR;
|
|
1468
|
+
if (!value) return path3.join(os3.homedir(), ".claude");
|
|
1469
|
+
if (value === "~") return os3.homedir();
|
|
1470
|
+
if (value.startsWith("~/") || value.startsWith("~\\")) {
|
|
1471
|
+
return path3.join(os3.homedir(), value.slice(2));
|
|
1472
|
+
}
|
|
1473
|
+
return path3.resolve(value);
|
|
1474
|
+
}
|
|
1475
|
+
var ClaudeSession = class {
|
|
1476
|
+
sessionId;
|
|
1477
|
+
cwd;
|
|
1478
|
+
configDir;
|
|
1479
|
+
jsonlPath;
|
|
1480
|
+
raw = "";
|
|
1481
|
+
proc = null;
|
|
1482
|
+
cursor = 0;
|
|
1483
|
+
// index into transcript split('\n')
|
|
1484
|
+
lastDataAt = 0;
|
|
1485
|
+
exited = false;
|
|
1486
|
+
exitCode = null;
|
|
1487
|
+
aborted = false;
|
|
1488
|
+
signal;
|
|
1489
|
+
o;
|
|
1490
|
+
constructor(opts = {}) {
|
|
1491
|
+
this.cwd = path3.resolve(opts.cwd ?? process.cwd());
|
|
1492
|
+
this.configDir = resolveConfigDir(opts.configDir);
|
|
1493
|
+
this.signal = opts.signal;
|
|
1494
|
+
this.sessionId = randomUUID();
|
|
1495
|
+
this.jsonlPath = path3.join(
|
|
1496
|
+
this.configDir,
|
|
1497
|
+
"projects",
|
|
1498
|
+
encodeCwd(this.cwd),
|
|
1499
|
+
`${this.sessionId}.jsonl`
|
|
1500
|
+
);
|
|
1501
|
+
this.o = {
|
|
1502
|
+
cwd: this.cwd,
|
|
1503
|
+
cliPath: opts.cliPath,
|
|
1504
|
+
configDir: this.configDir,
|
|
1505
|
+
model: opts.model,
|
|
1506
|
+
settingSources: opts.settingSources,
|
|
1507
|
+
extraArgs: opts.extraArgs ?? [],
|
|
1508
|
+
ignoreAnthropicApiKey: opts.ignoreAnthropicApiKey,
|
|
1509
|
+
cols: opts.cols ?? 200,
|
|
1510
|
+
rows: opts.rows ?? 50,
|
|
1511
|
+
bootMinMs: opts.bootMinMs ?? 3e3,
|
|
1512
|
+
bootQuietMs: opts.bootQuietMs ?? 1500,
|
|
1513
|
+
bootMaxMs: opts.bootMaxMs ?? 25e3,
|
|
1514
|
+
pollMs: opts.pollMs ?? 250,
|
|
1515
|
+
// Agentic turns (tool loops) routinely run for many minutes; a short
|
|
1516
|
+
// cap would surface as a mid-task error result. 30 min mirrors the
|
|
1517
|
+
// proxy-tool ceiling rather than a chat-reply expectation.
|
|
1518
|
+
turnTimeoutMs: opts.turnTimeoutMs ?? 18e5,
|
|
1519
|
+
bracketedPaste: opts.bracketedPaste ?? true,
|
|
1520
|
+
submitMinMs: opts.submitMinMs ?? 200,
|
|
1521
|
+
submitConfirmMs: opts.submitConfirmMs ?? 1500,
|
|
1522
|
+
submitMaxRetries: opts.submitMaxRetries ?? 8,
|
|
1523
|
+
debug: opts.debug ?? false
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
async start() {
|
|
1527
|
+
if (this.signal?.aborted) throw new Error("aborted before start");
|
|
1528
|
+
this.signal?.addEventListener(
|
|
1529
|
+
"abort",
|
|
1530
|
+
() => {
|
|
1531
|
+
this.aborted = true;
|
|
1532
|
+
this.dispose();
|
|
1533
|
+
},
|
|
1534
|
+
{ once: true }
|
|
1535
|
+
);
|
|
1536
|
+
const claude = resolveClaude(this.o.cliPath ?? "claude");
|
|
1537
|
+
const args = ["--session-id", this.sessionId];
|
|
1538
|
+
if (this.o.model) args.push("--model", this.o.model);
|
|
1539
|
+
if (this.o.settingSources !== null && this.o.settingSources !== void 0) {
|
|
1540
|
+
args.push("--setting-sources", this.o.settingSources);
|
|
1541
|
+
}
|
|
1542
|
+
if (this.o.extraArgs && this.o.extraArgs.length) args.push(...this.o.extraArgs);
|
|
1543
|
+
if (this.o.debug)
|
|
1544
|
+
process.stderr.write(`[session] spawn: ${claude} ${args.join(" ")}
|
|
1545
|
+
`);
|
|
1546
|
+
this.lastDataAt = Date.now();
|
|
1547
|
+
this.proc = Bun.spawn([claude, ...args], {
|
|
1548
|
+
cwd: this.cwd,
|
|
1549
|
+
env: {
|
|
1550
|
+
...process.env,
|
|
1551
|
+
CLAUDE_CONFIG_DIR: this.o.configDir,
|
|
1552
|
+
TERM: "xterm-256color",
|
|
1553
|
+
...this.o.ignoreAnthropicApiKey ? { ANTHROPIC_API_KEY: void 0, ANTHROPIC_AUTH_TOKEN: void 0 } : {}
|
|
1554
|
+
},
|
|
1555
|
+
terminal: {
|
|
1556
|
+
cols: this.o.cols,
|
|
1557
|
+
rows: this.o.rows,
|
|
1558
|
+
data: (_term, d) => {
|
|
1559
|
+
this.lastDataAt = Date.now();
|
|
1560
|
+
const chunk = Buffer.from(d).toString("utf8");
|
|
1561
|
+
this.raw += chunk;
|
|
1562
|
+
if (this.o.debug) process.stdout.write(chunk);
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
});
|
|
1566
|
+
this.proc.exited.then((code) => {
|
|
1567
|
+
this.exitCode = typeof code === "number" ? code : null;
|
|
1568
|
+
this.exited = true;
|
|
1569
|
+
this.proc = null;
|
|
1570
|
+
}).catch(() => {
|
|
1571
|
+
this.exited = true;
|
|
1572
|
+
this.proc = null;
|
|
1573
|
+
});
|
|
1574
|
+
await this.waitForBoot();
|
|
1575
|
+
this.cursor = this.lineCount();
|
|
1576
|
+
}
|
|
1577
|
+
/** Wait until the TUI has been quiet for bootQuietMs (Ink ready), bounded by
|
|
1578
|
+
* bootMinMs..bootMaxMs. */
|
|
1579
|
+
async waitForBoot() {
|
|
1580
|
+
const start = Date.now();
|
|
1581
|
+
while (Date.now() - start < this.o.bootMaxMs) {
|
|
1582
|
+
await delay(150);
|
|
1583
|
+
if (this.aborted) throw new Error("aborted during boot");
|
|
1584
|
+
if (this.exited) {
|
|
1585
|
+
throw new Error(this.failureMessage("claude exited during boot", true));
|
|
1586
|
+
}
|
|
1587
|
+
const elapsed = Date.now() - start;
|
|
1588
|
+
const sinceData = Date.now() - this.lastDataAt;
|
|
1589
|
+
if (elapsed >= this.o.bootMinMs && sinceData >= this.o.bootQuietMs) return;
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
/** Submit the freshly-injected prompt and confirm the turn was actually
|
|
1593
|
+
* accepted. A large bracketed paste collapses into a "[Pasted text]"
|
|
1594
|
+
* placeholder; an Enter sent while claude is still ingesting the paste is
|
|
1595
|
+
* silently dropped, so a single fixed-delay Enter races the paste and can
|
|
1596
|
+
* leave the prompt sitting unsubmitted (→ hang until turnTimeoutMs). Send
|
|
1597
|
+
* Enter, then poll for transcript growth past the cursor (the turn's records
|
|
1598
|
+
* are written on acceptance); resend Enter until accepted or the retry
|
|
1599
|
+
* budget is spent. Polling growth (not a blind delay) also stops us from
|
|
1600
|
+
* sending a stray Enter once the turn is in flight. */
|
|
1601
|
+
async submitTurn() {
|
|
1602
|
+
await delay(this.o.submitMinMs);
|
|
1603
|
+
for (let attempt = 0; attempt < this.o.submitMaxRetries; attempt++) {
|
|
1604
|
+
if (this.aborted || this.exited || !this.proc) return;
|
|
1605
|
+
this.proc.terminal.write("\r");
|
|
1606
|
+
const until = Date.now() + this.o.submitConfirmMs;
|
|
1607
|
+
while (Date.now() < until) {
|
|
1608
|
+
await delay(80);
|
|
1609
|
+
if (this.aborted || this.exited) return;
|
|
1610
|
+
if (this.lineCount() > this.cursor) return;
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
readRawLines() {
|
|
1615
|
+
try {
|
|
1616
|
+
return fs3.readFileSync(this.jsonlPath, "utf8").split("\n");
|
|
1617
|
+
} catch {
|
|
1618
|
+
return [];
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
/** Count of complete lines (split('\n') minus the trailing/partial element). */
|
|
1622
|
+
lineCount() {
|
|
1623
|
+
const lines = this.readRawLines();
|
|
1624
|
+
return lines.length > 0 ? lines.length - 1 : 0;
|
|
1625
|
+
}
|
|
1626
|
+
rawTail(max = 600) {
|
|
1627
|
+
const clean = this.raw.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "").replace(/\s+/g, " ").trim();
|
|
1628
|
+
return clean.length > max ? clean.slice(-max) : clean;
|
|
1629
|
+
}
|
|
1630
|
+
failureMessage(reason, includeRaw = false) {
|
|
1631
|
+
const parts = [
|
|
1632
|
+
`${reason} (sessionId=${this.sessionId}, jsonlPath=${this.jsonlPath}, exitCode=${this.exitCode ?? "unknown"})`
|
|
1633
|
+
];
|
|
1634
|
+
if (includeRaw) {
|
|
1635
|
+
const tail = this.rawTail();
|
|
1636
|
+
if (tail) parts.push(`terminalTail=${JSON.stringify(tail)}`);
|
|
1637
|
+
}
|
|
1638
|
+
return parts.join("; ");
|
|
1639
|
+
}
|
|
1640
|
+
/**
|
|
1641
|
+
* Inject a turn into the live session and return the assistant reply once a
|
|
1642
|
+
* terminal stop_reason is observed in the transcript.
|
|
1643
|
+
*/
|
|
1644
|
+
async ask(prompt, perTurnTimeoutMs) {
|
|
1645
|
+
if (this.aborted) throw new Error("aborted");
|
|
1646
|
+
if (!this.proc || this.exited)
|
|
1647
|
+
throw new Error("session not started or already exited");
|
|
1648
|
+
const timeout = perTurnTimeoutMs ?? this.o.turnTimeoutMs;
|
|
1649
|
+
const t0 = Date.now();
|
|
1650
|
+
if (this.o.bracketedPaste) {
|
|
1651
|
+
this.proc.terminal.write("\x1B[200~" + prompt + "\x1B[201~");
|
|
1652
|
+
} else {
|
|
1653
|
+
this.proc.terminal.write(prompt);
|
|
1654
|
+
}
|
|
1655
|
+
await this.submitTurn();
|
|
1656
|
+
const collected = [];
|
|
1657
|
+
let lastUsage = null;
|
|
1658
|
+
let stopReason = null;
|
|
1659
|
+
const deadline = Date.now() + timeout;
|
|
1660
|
+
while (Date.now() < deadline) {
|
|
1661
|
+
await delay(this.o.pollMs);
|
|
1662
|
+
if (this.aborted) throw new Error("aborted mid-turn");
|
|
1663
|
+
const lines = this.readRawLines();
|
|
1664
|
+
const lastComplete = lines.length - 1;
|
|
1665
|
+
if (lastComplete <= this.cursor) {
|
|
1666
|
+
if (this.exited) throw new Error(this.failureMessage("claude exited mid-turn", true));
|
|
1667
|
+
continue;
|
|
1668
|
+
}
|
|
1669
|
+
for (let i = this.cursor; i < lastComplete; i++) {
|
|
1670
|
+
const s = lines[i];
|
|
1671
|
+
if (!s || !s.trim()) continue;
|
|
1672
|
+
let rec;
|
|
1673
|
+
try {
|
|
1674
|
+
rec = JSON.parse(s);
|
|
1675
|
+
} catch {
|
|
1676
|
+
continue;
|
|
1677
|
+
}
|
|
1678
|
+
if (rec.type === "assistant" && rec.message) {
|
|
1679
|
+
for (const b of rec.message.content ?? []) {
|
|
1680
|
+
if (b?.type === "text" && typeof b.text === "string")
|
|
1681
|
+
collected.push(b.text);
|
|
1682
|
+
}
|
|
1683
|
+
if (rec.message.usage) lastUsage = rec.message.usage;
|
|
1684
|
+
if (rec.message.stop_reason && TERMINAL_STOP.has(rec.message.stop_reason)) {
|
|
1685
|
+
stopReason = rec.message.stop_reason;
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
this.cursor = lastComplete;
|
|
1690
|
+
if (stopReason) break;
|
|
1691
|
+
}
|
|
1692
|
+
if (!stopReason) {
|
|
1693
|
+
throw new Error(
|
|
1694
|
+
this.failureMessage(
|
|
1695
|
+
`turn timed out after ${timeout}ms (no terminal assistant record; collected ${collected.length} text block(s))`
|
|
1696
|
+
)
|
|
1697
|
+
);
|
|
1698
|
+
}
|
|
1699
|
+
const u = lastUsage ?? {};
|
|
1700
|
+
return {
|
|
1701
|
+
text: collected.join("\n").trim(),
|
|
1702
|
+
stopReason,
|
|
1703
|
+
usage: lastUsage,
|
|
1704
|
+
cacheReadTokens: u.cache_read_input_tokens ?? 0,
|
|
1705
|
+
cacheCreationTokens: u.cache_creation_input_tokens ?? 0,
|
|
1706
|
+
ephemeral1hTokens: u.cache_creation?.ephemeral_1h_input_tokens ?? 0,
|
|
1707
|
+
ephemeral5mTokens: u.cache_creation?.ephemeral_5m_input_tokens ?? 0,
|
|
1708
|
+
inputTokens: u.input_tokens ?? 0,
|
|
1709
|
+
outputTokens: u.output_tokens ?? 0,
|
|
1710
|
+
elapsedMs: Date.now() - t0
|
|
1711
|
+
};
|
|
1712
|
+
}
|
|
1713
|
+
/**
|
|
1714
|
+
* Like ask(), but instead of collecting the reply text it re-emits each NEW
|
|
1715
|
+
* raw JSONL transcript line via onLine (verbatim) until a terminal
|
|
1716
|
+
* stop_reason. Returns the terminal stop_reason + the last assistant usage.
|
|
1717
|
+
* Used by the opencode plugin transport shim, which feeds these raw lines
|
|
1718
|
+
* into the existing stream-json line handler unchanged.
|
|
1719
|
+
*/
|
|
1720
|
+
async tailTurn(prompt, onLine, perTurnTimeoutMs) {
|
|
1721
|
+
if (this.aborted) throw new Error("aborted");
|
|
1722
|
+
if (!this.proc || this.exited)
|
|
1723
|
+
throw new Error("session not started or already exited");
|
|
1724
|
+
const timeout = perTurnTimeoutMs ?? this.o.turnTimeoutMs;
|
|
1725
|
+
if (this.o.bracketedPaste) {
|
|
1726
|
+
this.proc.terminal.write("\x1B[200~" + prompt + "\x1B[201~");
|
|
1727
|
+
} else {
|
|
1728
|
+
this.proc.terminal.write(prompt);
|
|
1729
|
+
}
|
|
1730
|
+
await this.submitTurn();
|
|
1731
|
+
let lastUsage = null;
|
|
1732
|
+
let totalOutput = 0;
|
|
1733
|
+
let stopReason = null;
|
|
1734
|
+
const deadline = Date.now() + timeout;
|
|
1735
|
+
while (Date.now() < deadline) {
|
|
1736
|
+
await delay(this.o.pollMs);
|
|
1737
|
+
if (this.aborted) throw new Error("aborted mid-turn");
|
|
1738
|
+
const lines = this.readRawLines();
|
|
1739
|
+
const lastComplete = lines.length - 1;
|
|
1740
|
+
if (lastComplete <= this.cursor) {
|
|
1741
|
+
if (this.exited) {
|
|
1742
|
+
throw new Error(this.failureMessage("claude exited mid-turn", true));
|
|
1743
|
+
}
|
|
1744
|
+
continue;
|
|
1745
|
+
}
|
|
1746
|
+
for (let i = this.cursor; i < lastComplete; i++) {
|
|
1747
|
+
const s = lines[i];
|
|
1748
|
+
if (!s || !s.trim()) continue;
|
|
1749
|
+
onLine(s);
|
|
1750
|
+
let rec;
|
|
1751
|
+
try {
|
|
1752
|
+
rec = JSON.parse(s);
|
|
1753
|
+
} catch {
|
|
1754
|
+
continue;
|
|
1755
|
+
}
|
|
1756
|
+
if (rec.type === "assistant" && rec.message) {
|
|
1757
|
+
if (rec.message.usage) {
|
|
1758
|
+
lastUsage = rec.message.usage;
|
|
1759
|
+
totalOutput += rec.message.usage.output_tokens ?? 0;
|
|
1760
|
+
}
|
|
1761
|
+
if (rec.message.stop_reason && TERMINAL_STOP.has(rec.message.stop_reason)) {
|
|
1762
|
+
stopReason = rec.message.stop_reason;
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
this.cursor = lastComplete;
|
|
1767
|
+
if (stopReason) break;
|
|
1768
|
+
}
|
|
1769
|
+
let usage = lastUsage;
|
|
1770
|
+
if (lastUsage) {
|
|
1771
|
+
usage = { ...lastUsage, output_tokens: totalOutput };
|
|
1772
|
+
if (Array.isArray(lastUsage.iterations) && lastUsage.iterations.length > 0) {
|
|
1773
|
+
const iters = lastUsage.iterations.map((it) => ({ ...it }));
|
|
1774
|
+
iters[iters.length - 1] = {
|
|
1775
|
+
...iters[iters.length - 1],
|
|
1776
|
+
output_tokens: totalOutput
|
|
1777
|
+
};
|
|
1778
|
+
usage.iterations = iters;
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
if (!stopReason) {
|
|
1782
|
+
throw new Error(
|
|
1783
|
+
this.failureMessage(
|
|
1784
|
+
`turn timed out after ${timeout}ms (no terminal assistant record)`
|
|
1785
|
+
)
|
|
1786
|
+
);
|
|
1787
|
+
}
|
|
1788
|
+
return { stopReason, usage };
|
|
1789
|
+
}
|
|
1790
|
+
dispose() {
|
|
1791
|
+
if (this.proc) {
|
|
1792
|
+
try {
|
|
1793
|
+
this.proc.terminal.write("");
|
|
1794
|
+
} catch {
|
|
1795
|
+
}
|
|
1796
|
+
try {
|
|
1797
|
+
this.proc.kill();
|
|
1798
|
+
} catch {
|
|
1799
|
+
}
|
|
1800
|
+
try {
|
|
1801
|
+
this.proc.terminal.close();
|
|
1802
|
+
} catch {
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
this.proc = null;
|
|
1806
|
+
}
|
|
1807
|
+
};
|
|
1808
|
+
|
|
1809
|
+
// src/claude-session-wrapper.ts
|
|
1810
|
+
function decodeUserEnvelope(chunk) {
|
|
1811
|
+
let parsed;
|
|
1812
|
+
try {
|
|
1813
|
+
parsed = JSON.parse(chunk);
|
|
1814
|
+
} catch {
|
|
1815
|
+
return chunk;
|
|
1816
|
+
}
|
|
1817
|
+
if (!parsed || parsed.type !== "user" || !parsed.message) return chunk;
|
|
1818
|
+
const content = parsed.message.content;
|
|
1819
|
+
if (typeof content === "string") return content;
|
|
1820
|
+
if (!Array.isArray(content)) return chunk;
|
|
1821
|
+
const parts = [];
|
|
1822
|
+
let dropped = 0;
|
|
1823
|
+
for (const block of content) {
|
|
1824
|
+
if (block?.type === "text" && typeof block.text === "string") {
|
|
1825
|
+
parts.push(block.text);
|
|
1826
|
+
} else if (block?.type === "tool_result") {
|
|
1827
|
+
const v = block.content;
|
|
1828
|
+
const text = typeof v === "string" ? v : Array.isArray(v) ? v.map((i) => i?.type === "text" ? i.text : "").filter(Boolean).join("\n") : "";
|
|
1829
|
+
parts.push(
|
|
1830
|
+
`[Tool result${block.tool_use_id ? ` ${block.tool_use_id}` : ""}]
|
|
1831
|
+
${text}`
|
|
1832
|
+
);
|
|
1833
|
+
} else {
|
|
1834
|
+
dropped++;
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
if (dropped > 0) {
|
|
1838
|
+
log.warn("interactive transport dropped non-text content blocks", {
|
|
1839
|
+
dropped
|
|
1840
|
+
});
|
|
1841
|
+
}
|
|
1842
|
+
return parts.join("\n\n");
|
|
1843
|
+
}
|
|
1844
|
+
function spawnInteractiveProcess(opts) {
|
|
1845
|
+
const extraArgs = [];
|
|
1846
|
+
if (opts.mcpConfigPaths && opts.mcpConfigPaths.length > 0) {
|
|
1847
|
+
extraArgs.push(
|
|
1848
|
+
"--mcp-config",
|
|
1849
|
+
...opts.mcpConfigPaths,
|
|
1850
|
+
"--strict-mcp-config"
|
|
1851
|
+
);
|
|
1852
|
+
}
|
|
1853
|
+
if (opts.permissionsAllow && opts.permissionsAllow.length > 0) {
|
|
1854
|
+
extraArgs.push(
|
|
1855
|
+
"--settings",
|
|
1856
|
+
JSON.stringify({ permissions: { allow: opts.permissionsAllow } })
|
|
1857
|
+
);
|
|
1858
|
+
}
|
|
1859
|
+
if (opts.permissionMode === "bypassPermissions") {
|
|
1860
|
+
log.warn(
|
|
1861
|
+
"interactive permissionMode bypassPermissions ignored: Claude Code prompts for confirmation in the TUI"
|
|
1862
|
+
);
|
|
1863
|
+
} else if (opts.permissionMode) {
|
|
1864
|
+
extraArgs.push("--permission-mode", opts.permissionMode);
|
|
1865
|
+
}
|
|
1866
|
+
if (opts.systemPromptFile) {
|
|
1867
|
+
extraArgs.push("--append-system-prompt-file", opts.systemPromptFile);
|
|
1868
|
+
}
|
|
1869
|
+
const session = new ClaudeSession({
|
|
1870
|
+
cwd: opts.cwd,
|
|
1871
|
+
cliPath: opts.cliPath,
|
|
1872
|
+
configDir: opts.configDir,
|
|
1873
|
+
model: opts.model,
|
|
1874
|
+
// Default null = normal CLAUDE.md + settings load, matching what the
|
|
1875
|
+
// headless spawn does. "" (skip everything) is for fast e2e runs only.
|
|
1876
|
+
settingSources: opts.settingSources === void 0 ? null : opts.settingSources,
|
|
1877
|
+
extraArgs,
|
|
1878
|
+
ignoreAnthropicApiKey: opts.ignoreAnthropicApiKey
|
|
1879
|
+
});
|
|
1880
|
+
log.info("prepared interactive claude session", {
|
|
1881
|
+
cwd: opts.cwd,
|
|
1882
|
+
cliPath: opts.cliPath ?? "claude",
|
|
1883
|
+
configDir: session.configDir,
|
|
1884
|
+
model: opts.model,
|
|
1885
|
+
sessionId: session.sessionId,
|
|
1886
|
+
jsonlPath: session.jsonlPath
|
|
1887
|
+
});
|
|
1888
|
+
const lineEmitter = new EventEmitter2();
|
|
1889
|
+
const errorHandlers = /* @__PURE__ */ new Set();
|
|
1890
|
+
let startPromise = null;
|
|
1891
|
+
const ensureStarted = () => {
|
|
1892
|
+
if (!startPromise) startPromise = session.start();
|
|
1893
|
+
return startPromise;
|
|
1894
|
+
};
|
|
1895
|
+
const emitResult = (subtype, isError, result, usage) => {
|
|
1896
|
+
lineEmitter.emit(
|
|
1897
|
+
"line",
|
|
1898
|
+
JSON.stringify({
|
|
1899
|
+
type: "result",
|
|
1900
|
+
subtype,
|
|
1901
|
+
is_error: isError,
|
|
1902
|
+
result,
|
|
1903
|
+
session_id: session.sessionId,
|
|
1904
|
+
usage: usage ?? {},
|
|
1905
|
+
total_cost_usd: null,
|
|
1906
|
+
duration_ms: 0
|
|
1907
|
+
})
|
|
1908
|
+
);
|
|
1909
|
+
};
|
|
1910
|
+
const runTurn = (userMsg) => {
|
|
1911
|
+
void (async () => {
|
|
1912
|
+
try {
|
|
1913
|
+
await ensureStarted();
|
|
1914
|
+
const { stopReason, usage } = await session.tailTurn(userMsg, (raw) => {
|
|
1915
|
+
lineEmitter.emit("line", raw);
|
|
1916
|
+
});
|
|
1917
|
+
const timedOut = !stopReason;
|
|
1918
|
+
emitResult(
|
|
1919
|
+
timedOut ? "error_during_execution" : stopReason,
|
|
1920
|
+
timedOut,
|
|
1921
|
+
timedOut ? "Interactive transport: the turn ended without a terminal stop_reason (turn timeout or claude exit). Output above may be incomplete." : void 0,
|
|
1922
|
+
usage
|
|
1923
|
+
);
|
|
1924
|
+
} catch (err) {
|
|
1925
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
1926
|
+
log.error("interactive turn failed", { error: e.message });
|
|
1927
|
+
emitResult(
|
|
1928
|
+
"error_during_execution",
|
|
1929
|
+
true,
|
|
1930
|
+
`Interactive transport failed: ${e.message}`
|
|
1931
|
+
);
|
|
1932
|
+
if (errorHandlers.size > 0) {
|
|
1933
|
+
for (const h of errorHandlers) h(e);
|
|
1934
|
+
} else {
|
|
1935
|
+
lineEmitter.emit("close");
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
})();
|
|
1939
|
+
};
|
|
1940
|
+
const proc = {
|
|
1941
|
+
stdin: {
|
|
1942
|
+
write(chunk) {
|
|
1943
|
+
const raw = typeof chunk === "string" && chunk.endsWith("\n") ? chunk.slice(0, -1) : chunk;
|
|
1944
|
+
runTurn(decodeUserEnvelope(raw));
|
|
1945
|
+
return true;
|
|
1946
|
+
},
|
|
1947
|
+
end() {
|
|
1948
|
+
}
|
|
1949
|
+
},
|
|
1950
|
+
stdout: null,
|
|
1951
|
+
stderr: null,
|
|
1952
|
+
pid: -1,
|
|
1953
|
+
killed: false,
|
|
1954
|
+
on(event, fn) {
|
|
1955
|
+
if (event === "error") errorHandlers.add(fn);
|
|
1956
|
+
return proc;
|
|
1957
|
+
},
|
|
1958
|
+
once() {
|
|
1959
|
+
return proc;
|
|
1960
|
+
},
|
|
1961
|
+
off(event, fn) {
|
|
1962
|
+
if (event === "error") errorHandlers.delete(fn);
|
|
1963
|
+
return proc;
|
|
1964
|
+
},
|
|
1965
|
+
kill() {
|
|
1966
|
+
try {
|
|
1967
|
+
session.dispose();
|
|
1968
|
+
} catch {
|
|
1969
|
+
}
|
|
1970
|
+
if (opts.systemPromptFile) {
|
|
1971
|
+
void unlink2(opts.systemPromptFile).catch(() => {
|
|
1972
|
+
});
|
|
1973
|
+
}
|
|
1974
|
+
proc.killed = true;
|
|
1975
|
+
return true;
|
|
1976
|
+
}
|
|
1977
|
+
};
|
|
1978
|
+
return {
|
|
1979
|
+
proc,
|
|
1980
|
+
lineEmitter,
|
|
1981
|
+
proxyServer: null,
|
|
1982
|
+
mcpHash: void 0,
|
|
1983
|
+
systemPromptFile: opts.systemPromptFile
|
|
1984
|
+
};
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
// src/proxy-mcp.ts
|
|
1988
|
+
import { createServer } from "http";
|
|
1989
|
+
import * as fs4 from "fs";
|
|
1990
|
+
import * as path4 from "path";
|
|
1432
1991
|
import * as crypto2 from "crypto";
|
|
1433
|
-
import { EventEmitter as
|
|
1992
|
+
import { EventEmitter as EventEmitter3 } from "events";
|
|
1434
1993
|
var PROTOCOL_VERSION = "2024-11-05";
|
|
1435
1994
|
var SERVER_NAME = "opencode_proxy";
|
|
1436
1995
|
var PROXY_TOOL_PREFIX = `mcp__${SERVER_NAME}__`;
|
|
@@ -1557,7 +2116,7 @@ var DEFAULT_PROXY_TOOLS = [
|
|
|
1557
2116
|
}
|
|
1558
2117
|
];
|
|
1559
2118
|
async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS) {
|
|
1560
|
-
const calls = new
|
|
2119
|
+
const calls = new EventEmitter3();
|
|
1561
2120
|
const pending = /* @__PURE__ */ new Map();
|
|
1562
2121
|
const server2 = createServer(async (req, res) => {
|
|
1563
2122
|
if (req.method !== "POST" || !req.url?.startsWith("/mcp")) {
|
|
@@ -1637,12 +2196,12 @@ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS) {
|
|
|
1637
2196
|
});
|
|
1638
2197
|
let timer = null;
|
|
1639
2198
|
const result = await new Promise(
|
|
1640
|
-
(
|
|
2199
|
+
(resolve4, reject) => {
|
|
1641
2200
|
const entry = {
|
|
1642
2201
|
id: callId,
|
|
1643
2202
|
toolName,
|
|
1644
2203
|
input,
|
|
1645
|
-
resolve:
|
|
2204
|
+
resolve: resolve4,
|
|
1646
2205
|
reject
|
|
1647
2206
|
};
|
|
1648
2207
|
pending.set(callId, entry);
|
|
@@ -1717,11 +2276,11 @@ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS) {
|
|
|
1717
2276
|
}
|
|
1718
2277
|
}
|
|
1719
2278
|
});
|
|
1720
|
-
await new Promise((
|
|
2279
|
+
await new Promise((resolve4, reject) => {
|
|
1721
2280
|
server2.once("error", reject);
|
|
1722
2281
|
server2.listen(0, "127.0.0.1", () => {
|
|
1723
2282
|
server2.off("error", reject);
|
|
1724
|
-
|
|
2283
|
+
resolve4();
|
|
1725
2284
|
});
|
|
1726
2285
|
});
|
|
1727
2286
|
const addr = server2.address();
|
|
@@ -1755,11 +2314,11 @@ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS) {
|
|
|
1755
2314
|
2
|
|
1756
2315
|
);
|
|
1757
2316
|
const hash = crypto2.createHash("sha256").update(body).digest("hex").slice(0, 12);
|
|
1758
|
-
const outPath =
|
|
2317
|
+
const outPath = path4.join(
|
|
1759
2318
|
pluginTmpDir(),
|
|
1760
2319
|
`proxy-${hash}.json`
|
|
1761
2320
|
);
|
|
1762
|
-
|
|
2321
|
+
fs4.writeFileSync(outPath, body, { encoding: "utf8", mode: 384 });
|
|
1763
2322
|
configFilePath = outPath;
|
|
1764
2323
|
return outPath;
|
|
1765
2324
|
},
|
|
@@ -1768,12 +2327,12 @@ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS) {
|
|
|
1768
2327
|
entry.reject(new Error("proxy MCP server closed"));
|
|
1769
2328
|
}
|
|
1770
2329
|
pending.clear();
|
|
1771
|
-
await new Promise((
|
|
1772
|
-
server2.close(() =>
|
|
2330
|
+
await new Promise((resolve4) => {
|
|
2331
|
+
server2.close(() => resolve4());
|
|
1773
2332
|
});
|
|
1774
2333
|
if (configFilePath) {
|
|
1775
2334
|
try {
|
|
1776
|
-
|
|
2335
|
+
fs4.unlinkSync(configFilePath);
|
|
1777
2336
|
} catch {
|
|
1778
2337
|
}
|
|
1779
2338
|
configFilePath = null;
|
|
@@ -1807,10 +2366,10 @@ function disallowedToolFlags(tools) {
|
|
|
1807
2366
|
return out;
|
|
1808
2367
|
}
|
|
1809
2368
|
function readBody(req) {
|
|
1810
|
-
return new Promise((
|
|
2369
|
+
return new Promise((resolve4, reject) => {
|
|
1811
2370
|
const chunks = [];
|
|
1812
2371
|
req.on("data", (chunk) => chunks.push(chunk));
|
|
1813
|
-
req.on("end", () =>
|
|
2372
|
+
req.on("end", () => resolve4(Buffer.concat(chunks).toString("utf8")));
|
|
1814
2373
|
req.on("error", reject);
|
|
1815
2374
|
});
|
|
1816
2375
|
}
|
|
@@ -1823,10 +2382,10 @@ function writeJson(res, body) {
|
|
|
1823
2382
|
}
|
|
1824
2383
|
|
|
1825
2384
|
// src/proxy-broker.ts
|
|
1826
|
-
import { EventEmitter as
|
|
2385
|
+
import { EventEmitter as EventEmitter4 } from "events";
|
|
1827
2386
|
var pendingByCallId = /* @__PURE__ */ new Map();
|
|
1828
2387
|
var callIdsBySession = /* @__PURE__ */ new Map();
|
|
1829
|
-
var emitter = new
|
|
2388
|
+
var emitter = new EventEmitter4();
|
|
1830
2389
|
var PENDING_PROXY_CALL_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
1831
2390
|
function eventName(sessionKey2) {
|
|
1832
2391
|
return `pending:${sessionKey2}`;
|
|
@@ -1948,11 +2507,11 @@ function rejectAllPendingProxyCallsForSession(sessionKey2, error) {
|
|
|
1948
2507
|
}
|
|
1949
2508
|
|
|
1950
2509
|
// src/claude-code-language-model.ts
|
|
1951
|
-
import { readFileSync as
|
|
1952
|
-
import { unlink as
|
|
1953
|
-
import { homedir as
|
|
1954
|
-
import { randomUUID as
|
|
1955
|
-
import { dirname as dirname3, join as
|
|
2510
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
2511
|
+
import { unlink as unlink3 } from "fs/promises";
|
|
2512
|
+
import { homedir as homedir4, tmpdir as tmpdir2 } from "os";
|
|
2513
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
2514
|
+
import { dirname as dirname3, join as join6 } from "path";
|
|
1956
2515
|
var DEFAULT_COMPACTION_MODEL = "claude-haiku-4-5";
|
|
1957
2516
|
function resolveCompactionModel(configured) {
|
|
1958
2517
|
const env = process.env.CLAUDE_CODE_COMPACTION_MODEL?.trim();
|
|
@@ -2141,9 +2700,9 @@ function makeAutoContinueMessage() {
|
|
|
2141
2700
|
}
|
|
2142
2701
|
});
|
|
2143
2702
|
}
|
|
2144
|
-
function readPromptFileIfPresent(
|
|
2703
|
+
function readPromptFileIfPresent(path6) {
|
|
2145
2704
|
try {
|
|
2146
|
-
const content =
|
|
2705
|
+
const content = readFileSync3(path6, "utf8").trim();
|
|
2147
2706
|
return content || void 0;
|
|
2148
2707
|
} catch {
|
|
2149
2708
|
return void 0;
|
|
@@ -2152,7 +2711,7 @@ function readPromptFileIfPresent(path5) {
|
|
|
2152
2711
|
function nearestWorkspaceAgentsPrompt(cwd) {
|
|
2153
2712
|
let dir = cwd;
|
|
2154
2713
|
while (true) {
|
|
2155
|
-
const content = readPromptFileIfPresent(
|
|
2714
|
+
const content = readPromptFileIfPresent(join6(dir, "AGENTS.md"));
|
|
2156
2715
|
if (content) return content;
|
|
2157
2716
|
const parent = dirname3(dir);
|
|
2158
2717
|
if (parent === dir) return void 0;
|
|
@@ -2203,8 +2762,8 @@ function buildAppendedSystemPrompt(cwd, includeMultiStepHint = true, extraSystem
|
|
|
2203
2762
|
for (const s of extraSystemContent) {
|
|
2204
2763
|
if (s.trim()) parts.push(s.trim());
|
|
2205
2764
|
}
|
|
2206
|
-
const configRoot = process.env.XDG_CONFIG_HOME ??
|
|
2207
|
-
const globalAgents = readPromptFileIfPresent(
|
|
2765
|
+
const configRoot = process.env.XDG_CONFIG_HOME ?? join6(homedir4(), ".config");
|
|
2766
|
+
const globalAgents = readPromptFileIfPresent(join6(configRoot, "opencode", "AGENTS.md"));
|
|
2208
2767
|
const workspaceAgents = nearestWorkspaceAgentsPrompt(cwd);
|
|
2209
2768
|
if (globalAgents) parts.push(globalAgents);
|
|
2210
2769
|
if (workspaceAgents && workspaceAgents !== globalAgents) parts.push(workspaceAgents);
|
|
@@ -2212,10 +2771,10 @@ function buildAppendedSystemPrompt(cwd, includeMultiStepHint = true, extraSystem
|
|
|
2212
2771
|
if (includeMultiStepHint) parts.push(MULTI_STEP_TASK_HINT);
|
|
2213
2772
|
const content = parts.join("\n\n");
|
|
2214
2773
|
if (!content) return void 0;
|
|
2215
|
-
const
|
|
2774
|
+
const path6 = join6(tmpdir2(), `opencode-cc-sys-${randomUUID3()}.md`);
|
|
2216
2775
|
try {
|
|
2217
|
-
writeFileSync3(
|
|
2218
|
-
return
|
|
2776
|
+
writeFileSync3(path6, content, "utf8");
|
|
2777
|
+
return path6;
|
|
2219
2778
|
} catch (err) {
|
|
2220
2779
|
log.warn("failed to write system prompt file", { error: String(err) });
|
|
2221
2780
|
return void 0;
|
|
@@ -2755,12 +3314,14 @@ var ClaudeCodeLanguageModel = class {
|
|
|
2755
3314
|
const proc = spawn2(this.config.cliPath, cliArgs, {
|
|
2756
3315
|
cwd,
|
|
2757
3316
|
stdio: ["pipe", "pipe", "pipe"],
|
|
2758
|
-
env: claudeSpawnEnv(
|
|
3317
|
+
env: claudeSpawnEnv({
|
|
3318
|
+
ignoreAnthropicApiKey: this.config.ignoreAnthropicApiKey
|
|
3319
|
+
}),
|
|
2759
3320
|
shell: process.platform === "win32"
|
|
2760
3321
|
});
|
|
2761
3322
|
if (systemPromptFile) {
|
|
2762
3323
|
proc.on("exit", () => {
|
|
2763
|
-
void
|
|
3324
|
+
void unlink3(systemPromptFile).catch(() => {
|
|
2764
3325
|
});
|
|
2765
3326
|
});
|
|
2766
3327
|
}
|
|
@@ -2771,7 +3332,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
2771
3332
|
const toolCalls = [];
|
|
2772
3333
|
const toolCallStreams = /* @__PURE__ */ new Map();
|
|
2773
3334
|
let gotPartialEvents = false;
|
|
2774
|
-
const result = await new Promise((
|
|
3335
|
+
const result = await new Promise((resolve4, reject) => {
|
|
2775
3336
|
const cleanup = () => {
|
|
2776
3337
|
try {
|
|
2777
3338
|
if (!proc.killed && proc.exitCode === null) proc.kill();
|
|
@@ -2879,7 +3440,7 @@ ${plan}
|
|
|
2879
3440
|
usage: msg.usage
|
|
2880
3441
|
};
|
|
2881
3442
|
cleanup();
|
|
2882
|
-
|
|
3443
|
+
resolve4({
|
|
2883
3444
|
...resultMeta,
|
|
2884
3445
|
text: responseText,
|
|
2885
3446
|
thinking: thinkingText,
|
|
@@ -2891,7 +3452,7 @@ ${plan}
|
|
|
2891
3452
|
});
|
|
2892
3453
|
rl.on("close", () => {
|
|
2893
3454
|
cleanup();
|
|
2894
|
-
|
|
3455
|
+
resolve4({
|
|
2895
3456
|
...resultMeta,
|
|
2896
3457
|
text: responseText,
|
|
2897
3458
|
thinking: thinkingText,
|
|
@@ -2996,6 +3557,10 @@ ${plan}
|
|
|
2996
3557
|
const toUsage = this.toUsage.bind(this);
|
|
2997
3558
|
const toFinishReason = this.toFinishReason.bind(this);
|
|
2998
3559
|
const handleControlRequest = this.handleControlRequest.bind(this);
|
|
3560
|
+
const flagOn = (v) => v !== void 0 && !["", "0", "false", "no", "off"].includes(v.trim().toLowerCase());
|
|
3561
|
+
const interactivePref = this.config.interactive ?? flagOn(process.env.CLAUDE_CODE_INTERACTIVE_TRANSPORT);
|
|
3562
|
+
const useInteractive = interactivePref && typeof globalThis.Bun?.Terminal === "function";
|
|
3563
|
+
const interactiveBypassRequested = this.config.interactiveBypass ?? flagOn(process.env.CLAUDE_CODE_INTERACTIVE_BYPASS);
|
|
2999
3564
|
if (scope === "no-tools" && !compactionMode) {
|
|
3000
3565
|
log.info("doStream no-tools title stub", {
|
|
3001
3566
|
compactionMode,
|
|
@@ -3118,81 +3683,143 @@ ${plan}
|
|
|
3118
3683
|
}
|
|
3119
3684
|
}
|
|
3120
3685
|
const setup = async () => {
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3686
|
+
if (useInteractive && !compactionMode) {
|
|
3687
|
+
const mcp = self.effectiveMcpConfig(cwd, void 0, runtimeStatus);
|
|
3688
|
+
if (activeProcess) {
|
|
3689
|
+
proc = activeProcess.proc;
|
|
3690
|
+
lineEmitter = activeProcess.lineEmitter;
|
|
3691
|
+
log.debug("reusing active interactive session", { sk });
|
|
3692
|
+
} else {
|
|
3693
|
+
const allow = [
|
|
3694
|
+
...mcp.allEnabledServerNames.map((n) => `mcp__${n}__*`),
|
|
3695
|
+
"mcp__opencode_proxy__*",
|
|
3696
|
+
...self.config.interactiveAllowTools ?? [
|
|
3697
|
+
"Bash",
|
|
3698
|
+
"Edit",
|
|
3699
|
+
"Write",
|
|
3700
|
+
"Read",
|
|
3701
|
+
"WebFetch"
|
|
3702
|
+
]
|
|
3703
|
+
];
|
|
3704
|
+
const systemPromptFile = self.config.interactiveSystemPrompt === false ? void 0 : buildAppendedSystemPrompt(
|
|
3705
|
+
cwd,
|
|
3706
|
+
self.config.multiStepContinuation !== false
|
|
3707
|
+
// Do not forward opencode's own system prompt into the
|
|
3708
|
+
// interactive TUI. Live subscription-account testing
|
|
3709
|
+
// showed that large forwarded payload can trigger Claude
|
|
3710
|
+
// Code's third-party-app usage gate, while our static
|
|
3711
|
+
// CLI/AGENTS/continuation prompt remains safe.
|
|
3712
|
+
);
|
|
3713
|
+
if (self.config.interactiveSystemPrompt === false) {
|
|
3714
|
+
log.warn(
|
|
3715
|
+
"interactive system prompt disabled; opencode agent prompts will not be appended"
|
|
3716
|
+
);
|
|
3717
|
+
}
|
|
3718
|
+
if (interactiveBypassRequested) {
|
|
3719
|
+
log.warn(
|
|
3720
|
+
"interactiveBypass ignored: Claude Code prompts for bypassPermissions confirmation in the interactive TUI"
|
|
3721
|
+
);
|
|
3722
|
+
}
|
|
3723
|
+
const ap = spawnInteractiveProcess({
|
|
3724
|
+
cwd,
|
|
3725
|
+
cliPath,
|
|
3726
|
+
configDir: self.config.configDir,
|
|
3727
|
+
model: effectiveModelId,
|
|
3728
|
+
mcpConfigPaths: mcp.paths,
|
|
3729
|
+
permissionsAllow: allow,
|
|
3730
|
+
systemPromptFile,
|
|
3731
|
+
ignoreAnthropicApiKey: self.config.ignoreAnthropicApiKey
|
|
3732
|
+
});
|
|
3733
|
+
ap.mcpHash = mcp.bridgedHash;
|
|
3734
|
+
setActiveProcess(sk, ap);
|
|
3735
|
+
proc = ap.proc;
|
|
3736
|
+
lineEmitter = ap.lineEmitter;
|
|
3737
|
+
activeProcess = ap;
|
|
3738
|
+
log.info("spawned interactive claude session", {
|
|
3739
|
+
sk,
|
|
3740
|
+
cliPath,
|
|
3741
|
+
configDir: self.config.configDir,
|
|
3742
|
+
model: effectiveModelId
|
|
3743
|
+
});
|
|
3147
3744
|
}
|
|
3148
|
-
const proxyDisallowed = resolvedProxy ? disallowedToolFlags(resolvedProxy) : [];
|
|
3149
|
-
const extraDisallowed = [];
|
|
3150
|
-
if (self.config.webSearch === "disabled") extraDisallowed.push("WebSearch");
|
|
3151
|
-
const allDisallowed = [...proxyDisallowed, ...extraDisallowed];
|
|
3152
|
-
const mcp = self.effectiveMcpConfig(
|
|
3153
|
-
cwd,
|
|
3154
|
-
proxyServer?.configPath(),
|
|
3155
|
-
runtimeStatus,
|
|
3156
|
-
excludeServers
|
|
3157
|
-
);
|
|
3158
|
-
const systemPromptFile = activeProcess ? void 0 : buildAppendedSystemPrompt(
|
|
3159
|
-
cwd,
|
|
3160
|
-
self.config.multiStepContinuation !== false,
|
|
3161
|
-
extractSystemMessages(options.prompt)
|
|
3162
|
-
);
|
|
3163
|
-
cliArgs = buildCliArgs({
|
|
3164
|
-
sessionKey: sk,
|
|
3165
|
-
skipPermissions,
|
|
3166
|
-
model: self.modelId,
|
|
3167
|
-
permissionMode: self.config.permissionMode,
|
|
3168
|
-
mcpConfig: mcp.paths,
|
|
3169
|
-
strictMcpConfig: self.config.strictMcpConfig,
|
|
3170
|
-
disallowedTools: allDisallowed.length > 0 ? allDisallowed : void 0,
|
|
3171
|
-
appendSystemPromptFile: systemPromptFile,
|
|
3172
|
-
...self.thinkingCliOptions(),
|
|
3173
|
-
cliVersion
|
|
3174
|
-
});
|
|
3175
|
-
spawnSystemPromptFile = systemPromptFile;
|
|
3176
|
-
spawnProxyServer = proxyServer;
|
|
3177
|
-
spawnMcpHash = mcp.bridgedHash;
|
|
3178
|
-
}
|
|
3179
|
-
if (activeProcess && !compactionMode) {
|
|
3180
|
-
proc = activeProcess.proc;
|
|
3181
|
-
lineEmitter = activeProcess.lineEmitter;
|
|
3182
|
-
log.debug("reusing active process", { sk });
|
|
3183
3745
|
} else {
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3746
|
+
let cliArgs;
|
|
3747
|
+
let spawnSystemPromptFile;
|
|
3748
|
+
let spawnProxyServer = null;
|
|
3749
|
+
let spawnMcpHash = null;
|
|
3750
|
+
if (compactionMode) {
|
|
3751
|
+
cliArgs = buildCliArgs({
|
|
3752
|
+
sessionKey: sk,
|
|
3753
|
+
skipPermissions,
|
|
3754
|
+
includeSessionId: false,
|
|
3755
|
+
model: effectiveModelId,
|
|
3756
|
+
permissionMode: self.config.permissionMode,
|
|
3757
|
+
cliVersion
|
|
3758
|
+
});
|
|
3759
|
+
} else {
|
|
3760
|
+
const discovery = self.effectiveMcpConfig(
|
|
3761
|
+
cwd,
|
|
3762
|
+
void 0,
|
|
3763
|
+
runtimeStatus
|
|
3764
|
+
);
|
|
3765
|
+
const proxyMcpTools = await self.resolvedProxyMcpTools(
|
|
3766
|
+
discovery.allEnabledServerNames
|
|
3767
|
+
);
|
|
3768
|
+
const excludeServers = proxyMcpTools ? new Set(discovery.allEnabledServerNames) : void 0;
|
|
3769
|
+
const combinedProxyTools = resolvedProxy || proxyMcpTools ? [...resolvedProxy ?? [], ...proxyMcpTools ?? []] : null;
|
|
3770
|
+
if (!proxyServer && combinedProxyTools) {
|
|
3771
|
+
proxyServer = await self.ensureProxyServer(combinedProxyTools, sk);
|
|
3772
|
+
}
|
|
3773
|
+
const proxyDisallowed = resolvedProxy ? disallowedToolFlags(resolvedProxy) : [];
|
|
3774
|
+
const extraDisallowed = [];
|
|
3775
|
+
if (self.config.webSearch === "disabled") extraDisallowed.push("WebSearch");
|
|
3776
|
+
const allDisallowed = [...proxyDisallowed, ...extraDisallowed];
|
|
3777
|
+
const mcp = self.effectiveMcpConfig(
|
|
3778
|
+
cwd,
|
|
3779
|
+
proxyServer?.configPath(),
|
|
3780
|
+
runtimeStatus,
|
|
3781
|
+
excludeServers
|
|
3782
|
+
);
|
|
3783
|
+
const systemPromptFile = activeProcess ? void 0 : buildAppendedSystemPrompt(
|
|
3784
|
+
cwd,
|
|
3785
|
+
self.config.multiStepContinuation !== false,
|
|
3786
|
+
extractSystemMessages(options.prompt)
|
|
3787
|
+
);
|
|
3788
|
+
cliArgs = buildCliArgs({
|
|
3789
|
+
sessionKey: sk,
|
|
3790
|
+
skipPermissions,
|
|
3791
|
+
model: self.modelId,
|
|
3792
|
+
permissionMode: self.config.permissionMode,
|
|
3793
|
+
mcpConfig: mcp.paths,
|
|
3794
|
+
strictMcpConfig: self.config.strictMcpConfig,
|
|
3795
|
+
disallowedTools: allDisallowed.length > 0 ? allDisallowed : void 0,
|
|
3796
|
+
appendSystemPromptFile: systemPromptFile,
|
|
3797
|
+
...self.thinkingCliOptions(),
|
|
3798
|
+
cliVersion
|
|
3799
|
+
});
|
|
3800
|
+
spawnSystemPromptFile = systemPromptFile;
|
|
3801
|
+
spawnProxyServer = proxyServer;
|
|
3802
|
+
spawnMcpHash = mcp.bridgedHash;
|
|
3803
|
+
}
|
|
3804
|
+
if (activeProcess && !compactionMode) {
|
|
3805
|
+
proc = activeProcess.proc;
|
|
3806
|
+
lineEmitter = activeProcess.lineEmitter;
|
|
3807
|
+
log.debug("reusing active process", { sk });
|
|
3808
|
+
} else {
|
|
3809
|
+
const ap = spawnClaudeProcess(
|
|
3810
|
+
cliPath,
|
|
3811
|
+
cliArgs,
|
|
3812
|
+
cwd,
|
|
3813
|
+
sk,
|
|
3814
|
+
spawnProxyServer,
|
|
3815
|
+
spawnMcpHash,
|
|
3816
|
+
spawnSystemPromptFile,
|
|
3817
|
+
self.config.ignoreAnthropicApiKey
|
|
3818
|
+
);
|
|
3819
|
+
proc = ap.proc;
|
|
3820
|
+
lineEmitter = ap.lineEmitter;
|
|
3821
|
+
activeProcess = ap;
|
|
3822
|
+
}
|
|
3196
3823
|
}
|
|
3197
3824
|
controller.enqueue({ type: "stream-start", warnings });
|
|
3198
3825
|
let currentTextId = null;
|
|
@@ -3651,11 +4278,6 @@ ${plan}
|
|
|
3651
4278
|
if (block.type === "tool_use" && block.id && block.name) {
|
|
3652
4279
|
noteToolActivity();
|
|
3653
4280
|
const parsedInput = block.input ?? {};
|
|
3654
|
-
toolCallsById.set(block.id, {
|
|
3655
|
-
id: block.id,
|
|
3656
|
-
name: block.name,
|
|
3657
|
-
input: parsedInput
|
|
3658
|
-
});
|
|
3659
4281
|
if (isAskUserQuestionTool(block.name)) {
|
|
3660
4282
|
const askId = startTextBlock();
|
|
3661
4283
|
controller.enqueue({
|
|
@@ -3708,6 +4330,11 @@ ${plan}
|
|
|
3708
4330
|
toolUseId: block.id
|
|
3709
4331
|
});
|
|
3710
4332
|
if (!skip) {
|
|
4333
|
+
toolCallsById.set(block.id, {
|
|
4334
|
+
id: block.id,
|
|
4335
|
+
name: block.name,
|
|
4336
|
+
input: parsedInput
|
|
4337
|
+
});
|
|
3711
4338
|
if (!executed) skipResultForIds.add(block.id);
|
|
3712
4339
|
controller.enqueue({
|
|
3713
4340
|
type: "tool-input-start",
|
|
@@ -3999,6 +4626,8 @@ ${plan}
|
|
|
3999
4626
|
};
|
|
4000
4627
|
const procErrorHandler = (err) => {
|
|
4001
4628
|
log.error("process error", { error: err.message });
|
|
4629
|
+
deleteActiveProcess(sk);
|
|
4630
|
+
deleteClaudeSessionId(sk);
|
|
4002
4631
|
if (controllerClosed) return;
|
|
4003
4632
|
if (drainBuffer.length > 0 || getPendingProxyCalls(sk).length > 0) {
|
|
4004
4633
|
rejectAllPendingProxyCallsForSession(
|
|
@@ -4322,7 +4951,7 @@ var defaultModels = {
|
|
|
4322
4951
|
|
|
4323
4952
|
// src/accounts.ts
|
|
4324
4953
|
import { chmod, lstat, mkdir, readlink, symlink, writeFile } from "fs/promises";
|
|
4325
|
-
import
|
|
4954
|
+
import path5 from "path";
|
|
4326
4955
|
var BASE_PROVIDER_ID = "claude-code";
|
|
4327
4956
|
var DEFAULT_ACCOUNT = "default";
|
|
4328
4957
|
var SHARED_CAPABILITY_ITEMS = [
|
|
@@ -4360,7 +4989,7 @@ function expandHome(value) {
|
|
|
4360
4989
|
const home = process.env.HOME ?? process.env.USERPROFILE;
|
|
4361
4990
|
if (value === "~") return home ?? value;
|
|
4362
4991
|
if (value.startsWith("~/") || value.startsWith("~\\")) {
|
|
4363
|
-
return home ?
|
|
4992
|
+
return home ? path5.join(home, value.slice(2)) : value;
|
|
4364
4993
|
}
|
|
4365
4994
|
return value;
|
|
4366
4995
|
}
|
|
@@ -4383,7 +5012,7 @@ async function ensureAccountRuntime(account, baseCliPath) {
|
|
|
4383
5012
|
baseCliPath,
|
|
4384
5013
|
expandedConfigDir
|
|
4385
5014
|
);
|
|
4386
|
-
return { cliPath, configDir };
|
|
5015
|
+
return { cliPath, configDir: expandedConfigDir };
|
|
4387
5016
|
}
|
|
4388
5017
|
async function ensureSharedCapabilities(targetRoot) {
|
|
4389
5018
|
const sourceRoot = expandHome("~/.claude");
|
|
@@ -4392,8 +5021,8 @@ async function ensureSharedCapabilities(targetRoot) {
|
|
|
4392
5021
|
}
|
|
4393
5022
|
}
|
|
4394
5023
|
async function ensureSharedCapabilityItem(sourceRoot, targetRoot, item) {
|
|
4395
|
-
const source =
|
|
4396
|
-
const target =
|
|
5024
|
+
const source = path5.join(sourceRoot, item);
|
|
5025
|
+
const target = path5.join(targetRoot, item);
|
|
4397
5026
|
let sourceStat;
|
|
4398
5027
|
try {
|
|
4399
5028
|
sourceStat = await lstat(source);
|
|
@@ -4404,8 +5033,8 @@ async function ensureSharedCapabilityItem(sourceRoot, targetRoot, item) {
|
|
|
4404
5033
|
const targetStat = await lstat(target);
|
|
4405
5034
|
if (targetStat.isSymbolicLink()) {
|
|
4406
5035
|
const current = await readlink(target);
|
|
4407
|
-
const resolvedCurrent =
|
|
4408
|
-
const resolvedSource =
|
|
5036
|
+
const resolvedCurrent = path5.resolve(path5.dirname(target), current);
|
|
5037
|
+
const resolvedSource = path5.resolve(source);
|
|
4409
5038
|
if (resolvedCurrent === resolvedSource) return;
|
|
4410
5039
|
}
|
|
4411
5040
|
log.warn("shared Claude capability already exists; leaving untouched", {
|
|
@@ -4420,11 +5049,11 @@ async function ensureSharedCapabilityItem(sourceRoot, targetRoot, item) {
|
|
|
4420
5049
|
await symlink(source, target, type);
|
|
4421
5050
|
}
|
|
4422
5051
|
async function writeAccountWrapper(account, baseCliPath, configDir) {
|
|
4423
|
-
const cacheRoot =
|
|
5052
|
+
const cacheRoot = path5.join(
|
|
4424
5053
|
process.env.XDG_CACHE_HOME ?? expandHome("~/.cache"),
|
|
4425
5054
|
"opencode-claude-code-plugin"
|
|
4426
5055
|
);
|
|
4427
|
-
const wrapperPath =
|
|
5056
|
+
const wrapperPath = path5.join(cacheRoot, `claude-${account}`);
|
|
4428
5057
|
const suffix = `@${account}`;
|
|
4429
5058
|
await mkdir(cacheRoot, { recursive: true });
|
|
4430
5059
|
const script = `#!/usr/bin/env bash
|
|
@@ -4464,14 +5093,14 @@ function titleizeAccount(account) {
|
|
|
4464
5093
|
|
|
4465
5094
|
// src/cleanup-stale.ts
|
|
4466
5095
|
import {
|
|
4467
|
-
existsSync as
|
|
4468
|
-
readFileSync as
|
|
5096
|
+
existsSync as existsSync4,
|
|
5097
|
+
readFileSync as readFileSync4,
|
|
4469
5098
|
realpathSync,
|
|
4470
5099
|
rmSync as rmSync2,
|
|
4471
5100
|
writeFileSync as writeFileSync4
|
|
4472
5101
|
} from "fs";
|
|
4473
|
-
import { homedir as
|
|
4474
|
-
import { join as
|
|
5102
|
+
import { homedir as homedir5 } from "os";
|
|
5103
|
+
import { join as join7, resolve as resolve3 } from "path";
|
|
4475
5104
|
import { fileURLToPath } from "url";
|
|
4476
5105
|
var STALE_PACKAGE_NAME = "opencode-claude-code-plugin";
|
|
4477
5106
|
var SUSPECT_DESCRIPTION_TOKEN = "Claude Code";
|
|
@@ -4479,20 +5108,20 @@ var alreadyRan = false;
|
|
|
4479
5108
|
function candidateCacheRoots() {
|
|
4480
5109
|
const xdg = process.env.XDG_CACHE_HOME;
|
|
4481
5110
|
return [
|
|
4482
|
-
xdg ?
|
|
4483
|
-
|
|
4484
|
-
|
|
5111
|
+
xdg ? join7(xdg, "opencode") : null,
|
|
5112
|
+
join7(homedir5(), ".cache", "opencode"),
|
|
5113
|
+
join7(homedir5(), "Library", "Caches", "opencode")
|
|
4485
5114
|
].filter((p) => Boolean(p));
|
|
4486
5115
|
}
|
|
4487
5116
|
function userOpencodeJsonPath() {
|
|
4488
|
-
const xdgConfig = process.env.XDG_CONFIG_HOME ??
|
|
4489
|
-
return
|
|
5117
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME ?? join7(homedir5(), ".config");
|
|
5118
|
+
return join7(xdgConfig, "opencode", "opencode.json");
|
|
4490
5119
|
}
|
|
4491
5120
|
function userIntendsToUseUnscoped() {
|
|
4492
5121
|
const cfg = userOpencodeJsonPath();
|
|
4493
|
-
if (!
|
|
5122
|
+
if (!existsSync4(cfg)) return false;
|
|
4494
5123
|
try {
|
|
4495
|
-
const json = JSON.parse(
|
|
5124
|
+
const json = JSON.parse(readFileSync4(cfg, "utf8"));
|
|
4496
5125
|
const plugins = json.plugin;
|
|
4497
5126
|
if (!Array.isArray(plugins)) return false;
|
|
4498
5127
|
return plugins.some(
|
|
@@ -4505,7 +5134,7 @@ function userIntendsToUseUnscoped() {
|
|
|
4505
5134
|
function ourLoadedDir() {
|
|
4506
5135
|
try {
|
|
4507
5136
|
const filePath = fileURLToPath(import.meta.url);
|
|
4508
|
-
return realpathSync(
|
|
5137
|
+
return realpathSync(resolve3(filePath, "..", ".."));
|
|
4509
5138
|
} catch {
|
|
4510
5139
|
return null;
|
|
4511
5140
|
}
|
|
@@ -4528,20 +5157,20 @@ function cleanupStaleUnscopedInstall() {
|
|
|
4528
5157
|
}
|
|
4529
5158
|
}
|
|
4530
5159
|
function cleanupOne(cacheRoot, ourDir) {
|
|
4531
|
-
if (!
|
|
4532
|
-
const stalePath =
|
|
4533
|
-
if (!
|
|
5160
|
+
if (!existsSync4(cacheRoot)) return;
|
|
5161
|
+
const stalePath = join7(cacheRoot, "node_modules", STALE_PACKAGE_NAME);
|
|
5162
|
+
if (!existsSync4(stalePath)) return;
|
|
4534
5163
|
let realStalePath = stalePath;
|
|
4535
5164
|
try {
|
|
4536
5165
|
realStalePath = realpathSync(stalePath);
|
|
4537
5166
|
} catch {
|
|
4538
5167
|
}
|
|
4539
5168
|
if (ourDir && realStalePath === ourDir) return;
|
|
4540
|
-
const pkgJsonPath =
|
|
4541
|
-
if (!
|
|
5169
|
+
const pkgJsonPath = join7(stalePath, "package.json");
|
|
5170
|
+
if (!existsSync4(pkgJsonPath)) return;
|
|
4542
5171
|
let pkg = {};
|
|
4543
5172
|
try {
|
|
4544
|
-
pkg = JSON.parse(
|
|
5173
|
+
pkg = JSON.parse(readFileSync4(pkgJsonPath, "utf8"));
|
|
4545
5174
|
} catch {
|
|
4546
5175
|
return;
|
|
4547
5176
|
}
|
|
@@ -4557,10 +5186,10 @@ function cleanupOne(cacheRoot, ourDir) {
|
|
|
4557
5186
|
});
|
|
4558
5187
|
return;
|
|
4559
5188
|
}
|
|
4560
|
-
const cachePkgJson =
|
|
4561
|
-
if (!
|
|
5189
|
+
const cachePkgJson = join7(cacheRoot, "package.json");
|
|
5190
|
+
if (!existsSync4(cachePkgJson)) return;
|
|
4562
5191
|
try {
|
|
4563
|
-
const cfg = JSON.parse(
|
|
5192
|
+
const cfg = JSON.parse(readFileSync4(cachePkgJson, "utf8"));
|
|
4564
5193
|
if (cfg?.dependencies?.[STALE_PACKAGE_NAME]) {
|
|
4565
5194
|
delete cfg.dependencies[STALE_PACKAGE_NAME];
|
|
4566
5195
|
writeFileSync4(cachePkgJson, JSON.stringify(cfg, null, 2) + "\n");
|
|
@@ -4581,6 +5210,21 @@ function pickOpencodeDirectory(input) {
|
|
|
4581
5210
|
if (isUsableDirectory(ctx.worktree)) return ctx.worktree;
|
|
4582
5211
|
return void 0;
|
|
4583
5212
|
}
|
|
5213
|
+
var warnedAnthropicApiKey = false;
|
|
5214
|
+
function warnIfAnthropicApiKey(ignore) {
|
|
5215
|
+
if (warnedAnthropicApiKey) return;
|
|
5216
|
+
if (!process.env.ANTHROPIC_API_KEY && !process.env.ANTHROPIC_AUTH_TOKEN) return;
|
|
5217
|
+
warnedAnthropicApiKey = true;
|
|
5218
|
+
if (ignore) {
|
|
5219
|
+
log.warn(
|
|
5220
|
+
"ANTHROPIC_API_KEY/ANTHROPIC_AUTH_TOKEN detected; stripping it from claude spawns (ignoreAnthropicApiKey) so requests use your subscription auth, not pay-as-you-go API billing."
|
|
5221
|
+
);
|
|
5222
|
+
} else {
|
|
5223
|
+
log.warn(
|
|
5224
|
+
"ANTHROPIC_API_KEY/ANTHROPIC_AUTH_TOKEN detected; claude may bill as pay-as-you-go API usage instead of your subscription / Agent SDK credit. Set provider option `ignoreAnthropicApiKey: true` to force subscription auth."
|
|
5225
|
+
);
|
|
5226
|
+
}
|
|
5227
|
+
}
|
|
4584
5228
|
function createClaudeCode(settings = {}) {
|
|
4585
5229
|
if (settings.logging) {
|
|
4586
5230
|
configureLogger({
|
|
@@ -4590,6 +5234,7 @@ function createClaudeCode(settings = {}) {
|
|
|
4590
5234
|
level: settings.logging.level ?? "info"
|
|
4591
5235
|
});
|
|
4592
5236
|
}
|
|
5237
|
+
warnIfAnthropicApiKey(settings.ignoreAnthropicApiKey);
|
|
4593
5238
|
const cliPath = settings.cliPath ?? process.env.CLAUDE_CLI_PATH ?? "claude";
|
|
4594
5239
|
const providerName = settings.providerID ?? settings.name ?? "claude-code";
|
|
4595
5240
|
const proxyTools = settings.proxyTools ?? ["Bash", "Edit", "Write", "WebFetch"];
|
|
@@ -4615,7 +5260,12 @@ function createClaudeCode(settings = {}) {
|
|
|
4615
5260
|
proxyOpencodeMcpTools: settings.proxyOpencodeMcpTools ?? true,
|
|
4616
5261
|
multiStepContinuation: settings.multiStepContinuation ?? true,
|
|
4617
5262
|
autoContinueIncompleteTurns: settings.autoContinueIncompleteTurns ?? "smart",
|
|
4618
|
-
compactionModel: settings.compactionModel
|
|
5263
|
+
compactionModel: settings.compactionModel,
|
|
5264
|
+
ignoreAnthropicApiKey: settings.ignoreAnthropicApiKey,
|
|
5265
|
+
interactive: settings.interactive,
|
|
5266
|
+
interactiveBypass: settings.interactiveBypass,
|
|
5267
|
+
interactiveAllowTools: settings.interactiveAllowTools,
|
|
5268
|
+
interactiveSystemPrompt: settings.interactiveSystemPrompt
|
|
4619
5269
|
});
|
|
4620
5270
|
};
|
|
4621
5271
|
const provider = function(modelId) {
|