@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.
@@ -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 spawn3, spawnSync as spawnSync3 } from "child_process";
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 readline3 from "readline";
982
+ import * as readline4 from "readline";
795
983
 
796
- // src/core/providers/jsonl.ts
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 = spawn2(options.command, options.args, {
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 = readline2.createInterface({ input: child.stdout, crlfDelay: Infinity });
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 = spawn3(this.executable, ["app-server"], { env: this.env, stdio: ["pipe", "pipe", "pipe"] });
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 = readline3.createInterface({ input: child.stdout });
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-2UG5CR5X.js.map
2695
+ //# sourceMappingURL=chunk-ZYHC7PXX.js.map