pi-acp 0.0.27 → 0.0.29

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
@@ -48,36 +48,6 @@ function terminalAuthLaunchSpec() {
48
48
 
49
49
  // src/acp/session.ts
50
50
  import { RequestError as RequestError2 } from "@agentclientprotocol/sdk";
51
-
52
- // src/acp/auth-required.ts
53
- import { RequestError } from "@agentclientprotocol/sdk";
54
- function maybeAuthRequiredError(err) {
55
- const msg = String(err?.message ?? err ?? "");
56
- const s = msg.toLowerCase();
57
- const patterns = [
58
- "api key",
59
- "apikey",
60
- "missing key",
61
- "no key",
62
- "not configured",
63
- "unauthorized",
64
- "authentication",
65
- "permission denied",
66
- "forbidden",
67
- "401",
68
- "403"
69
- ];
70
- const hit = patterns.some((p) => s.includes(p));
71
- if (!hit) return null;
72
- return RequestError.authRequired(
73
- {
74
- authMethods: getAuthMethods()
75
- },
76
- "Configure an API key or log in with an OAuth provider."
77
- );
78
- }
79
-
80
- // src/acp/session.ts
81
51
  import { readFileSync as readFileSync3 } from "fs";
82
52
  import { isAbsolute, resolve as resolvePath } from "path";
83
53
 
@@ -309,27 +279,68 @@ var PiRpcProcess = class _PiRpcProcess {
309
279
  if (!res.success) throw new Error(`pi get_commands failed: ${res.error ?? JSON.stringify(res.data)}`);
310
280
  return res.data;
311
281
  }
282
+ async sendExtensionUiResponse(response) {
283
+ await this.writeLine(`${JSON.stringify({ type: "extension_ui_response", ...response })}
284
+ `);
285
+ }
312
286
  request(cmd) {
313
287
  const id = crypto.randomUUID();
314
288
  const withId = { ...cmd, id };
315
- const line = JSON.stringify(withId) + "\n";
289
+ const line = `${JSON.stringify(withId)}
290
+ `;
316
291
  return new Promise((resolve4, reject) => {
317
292
  this.pending.set(id, { resolve: resolve4, reject });
293
+ void this.writeLine(line).catch((error) => {
294
+ this.pending.delete(id);
295
+ reject(error);
296
+ });
297
+ });
298
+ }
299
+ writeLine(line) {
300
+ return new Promise((resolve4, reject) => {
318
301
  try {
319
- this.child.stdin.write(line, (err) => {
320
- if (err) {
321
- this.pending.delete(id);
322
- reject(err);
302
+ this.child.stdin.write(line, (error) => {
303
+ if (error) {
304
+ reject(error);
305
+ return;
323
306
  }
307
+ resolve4();
324
308
  });
325
- } catch (e) {
326
- this.pending.delete(id);
327
- reject(e);
309
+ } catch (error) {
310
+ reject(error);
328
311
  }
329
312
  });
330
313
  }
331
314
  };
332
315
 
316
+ // src/acp/auth-required.ts
317
+ import { RequestError } from "@agentclientprotocol/sdk";
318
+ function maybeAuthRequiredError(err) {
319
+ const msg = String(err?.message ?? err ?? "");
320
+ const s = msg.toLowerCase();
321
+ const patterns = [
322
+ "api key",
323
+ "apikey",
324
+ "missing key",
325
+ "no key",
326
+ "not configured",
327
+ "unauthorized",
328
+ "authentication",
329
+ "permission denied",
330
+ "forbidden",
331
+ "401",
332
+ "403"
333
+ ];
334
+ const hit = patterns.some((p) => s.includes(p));
335
+ if (!hit) return null;
336
+ return RequestError.authRequired(
337
+ {
338
+ authMethods: getAuthMethods()
339
+ },
340
+ "Configure an API key or log in with an OAuth provider."
341
+ );
342
+ }
343
+
333
344
  // src/acp/session-store.ts
334
345
  import { mkdirSync, readFileSync, writeFileSync } from "fs";
335
346
  import { dirname } from "path";
@@ -391,37 +402,6 @@ var SessionStore = class {
391
402
  }
392
403
  };
393
404
 
394
- // src/acp/translate/pi-tools.ts
395
- function toolResultToText(result) {
396
- if (!result) return "";
397
- const content = result.content;
398
- if (Array.isArray(content)) {
399
- const texts = content.map((c) => c?.type === "text" && typeof c.text === "string" ? c.text : "").filter(Boolean);
400
- if (texts.length) return texts.join("");
401
- }
402
- const details = result?.details;
403
- const diff = details?.diff;
404
- if (typeof diff === "string" && diff.trim()) {
405
- return diff;
406
- }
407
- 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);
408
- const stderr = (typeof details?.stderr === "string" ? details.stderr : void 0) ?? (typeof result?.stderr === "string" ? result.stderr : void 0);
409
- 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);
410
- if (typeof stdout === "string" && stdout.trim() || typeof stderr === "string" && stderr.trim()) {
411
- const parts = [];
412
- if (typeof stdout === "string" && stdout.trim()) parts.push(stdout);
413
- if (typeof stderr === "string" && stderr.trim()) parts.push(`stderr:
414
- ${stderr}`);
415
- if (typeof exitCode === "number") parts.push(`exit code: ${exitCode}`);
416
- return parts.join("\n\n").trimEnd();
417
- }
418
- try {
419
- return JSON.stringify(result, null, 2);
420
- } catch {
421
- return String(result);
422
- }
423
- }
424
-
425
405
  // src/acp/slash-commands.ts
