@justin06lee/yagami 0.6.0 → 0.8.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.
@@ -183,7 +183,7 @@ function resolveClaudeExecutable(explicit) {
183
183
  }
184
184
 
185
185
  // src/version.ts
186
- var VERSION = "0.5.0";
186
+ var VERSION = "0.8.1";
187
187
 
188
188
  // src/core/providers/acp.ts
189
189
  import { spawn, spawnSync } from "child_process";
@@ -198,6 +198,96 @@ import {
198
198
  PROTOCOL_VERSION
199
199
  } from "@agentclientprotocol/sdk";
200
200
 
201
+ // src/core/interaction.ts
202
+ function record(value) {
203
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
204
+ }
205
+ function optionsOf(schema) {
206
+ const oneOf = Array.isArray(schema["oneOf"]) ? schema["oneOf"] : void 0;
207
+ if (oneOf) {
208
+ const options = oneOf.flatMap((item) => {
209
+ const value = record(item);
210
+ if (!value || typeof value["const"] !== "string") return [];
211
+ return [{
212
+ value: value["const"],
213
+ label: typeof value["title"] === "string" ? value["title"] : value["const"],
214
+ ...typeof value["description"] === "string" ? { description: value["description"] } : {}
215
+ }];
216
+ });
217
+ if (options.length > 0) return options;
218
+ }
219
+ const values = Array.isArray(schema["enum"]) ? schema["enum"].filter((value) => typeof value === "string") : [];
220
+ return values.length > 0 ? values.map((value) => ({ value, label: value })) : void 0;
221
+ }
222
+ function inputFields(schema) {
223
+ const root = record(schema);
224
+ const properties = record(root?.["properties"]);
225
+ if (!properties) return [];
226
+ const required = new Set(
227
+ Array.isArray(root?.["required"]) ? root["required"].filter((value) => typeof value === "string") : []
228
+ );
229
+ return Object.entries(properties).flatMap(([id, value]) => {
230
+ const property = record(value);
231
+ if (!property || typeof property["type"] !== "string") return [];
232
+ let type;
233
+ let options = optionsOf(property);
234
+ if (property["type"] === "array") {
235
+ type = "multiselect";
236
+ options = optionsOf(record(property["items"]) ?? {});
237
+ } else if (property["type"] === "string" && options) {
238
+ type = "select";
239
+ } else if (["string", "number", "integer", "boolean"].includes(property["type"])) {
240
+ type = property["type"];
241
+ } else {
242
+ return [];
243
+ }
244
+ const defaultValue = property["default"];
245
+ return [{
246
+ id,
247
+ label: typeof property["title"] === "string" ? property["title"] : id,
248
+ type,
249
+ required: required.has(id),
250
+ ...typeof property["description"] === "string" ? { description: property["description"] } : {},
251
+ ...options ? { options } : {},
252
+ ...typeof property["format"] === "string" ? { format: property["format"] } : {},
253
+ ...typeof property["minimum"] === "number" ? { minimum: property["minimum"] } : {},
254
+ ...typeof property["maximum"] === "number" ? { maximum: property["maximum"] } : {},
255
+ ...typeof property["minLength"] === "number" ? { minLength: property["minLength"] } : {},
256
+ ...typeof property["maxLength"] === "number" ? { maxLength: property["maxLength"] } : {},
257
+ ...typeof defaultValue === "string" || typeof defaultValue === "number" || typeof defaultValue === "boolean" || Array.isArray(defaultValue) && defaultValue.every((item) => typeof item === "string") ? { default: defaultValue } : {}
258
+ }];
259
+ });
260
+ }
261
+ function elicitationRequest(provider, sessionId, raw) {
262
+ const mode = raw["mode"];
263
+ if (mode === "url") {
264
+ return {
265
+ provider,
266
+ ...sessionId ? { sessionId } : {},
267
+ kind: "url",
268
+ message: typeof raw["message"] === "string" ? raw["message"] : "Open the requested URL",
269
+ ...typeof raw["serverName"] === "string" ? { source: raw["serverName"] } : {},
270
+ ...typeof raw["url"] === "string" ? { url: raw["url"] } : {},
271
+ raw
272
+ };
273
+ }
274
+ return {
275
+ provider,
276
+ ...sessionId ? { sessionId } : {},
277
+ kind: "form",
278
+ message: typeof raw["message"] === "string" ? raw["message"] : "Input requested",
279
+ ...typeof raw["serverName"] === "string" ? { source: raw["serverName"] } : {},
280
+ fields: inputFields(raw["requestedSchema"]),
281
+ raw
282
+ };
283
+ }
284
+ function declineInput() {
285
+ return { action: "decline" };
286
+ }
287
+ function elicitationResponse(response) {
288
+ return response.action === "accept" ? { action: "accept", content: response.values ?? null } : { action: response.action };
289
+ }
290
+
201
291
  // src/core/providers/queue.ts
