@vedmalex/ai-connect 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bun/index.js CHANGED
@@ -1,3 +1,125 @@
1
+ // src/cli-presets.ts
2
+ var AI_CONNECT_DEFAULT_CLI_PRESETS = {
3
+ pi: {
4
+ id: "pi",
5
+ label: "pi (coding agent)",
6
+ command: "pi",
7
+ transportId: "pi-cli",
8
+ options: {
9
+ preset: "pi",
10
+ // pi print mode emits the answer as plain text on stdout (no JSON wrapper),
11
+ // and has no ACP mode — so it uses the raw `text` parser and no discovery sidecar.
12
+ // pi takes file/image attachments as separate `@<path>` argv tokens (REQ-024);
13
+ // {files} expands one `@<staged-path>` token per attachment. pi stages all
14
+ // categories (PDFs as-is, no extraction guarantee).
15
+ argsTemplate: ["--print", "--model", "{model}", "{files}", "{prompt}"],
16
+ discovery: {
17
+ via: "none"
18
+ },
19
+ parser: {
20
+ kind: "text"
21
+ },
22
+ fileInput: {
23
+ placement: "args",
24
+ perFileArgs: ["@{path}"]
25
+ }
26
+ }
27
+ },
28
+ claude: {
29
+ id: "claude",
30
+ label: "Claude CLI",
31
+ command: "claude",
32
+ transportId: "claude-cli",
33
+ options: {
34
+ preset: "claude",
35
+ argsTemplate: ["--print", "--output-format", "json", "--model", "{model}", "{prompt}"],
36
+ discovery: {
37
+ via: "acp",
38
+ acp: {
39
+ transportId: "claude-code-acp"
40
+ }
41
+ },
42
+ parser: {
43
+ kind: "json",
44
+ textPath: "__preset__:claude.result",
45
+ usagePath: "usage",
46
+ errorPath: "__preset__:claude.error"
47
+ }
48
+ }
49
+ },
50
+ openclaude: {
51
+ id: "openclaude",
52
+ label: "OpenClaude CLI",
53
+ command: "openclaude",
54
+ transportId: "openclaude-cli",
55
+ options: {
56
+ preset: "openclaude",
57
+ argsTemplate: ["--print", "--output-format", "json", "--model", "{model}", "{prompt}"],
58
+ parser: {
59
+ kind: "json",
60
+ textPath: "__preset__:claude.result",
61
+ usagePath: "usage",
62
+ errorPath: "__preset__:claude.error"
63
+ }
64
+ }
65
+ },
66
+ codex: {
67
+ id: "codex",
68
+ label: "Codex CLI",
69
+ command: "codex",
70
+ transportId: "codex-cli",
71
+ options: {
72
+ preset: "codex",
73
+ // codex exec takes image attachments via `-i/--image <FILE>` flags
74
+ // (REQ-024); {files} expands a `--image <staged-path>` pair per image.
75
+ // Images only — a non-image attachment cleanly rejects pre-spawn.
76
+ argsTemplate: [
77
+ "exec",
78
+ "--json",
79
+ "--output-last-message",
80
+ "{output_file}",
81
+ "--model",
82
+ "{model}",
83
+ "{files}",
84
+ "{prompt}"
85
+ ],
86
+ discovery: {
87
+ via: "acp",
88
+ acp: {
89
+ transportId: "codex-acp"
90
+ }
91
+ },
92
+ fileInput: {
93
+ placement: "args",
94
+ perFileArgs: ["--image", "{path}"],
95
+ categories: ["image"]
96
+ },
97
+ parser: {
98
+ kind: "jsonl",
99
+ text: {
100
+ path: "__preset__:codex.text"
101
+ },
102
+ usage: {
103
+ path: "__preset__:codex.usage"
104
+ },
105
+ error: {
106
+ path: "__preset__:codex.error"
107
+ }
108
+ }
109
+ }
110
+ }
111
+ };
112
+ var AI_CONNECT_DEFAULT_CLI_COMMANDS = {
113
+ "anthropic:claude-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.claude.command,
114
+ "anthropic:openclaude-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.openclaude.command,
115
+ "openclaude:openclaude-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.openclaude.command,
116
+ "openai:codex-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.codex.command,
117
+ "pi:pi-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.pi.command
118
+ };
119
+ function getCliTransportPreset(presetId) {
120
+ return AI_CONNECT_DEFAULT_CLI_PRESETS[presetId];
121
+ }
122
+
1
123
  // src/errors.ts
2
124
  var AiConnectError = class extends Error {
3
125
  code;
@@ -345,9 +467,38 @@ function normalizeTransport(providerId, input) {
345
467
  ...fallback !== void 0 ? { fallback } : {}
346
468
  };
347
469
  })();
470
+ const fileInput = (() => {
471
+ const fi = descriptor.cli?.fileInput;
472
+ if (!fi) {
473
+ return void 0;
474
+ }
475
+ const validCategories = ["image", "document", "text", "other"];
476
+ if (fi.categories) {
477
+ for (const c of fi.categories) {
478
+ assert(
479
+ validCategories.includes(c),
480
+ `Unsupported CLI fileInput category "${String(c)}" for provider "${providerId}".`
481
+ );
482
+ }
483
+ }
484
+ const placement = fi.placement ?? "args";
485
+ assert(
486
+ placement === "args" || placement === "prompt",
487
+ `Unsupported CLI fileInput placement "${String(placement)}" for provider "${providerId}".`
488
+ );
489
+ return {
490
+ placement,
491
+ ...fi.perFileArgs ? { perFileArgs: fi.perFileArgs.map((part) => String(part)) } : {},
492
+ ...fi.mentionTemplate?.trim() ? { mentionTemplate: fi.mentionTemplate.trim() } : {},
493
+ ...fi.separator !== void 0 ? { separator: fi.separator } : {},
494
+ ...fi.categories ? { categories: [...fi.categories] } : {},
495
+ ...fi.stagingDir?.trim() ? { stagingDir: fi.stagingDir.trim() } : {}
496
+ };
497
+ })();
348
498
  return {
349
499
  ...normalized,
350
- ...discovery ? { discovery } : {}
500
+ ...discovery ? { discovery } : {},
501
+ ...fileInput ? { fileInput } : {}
351
502
  };