426
406
  import { existsSync, readdirSync, readFileSync as readFileSync2 } from "fs";
427
407
  import { homedir as homedir2 } from "os";
@@ -546,7 +526,44 @@ function expandSlashCommand(text, fileCommands) {
546
526
  return substituteArgs(cmd.content, args);
547
527
  }
548
528
 
529
+ // src/acp/translate/pi-tools.ts
530
+ function toolResultToText(result) {
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
+ const details = result?.details;
538
+ const diff = details?.diff;
539
+ if (typeof diff === "string" && diff.trim()) {
540
+ return diff;
541
+ }
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
+ const stderr = (typeof details?.stderr === "string" ? details.stderr : void 0) ?? (typeof result?.stderr === "string" ? result.stderr : void 0);
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);
545
+ if (typeof stdout === "string" && stdout.trim() || typeof stderr === "string" && stderr.trim()) {
546
+ const parts = [];
547
+ if (typeof stdout === "string" && stdout.trim()) parts.push(stdout);
548
+ if (typeof stderr === "string" && stderr.trim()) parts.push(`stderr:
549
+ ${stderr}`);
550
+ if (typeof exitCode === "number") parts.push(`exit code: ${exitCode}`);
551
+ return parts.join("\n\n").trimEnd();
552
+ }
553
+ try {
554
+ return JSON.stringify(result, null, 2);
555
+ } catch {
556
+ return String(result);
557
+ }
558
+ }
559
+
549
560
  // src/acp/session.ts
