@vedmalex/ai-connect 0.5.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/README.md +59 -0
- package/dist/browser/index.js +190 -110
- package/dist/browser/index.js.map +3 -3
- package/dist/bun/index.js +315 -135
- package/dist/bun/index.js.map +3 -3
- package/dist/bun/local.js +315 -137
- package/dist/bun/local.js.map +3 -3
- package/dist/node/index.js +315 -135
- package/dist/node/index.js.map +3 -3
- package/dist/node/local.js +315 -137
- package/dist/node/local.js.map +3 -3
- package/dist/types/cli-presets.d.ts.map +1 -1
- package/dist/types/cli.d.ts.map +1 -1
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/files.d.ts +6 -0
- package/dist/types/files.d.ts.map +1 -1
- package/dist/types/types.d.ts +40 -0
- package/dist/types/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/bun/local.js
CHANGED
|
@@ -2,6 +2,128 @@
|
|
|
2
2
|
import { accessSync, constants } from "node:fs";
|
|
3
3
|
import { delimiter, join } from "node:path";
|
|
4
4
|
|
|
5
|
+
// src/cli-presets.ts
|
|
6
|
+
var AI_CONNECT_DEFAULT_CLI_PRESETS = {
|
|
7
|
+
pi: {
|
|
8
|
+
id: "pi",
|
|
9
|
+
label: "pi (coding agent)",
|
|
10
|
+
command: "pi",
|
|
11
|
+
transportId: "pi-cli",
|
|
12
|
+
options: {
|
|
13
|
+
preset: "pi",
|
|
14
|
+
// pi print mode emits the answer as plain text on stdout (no JSON wrapper),
|
|
15
|
+
// and has no ACP mode — so it uses the raw `text` parser and no discovery sidecar.
|
|
16
|
+
// pi takes file/image attachments as separate `@<path>` argv tokens (REQ-024);
|
|
17
|
+
// {files} expands one `@<staged-path>` token per attachment. pi stages all
|
|
18
|
+
// categories (PDFs as-is, no extraction guarantee).
|
|
19
|
+
argsTemplate: ["--print", "--model", "{model}", "{files}", "{prompt}"],
|
|
20
|
+
discovery: {
|
|
21
|
+
via: "none"
|
|
22
|
+
},
|
|
23
|
+
parser: {
|
|
24
|
+
kind: "text"
|
|
25
|
+
},
|
|
26
|
+
fileInput: {
|
|
27
|
+
placement: "args",
|
|
28
|
+
perFileArgs: ["@{path}"]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
claude: {
|
|
33
|
+
id: "claude",
|
|
34
|
+
label: "Claude CLI",
|
|
35
|
+
command: "claude",
|
|
36
|
+
transportId: "claude-cli",
|
|
37
|
+
options: {
|
|
38
|
+
preset: "claude",
|
|
39
|
+
argsTemplate: ["--print", "--output-format", "json", "--model", "{model}", "{prompt}"],
|
|
40
|
+
discovery: {
|
|
41
|
+
via: "acp",
|
|
42
|
+
acp: {
|
|
43
|
+
transportId: "claude-code-acp"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
parser: {
|
|
47
|
+
kind: "json",
|
|
48
|
+
textPath: "__preset__:claude.result",
|
|
49
|
+
usagePath: "usage",
|
|
50
|
+
errorPath: "__preset__:claude.error"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
openclaude: {
|
|
55
|
+
id: "openclaude",
|
|
56
|
+
label: "OpenClaude CLI",
|
|
57
|
+
command: "openclaude",
|
|
58
|
+
transportId: "openclaude-cli",
|
|
59
|
+
options: {
|
|
60
|
+
preset: "openclaude",
|
|
61
|
+
argsTemplate: ["--print", "--output-format", "json", "--model", "{model}", "{prompt}"],
|
|
62
|
+
parser: {
|
|
63
|
+
kind: "json",
|
|
64
|
+
textPath: "__preset__:claude.result",
|
|
65
|
+
usagePath: "usage",
|
|
66
|
+
errorPath: "__preset__:claude.error"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
codex: {
|
|
71
|
+
id: "codex",
|
|
72
|
+
label: "Codex CLI",
|
|
73
|
+
command: "codex",
|
|
74
|
+
transportId: "codex-cli",
|
|
75
|
+
options: {
|
|
76
|
+
preset: "codex",
|
|
77
|
+
// codex exec takes image attachments via `-i/--image <FILE>` flags
|
|
78
|
+
// (REQ-024); {files} expands a `--image <staged-path>` pair per image.
|
|
79
|
+
// Images only — a non-image attachment cleanly rejects pre-spawn.
|
|
80
|
+
argsTemplate: [
|
|
81
|
+
"exec",
|
|
82
|
+
"--json",
|
|
83
|
+
"--output-last-message",
|
|
84
|
+
"{output_file}",
|
|
85
|
+
"--model",
|
|
86
|
+
"{model}",
|
|
87
|
+
"{files}",
|
|
88
|
+
"{prompt}"
|
|
89
|
+
],
|
|
90
|
+
discovery: {
|
|
91
|
+
via: "acp",
|
|
92
|
+
acp: {
|
|
93
|
+
transportId: "codex-acp"
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
fileInput: {
|
|
97
|
+
placement: "args",
|
|
98
|
+
perFileArgs: ["--image", "{path}"],
|
|
99
|
+
categories: ["image"]
|
|
100
|
+
},
|
|
101
|
+
parser: {
|
|
102
|
+
kind: "jsonl",
|
|
103
|
+
text: {
|
|
104
|
+
path: "__preset__:codex.text"
|
|
105
|
+
},
|
|
106
|
+
usage: {
|
|
107
|
+
path: "__preset__:codex.usage"
|
|
108
|
+
},
|
|
109
|
+
error: {
|
|
110
|
+
path: "__preset__:codex.error"
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
var AI_CONNECT_DEFAULT_CLI_COMMANDS = {
|
|
117
|
+
"anthropic:claude-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.claude.command,
|
|
118
|
+
"anthropic:openclaude-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.openclaude.command,
|
|
119
|
+
"openclaude:openclaude-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.openclaude.command,
|
|
120
|
+
"openai:codex-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.codex.command,
|
|
121
|
+
"pi:pi-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.pi.command
|
|
122
|
+
};
|
|
123
|
+
function getCliTransportPreset(presetId) {
|
|
124
|
+
return AI_CONNECT_DEFAULT_CLI_PRESETS[presetId];
|
|
125
|
+
}
|
|
126
|
+
|
|
5
127
|
// src/errors.ts
|
|
6
128
|
var AiConnectError = class extends Error {
|
|
7
129
|
code;
|
|
@@ -346,9 +468,38 @@ function normalizeTransport(providerId, input) {
|
|
|
346
468
|
...fallback !== void 0 ? { fallback } : {}
|
|
347
469
|
};
|
|
348
470
|
})();
|
|
471
|
+
const fileInput = (() => {
|
|
472
|
+
const fi = descriptor.cli?.fileInput;
|
|
473
|
+
if (!fi) {
|
|
474
|
+
return void 0;
|
|
475
|
+
}
|
|
476
|
+
const validCategories = ["image", "document", "text", "other"];
|
|
477
|
+
if (fi.categories) {
|
|
478
|
+
for (const c of fi.categories) {
|
|
479
|
+
assert(
|
|
480
|
+
validCategories.includes(c),
|
|
481
|
+
`Unsupported CLI fileInput category "${String(c)}" for provider "${providerId}".`
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
const placement = fi.placement ?? "args";
|
|
486
|
+
assert(
|
|
487
|
+
placement === "args" || placement === "prompt",
|
|
488
|
+
`Unsupported CLI fileInput placement "${String(placement)}" for provider "${providerId}".`
|
|
489
|
+
);
|
|
490
|
+
return {
|
|
491
|
+
placement,
|
|
492
|
+
...fi.perFileArgs ? { perFileArgs: fi.perFileArgs.map((part) => String(part)) } : {},
|
|
493
|
+
...fi.mentionTemplate?.trim() ? { mentionTemplate: fi.mentionTemplate.trim() } : {},
|
|
494
|
+
...fi.separator !== void 0 ? { separator: fi.separator } : {},
|
|
495
|
+
...fi.categories ? { categories: [...fi.categories] } : {},
|
|
496
|
+
...fi.stagingDir?.trim() ? { stagingDir: fi.stagingDir.trim() } : {}
|
|
497
|
+
};
|
|
498
|
+
})();
|
|
349
499
|
return {
|
|
350
500
|
...normalized,
|
|
351
|
-
...discovery ? { discovery } : {}
|
|
501
|
+
...discovery ? { discovery } : {},
|
|
502
|
+
...fileInput ? { fileInput } : {}
|
|
352
503
|
};
|
|
353
504
|
})();
|
|
354
505
|
const normalizedAuth = descriptor.auth?.methodId?.trim() ? {
|
|
@@ -436,7 +587,40 @@ function normalizeRuntime(transport, runtime) {
|
|
|
436
587
|
}
|
|
437
588
|
return "universal";
|
|
438
589
|
}
|
|
590
|
+
function cliPresetIdForTransportId(transportId) {
|
|
591
|
+
switch (transportId) {
|
|
592
|
+
case "pi-cli":
|
|
593
|
+
return "pi";
|
|
594
|
+
case "claude-cli":
|
|
595
|
+
return "claude";
|
|
596
|
+
case "openclaude-cli":
|
|
597
|
+
return "openclaude";
|
|
598
|
+
case "codex-cli":
|
|
599
|
+
return "codex";
|
|
600
|
+
default:
|
|
601
|
+
return void 0;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
function cliFileInputCategories(transport) {
|
|
605
|
+
if (transport.kind !== "cli") {
|
|
606
|
+
return /* @__PURE__ */ new Set();
|
|
607
|
+
}
|
|
608
|
+
const presetId = transport.cli?.preset ?? cliPresetIdForTransportId(transport.id);
|
|
609
|
+
const presetFileInput = presetId ? AI_CONNECT_DEFAULT_CLI_PRESETS[presetId]?.options.fileInput : void 0;
|
|
610
|
+
const routeFileInput = transport.cli?.fileInput;
|
|
611
|
+
if (!presetFileInput && !routeFileInput) {
|
|
612
|
+
return /* @__PURE__ */ new Set();
|
|
613
|
+
}
|
|
614
|
+
const merged = {
|
|
615
|
+
...presetFileInput ?? {},
|
|
616
|
+
...routeFileInput ?? {}
|
|
617
|
+
};
|
|
618
|
+
return new Set(
|
|
619
|
+
merged.categories ?? ["image", "document", "text", "other"]
|
|
620
|
+
);
|
|
621
|
+
}
|
|
439
622
|
function defaultCapabilities(transport, runtime) {
|
|
623
|
+
const cliFileCategories = cliFileInputCategories(transport);
|
|
440
624
|
const localOnly = runtime === "local" || transport.kind === "acp" || transport.kind === "cli" || transport.kind === "server";
|
|
441
625
|
const browserSafe = !localOnly;
|
|
442
626
|
return {
|
|
@@ -451,10 +635,12 @@ function defaultCapabilities(transport, runtime) {
|
|
|
451
635
|
supportsClientToolExecution: transport.kind === "api",
|
|
452
636
|
// C6 / F-P-4: api routes now advertise document + image input parity with acp.
|
|
453
637
|
// The per-account `capabilities` override remains the escape hatch for
|
|
454
|
-
// text-only api proxies that must opt out.
|
|
455
|
-
|
|
638
|
+
// text-only api proxies that must opt out. REQ-024: a cli route advertises
|
|
639
|
+
// document/image input ONLY for the categories its fileInput actually stages
|
|
640
|
+
// (category-aware so e.g. an image-only cli cleanly rejects a PDF pre-spawn).
|
|
641
|
+
supportsFileUpload: transport.kind === "acp" || transport.kind === "api" || cliFileCategories.has("document"),
|
|
456
642
|
supportsFileOutput: transport.kind === "acp",
|
|
457
|
-
supportsImageInput: transport.kind === "acp" || transport.kind === "api",
|
|
643
|
+
supportsImageInput: transport.kind === "acp" || transport.kind === "api" || cliFileCategories.has("image"),
|
|
458
644
|
supportsImageOutput: transport.kind === "acp",
|
|
459
645
|
supportsProfileVerification: true,
|
|
460
646
|
supportsCredentialRotation: false,
|
|
@@ -1005,6 +1191,10 @@ function extension(value) {
|
|
|
1005
1191
|
}
|
|
1006
1192
|
return name.slice(index + 1).toLowerCase();
|
|
1007
1193
|
}
|
|
1194
|
+
function extensionForMime(mimeType) {
|
|
1195
|
+
const subtype = mimeType.split("/")[1]?.toLowerCase().replace(/[^a-z0-9.+-]/g, "");
|
|
1196
|
+
return subtype && subtype.length > 0 ? subtype : "bin";
|
|
1197
|
+
}
|
|
1008
1198
|
function isRemoteUrl(value) {
|
|
1009
1199
|
try {
|
|
1010
1200
|
const url = new URL(value);
|
|
@@ -8175,114 +8365,6 @@ import { spawn as spawn2 } from "node:child_process";
|
|
|
8175
8365
|
import fs2 from "node:fs/promises";
|
|
8176
8366
|
import os2 from "node:os";
|
|
8177
8367
|
import path2 from "node:path";
|
|
8178
|
-
|
|
8179
|
-
// src/cli-presets.ts
|
|
8180
|
-
var AI_CONNECT_DEFAULT_CLI_PRESETS = {
|
|
8181
|
-
pi: {
|
|
8182
|
-
id: "pi",
|
|
8183
|
-
label: "pi (coding agent)",
|
|
8184
|
-
command: "pi",
|
|
8185
|
-
transportId: "pi-cli",
|
|
8186
|
-
options: {
|
|
8187
|
-
preset: "pi",
|
|
8188
|
-
// pi print mode emits the answer as plain text on stdout (no JSON wrapper),
|
|
8189
|
-
// and has no ACP mode — so it uses the raw `text` parser and no discovery sidecar.
|
|
8190
|
-
argsTemplate: ["--print", "--model", "{model}", "{prompt}"],
|
|
8191
|
-
discovery: {
|
|
8192
|
-
via: "none"
|
|
8193
|
-
},
|
|
8194
|
-
parser: {
|
|
8195
|
-
kind: "text"
|
|
8196
|
-
}
|
|
8197
|
-
}
|
|
8198
|
-
},
|
|
8199
|
-
claude: {
|
|
8200
|
-
id: "claude",
|
|
8201
|
-
label: "Claude CLI",
|
|
8202
|
-
command: "claude",
|
|
8203
|
-
transportId: "claude-cli",
|
|
8204
|
-
options: {
|
|
8205
|
-
preset: "claude",
|
|
8206
|
-
argsTemplate: ["--print", "--output-format", "json", "--model", "{model}", "{prompt}"],
|
|
8207
|
-
discovery: {
|
|
8208
|
-
via: "acp",
|
|
8209
|
-
acp: {
|
|
8210
|
-
transportId: "claude-code-acp"
|
|
8211
|
-
}
|
|
8212
|
-
},
|
|
8213
|
-
parser: {
|
|
8214
|
-
kind: "json",
|
|
8215
|
-
textPath: "__preset__:claude.result",
|
|
8216
|
-
usagePath: "usage",
|
|
8217
|
-
errorPath: "__preset__:claude.error"
|
|
8218
|
-
}
|
|
8219
|
-
}
|
|
8220
|
-
},
|
|
8221
|
-
openclaude: {
|
|
8222
|
-
id: "openclaude",
|
|
8223
|
-
label: "OpenClaude CLI",
|
|
8224
|
-
command: "openclaude",
|
|
8225
|
-
transportId: "openclaude-cli",
|
|
8226
|
-
options: {
|
|
8227
|
-
preset: "openclaude",
|
|
8228
|
-
argsTemplate: ["--print", "--output-format", "json", "--model", "{model}", "{prompt}"],
|
|
8229
|
-
parser: {
|
|
8230
|
-
kind: "json",
|
|
8231
|
-
textPath: "__preset__:claude.result",
|
|
8232
|
-
usagePath: "usage",
|
|
8233
|
-
errorPath: "__preset__:claude.error"
|
|
8234
|
-
}
|
|
8235
|
-
}
|
|
8236
|
-
},
|
|
8237
|
-
codex: {
|
|
8238
|
-
id: "codex",
|
|
8239
|
-
label: "Codex CLI",
|
|
8240
|
-
command: "codex",
|
|
8241
|
-
transportId: "codex-cli",
|
|
8242
|
-
options: {
|
|
8243
|
-
preset: "codex",
|
|
8244
|
-
argsTemplate: [
|
|
8245
|
-
"exec",
|
|
8246
|
-
"--json",
|
|
8247
|
-
"--output-last-message",
|
|
8248
|
-
"{output_file}",
|
|
8249
|
-
"--model",
|
|
8250
|
-
"{model}",
|
|
8251
|
-
"{prompt}"
|
|
8252
|
-
],
|
|
8253
|
-
discovery: {
|
|
8254
|
-
via: "acp",
|
|
8255
|
-
acp: {
|
|
8256
|
-
transportId: "codex-acp"
|
|
8257
|
-
}
|
|
8258
|
-
},
|
|
8259
|
-
parser: {
|
|
8260
|
-
kind: "jsonl",
|
|
8261
|
-
text: {
|
|
8262
|
-
path: "__preset__:codex.text"
|
|
8263
|
-
},
|
|
8264
|
-
usage: {
|
|
8265
|
-
path: "__preset__:codex.usage"
|
|
8266
|
-
},
|
|
8267
|
-
error: {
|
|
8268
|
-
path: "__preset__:codex.error"
|
|
8269
|
-
}
|
|
8270
|
-
}
|
|
8271
|
-
}
|
|
8272
|
-
}
|
|
8273
|
-
};
|
|
8274
|
-
var AI_CONNECT_DEFAULT_CLI_COMMANDS = {
|
|
8275
|
-
"anthropic:claude-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.claude.command,
|
|
8276
|
-
"anthropic:openclaude-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.openclaude.command,
|
|
8277
|
-
"openclaude:openclaude-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.openclaude.command,
|
|
8278
|
-
"openai:codex-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.codex.command,
|
|
8279
|
-
"pi:pi-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.pi.command
|
|
8280
|
-
};
|
|
8281
|
-
function getCliTransportPreset(presetId) {
|
|
8282
|
-
return AI_CONNECT_DEFAULT_CLI_PRESETS[presetId];
|
|
8283
|
-
}
|
|
8284
|
-
|
|
8285
|
-
// src/cli.ts
|
|
8286
8368
|
var CLI_PRESET_ACP_DISCOVERY_DEFAULTS = {
|
|
8287
8369
|
claude: {
|
|
8288
8370
|
transportId: "claude-code-acp",
|
|
@@ -8313,25 +8395,26 @@ function splitCommandLine2(commandLine) {
|
|
|
8313
8395
|
function quoteArg(value) {
|
|
8314
8396
|
return /\s/.test(value) ? JSON.stringify(value) : value;
|
|
8315
8397
|
}
|
|
8316
|
-
function buildCliPrompt(context) {
|
|
8398
|
+
function buildCliPrompt(context, fileInputEnabled) {
|
|
8317
8399
|
if (context.request.operation !== "text") {
|
|
8318
8400
|
throw new AiConnectError(
|
|
8319
8401
|
"not_supported",
|
|
8320
8402
|
`CLI transport "${context.route.transport.id}" only supports text requests.`
|
|
8321
8403
|
);
|
|
8322
8404
|
}
|
|
8323
|
-
if (context.request.image
|
|
8324
|
-
(file) => portableFileCategory(file) === "image" || portableFileCategory(file) === "document"
|
|
8325
|
-
)) {
|
|
8405
|
+
if (context.request.image) {
|
|
8326
8406
|
throw new AiConnectError(
|
|
8327
8407
|
"unsupported_capability",
|
|
8328
|
-
`CLI transport "${context.route.transport.id}" does not support image
|
|
8408
|
+
`CLI transport "${context.route.transport.id}" does not support image output.`
|
|
8329
8409
|
);
|
|
8330
8410
|
}
|
|
8331
|
-
if (context.request.attachments.length > 0) {
|
|
8411
|
+
if (!fileInputEnabled && context.request.attachments.length > 0) {
|
|
8412
|
+
const hasBinary = context.request.attachments.some(
|
|
8413
|
+
(file) => portableFileCategory(file) === "image" || portableFileCategory(file) === "document"
|
|
8414
|
+
);
|
|
8332
8415
|
throw new AiConnectError(
|
|
8333
|
-
"not_supported",
|
|
8334
|
-
`CLI transport "${context.route.transport.id}" does not support attachments.`
|
|
8416
|
+
hasBinary ? "unsupported_capability" : "not_supported",
|
|
8417
|
+
hasBinary ? `CLI transport "${context.route.transport.id}" does not support image or document inputs.` : `CLI transport "${context.route.transport.id}" does not support attachments.`
|
|
8335
8418
|
);
|
|
8336
8419
|
}
|
|
8337
8420
|
return context.request.messages.map((message) => {
|
|
@@ -8476,11 +8559,16 @@ function createCliDiscoveryAcpRoute(route) {
|
|
|
8476
8559
|
function resolveCliTransportOptions(route) {
|
|
8477
8560
|
const presetId = route.transport.cli?.preset ?? defaultCliPresetIdForRoute(route);
|
|
8478
8561
|
const preset = presetId ? getCliTransportPreset(presetId) : void 0;
|
|
8562
|
+
const mergedFileInput = preset?.options.fileInput || route.transport.cli?.fileInput ? {
|
|
8563
|
+
...preset?.options.fileInput ?? {},
|
|
8564
|
+
...route.transport.cli?.fileInput ?? {}
|
|
8565
|
+
} : void 0;
|
|
8479
8566
|
const merged = {
|
|
8480
8567
|
...preset?.options ?? {},
|
|
8481
8568
|
...route.transport.cli ?? {},
|
|
8482
8569
|
...route.transport.cli?.argsTemplate ? { argsTemplate: route.transport.cli.argsTemplate } : {},
|
|
8483
8570
|
...route.transport.cli?.parser ? { parser: route.transport.cli.parser } : {},
|
|
8571
|
+
...mergedFileInput ? { fileInput: mergedFileInput } : {},
|
|
8484
8572
|
...presetId ? { preset: presetId } : {}
|
|
8485
8573
|
};
|
|
8486
8574
|
if (!merged.argsTemplate?.length) {
|
|
@@ -8491,15 +8579,73 @@ function resolveCliTransportOptions(route) {
|
|
|
8491
8579
|
}
|
|
8492
8580
|
return merged;
|
|
8493
8581
|
}
|
|
8494
|
-
async function
|
|
8582
|
+
async function createCliTempDir(baseDir, prefix) {
|
|
8495
8583
|
const tempDir = await fs2.mkdtemp(
|
|
8496
|
-
path2.join(os2.tmpdir(), "ai-connect-cli-
|
|
8584
|
+
path2.join(baseDir ?? os2.tmpdir(), prefix ?? "ai-connect-cli-")
|
|
8497
8585
|
);
|
|
8498
8586
|
return {
|
|
8499
8587
|
tempDir,
|
|
8500
8588
|
outputFile: path2.join(tempDir, "last-message.txt")
|
|
8501
8589
|
};
|
|
8502
8590
|
}
|
|
8591
|
+
function safeStagedBasename(name, index, mimeType) {
|
|
8592
|
+
const base = path2.basename(name.replace(/\\/g, "/"));
|
|
8593
|
+
if (base === "" || base === "." || base === "..") {
|
|
8594
|
+
return `attachment-${index}.${extensionForMime(mimeType)}`;
|
|
8595
|
+
}
|
|
8596
|
+
return base;
|
|
8597
|
+
}
|
|
8598
|
+
async function stageCliAttachments(attachments, attachDir, fileInput) {
|
|
8599
|
+
const categories = new Set(
|
|
8600
|
+
fileInput.categories ?? ["image", "document", "text", "other"]
|
|
8601
|
+
);
|
|
8602
|
+
await fs2.mkdir(attachDir, { recursive: true });
|
|
8603
|
+
const refs = [];
|
|
8604
|
+
const usedNames = /* @__PURE__ */ new Map();
|
|
8605
|
+
for (let i = 0; i < attachments.length; i += 1) {
|
|
8606
|
+
const file = preparePortableFile(attachments[i]);
|
|
8607
|
+
const payload = await materializePortableFile(file);
|
|
8608
|
+
if (!categories.has(payload.category)) {
|
|
8609
|
+
continue;
|
|
8610
|
+
}
|
|
8611
|
+
let destName = safeStagedBasename(payload.name, i, payload.mimeType);
|
|
8612
|
+
const seen = usedNames.get(destName);
|
|
8613
|
+
if (seen !== void 0) {
|
|
8614
|
+
destName = `${i}-${destName}`;
|
|
8615
|
+
}
|
|
8616
|
+
usedNames.set(destName, i);
|
|
8617
|
+
const dest = path2.join(attachDir, destName);
|
|
8618
|
+
if (payload.category === "text") {
|
|
8619
|
+
if (payload.text !== void 0) {
|
|
8620
|
+
await fs2.writeFile(dest, payload.text, "utf8");
|
|
8621
|
+
refs.push(dest);
|
|
8622
|
+
continue;
|
|
8623
|
+
}
|
|
8624
|
+
} else {
|
|
8625
|
+
const bytes = payload.base64 ? Buffer.from(payload.base64, "base64") : await portableFileToBytes(file);
|
|
8626
|
+
if (bytes !== void 0) {
|
|
8627
|
+
await fs2.writeFile(dest, bytes);
|
|
8628
|
+
refs.push(dest);
|
|
8629
|
+
continue;
|
|
8630
|
+
}
|
|
8631
|
+
}
|
|
8632
|
+
if (payload.uri) {
|
|
8633
|
+
refs.push(payload.uri);
|
|
8634
|
+
}
|
|
8635
|
+
}
|
|
8636
|
+
return refs;
|
|
8637
|
+
}
|
|
8638
|
+
function expandFileArgs(staged, fileInput) {
|
|
8639
|
+
const perFileArgs = fileInput?.perFileArgs ?? ["@{path}"];
|
|
8640
|
+
if (fileInput?.placement === "prompt") {
|
|
8641
|
+
return [];
|
|
8642
|
+
}
|
|
8643
|
+
return staged.flatMap(
|
|
8644
|
+
(ref) => perFileArgs.map(
|
|
8645
|
+
(tpl) => tpl.replace("{path}", ref).replace("{name}", path2.basename(ref))
|
|
8646
|
+
)
|
|
8647
|
+
);
|
|
8648
|
+
}
|
|
8503
8649
|
function materializeTemplateArg(templateArg, values, parameterKeys) {
|
|
8504
8650
|
switch (templateArg) {
|
|
8505
8651
|
case "{prompt}":
|
|
@@ -8526,31 +8672,62 @@ function materializeTemplateArg(templateArg, values, parameterKeys) {
|
|
|
8526
8672
|
}
|
|
8527
8673
|
async function buildCliInvocation(context, options) {
|
|
8528
8674
|
const route = context.route;
|
|
8529
|
-
const prompt = buildCliPrompt(context);
|
|
8530
8675
|
const cliOptions = resolveCliTransportOptions(route);
|
|
8676
|
+
const fileInput = cliOptions.fileInput;
|
|
8677
|
+
const attachments = context.request.attachments;
|
|
8678
|
+
const hasStageable = Boolean(fileInput) && attachments.length > 0;
|
|
8679
|
+
let prompt = buildCliPrompt(context, Boolean(fileInput));
|
|
8531
8680
|
const commandLine = resolveCliCommand(route, options);
|
|
8532
8681
|
const resolved = splitCommandLine2(commandLine);
|
|
8533
8682
|
const parameterKeys = [];
|
|
8534
8683
|
let tempDir;
|
|
8535
8684
|
let outputFile;
|
|
8536
|
-
|
|
8537
|
-
|
|
8685
|
+
const needsOutputFile = cliOptions.argsTemplate.includes("{output_file}");
|
|
8686
|
+
if (needsOutputFile || hasStageable) {
|
|
8687
|
+
const created = await createCliTempDir(
|
|
8688
|
+
fileInput?.stagingDir ?? options?.staging?.dir,
|
|
8689
|
+
options?.staging?.prefix
|
|
8690
|
+
);
|
|
8538
8691
|
tempDir = created.tempDir;
|
|
8539
8692
|
outputFile = created.outputFile;
|
|
8540
8693
|
}
|
|
8694
|
+
let staged = [];
|
|
8695
|
+
if (hasStageable && tempDir && fileInput) {
|
|
8696
|
+
staged = await stageCliAttachments(
|
|
8697
|
+
attachments,
|
|
8698
|
+
path2.join(tempDir, "attachments"),
|
|
8699
|
+
fileInput
|
|
8700
|
+
);
|
|
8701
|
+
if (fileInput.placement === "prompt" && staged.length > 0) {
|
|
8702
|
+
const separator = fileInput.separator ?? " ";
|
|
8703
|
+
const mentionTemplate = fileInput.mentionTemplate ?? "@{path}";
|
|
8704
|
+
const mentions = staged.map(
|
|
8705
|
+
(ref) => mentionTemplate.replace("{path}", ref).replace("{name}", path2.basename(ref))
|
|
8706
|
+
).join(separator);
|
|
8707
|
+
prompt = `${prompt}${separator}${mentions}`;
|
|
8708
|
+
}
|
|
8709
|
+
}
|
|
8541
8710
|
const args = [
|
|
8542
8711
|
...resolved.args,
|
|
8543
|
-
...cliOptions.argsTemplate.
|
|
8544
|
-
(part)
|
|
8545
|
-
|
|
8546
|
-
|
|
8547
|
-
|
|
8548
|
-
|
|
8549
|
-
|
|
8550
|
-
|
|
8551
|
-
|
|
8552
|
-
|
|
8553
|
-
|
|
8712
|
+
...cliOptions.argsTemplate.flatMap((part) => {
|
|
8713
|
+
if (part === "{files}") {
|
|
8714
|
+
if (staged.length > 0) {
|
|
8715
|
+
parameterKeys.push("{files}");
|
|
8716
|
+
}
|
|
8717
|
+
return expandFileArgs(staged, fileInput);
|
|
8718
|
+
}
|
|
8719
|
+
return [
|
|
8720
|
+
materializeTemplateArg(
|
|
8721
|
+
part,
|
|
8722
|
+
{
|
|
8723
|
+
prompt,
|
|
8724
|
+
model: route.model,
|
|
8725
|
+
...outputFile ? { outputFile } : {}
|
|
8726
|
+
},
|
|
8727
|
+
parameterKeys
|
|
8728
|
+
)
|
|
8729
|
+
];
|
|
8730
|
+
})
|
|
8554
8731
|
];
|
|
8555
8732
|
return {
|
|
8556
8733
|
command: resolved.command,
|
|
@@ -8559,6 +8736,7 @@ async function buildCliInvocation(context, options) {
|
|
|
8559
8736
|
env: buildCliEnvironment(options),
|
|
8560
8737
|
...tempDir ? { tempDir } : {},
|
|
8561
8738
|
...outputFile ? { outputFile } : {},
|
|
8739
|
+
...options?.staging?.keep ? { keepStaging: true } : {},
|
|
8562
8740
|
parameterKeys
|
|
8563
8741
|
};
|
|
8564
8742
|
}
|
|
@@ -8947,7 +9125,7 @@ async function executeCliInvocation(invocation, timeoutMs, phases, signal) {
|
|
|
8947
9125
|
});
|
|
8948
9126
|
}
|
|
8949
9127
|
async function cleanupCliInvocation(invocation) {
|
|
8950
|
-
if (invocation.tempDir) {
|
|
9128
|
+
if (invocation.tempDir && !invocation.keepStaging) {
|
|
8951
9129
|
await fs2.rm(invocation.tempDir, { recursive: true, force: true });
|
|
8952
9130
|
}
|
|
8953
9131
|
}
|