pi-acp 0.0.28 → 0.0.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -529,16 +529,16 @@ function expandSlashCommand(text, fileCommands) {
529
529
  // src/acp/translate/pi-tools.ts
530
530
  function toolResultToText(result) {
531
531
  if (!result) return "";
532
- const content = result.content;
533
- if (Array.isArray(content)) {
534
- const texts = content.map((c) => c?.type === "text" && typeof c.text === "string" ? c.text : "").filter(Boolean);
535
- if (texts.length) return texts.join("");
536
- }
537
532
  const details = result?.details;
538
533
  const diff = details?.diff;
539
534
  if (typeof diff === "string" && diff.trim()) {
540
535
  return diff;
541
536
  }
537
+ const content = result.content;
538
+ if (Array.isArray(content)) {
539
+ const texts = content.map((c) => c?.type === "text" && typeof c.text === "string" ? c.text : "").filter(Boolean);
540
+ if (texts.length) return texts.join("");
541
+ }
542
542
  const stdout = (typeof details?.stdout === "string" ? details.stdout : void 0) ?? (typeof result?.stdout === "string" ? result.stdout : void 0) ?? (typeof details?.output === "string" ? details.output : void 0) ?? (typeof result?.output === "string" ? result.output : void 0);
543
543
  const stderr = (typeof details?.stderr === "string" ? details.stderr : void 0) ?? (typeof result?.stderr === "string" ? result.stderr : void 0);
544
544
  const exitCode = (typeof details?.exitCode === "number" ? details.exitCode : void 0) ?? (typeof result?.exitCode === "number" ? result.exitCode : void 0) ?? (typeof details?.code === "number" ? details.code : void 0) ?? (typeof result?.code === "number" ? result.code : void 0);
@@ -576,8 +576,58 @@ function findUniqueLineNumber(text, needle) {
576
576
  }
577
577
  return line;
578
578
  }
579
+ function getToolPath(args) {
580
+ const record = args;
581
+ if (typeof record?.path === "string") return record.path;
582
+ if (typeof record?.file_path === "string") return record.file_path;
583
+ return void 0;
584
+ }
585
+ function getParsedEdits(args) {
586
+ const record = args;
587
+ const parsed = [];
588
+ if (typeof record?.oldText === "string" && typeof record?.newText === "string") {
589
+ parsed.push({ oldText: record.oldText, newText: record.newText });
590
+ }
591
+ let edits = record?.edits;
592
+ if (typeof edits === "string") {
593
+ try {
594
+ edits = JSON.parse(edits);
595
+ } catch {
596
+ edits = void 0;
597
+ }
598
+ }
599
+ if (Array.isArray(edits)) {
600
+ for (const edit of edits) {
601
+ const item = edit;
602
+ if (typeof item?.oldText === "string" && typeof item?.newText === "string") {
603
+ parsed.push({ oldText: item.oldText, newText: item.newText });
604
+ }
605
+ }
606
+ }
607
+ return parsed;
608
+ }
609
+ function getEditOldTexts(args) {
610
+ const record = args;
611
+ const oldTexts = getParsedEdits(args).map((edit) => edit.oldText);
612
+ if (typeof record?.oldText === "string" && !oldTexts.includes(record.oldText)) oldTexts.push(record.oldText);
613
+ let edits = record?.edits;
614
+ if (typeof edits === "string") {
615
+ try {
616
+ edits = JSON.parse(edits);
617
+ } catch {
618
+ edits = void 0;
619
+ }
620
+ }
621
+ if (Array.isArray(edits)) {
622
+ for (const edit of edits) {
623
+ const oldText = edit?.oldText;
624
+ if (typeof oldText === "string" && !oldTexts.includes(oldText)) oldTexts.push(oldText);
625
+ }
626
+ }
627
+ return oldTexts;
628
+ }
579
629
  function toToolCallLocations(args, cwd, line) {
580
- const path = typeof args?.path === "string" ? args.path : void 0;
630
+ const path = getToolPath(args);
581
631
  if (!path) return void 0;
582
632
  const resolvedPath = isAbsolute(path) ? path : resolvePath(cwd, path);
583
633
  return [{ path: resolvedPath, ...typeof line === "number" ? { line } : {} }];
@@ -677,8 +727,7 @@ var PiAcpSession = class {
677
727
  cwd;
678
728
  mcpServers;
679
729
  startupInfo = null;
680
- startupInfoSentOutOfTurn = false;
681
- startupInfoSentInPrompt = false;
730
+ startupInfoSent = false;
682
731
  proc;
683
732
  conn;
684
733
  fileCommands;
@@ -695,10 +744,11 @@ var PiAcpSession = class {
695
744
  // pi can emit multiple `turn_end` events for a single user prompt (e.g. after tool_use).
696
745
  // The overall agent loop completes when `agent_end` is emitted.
697
746
  inAgentLoop = false;
698
- // For ACP diff support: capture file contents before edits, then emit ToolCallContent {type:"diff"}.
699
- // This is due to pi sending diff as a string as opposed to ACP expected diff format.
700
- // Compatible format may need to be implemented in pi in the future.
701
- editSnapshots = /* @__PURE__ */ new Map();
747
+ // For ACP diff support: capture file contents before edit/write mutations,
748
+ // then emit ToolCallContent {type:"diff"}. Compatible structured edit/write
749
+ // events may need to be implemented in pi in the future.
750
+ fileSnapshots = /* @__PURE__ */ new Map();
751
+ fileMutationToolCallIds = /* @__PURE__ */ new Set();
702
752
  // Ensure `session/update` notifications are sent in order and can be awaited
703
753
  // before completing a `session/prompt` request.
704
754
  lastEmit = Promise.resolve();
@@ -713,8 +763,7 @@ var PiAcpSession = class {
713
763
  }
714
764
  setStartupInfo(text) {
715
765
  this.startupInfo = text;
716
- this.startupInfoSentOutOfTurn = false;
717
- this.startupInfoSentInPrompt = false;
766
+ this.startupInfoSent = false;
718
767
  }
719
768
  /**
720
769
  * Best-effort attempt to send startup info outside of a prompt turn.
@@ -722,23 +771,14 @@ var PiAcpSession = class {
722
771
  * callers can invoke this shortly after session/new returns.
723
772
  */
724
773
  sendStartupInfoIfPending() {
725
- if (this.startupInfoSentOutOfTurn || !this.startupInfo) return;
726
- this.startupInfoSentOutOfTurn = true;
727
- this.emit({
728
- sessionUpdate: "agent_message_chunk",
729
- content: { type: "text", text: this.startupInfo }
730
- });
731
- }
732
- sendStartupInfoOnFirstPromptIfPending() {
733
- if (this.startupInfoSentInPrompt || !this.startupInfo) return;
734
- this.startupInfoSentInPrompt = true;
774
+ if (this.startupInfoSent || !this.startupInfo) return;
775
+ this.startupInfoSent = true;
735
776
  this.emit({
736
777
  sessionUpdate: "agent_message_chunk",
737
778
  content: { type: "text", text: this.startupInfo }
738
779
  });
739
780
  }
740
781
  async prompt(message, images = []) {
741
- this.sendStartupInfoOnFirstPromptIfPending();
742
782
  const expandedMessage = expandSlashCommand(message, this.fileCommands);
743
783
  const turnPromise = new Promise((resolve4, reject) => {
744
784
  const queued = { message: expandedMessage, images, resolve: resolve4, reject };
@@ -889,16 +929,25 @@ var PiAcpSession = class {
889
929
  const toolName = String(ev.toolName ?? "tool");
890
930
  const args = ev.args;
891
931
  let line;
892
- if (toolName === "edit") {
893
- const p = typeof args?.path === "string" ? args.path : void 0;
932
+ const isFileMutation = toolName === "edit" || toolName === "write";
933
+ let snapshotOldText;
934
+ if (isFileMutation) {
935
+ this.fileMutationToolCallIds.add(toolCallId);
936
+ const p = getToolPath(args);
894
937
  if (p) {
895
938
  try {
896
939
  const abs = isAbsolute(p) ? p : resolvePath(this.cwd, p);
897
- const oldText = readFileSync3(abs, "utf8");
898
- this.editSnapshots.set(toolCallId, { path: p, oldText });
899
- const needle = typeof args?.oldText === "string" ? args.oldText : "";
900
- line = findUniqueLineNumber(oldText, needle);
940
+ snapshotOldText = readFileSync3(abs, "utf8");
941
+ this.fileSnapshots.set(toolCallId, { path: p, oldText: snapshotOldText });
942
+ if (toolName === "edit") {
943
+ for (const needle of getEditOldTexts(args)) {
944
+ line = findUniqueLineNumber(snapshotOldText, needle);
945
+ if (typeof line === "number") break;
946
+ }
947
+ }
901
948
  } catch {
949
+ snapshotOldText = null;
950
+ this.fileSnapshots.set(toolCallId, { path: p, oldText: null });
902
951
  }
903
952
  }
904
953
  }
@@ -930,13 +979,13 @@ var PiAcpSession = class {
930
979
  const toolCallId = String(ev.toolCallId ?? "");
931
980
  if (!toolCallId) break;
932
981
  const partial = ev.partialResult;
933
- const text = toolResultToText(partial);
982
+ const text = this.fileMutationToolCallIds.has(toolCallId) ? "" : toolResultToText(partial);
934
983
  this.emit({
935
984
  sessionUpdate: "tool_call_update",
936
985
  toolCallId,
937
986
  status: "in_progress",
938
987
  content: text ? [{ type: "content", content: { type: "text", text } }] : void 0,
939
- rawOutput: partial
988
+ ...this.fileMutationToolCallIds.has(toolCallId) ? {} : { rawOutput: partial }
940
989
  });
941
990
  break;
942
991
  }
@@ -946,27 +995,28 @@ var PiAcpSession = class {
946
995
  const result = ev.result;
947
996
  const isError = Boolean(ev.isError);
948
997
  const text = toolResultToText(result);
949
- const snapshot = this.editSnapshots.get(toolCallId);
998
+ const snapshot = this.fileSnapshots.get(toolCallId);
950
999
  let content;
1000
+ let hasStructuredDiff = false;
951
1001
  if (!isError && snapshot) {
952
1002
  try {
953
1003
  const abs = isAbsolute(snapshot.path) ? snapshot.path : resolvePath(this.cwd, snapshot.path);
954
1004
  const newText = readFileSync3(abs, "utf8");
955
- if (newText !== snapshot.oldText) {
1005
+ if (snapshot.oldText === null || newText !== snapshot.oldText) {
1006
+ hasStructuredDiff = true;
956
1007
  content = [
957
1008
  {
958
1009
  type: "diff",
959
1010
  path: snapshot.path,
960
1011
  oldText: snapshot.oldText,
961
1012
  newText
962
- },
963
- ...text ? [{ type: "content", content: { type: "text", text } }] : []
1013
+ }
964
1014
  ];
965
1015
  }
966
1016
  } catch {
967
1017
  }
968
1018
  }
969
- if (!content && text) {
1019
+ if (!content && !hasStructuredDiff && text) {
970
1020
  content = [{ type: "content", content: { type: "text", text } }];
971
1021
  }
972
1022
  this.emit({
@@ -974,10 +1024,11 @@ var PiAcpSession = class {
974
1024
  toolCallId,
975
1025
  status: isError ? "failed" : "completed",
976
1026
  content,
977
- rawOutput: result
1027
+ ...hasStructuredDiff ? {} : { rawOutput: result }
978
1028
  });
979
1029
  this.currentToolCalls.delete(toolCallId);
980
- this.editSnapshots.delete(toolCallId);
1030
+ this.fileSnapshots.delete(toolCallId);
1031
+ this.fileMutationToolCallIds.delete(toolCallId);
981
1032
  break;
982
1033
  }
983
1034
  case "extension_ui_request": {
@@ -1441,10 +1492,9 @@ function listPiSessions() {
1441
1492
  });
1442
1493
  return items;
1443
1494
  }
1444
- function findPiSessionFile(sessionId) {
1495
+ function findPiSession(sessionId) {
1445
1496
  const all = listPiSessions();
1446
- const found = all.find((s) => s.sessionId === sessionId);
1447
- return found?.sessionFile ?? null;
1497
+ return all.find((s) => s.sessionId === sessionId) ?? null;
1448
1498
  }
1449
1499
 
1450
1500
  // src/acp/translate/pi-messages.ts
@@ -1600,6 +1650,8 @@ import { existsSync as existsSync4, readFileSync as readFileSync6, realpathSync,
1600
1650
  import { join as join5, dirname as dirname2, basename } from "path";
1601
1651
  import { spawnSync } from "child_process";
1602
1652
  import { fileURLToPath } from "url";
1653
+ var MODEL_CONFIG_ID = "model";
1654
+ var THOUGHT_LEVEL_CONFIG_ID = "thought_level";
1603
1655
  function builtinAvailableCommands() {
1604
1656
  return [
1605
1657
  {
@@ -1656,6 +1708,7 @@ var PiAcpAgent = class {
1656
1708
  conn;
1657
1709
  sessions = new SessionManager();
1658
1710
  store = new SessionStore();
1711
+ restoringSessions = /* @__PURE__ */ new Map();
1659
1712
  dispose() {
1660
1713
  this.sessions.disposeAll();
1661
1714
  }
@@ -1676,6 +1729,66 @@ var PiAcpAgent = class {
1676
1729
  }
1677
1730
  this.store.delete(sessionId);
1678
1731
  }
1732
+ findStoredSession(sessionId) {
1733
+ const stored = this.store.get(sessionId);
1734
+ if (stored?.cwd && stored?.sessionFile) {
1735
+ return { cwd: stored.cwd, sessionFile: stored.sessionFile };
1736
+ }
1737
+ const piSession = findPiSession(sessionId);
1738
+ if (!piSession) return null;
1739
+ this.store.upsert({
1740
+ sessionId,
1741
+ cwd: piSession.cwd,
1742
+ sessionFile: piSession.sessionFile
1743
+ });
1744
+ return {
1745
+ cwd: piSession.cwd,
1746
+ sessionFile: piSession.sessionFile
1747
+ };
1748
+ }
1749
+ async restoreSession(sessionId, opts) {
1750
+ const existing = this.sessions.maybeGet(sessionId);
1751
+ if (existing) return existing;
1752
+ const inFlight = this.restoringSessions.get(sessionId);
1753
+ if (inFlight) return inFlight;
1754
+ const restorePromise = (async () => {
1755
+ const stored = this.findStoredSession(sessionId);
1756
+ if (!stored) {
1757
+ throw RequestError3.invalidParams(`Unknown sessionId: ${sessionId}`);
1758
+ }
1759
+ const cwd = opts?.cwd ?? stored.cwd;
1760
+ let proc;
1761
+ try {
1762
+ proc = await PiRpcProcess.spawn({
1763
+ cwd,
1764
+ sessionPath: stored.sessionFile,
1765
+ piCommand: process.env.PI_ACP_PI_COMMAND
1766
+ });
1767
+ } catch (e) {
1768
+ if (e?.name === "PiRpcSpawnError") {
1769
+ throw RequestError3.internalError({ code: e?.code }, String(e?.message ?? e));
1770
+ }
1771
+ throw e;
1772
+ }
1773
+ const fileCommands = loadSlashCommands(cwd);
1774
+ const session = this.sessions.getOrCreate(sessionId, {
1775
+ cwd,
1776
+ mcpServers: opts?.mcpServers ?? [],
1777
+ conn: this.conn,
1778
+ proc,
1779
+ fileCommands
1780
+ });
1781
+ this.lastSessionCwd = cwd;
1782
+ this.store.upsert({ sessionId, cwd, sessionFile: stored.sessionFile });
1783
+ return session;
1784
+ })();
1785
+ this.restoringSessions.set(sessionId, restorePromise);
1786
+ try {
1787
+ return await restorePromise;
1788
+ } finally {
1789
+ this.restoringSessions.delete(sessionId);
1790
+ }
1791
+ }
1679
1792
  async initialize(params) {
1680
1793
  const supportedVersion = 1;
1681
1794
  const requested = params.protocolVersion;
@@ -1763,8 +1876,10 @@ var PiAcpAgent = class {
1763
1876
  "Configure an API key or log in with an OAuth provider."
1764
1877
  );
1765
1878
  }
1766
- const models = await getModelState(session.proc, { state, availableModels });
1767
- const thinking = await getThinkingState(session.proc, { state });
1879
+ const { configOptions, models, modes } = await getSessionConfiguration(session.proc, {
1880
+ state,
1881
+ availableModels
1882
+ });
1768
1883
  const quietStartup = getQuietStartup(params.cwd);
1769
1884
  const updateNotice = buildUpdateNotice();
1770
1885
  const preludeText = quietStartup ? updateNotice ? updateNotice + "\n" : "" : buildStartupInfo({
@@ -1777,8 +1892,9 @@ var PiAcpAgent = class {
1777
1892
  this.sessions.closeAllExcept?.(session.sessionId);
1778
1893
  const response = {
1779
1894
  sessionId: session.sessionId,
1895
+ configOptions,
1780
1896
  models,
1781
- modes: thinking,
1897
+ modes,
1782
1898
  _meta: {
1783
1899
  piAcp: {
1784
1900
  startupInfo: preludeText || null
@@ -1819,7 +1935,7 @@ var PiAcpAgent = class {
1819
1935
  return;
1820
1936
  }
1821
1937
  async prompt(params) {
1822
- const session = this.sessions.get(params.sessionId);
1938
+ const session = await this.restoreSession(params.sessionId);
1823
1939
  const { message, images } = promptToPiMessage(params.prompt);
1824
1940
  if (images.length === 0 && message.trimStart().startsWith("/")) {
1825
1941
  const trimmed = message.trim();
@@ -2192,7 +2308,8 @@ ${JSON.stringify(stats, null, 2)}`;
2192
2308
  return { stopReason };
2193
2309
  }
2194
2310
  async cancel(params) {
2195
- const session = this.sessions.get(params.sessionId);
2311
+ const session = this.sessions.maybeGet(params.sessionId);
2312
+ if (!session) return;
2196
2313
  await session.cancel();
2197
2314
  }
2198
2315
  async unstable_listSessions(params) {
@@ -2218,38 +2335,22 @@ ${JSON.stringify(stats, null, 2)}`;
2218
2335
  }
2219
2336
  this.sessions.close(params.sessionId);
2220
2337
  this.lastSessionCwd = params.cwd;
2221
- const stored = this.store.get(params.sessionId);
2222
- const sessionFile = stored?.sessionFile ?? findPiSessionFile(params.sessionId);
2223
- if (!sessionFile) {
2338
+ const stored = this.findStoredSession(params.sessionId);
2339
+ if (!stored) {
2224
2340
  throw RequestError3.invalidParams(`Unknown sessionId: ${params.sessionId}`);
2225
2341
  }
2226
- let proc;
2227
- try {
2228
- proc = await PiRpcProcess.spawn({
2229
- cwd: params.cwd,
2230
- sessionPath: sessionFile,
2231
- piCommand: process.env.PI_ACP_PI_COMMAND
2232
- });
2233
- } catch (e) {
2234
- if (e?.name === "PiRpcSpawnError") {
2235
- throw RequestError3.internalError({ code: e?.code }, String(e?.message ?? e));
2236
- }
2237
- throw e;
2238
- }
2239
- const fileCommands = loadSlashCommands(params.cwd);
2240
2342
  const enableSkillCommands = getEnableSkillCommands(params.cwd);
2241
- const session = this.sessions.getOrCreate(params.sessionId, {
2343
+ const session = await this.restoreSession(params.sessionId, {
2242
2344
  cwd: params.cwd,
2243
- mcpServers: params.mcpServers,
2244
- conn: this.conn,
2245
- proc,
2246
- fileCommands
2345
+ mcpServers: params.mcpServers
2247
2346
  });
2347
+ const proc = session.proc;
2348
+ const fileCommands = loadSlashCommands(params.cwd);
2248
2349
  this.sessions.closeAllExcept?.(session.sessionId);
2249
2350
  this.store.upsert({
2250
2351
  sessionId: params.sessionId,
2251
2352
  cwd: params.cwd,
2252
- sessionFile
2353
+ sessionFile: stored.sessionFile
2253
2354
  });
2254
2355
  const data = await proc.getMessages();
2255
2356
  const messages = Array.isArray(data?.messages) ? data.messages : [];
@@ -2308,11 +2409,11 @@ ${JSON.stringify(stats, null, 2)}`;
2308
2409
  });
2309
2410
  }
2310
2411
  }
2311
- const models = await getModelState(proc);
2312
- const thinking = await getThinkingState(proc);
2412
+ const { configOptions, models, modes } = await getSessionConfiguration(proc);
2313
2413
  const response = {
2414
+ configOptions,
2314
2415
  models,
2315
- modes: thinking,
2416
+ modes,
2316
2417
  _meta: {
2317
2418
  piAcp: {
2318
2419
  startupInfo: null
@@ -2349,32 +2450,12 @@ ${JSON.stringify(stats, null, 2)}`;
2349
2450
  return response;
2350
2451
  }
2351
2452
  async unstable_setSessionModel(params) {
2352
- const session = this.sessions.get(params.sessionId);
2353
- let provider = null;
2354
- let modelId = null;
2355
- if (params.modelId.includes("/")) {
2356
- const [p, ...rest] = params.modelId.split("/");
2357
- provider = p;
2358
- modelId = rest.join("/");
2359
- } else {
2360
- modelId = params.modelId;
2361
- }
2362
- if (!provider) {
2363
- const data = await session.proc.getAvailableModels();
2364
- const models = Array.isArray(data?.models) ? data.models : [];
2365
- const found = models.find((m) => String(m?.id) === modelId);
2366
- if (found) {
2367
- provider = String(found.provider);
2368
- modelId = String(found.id);
2369
- }
2370
- }
2371
- if (!provider || !modelId) {
2372
- throw RequestError3.invalidParams(`Unknown modelId: ${params.modelId}`);
2373
- }
2374
- await session.proc.setModel(provider, modelId);
2453
+ const session = await this.restoreSession(params.sessionId);
2454
+ await setSessionModel(session.proc, params.modelId);
2455
+ await emitConfigOptionsUpdate(this.conn, session.sessionId, session.proc);
2375
2456
  }
2376
2457
  async setSessionMode(params) {
2377
- const session = this.sessions.get(params.sessionId);
2458
+ const session = await this.restoreSession(params.sessionId);
2378
2459
  const mode = String(params.modeId);
2379
2460
  if (!isThinkingLevel(mode)) {
2380
2461
  throw RequestError3.invalidParams(`Unknown modeId: ${mode}`);
@@ -2387,8 +2468,35 @@ ${JSON.stringify(stats, null, 2)}`;
2387
2468
  currentModeId: mode
2388
2469
  }
2389
2470
  });
2471
+ await emitConfigOptionsUpdate(this.conn, session.sessionId, session.proc);
2390
2472
  return {};
2391
2473
  }
2474
+ async setSessionConfigOption(params) {
2475
+ const session = await this.restoreSession(params.sessionId);
2476
+ const configId = String(params.configId);
2477
+ if (typeof params.value !== "string") {
2478
+ throw RequestError3.invalidParams(`Expected string value for config option: ${configId}`);
2479
+ }
2480
+ if (configId === MODEL_CONFIG_ID) {
2481
+ await setSessionModel(session.proc, params.value);
2482
+ } else if (configId === THOUGHT_LEVEL_CONFIG_ID) {
2483
+ if (!isThinkingLevel(params.value)) {
2484
+ throw RequestError3.invalidParams(`Unknown thinking level: ${params.value}`);
2485
+ }
2486
+ await session.proc.setThinkingLevel(params.value);
2487
+ void this.conn.sessionUpdate({
2488
+ sessionId: session.sessionId,
2489
+ update: {
2490
+ sessionUpdate: "current_mode_update",
2491
+ currentModeId: params.value
2492
+ }
2493
+ });
2494
+ } else {
2495
+ throw RequestError3.invalidParams(`Unknown config option: ${configId}`);
2496
+ }
2497
+ const configOptions = await emitConfigOptionsUpdate(this.conn, session.sessionId, session.proc);
2498
+ return { configOptions };
2499
+ }
2392
2500
  };
2393
2501
  function isThinkingLevel(x) {
2394
2502
  return x === "off" || x === "minimal" || x === "low" || x === "medium" || x === "high" || x === "xhigh";
@@ -2414,6 +2522,47 @@ async function getThinkingState(proc, pre) {
2414
2522
  }))
2415
2523
  };
2416
2524
  }
2525
+ async function getSessionConfiguration(proc, pre) {
2526
+ const [models, modes] = await Promise.all([getModelState(proc, pre), getThinkingState(proc, { state: pre?.state })]);
2527
+ return {
2528
+ configOptions: buildConfigOptions({ models, modes }),
2529
+ models,
2530
+ modes
2531
+ };
2532
+ }
2533
+ function buildConfigOptions(state) {
2534
+ const configOptions = [
2535
+ {
2536
+ type: "select",
2537
+ id: THOUGHT_LEVEL_CONFIG_ID,
2538
+ category: "thought_level",
2539
+ name: "Thinking",
2540
+ description: "Set the reasoning effort for this session",
2541
+ currentValue: state.modes.currentModeId,
2542
+ options: state.modes.availableModes.map((mode) => ({
2543
+ value: mode.id,
2544
+ name: mode.name,
2545
+ description: mode.description ?? null
2546
+ }))
2547
+ }
2548
+ ];
2549
+ if (state.models?.availableModels.length) {
2550
+ configOptions.unshift({
2551
+ type: "select",
2552
+ id: MODEL_CONFIG_ID,
2553
+ category: "model",
2554
+ name: "Model",
2555
+ description: "Select the model for this session",
2556
+ currentValue: state.models.currentModelId,
2557
+ options: state.models.availableModels.map((model) => ({
2558
+ value: model.modelId,
2559
+ name: model.name,
2560
+ description: model.description ?? null
2561
+ }))
2562
+ });
2563
+ }
2564
+ return configOptions;
2565
+ }
2417
2566
  async function getModelState(proc, pre) {
2418
2567
  let availableModels = [];
2419
2568
  const data = pre?.availableModels ?? await (async () => {
@@ -2453,9 +2602,44 @@ async function getModelState(proc, pre) {
2453
2602
  if (!currentModelId) currentModelId = availableModels[0]?.modelId ?? "default";
2454
2603
  return {
2455
2604
  availableModels,
2456
- currentModelId
2605
+ currentModelId: currentModelId ?? availableModels[0]?.modelId ?? "default"
2457
2606
  };
2458
2607
  }
2608
+ async function emitConfigOptionsUpdate(conn, sessionId, proc) {
2609
+ const { configOptions } = await getSessionConfiguration(proc);
2610
+ await conn.sessionUpdate({
2611
+ sessionId,
2612
+ update: {
2613
+ sessionUpdate: "config_option_update",
2614
+ configOptions
2615
+ }
2616
+ });
2617
+ return configOptions;
2618
+ }
2619
+ async function setSessionModel(proc, requestedModelId) {
2620
+ let provider = null;
2621
+ let modelId = null;
2622
+ if (requestedModelId.includes("/")) {
2623
+ const [candidateProvider, ...rest] = requestedModelId.split("/");
2624
+ provider = candidateProvider;
2625
+ modelId = rest.join("/");
2626
+ } else {
2627
+ modelId = requestedModelId;
2628
+ }
2629
+ if (!provider) {
2630
+ const data = await proc.getAvailableModels();
2631
+ const models = Array.isArray(data?.models) ? data.models : [];
2632
+ const found = models.find((m) => String(m?.id) === modelId);
2633
+ if (found) {
2634
+ provider = String(found.provider);
2635
+ modelId = String(found.id);
2636
+ }
2637
+ }
2638
+ if (!provider || !modelId) {
2639
+ throw RequestError3.invalidParams(`Unknown modelId: ${requestedModelId}`);
2640
+ }
2641
+ await proc.setModel(provider, modelId);
2642
+ }
2459
2643
  function isSemver(v) {
2460
2644
  return /^\d+\.\d+\.\d+(?:[-+].+)?$/.test(v);
2461
2645
  }