@vedmalex/ai-connect 0.2.1 → 0.5.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.
@@ -219,11 +219,7 @@ function normalizeTransport(providerId, input) {
219
219
  ...selector.whereEquals !== void 0 ? { whereEquals: selector.whereEquals } : {}
220
220
  };
221
221
  };
222
- const parser = (() => {
223
- if (!descriptor.cli?.parser) {
224
- return void 0;
225
- }
226
- const cliParser = descriptor.cli.parser;
222
+ const normalizeCliParser = (cliParser) => {
227
223
  if (cliParser.kind === "json") {
228
224
  assert(
229
225
  cliParser.textPath.trim().length > 0,
@@ -236,13 +232,22 @@ function normalizeTransport(providerId, input) {
236
232
  ...cliParser.usagePath?.trim() ? { usagePath: cliParser.usagePath.trim() } : {}
237
233
  };
238
234
  }
235
+ if (cliParser.kind === "text") {
236
+ return {
237
+ kind: "text",
238
+ // Default trim=true, stripAnsi=false; only persist explicit overrides.
239
+ ...cliParser.trim === false ? { trim: false } : {},
240
+ ...cliParser.stripAnsi === true ? { stripAnsi: true } : {}
241
+ };
242
+ }
239
243
  return {
240
244
  kind: "jsonl",
241
245
  text: normalizeSelector(cliParser.text, "text"),
242
246
  ...cliParser.error ? { error: normalizeSelector(cliParser.error, "error") } : {},
243
247
  ...cliParser.usage ? { usage: normalizeSelector(cliParser.usage, "usage") } : {}
244
248
  };
245
- })();
249
+ };
250
+ const parser = descriptor.cli?.parser ? normalizeCliParser(descriptor.cli.parser) : void 0;
246
251
  const normalized = {
247
252
  ...descriptor.cli.preset ? { preset: descriptor.cli.preset } : {},
248
253
  ...descriptor.cli.argsTemplate ? {
@@ -254,11 +259,13 @@ function normalizeTransport(providerId, input) {
254
259
  if (!descriptor.cli?.discovery) {
255
260
  return void 0;
256
261
  }
257
- const via = descriptor.cli.discovery.via ?? "none";
258
- assert(
259
- via === "none" || via === "acp",
260
- `Unsupported CLI discovery mode "${String(via)}" for provider "${providerId}".`
261
- );
262
+ const explicitVia = descriptor.cli.discovery.via;
263
+ if (explicitVia !== void 0) {
264
+ assert(
265
+ explicitVia === "none" || explicitVia === "acp" || explicitVia === "command" || explicitVia === "static",
266
+ `Unsupported CLI discovery mode "${String(explicitVia)}" for provider "${providerId}".`
267
+ );
268
+ }
262
269
  const launch = descriptor.cli.discovery.acp?.launch ? (() => {
263
270
  const contextMode = descriptor.cli.discovery?.acp?.launch?.contextMode ?? "workspace";
264
271
  const skillsMode = descriptor.cli.discovery?.acp?.launch?.skillsMode ?? "default";
@@ -279,7 +286,7 @@ function normalizeTransport(providerId, input) {
279
286
  methodId: descriptor.cli.discovery.acp.auth.methodId.trim(),
280
287
  params: descriptor.cli.discovery.acp.auth.params ?? {}
281
288
  } : void 0;
282
- const acp = via === "acp" ? {
289
+ const acp = descriptor.cli.discovery.acp ? {
283
290
  ...descriptor.cli.discovery.acp?.providerId?.trim() ? { providerId: descriptor.cli.discovery.acp.providerId.trim() } : {},
284
291
  ...descriptor.cli.discovery.acp?.transportId?.trim() ? {
285
292
  transportId: descriptor.cli.discovery.acp.transportId.trim()
@@ -287,9 +294,55 @@ function normalizeTransport(providerId, input) {
287
294
  ...auth ? { auth } : {},
288
295
  ...launch ? { launch } : {}
289
296
  } : void 0;
297
+ const commandInput = descriptor.cli.discovery.command;
298
+ const command = commandInput ? (() => {
299
+ assert(
300
+ Array.isArray(commandInput.argsTemplate) && commandInput.argsTemplate.length > 0,
301
+ `CLI discovery command.argsTemplate must be a non-empty array for provider "${providerId}".`
302
+ );
303
+ const cmdParserInput = commandInput.parser ?? { kind: "text" };
304
+ const cmdParser = cmdParserInput.kind === "text" ? {
305
+ kind: "text",
306
+ ...cmdParserInput.trim === false ? { trim: false } : {},
307
+ ...cmdParserInput.stripAnsi === true ? { stripAnsi: true } : {}
308
+ } : { kind: cmdParserInput.kind };
309
+ if (cmdParser.kind !== "text") {
310
+ assert(
311
+ Boolean(commandInput.models?.idPath?.trim()),
312
+ `CLI discovery command.models.idPath is required for a ${cmdParser.kind} parser for provider "${providerId}".`
313
+ );
314
+ }
315
+ const m = commandInput.models;
316
+ const models = m?.idPath?.trim() ? {
317
+ ...m.path?.trim() ? { path: m.path.trim() } : {},
318
+ idPath: m.idPath.trim(),
319
+ ...m.namePath?.trim() ? { namePath: m.namePath.trim() } : {},
320
+ ...m.descriptionPath?.trim() ? { descriptionPath: m.descriptionPath.trim() } : {},
321
+ ...m.contextLengthPath?.trim() ? { contextLengthPath: m.contextLengthPath.trim() } : {}
322
+ } : void 0;
323
+ return {
324
+ ...commandInput.command?.trim() ? { command: commandInput.command.trim() } : {},
325
+ argsTemplate: commandInput.argsTemplate.map((part) => String(part)),
326
+ parser: cmdParser,
327
+ ...models ? { models } : {}
328
+ };
329
+ })() : void 0;
330
+ assert(
331
+ explicitVia !== "command" || command !== void 0,
332
+ `CLI discovery via:"command" requires a discovery.command block for provider "${providerId}".`
333
+ );
334
+ const fallback = descriptor.cli.discovery.fallback;
335
+ if (fallback !== void 0) {
336
+ assert(
337
+ fallback === "static" || fallback === "none",
338
+ `Unsupported CLI discovery fallback "${String(fallback)}" for provider "${providerId}".`
339
+ );
340
+ }
290
341
  return {
291
- via,
292
- ...acp ? { acp } : {}
342
+ ...explicitVia !== void 0 ? { via: explicitVia } : {},
343
+ ...acp ? { acp } : {},
344
+ ...command ? { command } : {},
345
+ ...fallback !== void 0 ? { fallback } : {}
293
346
  };
294
347
  })();
295
348
  return {
@@ -330,7 +383,7 @@ function normalizeTransport(providerId, input) {
330
383
  };
331
384
  }
332
385
  if (descriptor.kind === "cli") {
333
- const inferredId2 = providerId === "anthropic" ? "claude-cli" : providerId === "openclaude" ? "openclaude-cli" : providerId === "openai" ? "codex-cli" : providerId === "gemini" ? "gemini-cli" : providerId === "qwen" ? "qwen-cli" : "cli";
386
+ const inferredId2 = providerId === "anthropic" ? "claude-cli" : providerId === "openclaude" ? "openclaude-cli" : providerId === "openai" ? "codex-cli" : providerId === "pi" ? "pi-cli" : "cli";
334
387
  return {
335
388
  kind: "cli",
336
389
  id: descriptor.id?.trim() || inferredId2,
@@ -347,7 +400,7 @@ function normalizeTransport(providerId, input) {
347
400
  ...descriptor.baseUrl?.trim() ? { baseUrl: descriptor.baseUrl.trim() } : {}
348
401
  };
349
402
  }
350
- const inferredId = providerId === "anthropic" ? "claude-code-acp" : providerId === "openai" ? "codex-acp" : providerId === "gemini" ? "gemini-acp" : "acp";
403
+ const inferredId = providerId === "anthropic" ? "claude-code-acp" : providerId === "openai" ? "codex-acp" : "acp";
351
404
  const normalizedLaunch = descriptor.launch ? (() => {
352
405
  const contextMode = descriptor.launch?.contextMode ?? "workspace";
353
406
  const skillsMode = descriptor.launch?.skillsMode ?? "default";
@@ -1585,18 +1638,14 @@ var MODEL_REFERENCE = {
1585
1638
  key: "gemini-3.1-flash-lite",
1586
1639
  contextLength: 1048576
1587
1640
  },
1641
+ "gemini-3.1-pro": { key: "gemini-3.1-pro", contextLength: 1048576 },
1588
1642
  "gemini-3-pro": { key: "gemini-3-pro", contextLength: 2097152 },
1589
1643
  "auto-gemini-3": { key: "auto-gemini-3", contextLength: 1048576 },
1590
1644
  "gemini-2.5-flash": { key: "gemini-2.5-flash", contextLength: 1048576 },
1591
1645
  "gemini-2.5-pro": { key: "gemini-2.5-pro", contextLength: 2097152 },
1592
1646
  "gemini-2.0-flash": { key: "gemini-2.0-flash", contextLength: 1048576 },
1593
1647
  "gemini-1.5-flash": { key: "gemini-1.5-flash", contextLength: 1048576 },
1594
- "gemini-1.5-pro": { key: "gemini-1.5-pro", contextLength: 2097152 },
1595
- // --- Qwen (catalog: qwen3-coder-plus) ---
1596
- "qwen3-coder-plus": { key: "qwen3-coder-plus", contextLength: 1048576 },
1597
- "qwen3-coder": { key: "qwen3-coder", contextLength: 262144 },
1598
- "qwen-max": { key: "qwen-max", contextLength: 32768 },
1599
- "qwen-plus": { key: "qwen-plus", contextLength: 131072 }
1648
+ "gemini-1.5-pro": { key: "gemini-1.5-pro", contextLength: 2097152 }
1600
1649
  };
1601
1650
  function normalizeModelKey(model) {
1602
1651
  let key = model.trim().toLowerCase();
@@ -1730,6 +1779,25 @@ function resolveModelContextWindow(input) {
1730
1779
  const fallback = isUsableContextLength(input.defaultContextWindow) ? input.defaultContextWindow : DEFAULT_CONTEXT_WINDOW;
1731
1780
  return { contextWindow: fallback, source: "default" };
1732
1781
  }
1782
+ function fillConfiguredContextLength(route, models) {
1783
+ if (!isUsableContextLength(route.contextWindow)) {
1784
+ return models;
1785
+ }
1786
+ const configured = route.contextWindow;
1787
+ return models.map((entry) => {
1788
+ if (isUsableContextLength(entry.contextLength)) {
1789
+ return entry;
1790
+ }
1791
+ if (route.model !== void 0 && entry.modelId !== route.model) {
1792
+ return entry;
1793
+ }
1794
+ return {
1795
+ ...entry,
1796
+ contextLength: configured,
1797
+ metadata: { ...entry.metadata ?? {}, contextWindowSource: "configured" }
1798
+ };
1799
+ });
1800
+ }
1733
1801
  function modelContextCacheKey(input) {
1734
1802
  if (typeof input === "string") {
1735
1803
  return { key: `::${input}`, model: input };
@@ -4726,18 +4794,19 @@ function canonicalGeminiImageModelId(modelId) {
4726
4794
  }
4727
4795
  function buildModelCatalog(route, availableModels, currentModelId) {
4728
4796
  const requestedModelId = route.model;
4729
- const requestedModelAdvertised = availableModels.some(
4797
+ const filledModels = fillConfiguredContextLength(route, availableModels);
4798
+ const requestedModelAdvertised = filledModels.some(
4730
4799
  (model) => model.modelId === requestedModelId
4731
4800
  );
4732
4801
  const canonicalModelId = route.provider === "gemini" ? canonicalGeminiImageModelId(requestedModelId) : void 0;
4733
- const resolvedModelId = requestedModelAdvertised ? requestedModelId : canonicalModelId && availableModels.some((model) => model.modelId === canonicalModelId) ? canonicalModelId : void 0;
4802
+ const resolvedModelId = requestedModelAdvertised ? requestedModelId : canonicalModelId && filledModels.some((model) => model.modelId === canonicalModelId) ? canonicalModelId : void 0;
4734
4803
  return {
4735
4804
  requestedModelId,
4736
4805
  requestedModelAdvertised,
4737
4806
  ...canonicalModelId ? { canonicalModelId } : {},
4738
4807
  ...resolvedModelId ? { resolvedModelId } : {},
4739
4808
  ...currentModelId ? { currentModelId } : {},
4740
- availableModels
4809
+ availableModels: filledModels
4741
4810
  };
4742
4811
  }
4743
4812
  function parseOpenAiModelCatalog(route, payload) {
@@ -6270,54 +6339,26 @@ function createDefaultRouteHandlers(options = {}) {
6270
6339
  var AI_CONNECT_DEFAULT_ACP_COMMANDS = {
6271
6340
  "anthropic:claude-code-acp": "npx -y @agentclientprotocol/claude-agent-acp@^0.25.0",
6272
6341
  "openai:codex-acp": "npx @zed-industries/codex-acp@^0.11.1",
6273
- "gemini:gemini-acp": "gemini --acp",
6274
- "qwen:qwen-acp": "qwen --acp",
6275
6342
  "opencode:opencode-acp": "opencode acp"
6276
6343
  };
6277
6344
 
6278
6345
  // src/cli-presets.ts
6279
6346
  var AI_CONNECT_DEFAULT_CLI_PRESETS = {
6280
- gemini: {
6281
- id: "gemini",
6282
- label: "Gemini CLI",
6283
- command: "gemini",
6284
- transportId: "gemini-cli",
6285
- options: {
6286
- preset: "gemini",
6287
- argsTemplate: ["-p", "{prompt}", "--output-format", "json", "--model", "{model}"],
6288
- discovery: {
6289
- via: "acp",
6290
- acp: {
6291
- transportId: "gemini-acp"
6292
- }
6293
- },
6294
- parser: {
6295
- kind: "json",
6296
- textPath: "response",
6297
- usagePath: "stats",
6298
- errorPath: "error"
6299
- }
6300
- }
6301
- },
6302
- qwen: {
6303
- id: "qwen",
6304
- label: "Qwen CLI",
6305
- command: "qwen",
6306
- transportId: "qwen-cli",
6347
+ pi: {
6348
+ id: "pi",
6349
+ label: "pi (coding agent)",
6350
+ command: "pi",
6351
+ transportId: "pi-cli",
6307
6352
  options: {
6308
- preset: "qwen",
6309
- argsTemplate: ["-p", "{prompt}", "--output-format", "json", "--model", "{model}"],
6353
+ preset: "pi",
6354
+ // pi print mode emits the answer as plain text on stdout (no JSON wrapper),
6355
+ // and has no ACP mode — so it uses the raw `text` parser and no discovery sidecar.
6356
+ argsTemplate: ["--print", "--model", "{model}", "{prompt}"],
6310
6357
  discovery: {
6311
- via: "acp",
6312
- acp: {
6313
- transportId: "qwen-acp"
6314
- }
6358
+ via: "none"
6315
6359
  },
6316
6360
  parser: {
6317
- kind: "json",
6318
- textPath: "__preset__:qwen.result",
6319
- usagePath: "__preset__:qwen.stats",
6320
- errorPath: "__preset__:qwen.error"
6361
+ kind: "text"
6321
6362
  }
6322
6363
  }
6323
6364
  },
@@ -6401,8 +6442,7 @@ var AI_CONNECT_DEFAULT_CLI_COMMANDS = {
6401
6442
  "anthropic:openclaude-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.openclaude.command,
6402
6443
  "openclaude:openclaude-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.openclaude.command,
6403
6444
  "openai:codex-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.codex.command,
6404
- "gemini:gemini-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.gemini.command,
6405
- "qwen:qwen-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.qwen.command
6445
+ "pi:pi-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.pi.command
6406
6446
  };
6407
6447
  function getCliTransportPreset(presetId) {
6408
6448
  return AI_CONNECT_DEFAULT_CLI_PRESETS[presetId];
@@ -6524,52 +6564,22 @@ var TEXT_PROVIDER_CATALOG = [
6524
6564
  runtime: "universal",
6525
6565
  defaultModel: "gemini-3.1-flash-lite",
6526
6566
  defaultBaseUrl: ""
6527
- },
6528
- {
6529
- providerId: "gemini",
6530
- providerLabel: "Gemini",
6531
- transportKind: "cli",
6532
- transportId: "gemini-cli",
6533
- transportLabel: "Gemini CLI",
6534
- runtime: "local",
6535
- defaultModel: "gemini-2.5-flash",
6536
- defaultCommand: AI_CONNECT_DEFAULT_CLI_COMMANDS["gemini:gemini-cli"]
6537
- },
6538
- {
6539
- providerId: "gemini",
6540
- providerLabel: "Gemini",
6541
- transportKind: "acp",
6542
- transportId: "gemini-acp",
6543
- transportLabel: "Gemini ACP",
6544
- runtime: "local",
6545
- defaultModel: "auto-gemini-3",
6546
- defaultCommand: AI_CONNECT_DEFAULT_ACP_COMMANDS["gemini:gemini-acp"]
6547
6567
  }
6548
6568
  ]
6549
6569
  },
6550
6570
  {
6551
- providerId: "qwen",
6552
- label: "Qwen",
6571
+ providerId: "pi",
6572
+ label: "pi",
6553
6573
  transports: [
6554
6574
  {
6555
- providerId: "qwen",
6556
- providerLabel: "Qwen",
6575
+ providerId: "pi",
6576
+ providerLabel: "pi",
6557
6577
  transportKind: "cli",
6558
- transportId: "qwen-cli",
6559
- transportLabel: "Qwen CLI",
6560
- runtime: "local",
6561
- defaultModel: "qwen3-coder-plus",
6562
- defaultCommand: AI_CONNECT_DEFAULT_CLI_COMMANDS["qwen:qwen-cli"]
6563
- },
6564
- {
6565
- providerId: "qwen",
6566
- providerLabel: "Qwen",
6567
- transportKind: "acp",
6568
- transportId: "qwen-acp",
6569
- transportLabel: "Qwen ACP",
6578
+ transportId: "pi-cli",
6579
+ transportLabel: "pi CLI",
6570
6580
  runtime: "local",
6571
- defaultModel: "default",
6572
- defaultCommand: AI_CONNECT_DEFAULT_ACP_COMMANDS["qwen:qwen-acp"]
6581
+ defaultModel: "gemini-3.1-pro-low",
6582
+ defaultCommand: AI_CONNECT_DEFAULT_CLI_COMMANDS["pi:pi-cli"]
6573
6583
  }
6574
6584
  ]
6575
6585
  },
@@ -6742,15 +6752,6 @@ function resolveHomeDir(env) {
6742
6752
  function resolveXdgConfigHome(env) {
6743
6753
  return env.XDG_CONFIG_HOME ?? path.join(resolveHomeDir(env), ".config");
6744
6754
  }
6745
- function resolveGeminiCliHome(env) {
6746
- return env.GEMINI_CLI_HOME ?? resolveHomeDir(env);
6747
- }
6748
- function resolveGeminiDir(env) {
6749
- return path.join(resolveGeminiCliHome(env), ".gemini");
6750
- }
6751
- function resolveQwenDir(env) {
6752
- return path.join(resolveHomeDir(env), ".qwen");
6753
- }
6754
6755
  function resolveCodexHome(env) {
6755
6756
  return env.CODEX_HOME ?? path.join(resolveHomeDir(env), ".codex");
6756
6757
  }
@@ -6796,118 +6797,6 @@ async function removeIfExists(targetPath) {
6796
6797
  }
6797
6798
  });
6798
6799
  }
6799
- async function readJsonFile(filePath) {
6800
- try {
6801
- const raw = await fs.readFile(filePath, "utf8");
6802
- const parsed = JSON.parse(raw);
6803
- return isRecord2(parsed) ? parsed : void 0;
6804
- } catch (error) {
6805
- if (error instanceof SyntaxError || error.code === "ENOENT") {
6806
- return void 0;
6807
- }
6808
- throw error;
6809
- }
6810
- }
6811
- function mergeRecordValues(current, next) {
6812
- const merged = { ...current };
6813
- for (const [key, value] of Object.entries(next)) {
6814
- const existing = merged[key];
6815
- if (isRecord2(existing) && isRecord2(value)) {
6816
- merged[key] = mergeRecordValues(existing, value);
6817
- continue;
6818
- }
6819
- merged[key] = value;
6820
- }
6821
- return merged;
6822
- }
6823
- async function writeJsonFile(filePath, value) {
6824
- await fs.mkdir(path.dirname(filePath), { recursive: true });
6825
- await fs.writeFile(filePath, `${JSON.stringify(value, null, 2)}
6826
- `, "utf8");
6827
- }
6828
- async function prepareGeminiLaunchRuntime(base, launch) {
6829
- if (launch.contextMode === "workspace" && launch.skillsMode === "default") {
6830
- return {
6831
- ...base,
6832
- launch
6833
- };
6834
- }
6835
- const sandbox = await createSandboxPaths("ai-connect-gemini-acp-");
6836
- const originalGeminiDir = resolveGeminiDir(base.env);
6837
- const sandboxGeminiDir = path.join(sandbox.root, ".gemini");
6838
- const settingsPath = path.join(sandboxGeminiDir, "settings.json");
6839
- const systemSettingsPath = path.join(sandbox.config, "gemini-settings.json");
6840
- const systemDefaultsPath = path.join(
6841
- sandbox.config,
6842
- "gemini-system-defaults.json"
6843
- );
6844
- const isolatedCwd = launch.contextMode === "clean" || launch.skillsMode === "disabled" ? sandbox.root : base.cwd;
6845
- await Promise.all(
6846
- [
6847
- "oauth_creds.json",
6848
- "google_accounts.json",
6849
- "mcp-oauth-tokens.json",
6850
- "a2a-oauth-tokens.json",
6851
- "installation_id"
6852
- ].map(
6853
- (fileName) => maybeCopyFile(
6854
- path.join(originalGeminiDir, fileName),
6855
- path.join(sandboxGeminiDir, fileName)
6856
- )
6857
- )
6858
- );
6859
- const settings = mergeRecordValues(
6860
- await readJsonFile(settingsPath) ?? {},
6861
- {
6862
- context: {
6863
- fileName: "AI_CONNECT_CONTEXT_DISABLED.md",
6864
- includeDirectoryTree: false,
6865
- memoryBoundaryMarkers: [],
6866
- includeDirectories: [],
6867
- loadMemoryFromIncludeDirectories: false,
6868
- discoveryMaxDirs: 0
6869
- },
6870
- admin: {
6871
- mcp: {
6872
- enabled: false,
6873
- config: {},
6874
- requiredConfig: {}
6875
- },
6876
- ...launch.skillsMode === "disabled" ? {
6877
- skills: {
6878
- enabled: false
6879
- }
6880
- } : {}
6881
- },
6882
- ...launch.skillsMode === "disabled" ? {
6883
- skills: {
6884
- enabled: false,
6885
- disabled: []
6886
- }
6887
- } : {}
6888
- }
6889
- );
6890
- await Promise.all([
6891
- writeJsonFile(settingsPath, settings),
6892
- writeJsonFile(systemSettingsPath, {}),
6893
- writeJsonFile(systemDefaultsPath, {})
6894
- ]);
6895
- return {
6896
- commandLine: base.commandLine,
6897
- cwd: isolatedCwd,
6898
- env: {
6899
- ...base.env,
6900
- HOME: sandbox.root,
6901
- GEMINI_CLI_HOME: sandbox.root,
6902
- GEMINI_CLI_SYSTEM_SETTINGS_PATH: systemSettingsPath,
6903
- GEMINI_CLI_SYSTEM_DEFAULTS_PATH: systemDefaultsPath
6904
- },
6905
- launch,
6906
- cleanup: async () => {
6907
- await removeIfExists(sandbox.root);
6908
- }
6909
- };
6910
- }
6911
6800
  async function prepareOpenCodeLaunchRuntime(base, launch) {
6912
6801
  if (launch.contextMode === "workspace" && launch.skillsMode === "default") {
6913
6802
  return {
@@ -6956,70 +6845,6 @@ async function prepareOpenCodeLaunchRuntime(base, launch) {
6956
6845
  }
6957
6846
  };
6958
6847
  }
6959
- async function prepareQwenLaunchRuntime(base, launch) {
6960
- if (launch.contextMode === "workspace" && launch.skillsMode === "default") {
6961
- return {
6962
- ...base,
6963
- launch
6964
- };
6965
- }
6966
- const sandbox = await createSandboxPaths("ai-connect-qwen-acp-");
6967
- const originalQwenDir = resolveQwenDir(base.env);
6968
- const sandboxQwenDir = path.join(sandbox.home, ".qwen");
6969
- const settingsPath = path.join(sandboxQwenDir, "settings.json");
6970
- const systemSettingsPath = path.join(sandbox.config, "qwen-settings.json");
6971
- const systemDefaultsPath = path.join(
6972
- sandbox.config,
6973
- "qwen-system-defaults.json"
6974
- );
6975
- await Promise.all(
6976
- [
6977
- "oauth_creds.json",
6978
- "mcp-oauth-tokens.json",
6979
- "installation_id",
6980
- ".env",
6981
- "settings.json"
6982
- ].map(
6983
- (fileName) => maybeCopyFile(
6984
- path.join(originalQwenDir, fileName),
6985
- path.join(sandboxQwenDir, fileName)
6986
- )
6987
- )
6988
- );
6989
- const settings = mergeRecordValues(
6990
- await readJsonFile(settingsPath) ?? {},
6991
- {
6992
- context: {
6993
- fileName: ["AI_CONNECT_CONTEXT_DISABLED.md"],
6994
- includeDirectories: [],
6995
- loadFromIncludeDirectories: false
6996
- },
6997
- model: {
6998
- skipStartupContext: true
6999
- }
7000
- }
7001
- );
7002
- await Promise.all([
7003
- writeJsonFile(settingsPath, settings),
7004
- writeJsonFile(systemSettingsPath, {}),
7005
- writeJsonFile(systemDefaultsPath, {})
7006
- ]);
7007
- const isolatedCwd = launch.contextMode === "clean" || launch.skillsMode === "disabled" ? sandbox.root : base.cwd;
7008
- return {
7009
- commandLine: base.commandLine,
7010
- cwd: isolatedCwd,
7011
- env: {
7012
- ...base.env,
7013
- HOME: sandbox.home,
7014
- QWEN_CODE_SYSTEM_SETTINGS_PATH: systemSettingsPath,
7015
- QWEN_CODE_SYSTEM_DEFAULTS_PATH: systemDefaultsPath
7016
- },
7017
- launch,
7018
- cleanup: async () => {
7019
- await removeIfExists(sandbox.root);
7020
- }
7021
- };
7022
- }
7023
6848
  async function prepareCodexLaunchRuntime(base, launch) {
7024
6849
  if (launch.contextMode === "workspace" && launch.skillsMode === "default") {
7025
6850
  return {
@@ -7137,15 +6962,9 @@ async function prepareAcpLaunchRuntime(route, options, commandLine, cwdOverride)
7137
6962
  ...options?.env ?? {}
7138
6963
  }
7139
6964
  };
7140
- if (route.transport.id === "gemini-acp") {
7141
- return prepareGeminiLaunchRuntime(base, launch);
7142
- }
7143
6965
  if (route.transport.id === "opencode-acp") {
7144
6966
  return prepareOpenCodeLaunchRuntime(base, launch);
7145
6967
  }
7146
- if (route.transport.id === "qwen-acp") {
7147
- return prepareQwenLaunchRuntime(base, launch);
7148
- }
7149
6968
  if (route.transport.id === "codex-acp") {
7150
6969
  return prepareCodexLaunchRuntime(base, launch);
7151
6970
  }
@@ -7726,10 +7545,6 @@ function buildAcpLifecycle(route, authRequest, mode) {
7726
7545
  steps.push("authenticate");
7727
7546
  }
7728
7547
  steps.push("session/new");
7729
- if (mode === "prompt" && route.transport.id === "gemini-acp") {
7730
- steps.push("session/set_model");
7731
- keys.push("session/set_model.modelId");
7732
- }
7733
7548
  if (mode === "prompt") {
7734
7549
  steps.push("session/prompt");
7735
7550
  }
@@ -7865,16 +7680,6 @@ var AcpConnection = class {
7865
7680
  );
7866
7681
  }
7867
7682
  const sessionId = session.sessionId;
7868
- if (context.route.transport.id === "gemini-acp") {
7869
- await measurePhase(
7870
- transport.phases ?? [],
7871
- "session/set_model",
7872
- async () => this.request("session/set_model", {
7873
- sessionId,
7874
- modelId: context.route.model
7875
- })
7876
- );
7877
- }
7878
7683
  this.activePrompt = {
7879
7684
  sessionId,
7880
7685
  text: "",
@@ -7966,6 +7771,10 @@ var AcpConnection = class {
7966
7771
  `ACP route "${context.route.id}" did not return an ACP model catalog in session/new.`
7967
7772
  );
7968
7773
  }
7774
+ catalog.availableModels = fillConfiguredContextLength(
7775
+ context.route,
7776
+ catalog.availableModels
7777
+ );
7969
7778
  return catalog;
7970
7779
  } finally {
7971
7780
  this.bumpIdleTimer();
@@ -8383,9 +8192,6 @@ var AcpConnection = class {
8383
8192
  });
8384
8193
  }
8385
8194
  };
8386
- function isGeminiAcpFallbackCandidate(route, commandLine) {
8387
- return route.transport.id === "gemini-acp" && commandLine.includes("--acp") && !commandLine.includes("--experimental-acp");
8388
- }
8389
8195
  function cacheKeyForConnection(route, runtime, options) {
8390
8196
  const envEntries = Object.entries(options.env).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key}=${value}`).join("");
8391
8197
  return [
@@ -8632,18 +8438,7 @@ function createAcpTransportManager(options) {
8632
8438
  }
8633
8439
  async function drivePrompt(context, onDelta) {
8634
8440
  const commandLine = resolveAcpCommand(context.route, options);
8635
- try {
8636
- return await runWithCommand(context, commandLine, onDelta);
8637
- } catch (error) {
8638
- if (!isGeminiAcpFallbackCandidate(context.route, commandLine)) {
8639
- throw error;
8640
- }
8641
- return await runWithCommand(
8642
- context,
8643
- commandLine.replace("--acp", "--experimental-acp"),
8644
- onDelta
8645
- );
8646
- }
8441
+ return await runWithCommand(context, commandLine, onDelta);
8647
8442
  }
8648
8443
  return {
8649
8444
  async runPrompt(context) {
@@ -8712,17 +8507,7 @@ function createAcpTransportManager(options) {
8712
8507
  },
8713
8508
  async discoverModels(context) {
8714
8509
  const commandLine = resolveAcpCommand(context.route, options);
8715
- try {
8716
- return await discoverWithCommand(context, commandLine);
8717
- } catch (error) {
8718
- if (!isGeminiAcpFallbackCandidate(context.route, commandLine)) {
8719
- throw error;
8720
- }
8721
- return await discoverWithCommand(
8722
- context,
8723
- commandLine.replace("--acp", "--experimental-acp")
8724
- );
8725
- }
8510
+ return await discoverWithCommand(context, commandLine);
8726
8511
  },
8727
8512
  async dispose() {
8728
8513
  const values = [...connectionPools.values()].flat();
@@ -8747,14 +8532,6 @@ import fs2 from "node:fs/promises";
8747
8532
  import os2 from "node:os";
8748
8533
  import path2 from "node:path";
8749
8534
  var CLI_PRESET_ACP_DISCOVERY_DEFAULTS = {
8750
- gemini: {
8751
- transportId: "gemini-acp",
8752
- providerId: "gemini"
8753
- },
8754
- qwen: {
8755
- transportId: "qwen-acp",
8756
- providerId: "qwen"
8757
- },
8758
8535
  claude: {
8759
8536
  transportId: "claude-code-acp",
8760
8537
  providerId: "anthropic"
@@ -8861,10 +8638,8 @@ function buildCliCwd(context, options) {
8861
8638
  }
8862
8639
  function defaultCliPresetIdForRoute(route) {
8863
8640
  switch (route.transport.id) {
8864
- case "gemini-cli":
8865
- return "gemini";
8866
- case "qwen-cli":
8867
- return "qwen";
8641
+ case "pi-cli":
8642
+ return "pi";
8868
8643
  case "claude-cli":
8869
8644
  return "claude";
8870
8645
  case "openclaude-cli":
@@ -8907,14 +8682,26 @@ function normalizeCliDiscoveryAcpSource(route, discovery) {
8907
8682
  };
8908
8683
  }
8909
8684
  function resolveCliDiscoverySource(route) {
8910
- const via = route.transport.cli?.discovery?.via ?? (defaultCliDiscoveryTransportIdForRoute(route) ? "acp" : "none");
8911
- if (via === "none") {
8912
- return { via: "none" };
8685
+ const discovery = route.transport.cli?.discovery;
8686
+ const hasModels = (route.advertisedModels?.length ?? 0) > 0;
8687
+ const hasAcpDefault = Boolean(defaultCliDiscoveryTransportIdForRoute(route));
8688
+ const via = discovery?.via ?? (discovery?.command ? "command" : hasAcpDefault ? "acp" : hasModels ? "static" : "none");
8689
+ switch (via) {
8690
+ case "none":
8691
+ return { via: "none" };
8692
+ case "static":
8693
+ return { via: "static" };
8694
+ case "command": {
8695
+ const command = discovery?.command;
8696
+ if (!command) {
8697
+ return { via: "none" };
8698
+ }
8699
+ const fallback = discovery?.fallback ?? (hasModels ? "static" : "none");
8700
+ return { via: "command", command, fallback };
8701
+ }
8702
+ default:
8703
+ return normalizeCliDiscoveryAcpSource(route, discovery?.acp);
8913
8704
  }
8914
- return normalizeCliDiscoveryAcpSource(
8915
- route,
8916
- route.transport.cli?.discovery?.acp
8917
- );
8918
8705
  }
8919
8706
  function createCliDiscoveryAcpRoute(route) {
8920
8707
  const discovery = resolveCliDiscoverySource(route);
@@ -9023,6 +8810,42 @@ async function buildCliInvocation(context, options) {
9023
8810
  parameterKeys
9024
8811
  };
9025
8812
  }
8813
+ function buildCliDiscoveryInvocation(route, command, options) {
8814
+ const commandLine = command.command?.trim() ? command.command : resolveCliCommand(route, options);
8815
+ const resolved = splitCommandLine2(commandLine);
8816
+ const parameterKeys = [];
8817
+ const args = [
8818
+ ...resolved.args,
8819
+ ...command.argsTemplate.map((part) => {
8820
+ if (part === "{model}") {
8821
+ parameterKeys.push("model");
8822
+ return route.model;
8823
+ }
8824
+ if (part.startsWith("--")) {
8825
+ parameterKeys.push(part);
8826
+ }
8827
+ return part;
8828
+ })
8829
+ ];
8830
+ return {
8831
+ command: resolved.command,
8832
+ args,
8833
+ cwd: path2.resolve(options?.cwd ?? process.cwd()),
8834
+ env: buildCliEnvironment(options),
8835
+ parameterKeys
8836
+ };
8837
+ }
8838
+ function buildStaticCliCatalog(route) {
8839
+ const models = route.advertisedModels.map((id) => ({
8840
+ modelId: id,
8841
+ name: id
8842
+ }));
8843
+ return buildModelCatalog(
8844
+ route,
8845
+ models,
8846
+ currentModelIdForRoute(route, route.advertisedModels)
8847
+ );
8848
+ }
9026
8849
  function statsToUsage(stats) {
9027
8850
  if (!stats || typeof stats !== "object") {
9028
8851
  return void 0;
@@ -9060,6 +8883,9 @@ function getValueByPath(value, dotPath) {
9060
8883
  }
9061
8884
  return current;
9062
8885
  }
8886
+ function splitJsonlLines(stdout) {
8887
+ return stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => JSON.parse(line));
8888
+ }
9063
8889
  function normalizeErrorMessage(value) {
9064
8890
  if (typeof value === "string" && value.trim()) {
9065
8891
  return value.trim();
@@ -9107,7 +8933,7 @@ function parseGenericJsonCli(stdout, parser) {
9107
8933
  };
9108
8934
  }
9109
8935
  function parseGenericJsonlCli(stdout, parser) {
9110
- const entries = stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => JSON.parse(line));
8936
+ const entries = splitJsonlLines(stdout);
9111
8937
  const errorValue = parser.error ? findJsonlSelection(entries, parser.error) : void 0;
9112
8938
  const errorMessage = normalizeErrorMessage(errorValue);
9113
8939
  if (errorMessage) {
@@ -9128,59 +8954,69 @@ function parseGenericJsonlCli(stdout, parser) {
9128
8954
  data: entries
9129
8955
  };
9130
8956
  }
9131
- function parseGeminiCli(stdout) {
9132
- const payload = JSON.parse(stdout);
9133
- if (typeof payload.error === "string") {
9134
- throw new AiConnectError("temporary_unavailable", payload.error);
8957
+ var ANSI_ESCAPE_PATTERN = /\[[0-?]*[ -/]*[@-~]/g;
8958
+ function parseTextCli(stdout, parser) {
8959
+ let text = stdout;
8960
+ if (parser.stripAnsi) {
8961
+ text = text.replace(ANSI_ESCAPE_PATTERN, "");
9135
8962
  }
9136
- if (typeof payload.response !== "string") {
8963
+ if (parser.trim !== false) {
8964
+ text = text.trim();
8965
+ }
8966
+ if (!text) {
9137
8967
  throw new AiConnectError(
9138
8968
  "temporary_unavailable",
9139
- "Gemini CLI JSON output did not include response text."
8969
+ "CLI text parser produced no output."
9140
8970
  );
9141
8971
  }
9142
- const usage = payload.stats ? statsToUsage(payload.stats) : void 0;
8972
+ return { text, data: stdout };
8973
+ }
8974
+ function modelInfoFromRecord(record, selector) {
8975
+ const rawId = getValueByPath(record, selector.idPath);
8976
+ const modelId = typeof rawId === "string" ? rawId.trim() : "";
8977
+ if (!modelId) {
8978
+ return void 0;
8979
+ }
8980
+ const rawName = selector.namePath ? getValueByPath(record, selector.namePath) : void 0;
8981
+ const name = typeof rawName === "string" && rawName.trim().length > 0 ? rawName : modelId;
8982
+ const rawDescription = selector.descriptionPath ? getValueByPath(record, selector.descriptionPath) : void 0;
8983
+ const description = typeof rawDescription === "string" && rawDescription.trim().length > 0 ? rawDescription : void 0;
8984
+ const rawContext = selector.contextLengthPath ? getValueByPath(record, selector.contextLengthPath) : void 0;
8985
+ const contextLength = typeof rawContext === "number" && Number.isFinite(rawContext) && rawContext > 0 ? Math.floor(rawContext) : void 0;
9143
8986
  return {
9144
- text: payload.response,
9145
- ...usage ? { usage } : {},
9146
- data: payload
8987
+ modelId,
8988
+ name,
8989
+ ...description ? { description } : {},
8990
+ ...contextLength !== void 0 ? { contextLength } : {}
9147
8991
  };
9148
8992
  }
9149
- function parseQwenCli(stdout) {
9150
- const payload = JSON.parse(stdout);
9151
- if (!Array.isArray(payload)) {
9152
- throw new AiConnectError(
9153
- "temporary_unavailable",
9154
- "Qwen CLI JSON output did not return a message array."
9155
- );
9156
- }
9157
- const resultMessage = payload.find(
9158
- (entry) => entry && typeof entry === "object" && entry.type === "result"
9159
- );
9160
- if (!resultMessage) {
9161
- throw new AiConnectError(
9162
- "temporary_unavailable",
9163
- "Qwen CLI JSON output did not contain a result message."
9164
- );
8993
+ function parseCliModelList(stdout, parser, selector) {
8994
+ if (parser.kind === "text") {
8995
+ let text = stdout;
8996
+ if (parser.stripAnsi) {
8997
+ text = text.replace(ANSI_ESCAPE_PATTERN, "");
8998
+ }
8999
+ return text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => ({ modelId: line, name: line }));
9165
9000
  }
9166
- if (resultMessage.is_error === true) {
9001
+ if (!selector) {
9167
9002
  throw new AiConnectError(
9168
- "temporary_unavailable",
9169
- typeof resultMessage.result === "string" ? resultMessage.result : "Qwen CLI returned an error result."
9003
+ "validation_error",
9004
+ `CLI ${parser.kind} discovery parser requires a models selector with idPath.`
9170
9005
  );
9171
9006
  }
9172
- if (typeof resultMessage.result !== "string") {
9173
- throw new AiConnectError(
9174
- "temporary_unavailable",
9175
- "Qwen CLI result message did not contain text output."
9176
- );
9007
+ const records = parser.kind === "jsonl" ? splitJsonlLines(stdout) : (() => {
9008
+ const payload = JSON.parse(stdout);
9009
+ const arr = selector.path ? getValueByPath(payload, selector.path) : payload;
9010
+ return Array.isArray(arr) ? arr : [];
9011
+ })();
9012
+ const models = [];
9013
+ for (const record of records) {
9014
+ const info = modelInfoFromRecord(record, selector);
9015
+ if (info) {
9016
+ models.push(info);
9017
+ }
9177
9018
  }
9178
- const usage = resultMessage.stats ? statsToUsage(resultMessage.stats) : void 0;
9179
- return {
9180
- text: resultMessage.result,
9181
- ...usage ? { usage } : {},
9182
- data: payload
9183
- };
9019
+ return models;
9184
9020
  }
9185
9021
  function parseClaudeCli(stdout) {
9186
9022
  const payload = JSON.parse(stdout);
@@ -9199,8 +9035,7 @@ function parseClaudeCli(stdout) {
9199
9035
  };
9200
9036
  }
9201
9037
  function parseCodexCli(stdout, outputFileContent) {
9202
- const lines = stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
9203
- const events = lines.map((line) => JSON.parse(line));
9038
+ const events = splitJsonlLines(stdout);
9204
9039
  let text = outputFileContent?.trim() || void 0;
9205
9040
  let usage;
9206
9041
  for (const event of events) {
@@ -9258,13 +9093,18 @@ function parseCliResult(route, result, outputFileContent) {
9258
9093
  `CLI route "${route.id}" declared a parser override but normalization did not preserve it.`
9259
9094
  );
9260
9095
  }
9261
- return parser.kind === "json" ? parseGenericJsonCli(stdout, parser) : parseGenericJsonlCli(stdout, parser);
9096
+ switch (parser.kind) {
9097
+ case "text":
9098
+ return parseTextCli(result.stdout, parser);
9099
+ case "json":
9100
+ return parseGenericJsonCli(stdout, parser);
9101
+ default:
9102
+ return parseGenericJsonlCli(stdout, parser);
9103
+ }
9262
9104
  }
9263
9105
  switch (cliOptions.preset) {
9264
- case "gemini":
9265
- return parseGeminiCli(stdout);
9266
- case "qwen":
9267
- return parseQwenCli(stdout);
9106
+ case "pi":
9107
+ return parseTextCli(stdout, { kind: "text" });
9268
9108
  case "claude":
9269
9109
  case "openclaude":
9270
9110
  return parseClaudeCli(stdout);
@@ -9361,6 +9201,88 @@ async function cleanupCliInvocation(invocation) {
9361
9201
  }
9362
9202
  function createCliTransportManager(options) {
9363
9203
  return {
9204
+ async discoverModels(context) {
9205
+ const source = resolveCliDiscoverySource(context.route);
9206
+ if (source.via === "none") {
9207
+ throw new AiConnectError(
9208
+ "not_supported",
9209
+ `CLI transport "${context.route.transport.id}" does not support model discovery (no list command, no ACP sidecar, and no configured models[]).`
9210
+ );
9211
+ }
9212
+ if (source.via === "acp") {
9213
+ throw new AiConnectError(
9214
+ "not_supported",
9215
+ `CLI route "${context.route.id}" resolves discovery via acp; dispatch to the acp discovery route instead.`
9216
+ );
9217
+ }
9218
+ if (source.via === "static") {
9219
+ return buildStaticCliCatalog(context.route);
9220
+ }
9221
+ const phases = [];
9222
+ const invocation = buildCliDiscoveryInvocation(
9223
+ context.route,
9224
+ source.command,
9225
+ options
9226
+ );
9227
+ context.telemetry?.captureTransport({
9228
+ protocol: "cli",
9229
+ endpoint: invocation.command,
9230
+ method: "process",
9231
+ bodyKeys: ["argv"],
9232
+ parameterKeys: invocation.parameterKeys,
9233
+ phases,
9234
+ stream: false
9235
+ });
9236
+ try {
9237
+ let models;
9238
+ try {
9239
+ const execution = await executeCliInvocation(
9240
+ invocation,
9241
+ options?.timeoutMs ?? 6e4,
9242
+ phases,
9243
+ context.abort.signal
9244
+ );
9245
+ if (execution.exitCode !== 0 || !execution.stdout.trim()) {
9246
+ throw new AiConnectError(
9247
+ "temporary_unavailable",
9248
+ execution.stderr.trim() || `CLI discovery command for "${context.route.transport.id}" exited with code ${execution.exitCode ?? "null"}.`
9249
+ );
9250
+ }
9251
+ models = parseCliModelList(
9252
+ execution.stdout,
9253
+ source.command.parser,
9254
+ source.command.models
9255
+ );
9256
+ } catch (error) {
9257
+ if (error instanceof AiConnectError && error.code === "aborted") {
9258
+ throw error;
9259
+ }
9260
+ if (source.fallback === "static") {
9261
+ return buildStaticCliCatalog(context.route);
9262
+ }
9263
+ throw error;
9264
+ }
9265
+ if (models.length === 0) {
9266
+ if (source.fallback === "static") {
9267
+ return buildStaticCliCatalog(context.route);
9268
+ }
9269
+ throw new AiConnectError(
9270
+ "temporary_unavailable",
9271
+ `CLI discovery command for "${context.route.transport.id}" returned no models.`
9272
+ );
9273
+ }
9274
+ return buildModelCatalog(
9275
+ context.route,
9276
+ models,
9277
+ currentModelIdForRoute(
9278
+ context.route,
9279
+ models.map((model) => model.modelId)
9280
+ )
9281
+ );
9282
+ } finally {
9283
+ await cleanupCliInvocation(invocation);
9284
+ }
9285
+ },
9364
9286
  async runPrompt(context) {
9365
9287
  const invocation = await buildCliInvocation(context, options);
9366
9288
  const phases = [];
@@ -9880,17 +9802,21 @@ function createLocalRouteHandlers(options = {}) {
9880
9802
  return cliTransport.runPrompt(context);
9881
9803
  },
9882
9804
  async discoverModels(context) {
9883
- const discoveryRoute = createCliDiscoveryAcpRoute(context.route);
9884
- if (!discoveryRoute) {
9885
- throw new AiConnectError(
9886
- "not_supported",
9887
- `CLI route "${context.route.id}" does not define a model discovery backend.`
9888
- );
9805
+ const source = resolveCliDiscoverySource(context.route);
9806
+ if (source.via === "acp") {
9807
+ const discoveryRoute = createCliDiscoveryAcpRoute(context.route);
9808
+ if (!discoveryRoute) {
9809
+ throw new AiConnectError(
9810
+ "not_supported",
9811
+ `CLI route "${context.route.id}" does not define a model discovery backend.`
9812
+ );
9813
+ }
9814
+ return acpTransport.discoverModels({
9815
+ ...context,
9816
+ route: discoveryRoute
9817
+ });
9889
9818
  }
9890
- return acpTransport.discoverModels({
9891
- ...context,
9892
- route: discoveryRoute
9893
- });
9819
+ return cliTransport.discoverModels(context);
9894
9820
  },
9895
9821
  async verify({ route, runtime }) {
9896
9822
  try {