352
503
  })();
353
504
  const normalizedAuth = descriptor.auth?.methodId?.trim() ? {
@@ -383,7 +534,7 @@ function normalizeTransport(providerId, input) {
383
534
  };
384
535
  }
385
536
  if (descriptor.kind === "cli") {
386
- const inferredId2 = providerId === "anthropic" ? "claude-cli" : providerId === "openclaude" ? "openclaude-cli" : providerId === "openai" ? "codex-cli" : providerId === "gemini" ? "gemini-cli" : providerId === "qwen" ? "qwen-cli" : "cli";
537
+ const inferredId2 = providerId === "anthropic" ? "claude-cli" : providerId === "openclaude" ? "openclaude-cli" : providerId === "openai" ? "codex-cli" : providerId === "pi" ? "pi-cli" : "cli";
387
538
  return {
388
539
  kind: "cli",
389
540
  id: descriptor.id?.trim() || inferredId2,
@@ -400,7 +551,7 @@ function normalizeTransport(providerId, input) {
400
551
  ...descriptor.baseUrl?.trim() ? { baseUrl: descriptor.baseUrl.trim() } : {}
401
552
  };
402
553
  }
403
- const inferredId = providerId === "anthropic" ? "claude-code-acp" : providerId === "openai" ? "codex-acp" : providerId === "gemini" ? "gemini-acp" : "acp";
554
+ const inferredId = providerId === "anthropic" ? "claude-code-acp" : providerId === "openai" ? "codex-acp" : "acp";
404
555
  const normalizedLaunch = descriptor.launch ? (() => {
405
556
  const contextMode = descriptor.launch?.contextMode ?? "workspace";
406
557
  const skillsMode = descriptor.launch?.skillsMode ?? "default";
@@ -435,7 +586,40 @@ function normalizeRuntime(transport, runtime) {
435
586
  }
436
587
  return "universal";
437
588
  }
589
+ function cliPresetIdForTransportId(transportId) {
590
+ switch (transportId) {
591
+ case "pi-cli":
592
+ return "pi";
593
+ case "claude-cli":
594
+ return "claude";
595
+ case "openclaude-cli":
596
+ return "openclaude";
597
+ case "codex-cli":
598
+ return "codex";
599
+ default:
600
+ return void 0;
601
+ }
602
+ }
603
+ function cliFileInputCategories(transport) {
604
+ if (transport.kind !== "cli") {
605
+ return /* @__PURE__ */ new Set();
606
+ }
607
+ const presetId = transport.cli?.preset ?? cliPresetIdForTransportId(transport.id);
608
+ const presetFileInput = presetId ? AI_CONNECT_DEFAULT_CLI_PRESETS[presetId]?.options.fileInput : void 0;
609
+ const routeFileInput = transport.cli?.fileInput;
610
+ if (!presetFileInput && !routeFileInput) {
611
+ return /* @__PURE__ */ new Set();
612
+ }
613
+ const merged = {
614
+ ...presetFileInput ?? {},
615
+ ...routeFileInput ?? {}
616
+ };
617
+ return new Set(
618
+ merged.categories ?? ["image", "document", "text", "other"]
619
+ );
620
+ }
438
621
  function defaultCapabilities(transport, runtime) {
622
+ const cliFileCategories = cliFileInputCategories(transport);
439
623
  const localOnly = runtime === "local" || transport.kind === "acp" || transport.kind === "cli" || transport.kind === "server";
440
624
  const browserSafe = !localOnly;
441
625
  return {
@@ -450,10 +634,12 @@ function defaultCapabilities(transport, runtime) {
450
634
  supportsClientToolExecution: transport.kind === "api",
451
635
  // C6 / F-P-4: api routes now advertise document + image input parity with acp.
452
636
  // The per-account `capabilities` override remains the escape hatch for
453
- // text-only api proxies that must opt out.
454
- supportsFileUpload: transport.kind === "acp" || transport.kind === "api",
637
+ // text-only api proxies that must opt out. REQ-024: a cli route advertises
638
+ // document/image input ONLY for the categories its fileInput actually stages
639
+ // (category-aware so e.g. an image-only cli cleanly rejects a PDF pre-spawn).
640
+ supportsFileUpload: transport.kind === "acp" || transport.kind === "api" || cliFileCategories.has("document"),
455
641
  supportsFileOutput: transport.kind === "acp",
456
- supportsImageInput: transport.kind === "acp" || transport.kind === "api",
642
+ supportsImageInput: transport.kind === "acp" || transport.kind === "api" || cliFileCategories.has("image"),
457
643
  supportsImageOutput: transport.kind === "acp",
458
644
  supportsProfileVerification: true,
459
645
  supportsCredentialRotation: false,
@@ -1004,6 +1190,10 @@ function extension(value) {
1004
1190
  }
1005
1191
  return name.slice(index + 1).toLowerCase();
1006
1192
  }
1193
+ function extensionForMime(mimeType) {
1194
+ const subtype = mimeType.split("/")[1]?.toLowerCase().replace(/[^a-z0-9.+-]/g, "");
1195
+ return subtype && subtype.length > 0 ? subtype : "bin";
1196
+ }
1007
1197
  function isRemoteUrl(value) {
1008
1198
  try {
1009
1199
  const url = new URL(value);
@@ -1638,18 +1828,14 @@ var MODEL_REFERENCE = {
1638
1828
  key: "gemini-3.1-flash-lite",
1639
1829
  contextLength: 1048576
1640
1830
  },
1831
+ "gemini-3.1-pro": { key: "gemini-3.1-pro", contextLength: 1048576 },
1641
1832
  "gemini-3-pro": { key: "gemini-3-pro", contextLength: 2097152 },
1642
1833
  "auto-gemini-3": { key: "auto-gemini-3", contextLength: 1048576 },
1643
1834
  "gemini-2.5-flash": { key: "gemini-2.5-flash", contextLength: 1048576 },
1644
1835
  "gemini-2.5-pro": { key: "gemini-2.5-pro", contextLength: 2097152 },
1645
1836
  "gemini-2.0-flash": { key: "gemini-2.0-flash", contextLength: 1048576 },
1646
1837
  "gemini-1.5-flash": { key: "gemini-1.5-flash", contextLength: 1048576 },
1647
- "gemini-1.5-pro": { key: "gemini-1.5-pro", contextLength: 2097152 },
1648
- // --- Qwen (catalog: qwen3-coder-plus) ---
1649
- "qwen3-coder-plus": { key: "qwen3-coder-plus", contextLength: 1048576 },
1650
- "qwen3-coder": { key: "qwen3-coder", contextLength: 262144 },
1651
- "qwen-max": { key: "qwen-max", contextLength: 32768 },
1652
- "qwen-plus": { key: "qwen-plus", contextLength: 131072 }
1838
+ "gemini-1.5-pro": { key: "gemini-1.5-pro", contextLength: 2097152 }
1653
1839
  };
1654
1840
  function normalizeModelKey(model) {
1655
1841
  let key = model.trim().toLowerCase();
@@ -6343,144 +6529,9 @@ function createDefaultRouteHandlers(options = {}) {
6343
6529
  var AI_CONNECT_DEFAULT_ACP_COMMANDS = {
6344
6530
  "anthropic:claude-code-acp": "npx -y @agentclientprotocol/claude-agent-acp@^0.25.0",
6345
6531
  "openai:codex-acp": "npx @zed-industries/codex-acp@^0.11.1",
6346
- "gemini:gemini-acp": "gemini --acp",
6347
- "qwen:qwen-acp": "qwen --acp",
6348
6532
  "opencode:opencode-acp": "opencode acp"
6349
6533
  };
6350
6534
 
6351
- // src/cli-presets.ts
6352
- var AI_CONNECT_DEFAULT_CLI_PRESETS = {
6353
- gemini: {
6354
- id: "gemini",
6355
- label: "Gemini CLI",
6356
- command: "gemini",
6357
- transportId: "gemini-cli",
6358
- options: {
6359
- preset: "gemini",
6360
- argsTemplate: ["-p", "{prompt}", "--output-format", "json", "--model", "{model}"],
6361
- discovery: {
6362
- via: "acp",
6363
- acp: {
6364
- transportId: "gemini-acp"
6365
- }
6366
- },
6367
- parser: {
6368
- kind: "json",
6369
- textPath: "response",
6370
- usagePath: "stats",
6371
- errorPath: "error"
6372
- }
6373
- }
6374
- },
6375
- qwen: {
6376
- id: "qwen",
6377
- label: "Qwen CLI",
6378
- command: "qwen",
6379
- transportId: "qwen-cli",
6380
- options: {
6381
- preset: "qwen",
6382
- argsTemplate: ["-p", "{prompt}", "--output-format", "json", "--model", "{model}"],
6383
- discovery: {
6384
- via: "acp",
6385
- acp: {
6386
- transportId: "qwen-acp"
6387
- }
6388
- },
6389
- parser: {
6390
- kind: "json",
6391
- textPath: "__preset__:qwen.result",
6392
- usagePath: "__preset__:qwen.stats",
6393
- errorPath: "__preset__:qwen.error"
6394
- }
6395
- }
6396
- },
6397
- claude: {
6398
- id: "claude",
6399
- label: "Claude CLI",
6400
- command: "claude",
6401
- transportId: "claude-cli",
6402
- options: {
6403
- preset: "claude",
6404
- argsTemplate: ["--print", "--output-format", "json", "--model", "{model}", "{prompt}"],
6405
- discovery: {
6406
- via: "acp",
6407
- acp: {
6408
- transportId: "claude-code-acp"
6409
- }
6410
- },
6411
- parser: {
6412
- kind: "json",
6413
- textPath: "__preset__:claude.result",
6414
- usagePath: "usage",
6415
- errorPath: "__preset__:claude.error"
6416
- }
6417
- }
6418
- },
6419
- openclaude: {
6420
- id: "openclaude",
6421
- label: "OpenClaude CLI",
6422
- command: "openclaude",
6423
- transportId: "openclaude-cli",
6424
- options: {
6425
- preset: "openclaude",
6426
- argsTemplate: ["--print", "--output-format", "json", "--model", "{model}", "{prompt}"],
6427
- parser: {
6428
- kind: "json",
6429
- textPath: "__preset__:claude.result",
6430
- usagePath: "usage",
6431
- errorPath: "__preset__:claude.error"
6432
- }
6433
- }
6434
- },
6435
- codex: {
6436
- id: "codex",
6437
- label: "Codex CLI",
6438
- command: "codex",
6439
- transportId: "codex-cli",
6440
- options: {
6441
- preset: "codex",
6442
- argsTemplate: [
6443
- "exec",
6444
- "--json",
6445
- "--output-last-message",
6446
- "{output_file}",
6447
- "--model",
6448
- "{model}",
6449
- "{prompt}"
6450
- ],
6451
- discovery: {
6452
- via: "acp",
6453
- acp: {
6454
- transportId: "codex-acp"
6455
- }
6456
- },
6457
- parser: {
6458
- kind: "jsonl",
6459
- text: {
6460
- path: "__preset__:codex.text"
6461
- },
6462
- usage: {
6463
- path: "__preset__:codex.usage"
6464
- },
6465
- error: {
6466
- path: "__preset__:codex.error"
6467
- }
6468
- }
6469
- }
6470
- }
6471
- };
6472
- var AI_CONNECT_DEFAULT_CLI_COMMANDS = {
6473
- "anthropic:claude-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.claude.command,
6474
- "anthropic:openclaude-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.openclaude.command,
6475
- "openclaude:openclaude-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.openclaude.command,
6476
- "openai:codex-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.codex.command,
6477
- "gemini:gemini-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.gemini.command,
6478
- "qwen:qwen-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.qwen.command
6479
- };
6480
- function getCliTransportPreset(presetId) {
6481
- return AI_CONNECT_DEFAULT_CLI_PRESETS[presetId];
6482
- }
6483
-
6484
6535
  // src/server-presets.ts
6485
6536
  var AI_CONNECT_DEFAULT_SERVER_PRESETS = {
6486
6537
  opencode: {
@@ -6597,52 +6648,22 @@ var TEXT_PROVIDER_CATALOG = [
6597
6648
  runtime: "universal",
6598
6649
  defaultModel: "gemini-3.1-flash-lite",
6599
6650
  defaultBaseUrl: ""
6600
- },
6601
- {
6602
- providerId: "gemini",
6603
- providerLabel: "Gemini",
6604
- transportKind: "cli",
6605
- transportId: "gemini-cli",
6606
- transportLabel: "Gemini CLI",
6607
- runtime: "local",
6608
- defaultModel: "gemini-2.5-flash",
6609
- defaultCommand: AI_CONNECT_DEFAULT_CLI_COMMANDS["gemini:gemini-cli"]
6610
- },
6611
- {
6612
- providerId: "gemini",
6613
- providerLabel: "Gemini",
6614
- transportKind: "acp",
6615
- transportId: "gemini-acp",
6616
- transportLabel: "Gemini ACP",
6617
- runtime: "local",
6618
- defaultModel: "auto-gemini-3",
6619
- defaultCommand: AI_CONNECT_DEFAULT_ACP_COMMANDS["gemini:gemini-acp"]
6620
6651
  }
6621
6652
  ]
6622
6653
  },
6623
6654
  {
6624
- providerId: "qwen",
6625
- label: "Qwen",
6655
+ providerId: "pi",
6656
+ label: "pi",
6626
6657
  transports: [
6627
6658
  {
6628
- providerId: "qwen",
6629
- providerLabel: "Qwen",
6659
+ providerId: "pi",
6660
+ providerLabel: "pi",
6630
6661
  transportKind: "cli",
6631
- transportId: "qwen-cli",
6632
- transportLabel: "Qwen CLI",
6662
+ transportId: "pi-cli",
6663
+ transportLabel: "pi CLI",
6633
6664
  runtime: "local",
6634
- defaultModel: "qwen3-coder-plus",
6635
- defaultCommand: AI_CONNECT_DEFAULT_CLI_COMMANDS["qwen:qwen-cli"]
6636
- },
6637
- {
6638
- providerId: "qwen",
6639
- providerLabel: "Qwen",
6640
- transportKind: "acp",
6641
- transportId: "qwen-acp",
6642
- transportLabel: "Qwen ACP",
6643
- runtime: "local",
6644
- defaultModel: "default",
6645
- defaultCommand: AI_CONNECT_DEFAULT_ACP_COMMANDS["qwen:qwen-acp"]
6665
+ defaultModel: "gemini-3.1-pro-low",
6666
+ defaultCommand: AI_CONNECT_DEFAULT_CLI_COMMANDS["pi:pi-cli"]
6646
6667
  }
6647
6668
  ]
6648
6669
  },
@@ -6815,15 +6836,6 @@ function resolveHomeDir(env) {
6815
6836
  function resolveXdgConfigHome(env) {
6816
6837
  return env.XDG_CONFIG_HOME ?? path.join(resolveHomeDir(env), ".config");
6817
6838
  }
6818
- function resolveGeminiCliHome(env) {
6819
- return env.GEMINI_CLI_HOME ?? resolveHomeDir(env);
6820
- }
6821
- function resolveGeminiDir(env) {
6822
- return path.join(resolveGeminiCliHome(env), ".gemini");
6823
- }
6824
- function resolveQwenDir(env) {
6825
- return path.join(resolveHomeDir(env), ".qwen");
6826
- }
6827
6839
  function resolveCodexHome(env) {
6828
6840
  return env.CODEX_HOME ?? path.join(resolveHomeDir(env), ".codex");
6829
6841
  }
@@ -6869,118 +6881,6 @@ async function removeIfExists(targetPath) {
6869
6881
  }
6870
6882
  });
6871
6883
  }
6872
- async function readJsonFile(filePath) {
6873
- try {
6874
- const raw = await fs.readFile(filePath, "utf8");
6875
- const parsed = JSON.parse(raw);
6876
- return isRecord2(parsed) ? parsed : void 0;
6877
- } catch (error) {
6878
- if (error instanceof SyntaxError || error.code === "ENOENT") {
6879
- return void 0;
6880
- }
6881
- throw error;
6882
- }
6883
- }
6884
- function mergeRecordValues(current, next) {
6885
- const merged = { ...current };
6886
- for (const [key, value] of Object.entries(next)) {
6887
- const existing = merged[key];
6888
- if (isRecord2(existing) && isRecord2(value)) {
6889
- merged[key] = mergeRecordValues(existing, value);
6890
- continue;
6891
- }
6892
- merged[key] = value;
6893
- }
6894
- return merged;
6895
- }
6896
- async function writeJsonFile(filePath, value) {
6897
- await fs.mkdir(path.dirname(filePath), { recursive: true });
6898
- await fs.writeFile(filePath, `${JSON.stringify(value, null, 2)}
6899
- `, "utf8");
6900
- }
6901
- async function prepareGeminiLaunchRuntime(base, launch) {
6902
- if (launch.contextMode === "workspace" && launch.skillsMode === "default") {
6903
- return {
6904
- ...base,
6905
- launch
6906
- };
6907
- }
6908
- const sandbox = await createSandboxPaths("ai-connect-gemini-acp-");
6909
- const originalGeminiDir = resolveGeminiDir(base.env);
6910
- const sandboxGeminiDir = path.join(sandbox.root, ".gemini");
6911
- const settingsPath = path.join(sandboxGeminiDir, "settings.json");
6912
- const systemSettingsPath = path.join(sandbox.config, "gemini-settings.json");
6913
- const systemDefaultsPath = path.join(
6914
- sandbox.config,
6915
- "gemini-system-defaults.json"
6916
- );
6917
- const isolatedCwd = launch.contextMode === "clean" || launch.skillsMode === "disabled" ? sandbox.root : base.cwd;
6918
- await Promise.all(
6919
- [
6920
- "oauth_creds.json",
6921
- "google_accounts.json",
6922
- "mcp-oauth-tokens.json",
6923
- "a2a-oauth-tokens.json",
6924
- "installation_id"
6925
- ].map(
6926
- (fileName) => maybeCopyFile(
6927
- path.join(originalGeminiDir, fileName),
6928
- path.join(sandboxGeminiDir, fileName)
6929
- )
6930
- )
6931
- );
6932
- const settings = mergeRecordValues(
6933
- await readJsonFile(settingsPath) ?? {},
6934
- {
6935
- context: {
6936
- fileName: "AI_CONNECT_CONTEXT_DISABLED.md",
6937
- includeDirectoryTree: false,
6938
- memoryBoundaryMarkers: [],
6939
- includeDirectories: [],
6940
- loadMemoryFromIncludeDirectories: false,
6941
- discoveryMaxDirs: 0
6942
- },
6943
- admin: {
6944
- mcp: {
6945
- enabled: false,
6946
- config: {},
6947
- requiredConfig: {}
6948
- },
6949
- ...launch.skillsMode === "disabled" ? {
6950
- skills: {
6951
- enabled: false
6952
- }
6953
- } : {}
6954
- },
6955
- ...launch.skillsMode === "disabled" ? {
6956
- skills: {
6957
- enabled: false,
6958
- disabled: []
6959
- }
6960
- } : {}
6961
- }
6962
- );
6963
- await Promise.all([
6964
- writeJsonFile(settingsPath, settings),
6965
- writeJsonFile(systemSettingsPath, {}),
6966
- writeJsonFile(systemDefaultsPath, {})
6967
- ]);
6968
- return {
6969
- commandLine: base.commandLine,
6970
- cwd: isolatedCwd,
6971
- env: {
6972
- ...base.env,
6973
- HOME: sandbox.root,
6974
- GEMINI_CLI_HOME: sandbox.root,
6975
- GEMINI_CLI_SYSTEM_SETTINGS_PATH: systemSettingsPath,
6976
- GEMINI_CLI_SYSTEM_DEFAULTS_PATH: systemDefaultsPath
6977
- },
6978
- launch,
6979
- cleanup: async () => {
6980
- await removeIfExists(sandbox.root);
6981
- }
6982
- };
6983
- }
6984
6884
  async function prepareOpenCodeLaunchRuntime(base, launch) {
6985
6885
  if (launch.contextMode === "workspace" && launch.skillsMode === "default") {
6986
6886
  return {
@@ -7029,70 +6929,6 @@ async function prepareOpenCodeLaunchRuntime(base, launch) {
7029
6929
  }
7030
6930
  };
7031
6931
  }
7032
- async function prepareQwenLaunchRuntime(base, launch) {
7033
- if (launch.contextMode === "workspace" && launch.skillsMode === "default") {
7034
- return {
7035
- ...base,
7036
- launch
7037
- };
7038
- }
7039
- const sandbox = await createSandboxPaths("ai-connect-qwen-acp-");
7040
- const originalQwenDir = resolveQwenDir(base.env);
7041
- const sandboxQwenDir = path.join(sandbox.home, ".qwen");
7042
- const settingsPath = path.join(sandboxQwenDir, "settings.json");
7043
- const systemSettingsPath = path.join(sandbox.config, "qwen-settings.json");
7044
- const systemDefaultsPath = path.join(
7045
- sandbox.config,
7046
- "qwen-system-defaults.json"
7047
- );
7048
- await Promise.all(
7049
- [
7050
- "oauth_creds.json",
7051
- "mcp-oauth-tokens.json",
7052
- "installation_id",
7053
- ".env",
7054
- "settings.json"
7055
- ].map(
7056
- (fileName) => maybeCopyFile(
7057
- path.join(originalQwenDir, fileName),
7058
- path.join(sandboxQwenDir, fileName)
7059
- )
7060
- )
7061
- );
7062
- const settings = mergeRecordValues(
7063
- await readJsonFile(settingsPath) ?? {},
7064
- {
7065
- context: {
7066
- fileName: ["AI_CONNECT_CONTEXT_DISABLED.md"],
7067
- includeDirectories: [],
7068
- loadFromIncludeDirectories: false
7069
- },
7070
- model: {
7071
- skipStartupContext: true
7072
- }
7073
- }
7074
- );
7075
- await Promise.all([
7076
- writeJsonFile(settingsPath, settings),
7077
- writeJsonFile(systemSettingsPath, {}),
7078
- writeJsonFile(systemDefaultsPath, {})
7079
- ]);
7080
- const isolatedCwd = launch.contextMode === "clean" || launch.skillsMode === "disabled" ? sandbox.root : base.cwd;
7081
- return {
7082
- commandLine: base.commandLine,
7083
- cwd: isolatedCwd,
7084
- env: {
7085
- ...base.env,
7086
- HOME: sandbox.home,
7087
- QWEN_CODE_SYSTEM_SETTINGS_PATH: systemSettingsPath,
7088
- QWEN_CODE_SYSTEM_DEFAULTS_PATH: systemDefaultsPath
7089
- },
7090
- launch,
7091
- cleanup: async () => {
7092
- await removeIfExists(sandbox.root);
7093
- }
7094
- };
7095
- }
7096
6932
  async function prepareCodexLaunchRuntime(base, launch) {
7097
6933
  if (launch.contextMode === "workspace" && launch.skillsMode === "default") {
7098
6934
  return {
@@ -7210,15 +7046,9 @@ async function prepareAcpLaunchRuntime(route, options, commandLine, cwdOverride)
7210
7046
  ...options?.env ?? {}
7211
7047
  }
7212
7048
  };
7213
- if (route.transport.id === "gemini-acp") {
7214
- return prepareGeminiLaunchRuntime(base, launch);
7215
- }
7216
7049
  if (route.transport.id === "opencode-acp") {
7217
7050
  return prepareOpenCodeLaunchRuntime(base, launch);
7218
7051
  }
7219
- if (route.transport.id === "qwen-acp") {
7220
- return prepareQwenLaunchRuntime(base, launch);
7221
- }
7222
7052
  if (route.transport.id === "codex-acp") {
7223
7053
  return prepareCodexLaunchRuntime(base, launch);
7224
7054
  }
@@ -7799,10 +7629,6 @@ function buildAcpLifecycle(route, authRequest, mode) {
7799
7629
  steps.push("authenticate");
7800
7630
  }
7801
7631
  steps.push("session/new");
7802
- if (mode === "prompt" && route.transport.id === "gemini-acp") {
7803
- steps.push("session/set_model");
7804
- keys.push("session/set_model.modelId");
7805
- }
7806
7632
  if (mode === "prompt") {
7807
7633
  steps.push("session/prompt");
7808
7634
  }
@@ -7938,16 +7764,6 @@ var AcpConnection = class {
7938
7764
  );
7939
7765
  }
7940
7766
  const sessionId = session.sessionId;
7941
- if (context.route.transport.id === "gemini-acp") {
7942
- await measurePhase(
7943
- transport.phases ?? [],
7944
- "session/set_model",
7945
- async () => this.request("session/set_model", {
7946
- sessionId,
7947
- modelId: context.route.model
7948
- })
7949
- );
7950
- }
7951
7767
  this.activePrompt = {
7952
7768
  sessionId,
7953
7769
  text: "",
@@ -8460,9 +8276,6 @@ var AcpConnection = class {
8460
8276
  });
8461
8277
  }
8462
8278
  };
8463
- function isGeminiAcpFallbackCandidate(route, commandLine) {
8464
- return route.transport.id === "gemini-acp" && commandLine.includes("--acp") && !commandLine.includes("--experimental-acp");
8465
- }
8466
8279
  function cacheKeyForConnection(route, runtime, options) {
8467
8280
  const envEntries = Object.entries(options.env).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key}=${value}`).join("");
8468
8281
  return [
@@ -8709,18 +8522,7 @@ function createAcpTransportManager(options) {
8709
8522
  }
8710
8523
  async function drivePrompt(context, onDelta) {
8711
8524
  const commandLine = resolveAcpCommand(context.route, options);
8712
- try {
8713
- return await runWithCommand(context, commandLine, onDelta);
8714
- } catch (error) {
8715
- if (!isGeminiAcpFallbackCandidate(context.route, commandLine)) {
8716
- throw error;
8717
- }
8718
- return await runWithCommand(
8719
- context,
8720
- commandLine.replace("--acp", "--experimental-acp"),
8721
- onDelta
8722
- );
8723
- }
8525
+ return await runWithCommand(context, commandLine, onDelta);
8724
8526
  }
8725
8527
  return {
8726
8528
  async runPrompt(context) {
@@ -8789,17 +8591,7 @@ function createAcpTransportManager(options) {
8789
8591
  },
8790
8592
  async discoverModels(context) {
8791
8593
  const commandLine = resolveAcpCommand(context.route, options);
8792
- try {
8793
- return await discoverWithCommand(context, commandLine);
8794
- } catch (error) {
8795
- if (!isGeminiAcpFallbackCandidate(context.route, commandLine)) {
8796
- throw error;
8797
- }
8798
- return await discoverWithCommand(
8799
- context,
8800
- commandLine.replace("--acp", "--experimental-acp")
8801
- );
8802
- }
8594
+ return await discoverWithCommand(context, commandLine);
8803
8595
  },
8804
8596
  async dispose() {
8805
8597
  const values = [...connectionPools.values()].flat();
@@ -8824,14 +8616,6 @@ import fs2 from "node:fs/promises";
8824
8616
  import os2 from "node:os";
8825
8617
  import path2 from "node:path";
8826
8618
  var CLI_PRESET_ACP_DISCOVERY_DEFAULTS = {
8827
- gemini: {
8828
- transportId: "gemini-acp",
8829
- providerId: "gemini"
8830
- },
8831
- qwen: {
8832
- transportId: "qwen-acp",
8833
- providerId: "qwen"
8834
- },
8835
8619
  claude: {
8836
8620
  transportId: "claude-code-acp",
8837
8621
  providerId: "anthropic"
@@ -8861,25 +8645,26 @@ function splitCommandLine2(commandLine) {
8861
8645
  function quoteArg(value) {
8862
8646
  return /\s/.test(value) ? JSON.stringify(value) : value;
8863
8647
  }
8864
- function buildCliPrompt(context) {
8648
+ function buildCliPrompt(context, fileInputEnabled) {
8865
8649
  if (context.request.operation !== "text") {
8866
8650
  throw new AiConnectError(
8867
8651
  "not_supported",
8868
8652
  `CLI transport "${context.route.transport.id}" only supports text requests.`
8869
8653
  );
8870
8654
  }
8871
- if (context.request.image || context.request.attachments.some(
8872
- (file) => portableFileCategory(file) === "image" || portableFileCategory(file) === "document"
8873
- )) {
8655
+ if (context.request.image) {
8874
8656
  throw new AiConnectError(
8875
8657
  "unsupported_capability",
8876
- `CLI transport "${context.route.transport.id}" does not support image or document inputs.`
8658
+ `CLI transport "${context.route.transport.id}" does not support image output.`
8877
8659
  );
8878
8660
  }
8879
- if (context.request.attachments.length > 0) {
8661
+ if (!fileInputEnabled && context.request.attachments.length > 0) {
8662
+ const hasBinary = context.request.attachments.some(
8663
+ (file) => portableFileCategory(file) === "image" || portableFileCategory(file) === "document"
8664
+ );
8880
8665
  throw new AiConnectError(
8881
- "not_supported",
8882
- `CLI transport "${context.route.transport.id}" does not support attachments.`
8666
+ hasBinary ? "unsupported_capability" : "not_supported",
8667
+ hasBinary ? `CLI transport "${context.route.transport.id}" does not support image or document inputs.` : `CLI transport "${context.route.transport.id}" does not support attachments.`
8883
8668
  );
8884
8669
  }
8885
8670
  return context.request.messages.map((message) => {
@@ -8938,10 +8723,8 @@ function buildCliCwd(context, options) {
8938
8723
  }
8939
8724
  function defaultCliPresetIdForRoute(route) {
8940
8725
  switch (route.transport.id) {
8941
- case "gemini-cli":
8942
- return "gemini";
8943
- case "qwen-cli":
8944
- return "qwen";
8726
+ case "pi-cli":
8727
+ return "pi";
8945
8728
  case "claude-cli":
8946
8729
  return "claude";
8947
8730
  case "openclaude-cli":
@@ -9026,11 +8809,16 @@ function createCliDiscoveryAcpRoute(route) {
9026
8809
  function resolveCliTransportOptions(route) {
9027
8810
  const presetId = route.transport.cli?.preset ?? defaultCliPresetIdForRoute(route);
9028
8811
  const preset = presetId ? getCliTransportPreset(presetId) : void 0;
8812
+ const mergedFileInput = preset?.options.fileInput || route.transport.cli?.fileInput ? {
8813
+ ...preset?.options.fileInput ?? {},
8814
+ ...route.transport.cli?.fileInput ?? {}
8815
+ } : void 0;
9029
8816
  const merged = {
9030
8817
  ...preset?.options ?? {},
9031
8818
  ...route.transport.cli ?? {},
9032
8819
  ...route.transport.cli?.argsTemplate ? { argsTemplate: route.transport.cli.argsTemplate } : {},
9033
8820
  ...route.transport.cli?.parser ? { parser: route.transport.cli.parser } : {},
8821
+ ...mergedFileInput ? { fileInput: mergedFileInput } : {},
9034
8822
  ...presetId ? { preset: presetId } : {}
9035
8823
  };
9036
8824
  if (!merged.argsTemplate?.length) {
@@ -9041,15 +8829,73 @@ function resolveCliTransportOptions(route) {
9041
8829
  }
9042
8830
  return merged;
9043
8831
  }
9044
- async function createOutputFile() {
8832
+ async function createCliTempDir(baseDir, prefix) {
9045
8833
  const tempDir = await fs2.mkdtemp(
9046
- path2.join(os2.tmpdir(), "ai-connect-cli-output-")
8834
+ path2.join(baseDir ?? os2.tmpdir(), prefix ?? "ai-connect-cli-")
9047
8835
  );
9048
8836
  return {
9049
8837
  tempDir,
9050
8838
  outputFile: path2.join(tempDir, "last-message.txt")
9051
8839
  };
9052
8840
  }
8841
+ function safeStagedBasename(name, index, mimeType) {
8842
+ const base = path2.basename(name.replace(/\\/g, "/"));
8843
+ if (base === "" || base === "." || base === "..") {
8844
+ return `attachment-${index}.${extensionForMime(mimeType)}`;
8845
+ }
8846
+ return base;
8847
+ }
8848
+ async function stageCliAttachments(attachments, attachDir, fileInput) {
8849
+ const categories = new Set(
8850
+ fileInput.categories ?? ["image", "document", "text", "other"]
8851
+ );
8852
+ await fs2.mkdir(attachDir, { recursive: true });
8853
+ const refs = [];
8854
+ const usedNames = /* @__PURE__ */ new Map();
8855
+ for (let i = 0; i < attachments.length; i += 1) {
8856
+ const file = preparePortableFile(attachments[i]);
8857
+ const payload = await materializePortableFile(file);
8858
+ if (!categories.has(payload.category)) {
8859
+ continue;
8860
+ }
8861
+ let destName = safeStagedBasename(payload.name, i, payload.mimeType);
8862
+ const seen = usedNames.get(destName);
8863
+ if (seen !== void 0) {
8864
+ destName = `${i}-${destName}`;
8865
+ }
8866
+ usedNames.set(destName, i);
8867
+ const dest = path2.join(attachDir, destName);
8868
+ if (payload.category === "text") {
8869
+ if (payload.text !== void 0) {
8870
+ await fs2.writeFile(dest, payload.text, "utf8");
8871
+ refs.push(dest);
8872
+ continue;
8873
+ }
8874
+ } else {
8875
+ const bytes = payload.base64 ? Buffer.from(payload.base64, "base64") : await portableFileToBytes(file);
8876
+ if (bytes !== void 0) {
8877
+ await fs2.writeFile(dest, bytes);
8878
+ refs.push(dest);
8879
+ continue;
8880
+ }
8881
+ }
8882
+ if (payload.uri) {
8883
+ refs.push(payload.uri);
8884
+ }
8885
+ }
8886
+ return refs;
8887
+ }
8888
+ function expandFileArgs(staged, fileInput) {
8889
+ const perFileArgs = fileInput?.perFileArgs ?? ["@{path}"];
8890
+ if (fileInput?.placement === "prompt") {
8891
+ return [];
8892
+ }
8893
+ return staged.flatMap(
8894
+ (ref) => perFileArgs.map(
8895
+ (tpl) => tpl.replace("{path}", ref).replace("{name}", path2.basename(ref))
8896
+ )
8897
+ );
8898
+ }
9053
8899
  function materializeTemplateArg(templateArg, values, parameterKeys) {
9054
8900
  switch (templateArg) {
9055
8901
  case "{prompt}":
@@ -9076,31 +8922,62 @@ function materializeTemplateArg(templateArg, values, parameterKeys) {
9076
8922
  }
9077
8923
  async function buildCliInvocation(context, options) {
9078
8924
  const route = context.route;
9079
- const prompt = buildCliPrompt(context);
9080
8925
  const cliOptions = resolveCliTransportOptions(route);
8926
+ const fileInput = cliOptions.fileInput;
8927
+ const attachments = context.request.attachments;
8928
+ const hasStageable = Boolean(fileInput) && attachments.length > 0;
8929
+ let prompt = buildCliPrompt(context, Boolean(fileInput));
9081
8930
  const commandLine = resolveCliCommand(route, options);
9082
8931
  const resolved = splitCommandLine2(commandLine);
9083
8932
  const parameterKeys = [];
9084
8933
  let tempDir;
9085
8934
  let outputFile;
9086
- if (cliOptions.argsTemplate.includes("{output_file}")) {
9087
- const created = await createOutputFile();
8935
+ const needsOutputFile = cliOptions.argsTemplate.includes("{output_file}");
8936
+ if (needsOutputFile || hasStageable) {
8937
+ const created = await createCliTempDir(
8938
+ fileInput?.stagingDir ?? options?.staging?.dir,
8939
+ options?.staging?.prefix
8940
+ );
9088
8941
  tempDir = created.tempDir;
9089
8942
  outputFile = created.outputFile;
9090
8943
  }
8944
+ let staged = [];
8945
+ if (hasStageable && tempDir && fileInput) {
8946
+ staged = await stageCliAttachments(
8947
+ attachments,
8948
+ path2.join(tempDir, "attachments"),
8949
+ fileInput
8950
+ );
8951
+ if (fileInput.placement === "prompt" && staged.length > 0) {
8952
+ const separator = fileInput.separator ?? " ";
8953
+ const mentionTemplate = fileInput.mentionTemplate ?? "@{path}";
8954
+ const mentions = staged.map(
8955
+ (ref) => mentionTemplate.replace("{path}", ref).replace("{name}", path2.basename(ref))
8956
+ ).join(separator);
8957
+ prompt = `${prompt}${separator}${mentions}`;
8958
+ }
8959
+ }
9091
8960
  const args = [
9092
8961
  ...resolved.args,
9093
- ...cliOptions.argsTemplate.map(
9094
- (part) => materializeTemplateArg(
9095
- part,
9096
- {
9097
- prompt,
9098
- model: route.model,
9099
- ...outputFile ? { outputFile } : {}
9100
- },
9101
- parameterKeys
9102
- )
9103
- )
8962
+ ...cliOptions.argsTemplate.flatMap((part) => {
8963
+ if (part === "{files}") {
8964
+ if (staged.length > 0) {
8965
+ parameterKeys.push("{files}");
8966
+ }
8967
+ return expandFileArgs(staged, fileInput);
8968
+ }
8969
+ return [
8970
+ materializeTemplateArg(
8971
+ part,
8972
+ {
8973
+ prompt,
8974
+ model: route.model,
8975
+ ...outputFile ? { outputFile } : {}
8976
+ },
8977
+ parameterKeys
8978
+ )
8979
+ ];
8980
+ })
9104
8981
  ];
9105
8982
  return {
9106
8983
  command: resolved.command,
@@ -9109,6 +8986,7 @@ async function buildCliInvocation(context, options) {
9109
8986
  env: buildCliEnvironment(options),
9110
8987
  ...tempDir ? { tempDir } : {},
9111
8988
  ...outputFile ? { outputFile } : {},
8989
+ ...options?.staging?.keep ? { keepStaging: true } : {},
9112
8990
  parameterKeys
9113
8991
  };
9114
8992
  }
@@ -9320,60 +9198,6 @@ function parseCliModelList(stdout, parser, selector) {
9320
9198
  }
9321
9199
  return models;
9322
9200
  }
9323
- function parseGeminiCli(stdout) {
9324
- const payload = JSON.parse(stdout);
9325
- if (typeof payload.error === "string") {
9326
- throw new AiConnectError("temporary_unavailable", payload.error);
9327
- }
9328
- if (typeof payload.response !== "string") {
9329
- throw new AiConnectError(
9330
- "temporary_unavailable",
9331
- "Gemini CLI JSON output did not include response text."
9332
- );
9333
- }
9334
- const usage = payload.stats ? statsToUsage(payload.stats) : void 0;
9335
- return {
9336
- text: payload.response,
9337
- ...usage ? { usage } : {},
9338
- data: payload
9339
- };
9340
- }
9341
- function parseQwenCli(stdout) {
9342
- const payload = JSON.parse(stdout);
9343
- if (!Array.isArray(payload)) {
9344
- throw new AiConnectError(
9345
- "temporary_unavailable",
9346
- "Qwen CLI JSON output did not return a message array."
9347
- );
9348
- }
9349
- const resultMessage = payload.find(
9350
- (entry) => entry && typeof entry === "object" && entry.type === "result"
9351
- );
9352
- if (!resultMessage) {
9353
- throw new AiConnectError(
9354
- "temporary_unavailable",
9355
- "Qwen CLI JSON output did not contain a result message."
9356
- );
9357
- }
9358
- if (resultMessage.is_error === true) {
9359
- throw new AiConnectError(
9360
- "temporary_unavailable",
9361
- typeof resultMessage.result === "string" ? resultMessage.result : "Qwen CLI returned an error result."
9362
- );
9363
- }
9364
- if (typeof resultMessage.result !== "string") {
9365
- throw new AiConnectError(
9366
- "temporary_unavailable",
9367
- "Qwen CLI result message did not contain text output."
9368
- );
9369
- }
9370
- const usage = resultMessage.stats ? statsToUsage(resultMessage.stats) : void 0;
9371
- return {
9372
- text: resultMessage.result,
9373
- ...usage ? { usage } : {},
9374
- data: payload
9375
- };
9376
- }
9377
9201
  function parseClaudeCli(stdout) {
9378
9202
  const payload = JSON.parse(stdout);
9379
9203
  const text = typeof payload.result === "string" ? payload.result : typeof payload.response === "string" ? payload.response : typeof payload.text === "string" ? payload.text : void 0;
@@ -9459,10 +9283,8 @@ function parseCliResult(route, result, outputFileContent) {
9459
9283
  }
9460
9284
  }
9461
9285
  switch (cliOptions.preset) {
9462
- case "gemini":
9463
- return parseGeminiCli(stdout);
9464
- case "qwen":
9465
- return parseQwenCli(stdout);
9286
+ case "pi":
9287
+ return parseTextCli(stdout, { kind: "text" });
9466
9288
  case "claude":
9467
9289
  case "openclaude":
9468
9290
  return parseClaudeCli(stdout);
@@ -9553,7 +9375,7 @@ async function executeCliInvocation(invocation, timeoutMs, phases, signal) {
9553
9375
  });
9554
9376
  }
9555
9377
  async function cleanupCliInvocation(invocation) {
9556
- if (invocation.tempDir) {
9378
+ if (invocation.tempDir && !invocation.keepStaging) {
9557
9379
  await fs2.rm(invocation.tempDir, { recursive: true, force: true });
9558
9380
  }
9559
9381
  }