@justin06lee/yagami 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -0
- package/dist/{chunk-GPX47OIO.js → chunk-D2PNH6GV.js} +2 -2
- package/dist/{chunk-2UG5CR5X.js → chunk-ZYHC7PXX.js} +615 -8
- package/dist/chunk-ZYHC7PXX.js.map +1 -0
- package/dist/cli.js +2 -2
- package/dist/{hostConfig-Bzr9JB8R.d.ts → hostConfig-HmZ3z297.d.ts} +95 -2
- package/dist/index.d.ts +11 -4
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +2 -2
- package/dist/server.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-2UG5CR5X.js.map +0 -1
- /package/dist/{chunk-GPX47OIO.js.map → chunk-D2PNH6GV.js.map} +0 -0
|
@@ -108,6 +108,9 @@ function parseModelRef(model, providerIds) {
|
|
|
108
108
|
function qualifiedModel(providerId, model) {
|
|
109
109
|
return `${providerId}:${model}`;
|
|
110
110
|
}
|
|
111
|
+
function isSessionProvider(p) {
|
|
112
|
+
return typeof p.openSession === "function";
|
|
113
|
+
}
|
|
111
114
|
|
|
112
115
|
// src/core/executable.ts
|
|
113
116
|
import * as fs from "fs";
|
|
@@ -439,6 +442,22 @@ var AcpProvider = class {
|
|
|
439
442
|
conn.close();
|
|
440
443
|
}
|
|
441
444
|
}
|
|
445
|
+
/**
|
|
446
|
+
* A live, interactive ACP session: the agent runs warm in the project
|
|
447
|
+
* directory with ITS OWN defaults — no plan-mode hardening, no forced
|
|
448
|
+
* config. Tool calls stream as normalized events and permission requests
|
|
449
|
+
* go to the host's handler, exactly as the agent's own client would ask.
|
|
450
|
+
*/
|
|
451
|
+
openSession(options) {
|
|
452
|
+
return new AcpAgentSession({
|
|
453
|
+
id: this.id,
|
|
454
|
+
modelConfigId: this.modelConfigId,
|
|
455
|
+
connect: (cwd) => this.connectImpl(cwd),
|
|
456
|
+
classify: (err, ctx) => this.classify(err, ctx),
|
|
457
|
+
selectModel: (conn, sessionId, configOptions, model) => this.selectModel(conn, sessionId, configOptions, model),
|
|
458
|
+
options
|
|
459
|
+
});
|
|
460
|
+
}
|
|
442
461
|
async selectModel(conn, sessionId, configOptions, model) {
|
|
443
462
|
const option = configOptions?.find((o) => o.id === this.modelConfigId) ?? configOptions?.find((o) => o.category === "model");
|
|
444
463
|
if (!option || option.type !== "select") {
|
|
@@ -495,6 +514,175 @@ var AcpProvider = class {
|
|
|
495
514
|
return `${plain ?? "unknown version"} \u26A0 no ACP handshake (${handshakeError ?? "unknown error"}) \u2014 too old, or a different program with the same name?`;
|
|
496
515
|
}
|
|
497
516
|
};
|
|
517
|
+
var AcpAgentSession = class {
|
|
518
|
+
constructor(cfg) {
|
|
519
|
+
this.cfg = cfg;
|
|
520
|
+
this.provider = cfg.id;
|
|
521
|
+
}
|
|
522
|
+
cfg;
|
|
523
|
+
provider;
|
|
524
|
+
conn;
|
|
525
|
+
sessionId;
|
|
526
|
+
queue = null;
|
|
527
|
+
opening;
|
|
528
|
+
closed = false;
|
|
529
|
+
costUsd;
|
|
530
|
+
/** Updates often omit title/kind — remember them from the start event. */
|
|
531
|
+
toolMeta = /* @__PURE__ */ new Map();
|
|
532
|
+
get id() {
|
|
533
|
+
return this.sessionId;
|
|
534
|
+
}
|
|
535
|
+
ensureOpen() {
|
|
536
|
+
this.opening ??= this.open();
|
|
537
|
+
return this.opening;
|
|
538
|
+
}
|
|
539
|
+
async open() {
|
|
540
|
+
const { options } = this.cfg;
|
|
541
|
+
const conn = await this.cfg.connect(options.cwd);
|
|
542
|
+
this.conn = conn;
|
|
543
|
+
let configOptions;
|
|
544
|
+
if (options.resume && supportsResume(conn.init)) {
|
|
545
|
+
const resumed = await conn.agent.resumeSession({ sessionId: options.resume, cwd: options.cwd }).catch((err) => {
|
|
546
|
+
throw this.cfg.classify(err);
|
|
547
|
+
});
|
|
548
|
+
this.sessionId = options.resume;
|
|
549
|
+
configOptions = resumed.configOptions;
|
|
550
|
+
} else {
|
|
551
|
+
const created = await conn.agent.newSession({ cwd: options.cwd, mcpServers: [] }).catch((err) => {
|
|
552
|
+
throw this.cfg.classify(err);
|
|
553
|
+
});
|
|
554
|
+
this.sessionId = created.sessionId;
|
|
555
|
+
configOptions = created.configOptions;
|
|
556
|
+
}
|
|
557
|
+
const sessionId = this.sessionId;
|
|
558
|
+
const mode = options.native?.mode;
|
|
559
|
+
if (mode) await conn.agent.setSessionMode({ sessionId, modeId: mode }).catch(() => {
|
|
560
|
+
});
|
|
561
|
+
if (options.model) await this.cfg.selectModel(conn, sessionId, configOptions, options.model);
|
|
562
|
+
conn.setHandlers({
|
|
563
|
+
onPermission: (p) => this.onPermission(p),
|
|
564
|
+
onUpdate: (n) => this.onUpdate(n)
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
async onPermission(p) {
|
|
568
|
+
const toolCall = p.toolCall;
|
|
569
|
+
const request = {
|
|
570
|
+
provider: this.provider,
|
|
571
|
+
...this.sessionId ? { sessionId: this.sessionId } : {},
|
|
572
|
+
tool: toolCall?.title ?? "tool",
|
|
573
|
+
...toolCall?.kind ? { kind: toolCall.kind } : {},
|
|
574
|
+
...toolCall?.title ? { title: toolCall.title } : {},
|
|
575
|
+
input: toolCall?.rawInput,
|
|
576
|
+
raw: p
|
|
577
|
+
};
|
|
578
|
+
let decision = "deny";
|
|
579
|
+
try {
|
|
580
|
+
decision = await this.cfg.options.permissions.decide(request);
|
|
581
|
+
} catch {
|
|
582
|
+
}
|
|
583
|
+
this.queue?.push({ type: "permission", request, decision });
|
|
584
|
+
const preferred = {
|
|
585
|
+
allow: ["allow_once", "allow_always"],
|
|
586
|
+
allow_always: ["allow_always", "allow_once"],
|
|
587
|
+
deny: ["reject_once", "reject_always"],
|
|
588
|
+
deny_always: ["reject_always", "reject_once"]
|
|
589
|
+
};
|
|
590
|
+
for (const kind of preferred[decision]) {
|
|
591
|
+
const option = p.options.find((o) => o.kind === kind);
|
|
592
|
+
if (option) return { outcome: { outcome: "selected", optionId: option.optionId } };
|
|
593
|
+
}
|
|
594
|
+
return rejectOption(p);
|
|
595
|
+
}
|
|
596
|
+
onUpdate(n) {
|
|
597
|
+
if (n.sessionId !== this.sessionId) return;
|
|
598
|
+
const u = n.update;
|
|
599
|
+
switch (u.sessionUpdate) {
|
|
600
|
+
case "agent_message_chunk":
|
|
601
|
+
if (u.content.type === "text") this.queue?.push({ type: "text", text: u.content.text });
|
|
602
|
+
break;
|
|
603
|
+
case "agent_thought_chunk":
|
|
604
|
+
if (u.content.type === "text") this.queue?.push({ type: "thinking", text: u.content.text });
|
|
605
|
+
break;
|
|
606
|
+
case "tool_call": {
|
|
607
|
+
const t = u;
|
|
608
|
+
const meta = { name: t.kind ?? "tool", ...t.title ? { title: t.title } : {}, ...t.kind ? { kind: t.kind } : {} };
|
|
609
|
+
this.toolMeta.set(t.toolCallId, meta);
|
|
610
|
+
this.queue?.push({
|
|
611
|
+
type: "tool_call",
|
|
612
|
+
id: t.toolCallId,
|
|
613
|
+
status: "started",
|
|
614
|
+
...meta,
|
|
615
|
+
...t.rawInput !== void 0 ? { input: t.rawInput } : {}
|
|
616
|
+
});
|
|
617
|
+
break;
|
|
618
|
+
}
|
|
619
|
+
case "tool_call_update": {
|
|
620
|
+
const t = u;
|
|
621
|
+
const known = this.toolMeta.get(t.toolCallId);
|
|
622
|
+
const meta = {
|
|
623
|
+
name: t.kind ?? known?.name ?? "tool",
|
|
624
|
+
...t.title ?? known?.title ? { title: t.title ?? known?.title } : {},
|
|
625
|
+
...t.kind ?? known?.kind ? { kind: t.kind ?? known?.kind } : {}
|
|
626
|
+
};
|
|
627
|
+
this.queue?.push({
|
|
628
|
+
type: "tool_call",
|
|
629
|
+
id: t.toolCallId,
|
|
630
|
+
status: t.status === "completed" ? "completed" : t.status === "failed" ? "failed" : "updated",
|
|
631
|
+
...meta,
|
|
632
|
+
...t.rawOutput !== void 0 ? { output: t.rawOutput } : {}
|
|
633
|
+
});
|
|
634
|
+
break;
|
|
635
|
+
}
|
|
636
|
+
case "usage_update": {
|
|
637
|
+
const cost = u.cost;
|
|
638
|
+
if (cost && typeof cost.amount === "number" && (cost.currency ?? "USD") === "USD") this.costUsd = cost.amount;
|
|
639
|
+
break;
|
|
640
|
+
}
|
|
641
|
+
default:
|
|
642
|
+
this.queue?.push({ type: "raw", provider: this.provider, payload: u });
|
|
643
|
+
break;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
async *send(input) {
|
|
647
|
+
if (this.closed) throw new ProviderError(this.provider, "session is closed");
|
|
648
|
+
if (this.queue) throw new ProviderError(this.provider, "a turn is already running");
|
|
649
|
+
await this.ensureOpen();
|
|
650
|
+
const sessionId = this.sessionId;
|
|
651
|
+
const text = typeof input === "string" ? input : input.filter((b) => b.type === "text").map((b) => b.text ?? "").join("\n");
|
|
652
|
+
const media = typeof input === "string" ? [] : input.filter((b) => b.type === "image");
|
|
653
|
+
const queue = new AsyncQueue();
|
|
654
|
+
this.queue = queue;
|
|
655
|
+
this.costUsd = void 0;
|
|
656
|
+
this.conn.agent.prompt({ sessionId, prompt: toAcpBlocks(text, media, this.provider) }).then((res) => {
|
|
657
|
+
queue.push({
|
|
658
|
+
type: "done",
|
|
659
|
+
usage: mapAcpUsage(res.usage ?? void 0),
|
|
660
|
+
...this.costUsd !== void 0 ? { costUsd: this.costUsd } : {},
|
|
661
|
+
stopReason: mapStopReason(res.stopReason)
|
|
662
|
+
});
|
|
663
|
+
queue.end();
|
|
664
|
+
}).catch((err) => queue.fail(this.cfg.classify(err)));
|
|
665
|
+
try {
|
|
666
|
+
yield { type: "session", sessionId };
|
|
667
|
+
for await (const event of queue) yield event;
|
|
668
|
+
} finally {
|
|
669
|
+
if (this.queue === queue) this.queue = null;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
async interrupt() {
|
|
673
|
+
if (!this.sessionId || !this.conn) return;
|
|
674
|
+
await this.conn.agent.cancel({ sessionId: this.sessionId }).catch(() => {
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
async close() {
|
|
678
|
+
if (this.closed) return;
|
|
679
|
+
this.closed = true;
|
|
680
|
+
this.queue?.fail(new ProviderError(this.provider, "session closed"));
|
|
681
|
+
this.queue = null;
|
|
682
|
+
this.conn?.close();
|
|
683
|
+
this.conn = void 0;
|
|
684
|
+
}
|
|
685
|
+
};
|
|
498
686
|
function supportsResume(init) {
|
|
499
687
|
const caps = init.agentCapabilities;
|
|
500
688
|
return caps?.sessionCapabilities?.resume !== void 0;
|
|
@@ -787,15 +975,419 @@ function mediaPrompt(text, media) {
|
|
|
787
975
|
}
|
|
788
976
|
|
|
789
977
|
// src/core/providers/codex.ts
|
|
790
|
-
import { spawn as
|
|
978
|
+
import { spawn as spawn4, spawnSync as spawnSync3 } from "child_process";
|
|
791
979
|
import * as fs4 from "fs";
|
|
792
980
|
import * as os4 from "os";
|
|
793
981
|
import * as path4 from "path";
|
|
794
|
-
import * as
|
|
982
|
+
import * as readline4 from "readline";
|
|
795
983
|
|
|
796
|
-
// src/core/providers/
|
|
984
|
+
// src/core/providers/codexSession.ts
|
|
797
985
|
import { spawn as spawn2 } from "child_process";
|
|
798
986
|
import * as readline2 from "readline";
|
|
987
|
+
var CodexAgentSession = class {
|
|
988
|
+
constructor(config) {
|
|
989
|
+
this.config = config;
|
|
990
|
+
}
|
|
991
|
+
config;
|
|
992
|
+
provider = "codex";
|
|
993
|
+
child;
|
|
994
|
+
nextId = 1;
|
|
995
|
+
pending = /* @__PURE__ */ new Map();
|
|
996
|
+
threadId;
|
|
997
|
+
currentTurnId;
|
|
998
|
+
queue = null;
|
|
999
|
+
lastUsage;
|
|
1000
|
+
opening;
|
|
1001
|
+
closed = false;
|
|
1002
|
+
/** Item text already emitted as deltas, so item/completed only fills gaps. */
|
|
1003
|
+
emitted = /* @__PURE__ */ new Map();
|
|
1004
|
+
get id() {
|
|
1005
|
+
return this.threadId;
|
|
1006
|
+
}
|
|
1007
|
+
fail(err) {
|
|
1008
|
+
for (const [, p] of this.pending) p.reject(err);
|
|
1009
|
+
this.pending.clear();
|
|
1010
|
+
this.queue?.fail(err);
|
|
1011
|
+
this.queue = null;
|
|
1012
|
+
}
|
|
1013
|
+
classify(err) {
|
|
1014
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1015
|
+
if (looksLikeAuthFailure(message)) {
|
|
1016
|
+
return new AuthRequiredError("codex", this.config.loginCommand, message.slice(0, 200));
|
|
1017
|
+
}
|
|
1018
|
+
return classifyProviderFailure("codex", this.config.loginCommand, err);
|
|
1019
|
+
}
|
|
1020
|
+
request(method, params) {
|
|
1021
|
+
const child = this.child;
|
|
1022
|
+
if (!child?.stdin?.writable) return Promise.reject(new ProviderError("codex", "app-server is not running"));
|
|
1023
|
+
const id = this.nextId++;
|
|
1024
|
+
const promise = new Promise((resolve, reject) => {
|
|
1025
|
+
this.pending.set(id, { resolve, reject });
|
|
1026
|
+
});
|
|
1027
|
+
child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, method, params })}
|
|
1028
|
+
`);
|
|
1029
|
+
return promise;
|
|
1030
|
+
}
|
|
1031
|
+
respond(id, result) {
|
|
1032
|
+
this.child?.stdin?.write(`${JSON.stringify({ jsonrpc: "2.0", id, result })}
|
|
1033
|
+
`);
|
|
1034
|
+
}
|
|
1035
|
+
notify(method, params) {
|
|
1036
|
+
this.child?.stdin?.write(`${JSON.stringify({ jsonrpc: "2.0", method, params })}
|
|
1037
|
+
`);
|
|
1038
|
+
}
|
|
1039
|
+
ensureOpen() {
|
|
1040
|
+
this.opening ??= this.open();
|
|
1041
|
+
return this.opening;
|
|
1042
|
+
}
|
|
1043
|
+
async open() {
|
|
1044
|
+
const { executable, env, options } = this.config;
|
|
1045
|
+
const child = spawn2(executable, ["app-server"], {
|
|
1046
|
+
cwd: options.cwd,
|
|
1047
|
+
env,
|
|
1048
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1049
|
+
});
|
|
1050
|
+
this.child = child;
|
|
1051
|
+
let stderr = "";
|
|
1052
|
+
child.stderr?.on("data", (d) => {
|
|
1053
|
+
stderr += d.toString();
|
|
1054
|
+
if (stderr.length > 16e3) stderr = stderr.slice(-8e3);
|
|
1055
|
+
});
|
|
1056
|
+
child.on("error", (err) => this.fail(this.classify(err)));
|
|
1057
|
+
child.on("exit", (code) => {
|
|
1058
|
+
if (this.closed) return;
|
|
1059
|
+
this.fail(this.classify(new Error(`codex app-server exited with code ${code}${stderr ? `: ${stderr.trim().slice(-400)}` : ""}`)));
|
|
1060
|
+
});
|
|
1061
|
+
const rl = readline2.createInterface({ input: child.stdout });
|
|
1062
|
+
rl.on("line", (line) => {
|
|
1063
|
+
let msg;
|
|
1064
|
+
try {
|
|
1065
|
+
msg = JSON.parse(line);
|
|
1066
|
+
} catch {
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
this.dispatch(msg);
|
|
1070
|
+
});
|
|
1071
|
+
await this.request("initialize", {
|
|
1072
|
+
clientInfo: { name: this.config.options.appName ?? "yagami", title: this.config.options.appName ?? "yagami", version: VERSION },
|
|
1073
|
+
capabilities: { experimentalApi: true, requestAttestation: false }
|
|
1074
|
+
});
|
|
1075
|
+
this.notify("initialized", {});
|
|
1076
|
+
const native = options.native ?? {};
|
|
1077
|
+
const overrides = {
|
|
1078
|
+
cwd: options.cwd,
|
|
1079
|
+
...options.model ? { model: options.model } : {},
|
|
1080
|
+
...native.approvalPolicy ? { approvalPolicy: native.approvalPolicy } : {},
|
|
1081
|
+
...native.sandbox ? { sandbox: native.sandbox } : {},
|
|
1082
|
+
...native.config ? { config: native.config } : {},
|
|
1083
|
+
...options.systemPrompt ? { developerInstructions: options.systemPrompt } : {}
|
|
1084
|
+
};
|
|
1085
|
+
if (options.resume) {
|
|
1086
|
+
try {
|
|
1087
|
+
const resumed = await this.request("thread/resume", { threadId: options.resume, ...overrides });
|
|
1088
|
+
this.threadId = resumed["thread"]?.id ?? options.resume;
|
|
1089
|
+
return;
|
|
1090
|
+
} catch {
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
const started = await this.request("thread/start", overrides);
|
|
1094
|
+
this.threadId = started["thread"]?.id;
|
|
1095
|
+
if (!this.threadId) throw new ProviderError("codex", "thread/start returned no thread id");
|
|
1096
|
+
}
|
|
1097
|
+
// ── inbound traffic ────────────────────────────────────────────────
|
|
1098
|
+
dispatch(msg) {
|
|
1099
|
+
if (msg.method === void 0 && msg.id !== void 0) {
|
|
1100
|
+
const pending = this.pending.get(msg.id);
|
|
1101
|
+
if (!pending) return;
|
|
1102
|
+
this.pending.delete(msg.id);
|
|
1103
|
+
if (msg.error) pending.reject(this.classify(new Error(msg.error.message ?? "codex error")));
|
|
1104
|
+
else pending.resolve(msg.result ?? {});
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
if (!msg.method) return;
|
|
1108
|
+
if (msg.id !== void 0) {
|
|
1109
|
+
void this.handleServerRequest(msg.method, msg.id, msg.params ?? {});
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
this.handleNotification(msg.method, msg.params ?? {});
|
|
1113
|
+
}
|
|
1114
|
+
push(event) {
|
|
1115
|
+
this.queue?.push(event);
|
|
1116
|
+
}
|
|
1117
|
+
handleNotification(method, params) {
|
|
1118
|
+
if (params["threadId"] !== void 0 && params["threadId"] !== this.threadId) return;
|
|
1119
|
+
switch (method) {
|
|
1120
|
+
case "item/agentMessage/delta": {
|
|
1121
|
+
const itemId = params["itemId"];
|
|
1122
|
+
const delta = params["delta"];
|
|
1123
|
+
this.emitted.set(itemId, (this.emitted.get(itemId) ?? 0) + delta.length);
|
|
1124
|
+
this.push({ type: "text", text: delta });
|
|
1125
|
+
break;
|
|
1126
|
+
}
|
|
1127
|
+
case "item/started":
|
|
1128
|
+
case "item/completed": {
|
|
1129
|
+
this.handleItem(params["item"], method === "item/completed");
|
|
1130
|
+
break;
|
|
1131
|
+
}
|
|
1132
|
+
case "thread/tokenUsage/updated": {
|
|
1133
|
+
const usage = params["tokenUsage"];
|
|
1134
|
+
const last = usage?.last;
|
|
1135
|
+
if (last) {
|
|
1136
|
+
this.lastUsage = {
|
|
1137
|
+
input_tokens: last["inputTokens"] ?? 0,
|
|
1138
|
+
output_tokens: last["outputTokens"] ?? 0,
|
|
1139
|
+
cache_read_input_tokens: last["cachedInputTokens"] ?? 0,
|
|
1140
|
+
cache_creation_input_tokens: last["cacheWriteInputTokens"] ?? 0
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
break;
|
|
1144
|
+
}
|
|
1145
|
+
case "turn/completed": {
|
|
1146
|
+
const turn = params["turn"];
|
|
1147
|
+
const queue = this.queue;
|
|
1148
|
+
this.queue = null;
|
|
1149
|
+
this.currentTurnId = void 0;
|
|
1150
|
+
if (!queue) break;
|
|
1151
|
+
if (turn.status === "failed") {
|
|
1152
|
+
queue.fail(this.classify(new Error(turn.error?.message ?? "turn failed")));
|
|
1153
|
+
} else {
|
|
1154
|
+
queue.push({
|
|
1155
|
+
type: "done",
|
|
1156
|
+
...this.lastUsage ? { usage: this.lastUsage } : {},
|
|
1157
|
+
stopReason: turn.status === "interrupted" ? "interrupted" : "end_turn"
|
|
1158
|
+
});
|
|
1159
|
+
queue.end();
|
|
1160
|
+
}
|
|
1161
|
+
break;
|
|
1162
|
+
}
|
|
1163
|
+
case "error": {
|
|
1164
|
+
this.push({ type: "raw", provider: "codex", payload: { method, params } });
|
|
1165
|
+
break;
|
|
1166
|
+
}
|
|
1167
|
+
default:
|
|
1168
|
+
break;
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
/** Normalize thread items into tool_call / thinking events. */
|
|
1172
|
+
handleItem(item, completed) {
|
|
1173
|
+
if (!item) return;
|
|
1174
|
+
const id = String(item["id"] ?? "");
|
|
1175
|
+
switch (item["type"]) {
|
|
1176
|
+
case "agentMessage": {
|
|
1177
|
+
if (!completed) break;
|
|
1178
|
+
const text = String(item["text"] ?? "");
|
|
1179
|
+
const seen = this.emitted.get(id) ?? 0;
|
|
1180
|
+
if (text.length > seen) this.push({ type: "text", text: text.slice(seen) });
|
|
1181
|
+
this.emitted.delete(id);
|
|
1182
|
+
break;
|
|
1183
|
+
}
|
|
1184
|
+
case "reasoning": {
|
|
1185
|
+
if (!completed) break;
|
|
1186
|
+
const summary = item["summary"]?.join("\n") ?? "";
|
|
1187
|
+
if (summary) this.push({ type: "thinking", text: summary });
|
|
1188
|
+
break;
|
|
1189
|
+
}
|
|
1190
|
+
case "commandExecution": {
|
|
1191
|
+
const failed = item["status"] === "failed" || item["status"] === "declined";
|
|
1192
|
+
this.push({
|
|
1193
|
+
type: "tool_call",
|
|
1194
|
+
id,
|
|
1195
|
+
name: "shell",
|
|
1196
|
+
status: completed ? failed ? "failed" : "completed" : "started",
|
|
1197
|
+
title: String(item["command"] ?? "command"),
|
|
1198
|
+
kind: "execute",
|
|
1199
|
+
input: { command: item["command"], cwd: item["cwd"] },
|
|
1200
|
+
...completed ? { output: { output: item["aggregatedOutput"], exitCode: item["exitCode"] } } : {}
|
|
1201
|
+
});
|
|
1202
|
+
break;
|
|
1203
|
+
}
|
|
1204
|
+
case "fileChange": {
|
|
1205
|
+
const changes = item["changes"] ?? [];
|
|
1206
|
+
const failed = item["status"] === "failed" || item["status"] === "declined";
|
|
1207
|
+
this.push({
|
|
1208
|
+
type: "tool_call",
|
|
1209
|
+
id,
|
|
1210
|
+
name: "apply_patch",
|
|
1211
|
+
status: completed ? failed ? "failed" : "completed" : "started",
|
|
1212
|
+
title: changes.map((c) => c.path).filter(Boolean).join(", ") || "file changes",
|
|
1213
|
+
kind: "edit",
|
|
1214
|
+
input: { changes }
|
|
1215
|
+
});
|
|
1216
|
+
break;
|
|
1217
|
+
}
|
|
1218
|
+
case "mcpToolCall": {
|
|
1219
|
+
this.push({
|
|
1220
|
+
type: "tool_call",
|
|
1221
|
+
id,
|
|
1222
|
+
name: `${String(item["server"] ?? "mcp")}.${String(item["tool"] ?? "tool")}`,
|
|
1223
|
+
status: completed ? item["status"] === "failed" ? "failed" : "completed" : "started",
|
|
1224
|
+
kind: "other",
|
|
1225
|
+
input: item["arguments"]
|
|
1226
|
+
});
|
|
1227
|
+
break;
|
|
1228
|
+
}
|
|
1229
|
+
case "webSearch": {
|
|
1230
|
+
this.push({
|
|
1231
|
+
type: "tool_call",
|
|
1232
|
+
id,
|
|
1233
|
+
name: "web_search",
|
|
1234
|
+
status: completed ? "completed" : "started",
|
|
1235
|
+
kind: "fetch",
|
|
1236
|
+
input: { query: item["query"] }
|
|
1237
|
+
});
|
|
1238
|
+
break;
|
|
1239
|
+
}
|
|
1240
|
+
case "userMessage":
|
|
1241
|
+
case "plan":
|
|
1242
|
+
break;
|
|
1243
|
+
default:
|
|
1244
|
+
this.push({ type: "raw", provider: "codex", payload: item });
|
|
1245
|
+
break;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
// ── approvals: forwarded to the host, answered like the TUI would ──
|
|
1249
|
+
async decide(request) {
|
|
1250
|
+
try {
|
|
1251
|
+
const decision = await this.config.options.permissions.decide(request);
|
|
1252
|
+
this.push({ type: "permission", request, decision });
|
|
1253
|
+
return decision;
|
|
1254
|
+
} catch {
|
|
1255
|
+
return "deny";
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
async handleServerRequest(method, id, params) {
|
|
1259
|
+
switch (method) {
|
|
1260
|
+
case "item/commandExecution/requestApproval": {
|
|
1261
|
+
const decision = await this.decide({
|
|
1262
|
+
provider: "codex",
|
|
1263
|
+
...this.threadId ? { sessionId: this.threadId } : {},
|
|
1264
|
+
tool: "shell",
|
|
1265
|
+
kind: "execute",
|
|
1266
|
+
title: String(params["command"] ?? "command"),
|
|
1267
|
+
input: { command: params["command"], cwd: params["cwd"], reason: params["reason"] },
|
|
1268
|
+
raw: params
|
|
1269
|
+
});
|
|
1270
|
+
this.respond(id, {
|
|
1271
|
+
decision: decision === "allow" ? "accept" : decision === "allow_always" ? "acceptForSession" : "decline"
|
|
1272
|
+
});
|
|
1273
|
+
break;
|
|
1274
|
+
}
|
|
1275
|
+
case "item/fileChange/requestApproval": {
|
|
1276
|
+
const decision = await this.decide({
|
|
1277
|
+
provider: "codex",
|
|
1278
|
+
...this.threadId ? { sessionId: this.threadId } : {},
|
|
1279
|
+
tool: "apply_patch",
|
|
1280
|
+
kind: "edit",
|
|
1281
|
+
title: String(params["reason"] ?? "apply file changes"),
|
|
1282
|
+
input: { reason: params["reason"], grantRoot: params["grantRoot"] },
|
|
1283
|
+
raw: params
|
|
1284
|
+
});
|
|
1285
|
+
this.respond(id, {
|
|
1286
|
+
decision: decision === "allow" ? "accept" : decision === "allow_always" ? "acceptForSession" : "decline"
|
|
1287
|
+
});
|
|
1288
|
+
break;
|
|
1289
|
+
}
|
|
1290
|
+
case "item/permissions/requestApproval": {
|
|
1291
|
+
const requested = params["permissions"];
|
|
1292
|
+
const decision = await this.decide({
|
|
1293
|
+
provider: "codex",
|
|
1294
|
+
...this.threadId ? { sessionId: this.threadId } : {},
|
|
1295
|
+
tool: "permissions",
|
|
1296
|
+
kind: "other",
|
|
1297
|
+
title: String(params["reason"] ?? "extra permissions"),
|
|
1298
|
+
input: requested,
|
|
1299
|
+
raw: params
|
|
1300
|
+
});
|
|
1301
|
+
const granted = decision === "allow" || decision === "allow_always";
|
|
1302
|
+
this.respond(id, {
|
|
1303
|
+
permissions: granted ? { network: requested?.["network"] ?? void 0, fileSystem: requested?.["fileSystem"] ?? void 0 } : {},
|
|
1304
|
+
scope: decision === "allow_always" ? "session" : "turn"
|
|
1305
|
+
});
|
|
1306
|
+
break;
|
|
1307
|
+
}
|
|
1308
|
+
// legacy approval shapes, still sent by some codepaths
|
|
1309
|
+
case "execCommandApproval":
|
|
1310
|
+
case "applyPatchApproval": {
|
|
1311
|
+
const decision = await this.decide({
|
|
1312
|
+
provider: "codex",
|
|
1313
|
+
...this.threadId ? { sessionId: this.threadId } : {},
|
|
1314
|
+
tool: method === "execCommandApproval" ? "shell" : "apply_patch",
|
|
1315
|
+
kind: method === "execCommandApproval" ? "execute" : "edit",
|
|
1316
|
+
title: String(params["command"] ?? params["reason"] ?? "approval"),
|
|
1317
|
+
input: params,
|
|
1318
|
+
raw: params
|
|
1319
|
+
});
|
|
1320
|
+
this.respond(id, {
|
|
1321
|
+
decision: decision === "allow" ? "approved" : decision === "allow_always" ? "approved_for_session" : { denied: { rejection: "denied by the user" } }
|
|
1322
|
+
});
|
|
1323
|
+
break;
|
|
1324
|
+
}
|
|
1325
|
+
case "item/tool/requestUserInput": {
|
|
1326
|
+
this.respond(id, { answers: {} });
|
|
1327
|
+
break;
|
|
1328
|
+
}
|
|
1329
|
+
case "mcpServer/elicitation/request": {
|
|
1330
|
+
this.respond(id, { action: "decline", content: null, _meta: null });
|
|
1331
|
+
break;
|
|
1332
|
+
}
|
|
1333
|
+
default: {
|
|
1334
|
+
this.child?.stdin?.write(
|
|
1335
|
+
`${JSON.stringify({ jsonrpc: "2.0", id, error: { code: -32601, message: `unsupported request: ${method}` } })}
|
|
1336
|
+
`
|
|
1337
|
+
);
|
|
1338
|
+
break;
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
// ── the ProviderSession surface ────────────────────────────────────
|
|
1343
|
+
async *send(input) {
|
|
1344
|
+
if (this.closed) throw new ProviderError("codex", "session is closed");
|
|
1345
|
+
if (this.queue) throw new ProviderError("codex", "a turn is already running");
|
|
1346
|
+
await this.ensureOpen();
|
|
1347
|
+
const blocks = typeof input === "string" ? [{ type: "text", text: input }] : input;
|
|
1348
|
+
const { paths, cleanup } = writeTempImages(blocks);
|
|
1349
|
+
const items = [];
|
|
1350
|
+
for (const block of blocks) {
|
|
1351
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
1352
|
+
items.push({ type: "text", text: block.text, text_elements: [] });
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
for (const p of paths) items.push({ type: "localImage", path: p });
|
|
1356
|
+
if (items.length === 0) items.push({ type: "text", text: "", text_elements: [] });
|
|
1357
|
+
const queue = new AsyncQueue();
|
|
1358
|
+
this.queue = queue;
|
|
1359
|
+
this.lastUsage = void 0;
|
|
1360
|
+
try {
|
|
1361
|
+
const result = await this.request("turn/start", {
|
|
1362
|
+
threadId: this.threadId,
|
|
1363
|
+
input: items,
|
|
1364
|
+
...this.config.options.effort ? { effort: this.config.options.effort } : {}
|
|
1365
|
+
});
|
|
1366
|
+
this.currentTurnId = result["turn"]?.id;
|
|
1367
|
+
yield { type: "session", sessionId: this.threadId };
|
|
1368
|
+
for await (const event of queue) yield event;
|
|
1369
|
+
} finally {
|
|
1370
|
+
cleanup();
|
|
1371
|
+
if (this.queue === queue) this.queue = null;
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
async interrupt() {
|
|
1375
|
+
if (!this.threadId || !this.currentTurnId) return;
|
|
1376
|
+
await this.request("turn/interrupt", { threadId: this.threadId, turnId: this.currentTurnId }).catch(() => {
|
|
1377
|
+
});
|
|
1378
|
+
}
|
|
1379
|
+
async close() {
|
|
1380
|
+
if (this.closed) return;
|
|
1381
|
+
this.closed = true;
|
|
1382
|
+
this.fail(new ProviderError("codex", "session closed"));
|
|
1383
|
+
this.child?.kill("SIGTERM");
|
|
1384
|
+
this.child = void 0;
|
|
1385
|
+
}
|
|
1386
|
+
};
|
|
1387
|
+
|
|
1388
|
+
// src/core/providers/jsonl.ts
|
|
1389
|
+
import { spawn as spawn3 } from "child_process";
|
|
1390
|
+
import * as readline3 from "readline";
|
|
799
1391
|
var ProcessExitError = class extends Error {
|
|
800
1392
|
constructor(exitCode, stderr) {
|
|
801
1393
|
super(`process exited with code ${exitCode}${stderr ? `: ${stderr.trim().split("\n").slice(-3).join(" | ")}` : ""}`);
|
|
@@ -808,7 +1400,7 @@ var ProcessExitError = class extends Error {
|
|
|
808
1400
|
};
|
|
809
1401
|
function spawnJsonl(options) {
|
|
810
1402
|
const queue = new AsyncQueue();
|
|
811
|
-
const child =
|
|
1403
|
+
const child = spawn3(options.command, options.args, {
|
|
812
1404
|
cwd: options.cwd,
|
|
813
1405
|
env: options.env,
|
|
814
1406
|
stdio: [options.stdin !== void 0 ? "pipe" : "ignore", "pipe", "pipe"]
|
|
@@ -818,7 +1410,7 @@ function spawnJsonl(options) {
|
|
|
818
1410
|
stderr += chunk.toString();
|
|
819
1411
|
if (stderr.length > 16e3) stderr = stderr.slice(-8e3);
|
|
820
1412
|
});
|
|
821
|
-
const rl =
|
|
1413
|
+
const rl = readline3.createInterface({ input: child.stdout, crlfDelay: Infinity });
|
|
822
1414
|
rl.on("line", (line) => {
|
|
823
1415
|
const trimmed = line.trim();
|
|
824
1416
|
if (!trimmed.startsWith("{")) return;
|
|
@@ -933,10 +1525,24 @@ var CodexProvider = class {
|
|
|
933
1525
|
if (req.signal?.aborted) return;
|
|
934
1526
|
if (!done) throw new ProviderError(this.id, "codex exited without completing the turn");
|
|
935
1527
|
}
|
|
1528
|
+
/**
|
|
1529
|
+
* A live, interactive Codex session (`codex app-server` — the TUI's own
|
|
1530
|
+
* engine): streamed tool events, the user's config.toml verbatim, and
|
|
1531
|
+
* approval requests forwarded to the host's permission handler. The
|
|
1532
|
+
* completion-turn `run()` path above stays for API-style callers.
|
|
1533
|
+
*/
|
|
1534
|
+
openSession(options) {
|
|
1535
|
+
return new CodexAgentSession({
|
|
1536
|
+
executable: this.executable,
|
|
1537
|
+
env: this.env,
|
|
1538
|
+
loginCommand: this.loginCommand,
|
|
1539
|
+
options
|
|
1540
|
+
});
|
|
1541
|
+
}
|
|
936
1542
|
/** Ask the app-server protocol for the model catalog (no tokens spent). */
|
|
937
1543
|
listModels() {
|
|
938
1544
|
return new Promise((resolve, reject) => {
|
|
939
|
-
const child =
|
|
1545
|
+
const child = spawn4(this.executable, ["app-server"], { env: this.env, stdio: ["pipe", "pipe", "pipe"] });
|
|
940
1546
|
let stderr = "";
|
|
941
1547
|
let settled = false;
|
|
942
1548
|
const finish = (fn) => {
|
|
@@ -954,7 +1560,7 @@ var CodexProvider = class {
|
|
|
954
1560
|
"close",
|
|
955
1561
|
() => finish(() => reject(classifyProviderFailure(this.id, this.loginCommand, new Error(stderr.trim() || "app-server exited"))))
|
|
956
1562
|
);
|
|
957
|
-
const rl =
|
|
1563
|
+
const rl = readline4.createInterface({ input: child.stdout });
|
|
958
1564
|
rl.on("line", (line) => {
|
|
959
1565
|
let msg;
|
|
960
1566
|
try {
|
|
@@ -2062,6 +2668,7 @@ export {
|
|
|
2062
2668
|
toApiError,
|
|
2063
2669
|
parseModelRef,
|
|
2064
2670
|
qualifiedModel,
|
|
2671
|
+
isSessionProvider,
|
|
2065
2672
|
findExecutable,
|
|
2066
2673
|
resolveExecutable,
|
|
2067
2674
|
resolveClaudeExecutable,
|
|
@@ -2085,4 +2692,4 @@ export {
|
|
|
2085
2692
|
ChatChunkTranslator,
|
|
2086
2693
|
modelListBody
|
|
2087
2694
|
};
|
|
2088
|
-
//# sourceMappingURL=chunk-
|
|
2695
|
+
//# sourceMappingURL=chunk-ZYHC7PXX.js.map
|