202
292
  var AsyncQueue = class {
203
293
  buffer = [];
@@ -277,8 +367,10 @@ var AcpProvider = class {
277
367
  systemPrompt: false,
278
368
  thinking: false,
279
369
  effort: false,
280
- streaming: "tokens"
370
+ streaming: "tokens",
371
+ serverTools: false
281
372
  };
373
+ sessionCapabilities = { fork: false };
282
374
  args;
283
375
  env;
284
376
  workDir;
@@ -323,6 +415,9 @@ var AcpProvider = class {
323
415
  const agent = new ClientSideConnection(
324
416
  () => ({
325
417
  requestPermission: (p) => handlers.onPermission ? handlers.onPermission(p) : rejectOption(p),
418
+ unstable_createElicitation: (p) => handlers.onInput ? handlers.onInput(p) : Promise.resolve({ action: "decline" }),
419
+ unstable_completeElicitation: () => {
420
+ },
326
421
  sessionUpdate: (n) => {
327
422
  handlers.onUpdate?.(n);
328
423
  }
@@ -343,7 +438,13 @@ var AcpProvider = class {
343
438
  agent.initialize({
344
439
  protocolVersion: PROTOCOL_VERSION,
345
440
  clientInfo: { name: this.appName, version: VERSION },
346
- clientCapabilities: { fs: { readTextFile: false, writeTextFile: false }, terminal: false }
441
+ clientCapabilities: {
442
+ fs: { readTextFile: false, writeTextFile: false },
443
+ terminal: false,
444
+ session: { configOptions: { boolean: {} } },
445
+ plan: {},
446
+ elicitation: { form: {}, url: {} }
447
+ }
347
448
  }).then((init) => {
348
449
  if (settled) return;
349
450
  settled = true;
@@ -410,6 +511,7 @@ var AcpProvider = class {
410
511
  });
411
512
  }
412
513
  if (req.model) await this.selectModel(conn, sessionId, configOptions, req.model);
514
+ if (req.effort) await this.selectEffort(conn, sessionId, configOptions, req.effort);
413
515
  const sid = sessionId;
414
516
  conn.setHandlers({
415
517
  onPermission: async (p) => rejectOption(p),
@@ -455,6 +557,7 @@ var AcpProvider = class {
455
557
  connect: (cwd) => this.connectImpl(cwd),
456
558
  classify: (err, ctx) => this.classify(err, ctx),
457
559
  selectModel: (conn, sessionId, configOptions, model) => this.selectModel(conn, sessionId, configOptions, model),
560
+ selectEffort: (conn, sessionId, configOptions, effort) => this.selectEffort(conn, sessionId, configOptions, effort),
458
561
  options
459
562
  });
460
563
  }
@@ -468,6 +571,16 @@ var AcpProvider = class {
468
571
  throw this.classify(err);
469
572
  });
470
573
  }
574
+ async selectEffort(conn, sessionId, configOptions, effort) {
575
+ const option = configOptions?.find(
576
+ (candidate) => candidate.category === "thought_level" || /^(?:thought[_-]?level|reasoning[_-]?effort|effort)$/i.test(candidate.id)
577
+ );
578
+ if (!option || option.type !== "select" || option.currentValue === effort) return;
579
+ if (!flattenSelectOptions(option).some((candidate) => candidate.value === effort)) return;
580
+ await conn.agent.setSessionConfigOption({ sessionId, configId: option.id, value: effort }).catch((err) => {
581
+ throw this.classify(err);
582
+ });
583
+ }
471
584
  async listModels() {
472
585
  const conn = await this.connectImpl(this.workDir);
473
586
  try {
@@ -476,10 +589,19 @@ var AcpProvider = class {
476
589
  });
477
590
  const option = created.configOptions?.find((o) => o.id === this.modelConfigId) ?? created.configOptions?.find((o) => o.category === "model");
478
591
  if (!option || option.type !== "select") return [];
592
+ const effortOption = created.configOptions?.find(
593
+ (candidate) => candidate.type === "select" && (candidate.category === "thought_level" || /^(?:thought[_-]?level|reasoning[_-]?effort|effort)$/i.test(candidate.id))
594
+ );
595
+ const efforts = effortOption?.type === "select" ? flattenSelectOptions(effortOption).map((entry) => ({
596
+ id: entry.value,
597
+ ...entry.description ? { description: entry.description } : {}
598
+ })) : [];
479
599
  return flattenSelectOptions(option).map((o) => ({
480
600
  id: o.value,
481
601
  display_name: o.name,
482
- ...o.description ? { description: o.description } : {}
602
+ ...o.description ? { description: o.description } : {},
603
+ ...efforts.length > 0 ? { reasoning_efforts: efforts } : {},
604
+ ...effortOption?.type === "select" ? { default_reasoning_effort: effortOption.currentValue } : {}
483
605
  }));
484
606
  } finally {
485
607
  conn.close();
@@ -559,8 +681,10 @@ var AcpAgentSession = class {
559
681
  if (mode) await conn.agent.setSessionMode({ sessionId, modeId: mode }).catch(() => {
560
682
  });
561
683
  if (options.model) await this.cfg.selectModel(conn, sessionId, configOptions, options.model);
684
+ if (options.effort) await this.cfg.selectEffort(conn, sessionId, configOptions, options.effort);
562
685
  conn.setHandlers({
563
686
  onPermission: (p) => this.onPermission(p),
687
+ onInput: (p) => this.onInput(p),
564
688
  onUpdate: (n) => this.onUpdate(n)
565
689
  });
566
690
  }
@@ -593,6 +717,23 @@ var AcpAgentSession = class {
593
717
  }
594
718
  return rejectOption(p);
595
719
  }
720
+ async onInput(p) {
721
+ const request = elicitationRequest(
722
+ this.provider,
723
+ this.sessionId,
724
+ p
725
+ );
726
+ const handler = this.cfg.options.input;
727
+ let response = declineInput();
728
+ if (handler) {
729
+ try {
730
+ response = await handler.respond(request);
731
+ } catch {
732
+ response = { action: "cancel" };
733
+ }
734
+ }
735
+ return elicitationResponse(response);
736
+ }
596
737
  onUpdate(n) {
597
738
  if (n.sessionId !== this.sessionId) return;
598
739
  const u = n.update;
@@ -603,6 +744,21 @@ var AcpAgentSession = class {
603
744
  case "agent_thought_chunk":
604
745
  if (u.content.type === "text") this.queue?.push({ type: "thinking", text: u.content.text });
605
746
  break;
747
+ case "plan":
748
+ this.queue?.push({ type: "plan", plan: acpPlan(u) });
749
+ break;
750
+ case "plan_update":
751
+ this.queue?.push({ type: "plan", plan: acpPlan(u.plan ?? {}) });
752
+ break;
753
+ case "plan_removed":
754
+ this.queue?.push({
755
+ type: "plan",
756
+ plan: {
757
+ ...typeof u.planId === "string" ? { id: u.planId } : {},
758
+ removed: true
759
+ }
760
+ });
761
+ break;
606
762
  case "tool_call": {
607
763
  const t = u;
608
764
  const meta = { name: t.kind ?? "tool", ...t.title ? { title: t.title } : {}, ...t.kind ? { kind: t.kind } : {} };
@@ -683,6 +839,24 @@ var AcpAgentSession = class {
683
839
  this.conn = void 0;
684
840
  }
685
841
  };
842
+ function acpPlan(raw) {
843
+ const entries = Array.isArray(raw["entries"]) ? raw["entries"].flatMap((value) => {
844
+ const entry = value;
845
+ if (typeof entry["content"] !== "string") return [];
846
+ const item = {
847
+ content: entry["content"],
848
+ status: entry["status"] === "completed" ? "completed" : entry["status"] === "in_progress" ? "in_progress" : "pending",
849
+ ...["high", "medium", "low"].includes(String(entry["priority"])) ? { priority: entry["priority"] } : {}
850
+ };
851
+ return [item];
852
+ }) : void 0;
853
+ return {
854
+ ...typeof raw["planId"] === "string" ? { id: raw["planId"] } : {},
855
+ ...entries ? { entries } : {},
856
+ ...typeof raw["content"] === "string" ? { markdown: raw["content"] } : {},
857
+ ...typeof raw["uri"] === "string" ? { uri: raw["uri"] } : {}
858
+ };
859
+ }
686
860
  function supportsResume(init) {
687
861
  const caps = init.agentCapabilities;
688
862
  return caps?.sessionCapabilities?.resume !== void 0;
@@ -763,6 +937,15 @@ var DENY_ALL_TOOLS = async (toolName) => ({
763
937
  message: `yagami is a completions-only endpoint; tool "${toolName}" is disabled.`,
764
938
  interrupt: true
765
939
  });
940
+ var allowOnly = (enabled) => {
941
+ const allowed = new Set(enabled);
942
+ return async (toolName, input) => allowed.has(toolName) ? { behavior: "allow", updatedInput: input } : {
943
+ behavior: "deny",
944
+ message: `yagami is a completions-only endpoint; tool "${toolName}" is disabled.`,
945
+ interrupt: true
946
+ };
947
+ };
948
+ var SERVER_TOOL_MAX_TURNS = 24;
766
949
  var ClaudeProvider = class {
767
950
  id = "claude";
768
951
  label = "Claude Code";
@@ -776,7 +959,8 @@ var ClaudeProvider = class {
776
959
  systemPrompt: true,
777
960
  thinking: true,
778
961
  effort: true,
779
- streaming: "tokens"
962
+ streaming: "tokens",
963
+ serverTools: true
780
964
  };
781
965
  configDir;
782
966
  workDir;
@@ -811,6 +995,11 @@ var ClaudeProvider = class {
811
995
  const onAbort = () => abortController.abort();
812
996
  req.signal?.addEventListener("abort", onAbort, { once: true });
813
997
  const options = { ...this.baseOptions(), abortController, includePartialMessages: true };
998
+ if (req.serverTools && req.serverTools.length > 0) {
999
+ options.tools = [...req.serverTools];
1000
+ options.canUseTool = allowOnly(req.serverTools);
1001
+ options.maxTurns = SERVER_TOOL_MAX_TURNS;
1002
+ }
814
1003
  if (req.model) options.model = req.model;
815
1004
  if (req.system !== void 0) options.systemPrompt = req.system;
816
1005
  if (req.resume) {
@@ -889,7 +1078,11 @@ var ClaudeProvider = class {
889
1078
  id: m.value,
890
1079
  display_name: m.displayName,
891
1080
  ...m.description ? { description: m.description } : {},
892
- ...m.resolvedModel ? { resolved_model: m.resolvedModel } : {}
1081
+ ...m.resolvedModel ? { resolved_model: m.resolvedModel } : {},
1082
+ ...m.supportedEffortLevels?.length ? { reasoning_efforts: m.supportedEffortLevels.map((id) => ({ id })) } : {},
1083
+ ...m.supportsAdaptiveThinking ? { supports_adaptive_thinking: true } : {},
1084
+ ...m.supportsFastMode ? { supports_fast_mode: true } : {},
1085
+ ...m.supportsAutoMode ? { supports_auto_mode: true } : {}
893
1086
  }));
894
1087
  } catch (err) {
895
1088
  throw classifyProviderFailure(this.id, this.loginCommand, err);
@@ -999,12 +1192,17 @@ var CodexAgentSession = class {
999
1192
  lastUsage;
1000
1193
  opening;
1001
1194
  closed = false;
1195
+ sending = false;
1196
+ turnAbort;
1197
+ reasoningEmitted = /* @__PURE__ */ new Map();
1198
+ incoming = /* @__PURE__ */ new Map();
1002
1199
  /** Item text already emitted as deltas, so item/completed only fills gaps. */
1003
1200
  emitted = /* @__PURE__ */ new Map();
1004
1201
  get id() {
1005
1202
  return this.threadId;
1006
1203
  }
1007
1204
  fail(err) {
1205
+ this.turnAbort?.abort();
1008
1206
  for (const [, p] of this.pending) p.reject(err);
1009
1207
  this.pending.clear();
1010
1208
  this.queue?.fail(err);
@@ -1029,6 +1227,7 @@ var CodexAgentSession = class {
1029
1227
  return promise;
1030
1228
  }
1031
1229
  respond(id, result) {
1230
+ if (this.incoming.get(id)?.signal.aborted) return;
1032
1231
  this.child?.stdin?.write(`${JSON.stringify({ jsonrpc: "2.0", id, result })}
1033
1232
  `);
1034
1233
  }
@@ -1082,13 +1281,20 @@ var CodexAgentSession = class {
1082
1281
  ...native.config ? { config: native.config } : {},
1083
1282
  ...options.systemPrompt ? { developerInstructions: options.systemPrompt } : {}
1084
1283
  };
1284
+ if (options.resume && (options.fork || options.forkAt)) {
1285
+ const forked = await this.request("thread/fork", {
1286
+ threadId: options.resume,
1287
+ ...options.forkAt ? { lastTurnId: options.forkAt } : {},
1288
+ ...overrides
1289
+ });
1290
+ this.threadId = forked["thread"]?.id;
1291
+ if (!this.threadId) throw new ProviderError("codex", "thread/fork returned no thread id");
1292
+ return;
1293
+ }
1085
1294
  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
- }
1295
+ const resumed = await this.request("thread/resume", { threadId: options.resume, ...overrides });
1296
+ this.threadId = resumed["thread"]?.id ?? options.resume;
1297
+ return;
1092
1298
  }
1093
1299
  const started = await this.request("thread/start", overrides);
1094
1300
  this.threadId = started["thread"]?.id;
@@ -1106,7 +1312,17 @@ var CodexAgentSession = class {
1106
1312
  }
1107
1313
  if (!msg.method) return;
1108
1314
  if (msg.id !== void 0) {
1109
- void this.handleServerRequest(msg.method, msg.id, msg.params ?? {});
1315
+ const id = msg.id;
1316
+ const controller = new AbortController();
1317
+ const parent = this.turnAbort?.signal;
1318
+ const cancel = () => controller.abort();
1319
+ if (parent?.aborted) controller.abort();
1320
+ else parent?.addEventListener("abort", cancel, { once: true });
1321
+ this.incoming.set(id, controller);
1322
+ void this.handleServerRequest(msg.method, id, msg.params ?? {}, controller.signal).finally(() => {
1323
+ parent?.removeEventListener("abort", cancel);
1324
+ this.incoming.delete(id);
1325
+ });
1110
1326
  return;
1111
1327
  }
1112
1328
  this.handleNotification(msg.method, msg.params ?? {});
@@ -1116,7 +1332,13 @@ var CodexAgentSession = class {
1116
1332
  }
1117
1333
  handleNotification(method, params) {
1118
1334
  if (params["threadId"] !== void 0 && params["threadId"] !== this.threadId) return;
1335
+ if (this.currentTurnId && typeof params["turnId"] === "string" && params["turnId"] !== this.currentTurnId) return;
1119
1336
  switch (method) {
1337
+ case "serverRequest/resolved": {
1338
+ const requestId = params["requestId"];
1339
+ if (typeof requestId === "string" || typeof requestId === "number") this.incoming.get(requestId)?.abort();
1340
+ break;
1341
+ }
1120
1342
  case "item/agentMessage/delta": {
1121
1343
  const itemId = params["itemId"];
1122
1344
  const delta = params["delta"];
@@ -1124,6 +1346,14 @@ var CodexAgentSession = class {
1124
1346
  this.push({ type: "text", text: delta });
1125
1347
  break;
1126
1348
  }
1349
+ case "item/reasoning/summaryTextDelta": {
1350
+ const delta = params["delta"];
1351
+ if (typeof delta !== "string") break;
1352
+ const key = `${String(params["itemId"])}:${String(params["summaryIndex"] ?? 0)}`;
1353
+ this.reasoningEmitted.set(key, (this.reasoningEmitted.get(key) ?? 0) + delta.length);
1354
+ this.push({ type: "thinking", text: delta });
1355
+ break;
1356
+ }
1127
1357
  case "item/started":
1128
1358
  case "item/completed": {
1129
1359
  this.handleItem(params["item"], method === "item/completed");
@@ -1142,11 +1372,32 @@ var CodexAgentSession = class {
1142
1372
  }
1143
1373
  break;
1144
1374
  }
1375
+ case "turn/plan/updated": {
1376
+ const steps = Array.isArray(params["plan"]) ? params["plan"] : [];
1377
+ this.push({
1378
+ type: "plan",
1379
+ plan: {
1380
+ ...typeof params["explanation"] === "string" ? { explanation: params["explanation"] } : {},
1381
+ entries: steps.flatMap((value) => {
1382
+ const step = value;
1383
+ if (typeof step["step"] !== "string") return [];
1384
+ return [{
1385
+ content: step["step"],
1386
+ status: codexPlanStatus(step["status"])
1387
+ }];
1388
+ })
1389
+ }
1390
+ });
1391
+ break;
1392
+ }
1145
1393
  case "turn/completed": {
1146
1394
  const turn = params["turn"];
1395
+ const turnId = params["turn"]?.id;
1396
+ if (this.currentTurnId && turnId && turnId !== this.currentTurnId) break;
1147
1397
  const queue = this.queue;
1148
1398
  this.queue = null;
1149
1399
  this.currentTurnId = void 0;
1400
+ this.turnAbort?.abort();
1150
1401
  if (!queue) break;
1151
1402
  if (turn.status === "failed") {
1152
1403
  queue.fail(this.classify(new Error(turn.error?.message ?? "turn failed")));
@@ -1183,8 +1434,13 @@ var CodexAgentSession = class {
1183
1434
  }
1184
1435
  case "reasoning": {
1185
1436
  if (!completed) break;
1186
- const summary = item["summary"]?.join("\n") ?? "";
1187
- if (summary) this.push({ type: "thinking", text: summary });
1437
+ const summaries = item["summary"] ?? [];
1438
+ summaries.forEach((summary, index) => {
1439
+ const key = `${id}:${index}`;
1440
+ const seen = this.reasoningEmitted.get(key) ?? 0;
1441
+ if (summary.length > seen) this.push({ type: "thinking", text: summary.slice(seen) });
1442
+ this.reasoningEmitted.delete(key);
1443
+ });
1188
1444
  break;
1189
1445
  }
1190
1446
  case "commandExecution": {
@@ -1237,8 +1493,59 @@ var CodexAgentSession = class {
1237
1493
  });
1238
1494
  break;
1239
1495
  }
1240
- case "userMessage":
1496
+ case "dynamicToolCall": {
1497
+ const failed = item["status"] === "failed" || item["success"] === false;
1498
+ const namespace = typeof item["namespace"] === "string" ? `${item["namespace"]}.` : "";
1499
+ this.push({
1500
+ type: "tool_call",
1501
+ id,
1502
+ name: `${namespace}${String(item["tool"] ?? "tool")}`,
1503
+ status: completed ? failed ? "failed" : "completed" : "started",
1504
+ kind: "other",
1505
+ input: item["arguments"],
1506
+ ...completed ? { output: item["contentItems"] } : {}
1507
+ });
1508
+ break;
1509
+ }
1510
+ case "collabAgentToolCall": {
1511
+ const status = item["status"];
1512
+ const failed = status === "failed" || status === "interrupted";
1513
+ const tool = collabToolName(item["tool"]);
1514
+ this.push({
1515
+ type: "tool_call",
1516
+ id,
1517
+ name: tool,
1518
+ status: completed ? failed ? "failed" : "completed" : "started",
1519
+ title: typeof item["prompt"] === "string" && item["prompt"] ? item["prompt"] : tool,
1520
+ kind: "other",
1521
+ input: {
1522
+ prompt: item["prompt"],
1523
+ model: item["model"],
1524
+ effort: item["reasoningEffort"],
1525
+ receiverThreadIds: item["receiverThreadIds"]
1526
+ },
1527
+ ...completed ? { output: item["agentsStates"] } : {}
1528
+ });
1529
+ break;
1530
+ }
1531
+ case "imageView": {
1532
+ this.push({
1533
+ type: "tool_call",
1534
+ id,
1535
+ name: "read_file",
1536
+ status: completed ? "completed" : "started",
1537
+ title: String(item["path"] ?? "image"),
1538
+ kind: "read",
1539
+ input: { path: item["path"] }
1540
+ });
1541
+ break;
1542
+ }
1241
1543
  case "plan":
1544
+ if (completed && typeof item["text"] === "string") {
1545
+ this.push({ type: "plan", plan: { id, markdown: item["text"] } });
1546
+ }
1547
+ break;
1548
+ case "userMessage":
1242
1549
  break;
1243
1550
  default:
1244
1551
  this.push({ type: "raw", provider: "codex", payload: item });
@@ -1246,16 +1553,17 @@ var CodexAgentSession = class {
1246
1553
  }
1247
1554
  }
1248
1555
  // ── approvals: forwarded to the host, answered like the TUI would ──
1249
- async decide(request) {
1556
+ async decide(request, signal) {
1557
+ if (signal?.aborted) return "deny";
1250
1558
  try {
1251
- const decision = await this.config.options.permissions.decide(request);
1559
+ const decision = await this.config.options.permissions.decide(request, signal);
1252
1560
  this.push({ type: "permission", request, decision });
1253
1561
  return decision;
1254
1562
  } catch {
1255
1563
  return "deny";
1256
1564
  }
1257
1565
  }
1258
- async handleServerRequest(method, id, params) {
1566
+ async handleServerRequest(method, id, params, signal) {
1259
1567
  switch (method) {
1260
1568
  case "item/commandExecution/requestApproval": {
1261
1569
  const decision = await this.decide({
@@ -1266,7 +1574,7 @@ var CodexAgentSession = class {
1266
1574
  title: String(params["command"] ?? "command"),
1267
1575
  input: { command: params["command"], cwd: params["cwd"], reason: params["reason"] },
1268
1576
  raw: params
1269
- });
1577
+ }, signal);
1270
1578
  this.respond(id, {
1271
1579
  decision: decision === "allow" ? "accept" : decision === "allow_always" ? "acceptForSession" : "decline"
1272
1580
  });
@@ -1281,7 +1589,7 @@ var CodexAgentSession = class {
1281
1589
  title: String(params["reason"] ?? "apply file changes"),
1282
1590
  input: { reason: params["reason"], grantRoot: params["grantRoot"] },
1283
1591
  raw: params
1284
- });
1592
+ }, signal);
1285
1593
  this.respond(id, {
1286
1594
  decision: decision === "allow" ? "accept" : decision === "allow_always" ? "acceptForSession" : "decline"
1287
1595
  });
@@ -1297,7 +1605,7 @@ var CodexAgentSession = class {
1297
1605
  title: String(params["reason"] ?? "extra permissions"),
1298
1606
  input: requested,
1299
1607
  raw: params
1300
- });
1608
+ }, signal);
1301
1609
  const granted = decision === "allow" || decision === "allow_always";
1302
1610
  this.respond(id, {
1303
1611
  permissions: granted ? { network: requested?.["network"] ?? void 0, fileSystem: requested?.["fileSystem"] ?? void 0 } : {},
@@ -1316,18 +1624,32 @@ var CodexAgentSession = class {
1316
1624
  title: String(params["command"] ?? params["reason"] ?? "approval"),
1317
1625
  input: params,
1318
1626
  raw: params
1319
- });
1627
+ }, signal);
1320
1628
  this.respond(id, {
1321
1629
  decision: decision === "allow" ? "approved" : decision === "allow_always" ? "approved_for_session" : { denied: { rejection: "denied by the user" } }
1322
1630
  });
1323
1631
  break;
1324
1632
  }
1325
1633
  case "item/tool/requestUserInput": {
1326
- this.respond(id, { answers: {} });
1634
+ const response = await this.input(codexQuestionRequest(this.threadId, params), signal);
1635
+ const values = response.action === "accept" ? response.values ?? {} : {};
1636
+ this.respond(id, {
1637
+ answers: Object.fromEntries(
1638
+ Object.entries(values).map(([key, value]) => [
1639
+ key,
1640
+ { answers: (Array.isArray(value) ? value : [value]).map(String) }
1641
+ ])
1642
+ )
1643
+ });
1327
1644
  break;
1328
1645
  }
1329
1646
  case "mcpServer/elicitation/request": {
1330
- this.respond(id, { action: "decline", content: null, _meta: null });
1647
+ const response = await this.input(elicitationRequest("codex", this.threadId, params), signal);
1648
+ this.respond(id, { ...elicitationResponse(response), _meta: null });
1649
+ break;
1650
+ }
1651
+ case "currentTime/read": {
1652
+ this.respond(id, { currentTimeAt: Math.floor(Date.now() / 1e3) });
1331
1653
  break;
1332
1654
  }
1333
1655
  default: {
@@ -1339,42 +1661,73 @@ var CodexAgentSession = class {
1339
1661
  }
1340
1662
  }
1341
1663
  }
1664
+ async input(request, signal) {
1665
+ const handler = this.config.options.input;
1666
+ if (!handler) return declineInput();
1667
+ try {
1668
+ if (signal?.aborted) return { action: "cancel" };
1669
+ return await handler.respond(request, signal);
1670
+ } catch {
1671
+ return { action: "cancel" };
1672
+ }
1673
+ }
1342
1674
  // ── the ProviderSession surface ────────────────────────────────────
1343
1675
  async *send(input) {
1344
1676
  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: [] });
1677
+ if (this.sending) throw new ProviderError("codex", "a turn is already running");
1678
+ this.sending = true;
1679
+ const controller = new AbortController();
1680
+ this.turnAbort = controller;
1681
+ let cleanup = () => {
1682
+ };
1357
1683
  const queue = new AsyncQueue();
1358
- this.queue = queue;
1359
- this.lastUsage = void 0;
1360
1684
  try {
1685
+ await this.ensureOpen();
1686
+ if (controller.signal.aborted) {
1687
+ yield { type: "done", stopReason: "interrupted" };
1688
+ return;
1689
+ }
1690
+ const blocks = typeof input === "string" ? [{ type: "text", text: input }] : input;
1691
+ const images = writeTempImages(blocks);
1692
+ cleanup = images.cleanup;
1693
+ const items = [];
1694
+ for (const block of blocks) {
1695
+ if (block.type === "text" && typeof block.text === "string") {
1696
+ items.push({ type: "text", text: block.text, text_elements: [] });
1697
+ }
1698
+ }
1699
+ for (const p of images.paths) items.push({ type: "localImage", path: p });
1700
+ if (items.length === 0) items.push({ type: "text", text: "", text_elements: [] });
1701
+ this.queue = queue;
1702
+ this.lastUsage = void 0;
1703
+ this.emitted.clear();
1704
+ this.reasoningEmitted.clear();
1361
1705
  const result = await this.request("turn/start", {
1362
1706
  threadId: this.threadId,
1363
1707
  input: items,
1364
1708
  ...this.config.options.effort ? { effort: this.config.options.effort } : {}
1365
1709
  });
1366
- this.currentTurnId = result["turn"]?.id;
1710
+ const turnId = result["turn"]?.id;
1711
+ if (this.queue === queue) this.currentTurnId = turnId;
1712
+ if (controller.signal.aborted && this.currentTurnId) await this.interrupt();
1367
1713
  yield { type: "session", sessionId: this.threadId };
1714
+ if (turnId) yield { type: "turn", id: turnId };
1368
1715
  for await (const event of queue) yield event;
1369
1716
  } finally {
1370
1717
  cleanup();
1718
+ controller.abort();
1719
+ if (this.queue === queue) await this.interrupt();
1371
1720
  if (this.queue === queue) this.queue = null;
1721
+ this.currentTurnId = void 0;
1722
+ this.turnAbort = void 0;
1723
+ this.sending = false;
1372
1724
  }
1373
1725
  }
1374
1726
  async interrupt() {
1375
- if (!this.threadId || !this.currentTurnId) return;
1376
- await this.request("turn/interrupt", { threadId: this.threadId, turnId: this.currentTurnId }).catch(() => {
1377
- });
1727
+ const request = this.threadId && this.currentTurnId ? this.request("turn/interrupt", { threadId: this.threadId, turnId: this.currentTurnId }).catch(() => {
1728
+ }) : Promise.resolve();
1729
+ this.turnAbort?.abort();
1730
+ await request;
1378
1731
  }
1379
1732
  async close() {
1380
1733
  if (this.closed) return;
@@ -1384,6 +1737,57 @@ var CodexAgentSession = class {
1384
1737
  this.child = void 0;
1385
1738
  }
1386
1739
  };
1740
+ function collabToolName(value) {
1741
+ const names = {
1742
+ spawnAgent: "spawn_agent",
1743
+ sendInput: "send_input",
1744
+ resumeAgent: "resume_agent",
1745
+ closeAgent: "close_agent",
1746
+ sendMessage: "send_message",
1747
+ followupTask: "followup_task",
1748
+ interruptAgent: "interrupt_agent",
1749
+ listAgents: "list_agents"
1750
+ };
1751
+ return names[String(value)] ?? String(value ?? "agent");
1752
+ }
1753
+ function codexPlanStatus(value) {
1754
+ return value === "completed" ? "completed" : value === "inProgress" || value === "in_progress" ? "in_progress" : "pending";
1755
+ }
1756
+ function codexQuestionRequest(sessionId, raw) {
1757
+ const questions = Array.isArray(raw["questions"]) ? raw["questions"] : [];
1758
+ const fields = questions.flatMap((value) => {
1759
+ const question = value;
1760
+ if (typeof question["id"] !== "string" || typeof question["question"] !== "string") return [];
1761
+ const choices = Array.isArray(question["options"]) ? question["options"].flatMap((option) => {
1762
+ const item = option;
1763
+ if (typeof item["label"] !== "string") return [];
1764
+ return [{
1765
+ value: item["label"],
1766
+ label: item["label"],
1767
+ ...typeof item["description"] === "string" ? { description: item["description"] } : {}
1768
+ }];
1769
+ }) : [];
1770
+ return [{
1771
+ id: question["id"],
1772
+ label: question["question"],
1773
+ ...typeof question["header"] === "string" ? { description: question["header"] } : {},
1774
+ type: choices.length > 0 ? "select" : "string",
1775
+ required: true,
1776
+ secret: question["isSecret"] === true,
1777
+ allowOther: question["isOther"] === true,
1778
+ ...choices.length > 0 ? { options: choices } : {}
1779
+ }];
1780
+ });
1781
+ return {
1782
+ provider: "codex",
1783
+ ...sessionId ? { sessionId } : {},
1784
+ kind: "questions",
1785
+ message: fields.length === 1 ? fields[0].label : "Input requested",
1786
+ fields,
1787
+ blocking: raw["isBlocking"] !== false,
1788
+ raw
1789
+ };
1790
+ }
1387
1791
 
1388
1792
  // src/core/providers/jsonl.ts
1389
1793
  import { spawn as spawn3 } from "child_process";
@@ -1452,8 +1856,10 @@ var CodexProvider = class {
1452
1856
  systemPrompt: false,
1453
1857
  thinking: false,
1454
1858
  effort: true,
1455
- streaming: "chunks"
1859
+ streaming: "chunks",
1860
+ serverTools: false
1456
1861
  };
1862
+ sessionCapabilities = { fork: true };
1457
1863
  workDir;
1458
1864
  sandbox;
1459
1865
  env;
@@ -1578,11 +1984,34 @@ var CodexProvider = class {
1578
1984
  finish(() => reject(classifyProviderFailure(this.id, this.loginCommand, new Error(msg.error?.message ?? "model/list failed"))));
1579
1985
  return;
1580
1986
  }
1581
- const models = (msg.result?.data ?? []).filter((m) => m["hidden"] !== true).map((m) => ({
1582
- id: String(m["id"] ?? m["model"]),
1583
- display_name: String(m["displayName"] ?? m["id"] ?? m["model"]),
1584
- ...typeof m["description"] === "string" ? { description: m["description"] } : {}
1585
- }));
1987
+ const models = (msg.result?.data ?? []).filter((m) => m["hidden"] !== true).map((m) => {
1988
+ const efforts = Array.isArray(m["supportedReasoningEfforts"]) ? m["supportedReasoningEfforts"].flatMap(
1989
+ (entry) => typeof entry["reasoningEffort"] === "string" ? [{
1990
+ id: entry["reasoningEffort"],
1991
+ ...typeof entry["description"] === "string" ? { description: entry["description"] } : {}
1992
+ }] : []
1993
+ ) : [];
1994
+ const tiers = Array.isArray(m["serviceTiers"]) ? m["serviceTiers"].flatMap(
1995
+ (entry) => typeof entry["id"] === "string" ? [{
1996
+ id: entry["id"],
1997
+ display_name: typeof entry["name"] === "string" ? entry["name"] : entry["id"],
1998
+ ...typeof entry["description"] === "string" ? { description: entry["description"] } : {}
1999
+ }] : []
2000
+ ) : [];
2001
+ return {
2002
+ id: String(m["id"] ?? m["model"]),
2003
+ display_name: String(m["displayName"] ?? m["id"] ?? m["model"]),
2004
+ ...typeof m["description"] === "string" ? { description: m["description"] } : {},
2005
+ ...efforts.length > 0 ? { reasoning_efforts: efforts } : {},
2006
+ ...typeof m["defaultReasoningEffort"] === "string" ? { default_reasoning_effort: m["defaultReasoningEffort"] } : {},
2007
+ ...Array.isArray(m["inputModalities"]) ? { input_modalities: m["inputModalities"].filter((value) => typeof value === "string") } : {},
2008
+ ...m["supportsPersonality"] === true ? { supports_personality: true } : {},
2009
+ ...typeof m["multiAgentVersion"] === "string" ? { multi_agent: m["multiAgentVersion"] } : {},
2010
+ ...tiers.length > 0 ? { service_tiers: tiers } : {},
2011
+ ...typeof m["defaultServiceTier"] === "string" ? { default_service_tier: m["defaultServiceTier"] } : {},
2012
+ ...m["isDefault"] === true ? { is_default: true } : {}
2013
+ };
2014
+ });
1586
2015
  finish(() => resolve(models));
1587
2016
  }
1588
2017
  });
@@ -1901,6 +2330,61 @@ var SseSynthesizer = class {
1901
2330
 
1902
2331
  // src/core/transcript.ts
1903
2332
  import { createHash } from "crypto";
2333
+
2334
+ // src/core/serverTools.ts
2335
+ var SERVER_TOOLS = {
2336
+ web_search: "WebSearch",
2337
+ web_fetch: "WebFetch"
2338
+ };
2339
+ function serverToolFor(type) {
2340
+ if (typeof type !== "string") return void 0;
2341
+ for (const [prefix, tool] of Object.entries(SERVER_TOOLS)) {
2342
+ if (type === prefix || type.startsWith(`${prefix}_`)) return tool;
2343
+ }
2344
+ return void 0;
2345
+ }
2346
+ function resolveServerTools(tools, toolChoice) {
2347
+ if (tools == null) {
2348
+ if (toolChoice != null) {
2349
+ throw new ApiError(
2350
+ 400,
2351
+ "invalid_request_error",
2352
+ "`tool_choice` was given without `tools`."
2353
+ );
2354
+ }
2355
+ return void 0;
2356
+ }
2357
+ if (!Array.isArray(tools)) {
2358
+ throw new ApiError(400, "invalid_request_error", "`tools` must be an array");
2359
+ }
2360
+ if (tools.length === 0) return void 0;
2361
+ const resolved = /* @__PURE__ */ new Set();
2362
+ for (const tool of tools) {
2363
+ const entry = tool;
2364
+ const mapped = serverToolFor(entry?.type);
2365
+ if (!mapped) {
2366
+ const label = typeof entry?.name === "string" ? `"${entry.name}"` : `type "${String(entry?.type)}"`;
2367
+ throw new ApiError(
2368
+ 400,
2369
+ "invalid_request_error",
2370
+ `yagami supports Anthropic server tools (${Object.keys(SERVER_TOOLS).join(", ")}) but not custom tools: ${label} would have to be executed by you, and this endpoint never emits tool_use blocks. Drop it, or ask for the result as text/JSON instead.`
2371
+ );
2372
+ }
2373
+ resolved.add(mapped);
2374
+ }
2375
+ const choice = toolChoice?.type;
2376
+ if (choice != null && choice !== "auto" && choice !== "none") {
2377
+ throw new ApiError(
2378
+ 400,
2379
+ "invalid_request_error",
2380
+ `\`tool_choice.type\` must be "auto" or "none" with server tools (got "${String(choice)}"): server tools run on the engine's side and cannot be forced.`
2381
+ );
2382
+ }
2383
+ if (choice === "none") return void 0;
2384
+ return [...resolved];
2385
+ }
2386
+
2387
+ // src/core/transcript.ts
1904
2388
  var IGNORABLE_PARAMS = [
1905
2389
  "max_tokens",
1906
2390
  "temperature",
@@ -1962,13 +2446,7 @@ function normalizeRequest(req) {
1962
2446
  if (req == null || typeof req !== "object") {
1963
2447
  throw new ApiError(400, "invalid_request_error", "request body must be a JSON object");
1964
2448
  }
1965
- if (req.tools != null || req.tool_choice != null) {
1966
- throw new ApiError(
1967
- 400,
1968
- "invalid_request_error",
1969
- "yagami does not support `tools`/`tool_choice`: the backing engine runs as a pure completions endpoint and never executes or emits tool calls."
1970
- );
1971
- }
2449
+ const serverTools = resolveServerTools(req.tools, req.tool_choice);
1972
2450
  if (!Array.isArray(req.messages) || req.messages.length === 0) {
1973
2451
  throw new ApiError(400, "invalid_request_error", "`messages` must be a non-empty array");
1974
2452
  }
@@ -2004,7 +2482,8 @@ function normalizeRequest(req) {
2004
2482
  messages,
2005
2483
  lastUserText: last.text,
2006
2484
  ...prefill !== void 0 ? { prefill } : {},
2007
- ignored: [...ignored]
2485
+ ignored: [...ignored],
2486
+ ...serverTools ? { serverTools } : {}
2008
2487
  };
2009
2488
  }
2010
2489
  function prefillDirective(prefill) {
@@ -2232,6 +2711,13 @@ ${norm.system}
2232
2711
 
2233
2712
  ${promptText}`;
2234
2713
  }
2714
+ if (norm.serverTools && !caps.serverTools) {
2715
+ throw new ApiError(
2716
+ 400,
2717
+ "invalid_request_error",
2718
+ `provider "${provider.id}" cannot run server tools (${norm.serverTools.join(", ")}). Answering without them would silently drop the lookup you asked for, so the request is refused instead. Use the claude provider, or drop \`tools\`.`
2719
+ );
2720
+ }
2235
2721
  const turn = {
2236
2722
  prompt: promptText,
2237
2723
  ...lastMedia.length > 0 ? { media: lastMedia } : {},
@@ -2239,7 +2725,8 @@ ${promptText}`;
2239
2725
  ...model ? { model } : {},
2240
2726
  ...resume ? { resume } : {},
2241
2727
  ...req.thinking != null && caps.thinking ? { thinking: req.thinking } : {},
2242
- ...typeof req.effort === "string" && caps.effort ? { effort: req.effort } : {}
2728
+ ...typeof req.effort === "string" && caps.effort ? { effort: req.effort } : {},
2729
+ ...norm.serverTools ? { serverTools: norm.serverTools } : {}
2243
2730
  };
2244
2731
  const requestedModel = model ? provider.id === this.defaultProviderId ? model : qualifiedModel(provider.id, model) : provider.id;
2245
2732
  return { provider, turn, norm, requestedModel, ignored, ...resume && resumeKey ? { resumeKey } : {} };
@@ -2499,7 +2986,7 @@ function chatToMessagesRequest(body) {
2499
2986
  throw new ApiError(
2500
2987
  400,
2501
2988
  "invalid_request_error",
2502
- "yagami does not support `tools`/function calling: the backing engine runs as a pure completions endpoint and never executes or emits tool calls."
2989
+ "yagami does not support OpenAI function calling: the backing engine never emits tool calls for you to execute. Anthropic **server** tools (web_search, web_fetch) do run \u2014 request them through the Anthropic dialect at /v1/messages."
2503
2990
  );
2504
2991
  }
2505
2992
  if (body.n != null && body.n !== 1) {
@@ -2692,4 +3179,4 @@ export {
2692
3179
  ChatChunkTranslator,
2693
3180
  modelListBody
2694
3181
  };
2695
- //# sourceMappingURL=chunk-ZYHC7PXX.js.map
3182
+ //# sourceMappingURL=chunk-U3RFT7QV.js.map