561
+ var CONFIRM_PERMISSION_OPTIONS = [
562
+ { optionId: "yes", name: "Yes", kind: "allow_once" },
563
+ { optionId: "no", name: "No", kind: "reject_once" }
564
+ ];
565
+ var EXTENSION_UI_RAW_INPUT_KEYS = ["title", "message", "options", "placeholder", "prefill"];
566
+ var CHOICE_OPTION_PREFIX = "choice-";
550
567
  function findUniqueLineNumber(text, needle) {
551
568
  if (!needle) return void 0;
552
569
  const first = text.indexOf(needle);
@@ -660,8 +677,7 @@ var PiAcpSession = class {
660
677
  cwd;
661
678
  mcpServers;
662
679
  startupInfo = null;
663
- startupInfoSentOutOfTurn = false;
664
- startupInfoSentInPrompt = false;
680
+ startupInfoSent = false;
665
681
  proc;
666
682
  conn;
667
683
  fileCommands;
@@ -696,8 +712,7 @@ var PiAcpSession = class {
696
712
  }
697
713
  setStartupInfo(text) {
698
714
  this.startupInfo = text;
699
- this.startupInfoSentOutOfTurn = false;
700
- this.startupInfoSentInPrompt = false;
715
+ this.startupInfoSent = false;
701
716
  }
702
717
  /**
703
718
  * Best-effort attempt to send startup info outside of a prompt turn.
@@ -705,23 +720,14 @@ var PiAcpSession = class {
705
720
  * callers can invoke this shortly after session/new returns.
706
721
  */
707
722
  sendStartupInfoIfPending() {
708
- if (this.startupInfoSentOutOfTurn || !this.startupInfo) return;
709
- this.startupInfoSentOutOfTurn = true;
710
- this.emit({
711
- sessionUpdate: "agent_message_chunk",
712
- content: { type: "text", text: this.startupInfo }
713
- });
714
- }
715
- sendStartupInfoOnFirstPromptIfPending() {
716
- if (this.startupInfoSentInPrompt || !this.startupInfo) return;
717
- this.startupInfoSentInPrompt = true;
723
+ if (this.startupInfoSent || !this.startupInfo) return;
724
+ this.startupInfoSent = true;
718
725
  this.emit({
719
726
  sessionUpdate: "agent_message_chunk",
720
727
  content: { type: "text", text: this.startupInfo }
721
728
  });
722
729
  }
723
730
  async prompt(message, images = []) {
724
- this.sendStartupInfoOnFirstPromptIfPending();
725
731
  const expandedMessage = expandSlashCommand(message, this.fileCommands);
726
732
  const turnPromise = new Promise((resolve4, reject) => {
727
733
  const queued = { message: expandedMessage, images, resolve: resolve4, reject };
@@ -963,6 +969,17 @@ var PiAcpSession = class {
963
969
  this.editSnapshots.delete(toolCallId);
964
970
  break;
965
971
  }
972
+ case "extension_ui_request": {
973
+ void this.handleExtensionUiRequest(ev).catch(() => {
974
+ const id = stringProp(ev, "id");
975
+ if (!id) {
976
+ return;
977
+ }
978
+ void this.proc.sendExtensionUiResponse({ id, cancelled: true }).catch(() => {
979
+ });
980
+ });
981
+ break;
982
+ }
966
983
  case "auto_retry_start": {
967
984
  this.emit({
968
985
  sessionUpdate: "agent_message_chunk",
@@ -1030,7 +1047,116 @@ var PiAcpSession = class {
1030
1047
  break;
1031
1048
  }
1032
1049
  }
1050
+ async handleExtensionUiRequest(ev) {
1051
+ const id = stringProp(ev, "id");
1052
+ const method = stringProp(ev, "method");
1053
+ if (!id) {
1054
+ return;
1055
+ }
1056
+ if (method === "select") {
1057
+ await this.handleExtensionSelect(ev, id);
1058
+ return;
1059
+ }
1060
+ if (method === "confirm") {
1061
+ await this.handleExtensionConfirm(ev, id);
1062
+ return;
1063
+ }
1064
+ if (method === "input" || method === "editor") {
1065
+ this.emit({
1066
+ sessionUpdate: "agent_message_chunk",
1067
+ content: {
1068
+ type: "text",
1069
+ text: `Pi ${method} UI request is not supported in ACP yet; cancelling it.`
1070
+ }
1071
+ });
1072
+ await this.proc.sendExtensionUiResponse({ id, cancelled: true });
1073
+ return;
1074
+ }
1075
+ if (method === "notify") {
1076
+ this.emit({
1077
+ sessionUpdate: "agent_message_chunk",
1078
+ content: { type: "text", text: stringProp(ev, "message") ?? "Pi notification" }
1079
+ });
1080
+ await this.proc.sendExtensionUiResponse({ id, cancelled: true });
1081
+ return;
1082
+ }
1083
+ await this.proc.sendExtensionUiResponse({ id, cancelled: true });
1084
+ }
1085
+ async handleExtensionSelect(ev, id) {
1086
+ const rawOptions = ev.options;
1087
+ const options = Array.isArray(rawOptions) ? rawOptions.map((option) => String(option)) : [];
1088
+ if (!options.length) {
1089
+ await this.proc.sendExtensionUiResponse({ id, cancelled: true });
1090
+ return;
1091
+ }
1092
+ const permissionOptions = options.map((name, index2) => ({
1093
+ optionId: `${CHOICE_OPTION_PREFIX}${index2}`,
1094
+ name,
1095
+ kind: "allow_once"
1096
+ }));
1097
+ const selected = await this.requestExtensionPermission(id, ev, permissionOptions);
1098
+ if (selected === null) {
1099
+ return;
1100
+ }
1101
+ const selectedOptionId = selected.outcome.outcome === "selected" ? selected.outcome.optionId : null;
1102
+ const index = selectedOptionId === null ? null : optionIndex(selectedOptionId);
1103
+ const value = index === null ? null : options.at(index) ?? null;
1104
+ await this.proc.sendExtensionUiResponse(value === null ? { id, cancelled: true } : { id, value });
1105
+ }
1106
+ async handleExtensionConfirm(ev, id) {
1107
+ const selected = await this.requestExtensionPermission(id, ev, CONFIRM_PERMISSION_OPTIONS);
1108
+ if (selected === null) {
1109
+ return;
1110
+ }
1111
+ if (selected.outcome.outcome === "cancelled") {
1112
+ await this.proc.sendExtensionUiResponse({ id, cancelled: true });
1113
+ return;
1114
+ }
1115
+ await this.proc.sendExtensionUiResponse({ id, confirmed: selected.outcome.optionId === "yes" });
1116
+ }
1117
+ async requestExtensionPermission(id, ev, options) {
1118
+ try {
1119
+ return await this.conn.requestPermission({
1120
+ sessionId: this.sessionId,
1121
+ toolCall: extensionUiToolCall(id, ev),
1122
+ options
1123
+ });
1124
+ } catch {
1125
+ await this.proc.sendExtensionUiResponse({ id, cancelled: true });
1126
+ return null;
1127
+ }
1128
+ }
1033
1129
  };
1130
+ function extensionUiToolCall(id, ev) {
1131
+ const method = stringProp(ev, "method") ?? "ui";
1132
+ const title = stringProp(ev, "title") ?? `Pi ${method}`;
1133
+ const rawInput = { method };
1134
+ for (const key of EXTENSION_UI_RAW_INPUT_KEYS) {
1135
+ if (Object.hasOwn(ev, key)) rawInput[key] = ev[key];
1136
+ }
1137
+ return {
1138
+ toolCallId: `pi-ui-${id}`,
1139
+ title,
1140
+ kind: "other",
1141
+ status: "pending",
1142
+ rawInput
1143
+ };
1144
+ }
1145
+ function stringProp(source, key) {
1146
+ const value = source[key];
1147
+ return typeof value === "string" ? value : null;
1148
+ }
1149
+ function optionIndex(optionId) {
1150
+ if (!optionId.startsWith(CHOICE_OPTION_PREFIX)) {
1151
+ return null;
1152
+ }
1153
+ const rawIndex = optionId.slice(CHOICE_OPTION_PREFIX.length);
1154
+ if (!rawIndex) {
1155
+ return null;
1156
+ }
1157
+ const index = Number(rawIndex);
1158
+ return Number.isSafeInteger(index) && index >= 0 && String(index) === rawIndex ? index : null;
1159
+ }
1034
1160
  function formatAutoRetryMessage(ev) {
1035
1161
  const attempt = Number(ev.attempt);
1036
1162
  const maxAttempts = Number(ev.maxAttempts);
@@ -1463,6 +1589,8 @@ import { existsSync as existsSync4, readFileSync as readFileSync6, realpathSync,
1463
1589
  import { join as join5, dirname as dirname2, basename } from "path";
1464
1590
  import { spawnSync } from "child_process";
1465
1591
  import { fileURLToPath } from "url";
1592
+ var MODEL_CONFIG_ID = "model";
1593
+ var THOUGHT_LEVEL_CONFIG_ID = "thought_level";
1466
1594
  function builtinAvailableCommands() {
1467
1595
  return [
1468
1596
  {
@@ -1626,8 +1754,10 @@ var PiAcpAgent = class {
1626
1754
  "Configure an API key or log in with an OAuth provider."
1627
1755
  );
1628
1756
  }
1629
- const models = await getModelState(session.proc, { state, availableModels });
1630
- const thinking = await getThinkingState(session.proc, { state });
1757
+ const { configOptions, models, modes } = await getSessionConfiguration(session.proc, {
1758
+ state,
1759
+ availableModels
1760
+ });
1631
1761
  const quietStartup = getQuietStartup(params.cwd);
1632
1762
  const updateNotice = buildUpdateNotice();
1633
1763
  const preludeText = quietStartup ? updateNotice ? updateNotice + "\n" : "" : buildStartupInfo({
@@ -1640,8 +1770,9 @@ var PiAcpAgent = class {
1640
1770
  this.sessions.closeAllExcept?.(session.sessionId);
1641
1771
  const response = {
1642
1772
  sessionId: session.sessionId,
1773
+ configOptions,
1643
1774
  models,
1644
- modes: thinking,
1775
+ modes,
1645
1776
  _meta: {
1646
1777
  piAcp: {
1647
1778
  startupInfo: preludeText || null
@@ -2171,11 +2302,11 @@ ${JSON.stringify(stats, null, 2)}`;
2171
2302
  });
2172
2303
  }
2173
2304
  }
2174
- const models = await getModelState(proc);
2175
- const thinking = await getThinkingState(proc);
2305
+ const { configOptions, models, modes } = await getSessionConfiguration(proc);
2176
2306
  const response = {
2307
+ configOptions,
2177
2308
  models,
2178
- modes: thinking,
2309
+ modes,
2179
2310
  _meta: {
2180
2311
  piAcp: {
2181
2312
  startupInfo: null
@@ -2213,28 +2344,8 @@ ${JSON.stringify(stats, null, 2)}`;
2213
2344
  }
2214
2345
  async unstable_setSessionModel(params) {
2215
2346
  const session = this.sessions.get(params.sessionId);
2216
- let provider = null;
2217
- let modelId = null;
2218
- if (params.modelId.includes("/")) {
2219
- const [p, ...rest] = params.modelId.split("/");
2220
- provider = p;
2221
- modelId = rest.join("/");
2222
- } else {
2223
- modelId = params.modelId;
2224
- }
2225
- if (!provider) {
2226
- const data = await session.proc.getAvailableModels();
2227
- const models = Array.isArray(data?.models) ? data.models : [];
2228
- const found = models.find((m) => String(m?.id) === modelId);
2229
- if (found) {
2230
- provider = String(found.provider);
2231
- modelId = String(found.id);
2232
- }
2233
- }
2234
- if (!provider || !modelId) {
2235
- throw RequestError3.invalidParams(`Unknown modelId: ${params.modelId}`);
2236
- }
2237
- await session.proc.setModel(provider, modelId);
2347
+ await setSessionModel(session.proc, params.modelId);
2348
+ await emitConfigOptionsUpdate(this.conn, session.sessionId, session.proc);
2238
2349
  }
2239
2350
  async setSessionMode(params) {
2240
2351
  const session = this.sessions.get(params.sessionId);
@@ -2250,8 +2361,35 @@ ${JSON.stringify(stats, null, 2)}`;
2250
2361
  currentModeId: mode
2251
2362
  }
2252
2363
  });
2364
+ await emitConfigOptionsUpdate(this.conn, session.sessionId, session.proc);
2253
2365
  return {};
2254
2366
  }
2367
+ async setSessionConfigOption(params) {
2368
+ const session = this.sessions.get(params.sessionId);
2369
+ const configId = String(params.configId);
2370
+ if (typeof params.value !== "string") {
2371
+ throw RequestError3.invalidParams(`Expected string value for config option: ${configId}`);
2372
+ }
2373
+ if (configId === MODEL_CONFIG_ID) {
2374
+ await setSessionModel(session.proc, params.value);
2375
+ } else if (configId === THOUGHT_LEVEL_CONFIG_ID) {
2376
+ if (!isThinkingLevel(params.value)) {
2377
+ throw RequestError3.invalidParams(`Unknown thinking level: ${params.value}`);
2378
+ }
2379
+ await session.proc.setThinkingLevel(params.value);
2380
+ void this.conn.sessionUpdate({
2381
+ sessionId: session.sessionId,
2382
+ update: {
2383
+ sessionUpdate: "current_mode_update",
2384
+ currentModeId: params.value
2385
+ }
2386
+ });
2387
+ } else {
2388
+ throw RequestError3.invalidParams(`Unknown config option: ${configId}`);
2389
+ }
2390
+ const configOptions = await emitConfigOptionsUpdate(this.conn, session.sessionId, session.proc);
2391
+ return { configOptions };
2392
+ }
2255
2393
  };
2256
2394
  function isThinkingLevel(x) {
2257
2395
  return x === "off" || x === "minimal" || x === "low" || x === "medium" || x === "high" || x === "xhigh";
@@ -2277,6 +2415,47 @@ async function getThinkingState(proc, pre) {
2277
2415
  }))
2278
2416
  };
2279
2417
  }
2418
+ async function getSessionConfiguration(proc, pre) {
2419
+ const [models, modes] = await Promise.all([getModelState(proc, pre), getThinkingState(proc, { state: pre?.state })]);
2420
+ return {
2421
+ configOptions: buildConfigOptions({ models, modes }),
2422
+ models,
2423
+ modes
2424
+ };
2425
+ }
2426
+ function buildConfigOptions(state) {
2427
+ const configOptions = [
2428
+ {
2429
+ type: "select",
2430
+ id: THOUGHT_LEVEL_CONFIG_ID,
2431
+ category: "thought_level",
2432
+ name: "Thinking",
2433
+ description: "Set the reasoning effort for this session",
2434
+ currentValue: state.modes.currentModeId,
2435
+ options: state.modes.availableModes.map((mode) => ({
2436
+ value: mode.id,
2437
+ name: mode.name,
2438
+ description: mode.description ?? null
2439
+ }))
2440
+ }
2441
+ ];
2442
+ if (state.models?.availableModels.length) {
2443
+ configOptions.unshift({
2444
+ type: "select",
2445
+ id: MODEL_CONFIG_ID,
2446
+ category: "model",
2447
+ name: "Model",
2448
+ description: "Select the model for this session",
2449
+ currentValue: state.models.currentModelId,
2450
+ options: state.models.availableModels.map((model) => ({
2451
+ value: model.modelId,
2452
+ name: model.name,
2453
+ description: model.description ?? null
2454
+ }))
2455
+ });
2456
+ }
2457
+ return configOptions;
2458
+ }
2280
2459
  async function getModelState(proc, pre) {
2281
2460
  let availableModels = [];
2282
2461
  const data = pre?.availableModels ?? await (async () => {
@@ -2316,9 +2495,44 @@ async function getModelState(proc, pre) {
2316
2495
  if (!currentModelId) currentModelId = availableModels[0]?.modelId ?? "default";
2317
2496
  return {
2318
2497
  availableModels,
2319
- currentModelId
2498
+ currentModelId: currentModelId ?? availableModels[0]?.modelId ?? "default"
2320
2499
  };
2321
2500
  }
2501
+ async function emitConfigOptionsUpdate(conn, sessionId, proc) {
2502
+ const { configOptions } = await getSessionConfiguration(proc);
2503
+ await conn.sessionUpdate({
2504
+ sessionId,
2505
+ update: {
2506
+ sessionUpdate: "config_option_update",
2507
+ configOptions
2508
+ }
2509
+ });
2510
+ return configOptions;
2511
+ }
2512
+ async function setSessionModel(proc, requestedModelId) {
2513
+ let provider = null;
2514
+ let modelId = null;
2515
+ if (requestedModelId.includes("/")) {
2516
+ const [candidateProvider, ...rest] = requestedModelId.split("/");
2517
+ provider = candidateProvider;
2518
+ modelId = rest.join("/");
2519
+ } else {
2520
+ modelId = requestedModelId;
2521
+ }
2522
+ if (!provider) {
2523
+ const data = await proc.getAvailableModels();
2524
+ const models = Array.isArray(data?.models) ? data.models : [];
2525
+ const found = models.find((m) => String(m?.id) === modelId);
2526
+ if (found) {
2527
+ provider = String(found.provider);
2528
+ modelId = String(found.id);
2529
+ }
2530
+ }
2531
+ if (!provider || !modelId) {
2532
+ throw RequestError3.invalidParams(`Unknown modelId: ${requestedModelId}`);
2533
+ }
2534
+ await proc.setModel(provider, modelId);
2535
+ }
2322
2536
  function isSemver(v) {
2323
2537
  return /^\d+\.\d+\.\d+(?:[-+].+)?$/.test(v);
2324
2538
  }