@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/node/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() ? {
|
|
@@ -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
|
-
|
|
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);
|
|
@@ -6342,112 +6532,6 @@ var AI_CONNECT_DEFAULT_ACP_COMMANDS = {
|
|
|
6342
6532
|
"opencode:opencode-acp": "opencode acp"
|
|
6343
6533
|
};
|
|
6344
6534
|
|
|
6345
|
-
// src/cli-presets.ts
|
|
6346
|
-
var AI_CONNECT_DEFAULT_CLI_PRESETS = {
|
|
6347
|
-
pi: {
|
|
6348
|
-
id: "pi",
|
|
6349
|
-
label: "pi (coding agent)",
|
|
6350
|
-
command: "pi",
|
|
6351
|
-
transportId: "pi-cli",
|
|
6352
|
-
options: {
|
|
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}"],
|
|
6357
|
-
discovery: {
|
|
6358
|
-
via: "none"
|
|
6359
|
-
},
|
|
6360
|
-
parser: {
|
|
6361
|
-
kind: "text"
|
|
6362
|
-
}
|
|
6363
|
-
}
|
|
6364
|
-
},
|
|
6365
|
-
claude: {
|
|
6366
|
-
id: "claude",
|
|
6367
|
-
label: "Claude CLI",
|
|
6368
|
-
command: "claude",
|
|
6369
|
-
transportId: "claude-cli",
|
|
6370
|
-
options: {
|
|
6371
|
-
preset: "claude",
|
|
6372
|
-
argsTemplate: ["--print", "--output-format", "json", "--model", "{model}", "{prompt}"],
|
|
6373
|
-
discovery: {
|
|
6374
|
-
via: "acp",
|
|
6375
|
-
acp: {
|
|
6376
|
-
transportId: "claude-code-acp"
|
|
6377
|
-
}
|
|
6378
|
-
},
|
|
6379
|
-
parser: {
|
|
6380
|
-
kind: "json",
|
|
6381
|
-
textPath: "__preset__:claude.result",
|
|
6382
|
-
usagePath: "usage",
|
|
6383
|
-
errorPath: "__preset__:claude.error"
|
|
6384
|
-
}
|
|
6385
|
-
}
|
|
6386
|
-
},
|
|
6387
|
-
openclaude: {
|
|
6388
|
-
id: "openclaude",
|
|
6389
|
-
label: "OpenClaude CLI",
|
|
6390
|
-
command: "openclaude",
|
|
6391
|
-
transportId: "openclaude-cli",
|
|
6392
|
-
options: {
|
|
6393
|
-
preset: "openclaude",
|
|
6394
|
-
argsTemplate: ["--print", "--output-format", "json", "--model", "{model}", "{prompt}"],
|
|
6395
|
-
parser: {
|
|
6396
|
-
kind: "json",
|
|
6397
|
-
textPath: "__preset__:claude.result",
|
|
6398
|
-
usagePath: "usage",
|
|
6399
|
-
errorPath: "__preset__:claude.error"
|
|
6400
|
-
}
|
|
6401
|
-
}
|
|
6402
|
-
},
|
|
6403
|
-
codex: {
|
|
6404
|
-
id: "codex",
|
|
6405
|
-
label: "Codex CLI",
|
|
6406
|
-
command: "codex",
|
|
6407
|
-
transportId: "codex-cli",
|
|
6408
|
-
options: {
|
|
6409
|
-
preset: "codex",
|
|
6410
|
-
argsTemplate: [
|
|
6411
|
-
"exec",
|
|
6412
|
-
"--json",
|
|
6413
|
-
"--output-last-message",
|
|
6414
|
-
"{output_file}",
|
|
6415
|
-
"--model",
|
|
6416
|
-
"{model}",
|
|
6417
|
-
"{prompt}"
|
|
6418
|
-
],
|
|
6419
|
-
discovery: {
|
|
6420
|
-
via: "acp",
|
|
6421
|
-
acp: {
|
|
6422
|
-
transportId: "codex-acp"
|
|
6423
|
-
}
|
|
6424
|
-
},
|
|
6425
|
-
parser: {
|
|
6426
|
-
kind: "jsonl",
|
|
6427
|
-
text: {
|
|
6428
|
-
path: "__preset__:codex.text"
|
|
6429
|
-
},
|
|
6430
|
-
usage: {
|
|
6431
|
-
path: "__preset__:codex.usage"
|
|
6432
|
-
},
|
|
6433
|
-
error: {
|
|
6434
|
-
path: "__preset__:codex.error"
|
|
6435
|
-
}
|
|
6436
|
-
}
|
|
6437
|
-
}
|
|
6438
|
-
}
|
|
6439
|
-
};
|
|
6440
|
-
var AI_CONNECT_DEFAULT_CLI_COMMANDS = {
|
|
6441
|
-
"anthropic:claude-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.claude.command,
|
|
6442
|
-
"anthropic:openclaude-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.openclaude.command,
|
|
6443
|
-
"openclaude:openclaude-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.openclaude.command,
|
|
6444
|
-
"openai:codex-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.codex.command,
|
|
6445
|
-
"pi:pi-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.pi.command
|
|
6446
|
-
};
|
|
6447
|
-
function getCliTransportPreset(presetId) {
|
|
6448
|
-
return AI_CONNECT_DEFAULT_CLI_PRESETS[presetId];
|
|
6449
|
-
}
|
|
6450
|
-
|
|
6451
6535
|
// src/server-presets.ts
|
|
6452
6536
|
var AI_CONNECT_DEFAULT_SERVER_PRESETS = {
|
|
6453
6537
|
opencode: {
|
|
@@ -8561,25 +8645,26 @@ function splitCommandLine2(commandLine) {
|
|
|
8561
8645
|
function quoteArg(value) {
|
|
8562
8646
|
return /\s/.test(value) ? JSON.stringify(value) : value;
|
|
8563
8647
|
}
|
|
8564
|
-
function buildCliPrompt(context) {
|
|
8648
|
+
function buildCliPrompt(context, fileInputEnabled) {
|
|
8565
8649
|
if (context.request.operation !== "text") {
|
|
8566
8650
|
throw new AiConnectError(
|
|
8567
8651
|
"not_supported",
|
|
8568
8652
|
`CLI transport "${context.route.transport.id}" only supports text requests.`
|
|
8569
8653
|
);
|
|
8570
8654
|
}
|
|
8571
|
-
if (context.request.image
|
|
8572
|
-
(file) => portableFileCategory(file) === "image" || portableFileCategory(file) === "document"
|
|
8573
|
-
)) {
|
|
8655
|
+
if (context.request.image) {
|
|
8574
8656
|
throw new AiConnectError(
|
|
8575
8657
|
"unsupported_capability",
|
|
8576
|
-
`CLI transport "${context.route.transport.id}" does not support image
|
|
8658
|
+
`CLI transport "${context.route.transport.id}" does not support image output.`
|
|
8577
8659
|
);
|
|
8578
8660
|
}
|
|
8579
|
-
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
|
+
);
|
|
8580
8665
|
throw new AiConnectError(
|
|
8581
|
-
"not_supported",
|
|
8582
|
-
`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.`
|
|
8583
8668
|
);
|
|
8584
8669
|
}
|
|
8585
8670
|
return context.request.messages.map((message) => {
|
|
@@ -8724,11 +8809,16 @@ function createCliDiscoveryAcpRoute(route) {
|
|
|
8724
8809
|
function resolveCliTransportOptions(route) {
|
|
8725
8810
|
const presetId = route.transport.cli?.preset ?? defaultCliPresetIdForRoute(route);
|
|
8726
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;
|
|
8727
8816
|
const merged = {
|
|
8728
8817
|
...preset?.options ?? {},
|
|
8729
8818
|
...route.transport.cli ?? {},
|
|
8730
8819
|
...route.transport.cli?.argsTemplate ? { argsTemplate: route.transport.cli.argsTemplate } : {},
|
|
8731
8820
|
...route.transport.cli?.parser ? { parser: route.transport.cli.parser } : {},
|
|
8821
|
+
...mergedFileInput ? { fileInput: mergedFileInput } : {},
|
|
8732
8822
|
...presetId ? { preset: presetId } : {}
|
|
8733
8823
|
};
|
|
8734
8824
|
if (!merged.argsTemplate?.length) {
|
|
@@ -8739,15 +8829,73 @@ function resolveCliTransportOptions(route) {
|
|
|
8739
8829
|
}
|
|
8740
8830
|
return merged;
|
|
8741
8831
|
}
|
|
8742
|
-
async function
|
|
8832
|
+
async function createCliTempDir(baseDir, prefix) {
|
|
8743
8833
|
const tempDir = await fs2.mkdtemp(
|
|
8744
|
-
path2.join(os2.tmpdir(), "ai-connect-cli-
|
|
8834
|
+
path2.join(baseDir ?? os2.tmpdir(), prefix ?? "ai-connect-cli-")
|
|
8745
8835
|
);
|
|
8746
8836
|
return {
|
|
8747
8837
|
tempDir,
|
|
8748
8838
|
outputFile: path2.join(tempDir, "last-message.txt")
|
|
8749
8839
|
};
|
|
8750
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
|
+
}
|
|
8751
8899
|
function materializeTemplateArg(templateArg, values, parameterKeys) {
|
|
8752
8900
|
switch (templateArg) {
|
|
8753
8901
|
case "{prompt}":
|
|
@@ -8774,31 +8922,62 @@ function materializeTemplateArg(templateArg, values, parameterKeys) {
|
|
|
8774
8922
|
}
|
|
8775
8923
|
async function buildCliInvocation(context, options) {
|
|
8776
8924
|
const route = context.route;
|
|
8777
|
-
const prompt = buildCliPrompt(context);
|
|
8778
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));
|
|
8779
8930
|
const commandLine = resolveCliCommand(route, options);
|
|
8780
8931
|
const resolved = splitCommandLine2(commandLine);
|
|
8781
8932
|
const parameterKeys = [];
|
|
8782
8933
|
let tempDir;
|
|
8783
8934
|
let outputFile;
|
|
8784
|
-
|
|
8785
|
-
|
|
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
|
+
);
|
|
8786
8941
|
tempDir = created.tempDir;
|
|
8787
8942
|
outputFile = created.outputFile;
|
|
8788
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
|
+
}
|
|
8789
8960
|
const args = [
|
|
8790
8961
|
...resolved.args,
|
|
8791
|
-
...cliOptions.argsTemplate.
|
|
8792
|
-
(part)
|
|
8793
|
-
|
|
8794
|
-
|
|
8795
|
-
|
|
8796
|
-
|
|
8797
|
-
|
|
8798
|
-
|
|
8799
|
-
|
|
8800
|
-
|
|
8801
|
-
|
|
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
|
+
})
|
|
8802
8981
|
];
|
|
8803
8982
|
return {
|
|
8804
8983
|
command: resolved.command,
|
|
@@ -8807,6 +8986,7 @@ async function buildCliInvocation(context, options) {
|
|
|
8807
8986
|
env: buildCliEnvironment(options),
|
|
8808
8987
|
...tempDir ? { tempDir } : {},
|
|
8809
8988
|
...outputFile ? { outputFile } : {},
|
|
8989
|
+
...options?.staging?.keep ? { keepStaging: true } : {},
|
|
8810
8990
|
parameterKeys
|
|
8811
8991
|
};
|
|
8812
8992
|
}
|
|
@@ -9195,7 +9375,7 @@ async function executeCliInvocation(invocation, timeoutMs, phases, signal) {
|
|
|
9195
9375
|
});
|
|
9196
9376
|
}
|
|
9197
9377
|
async function cleanupCliInvocation(invocation) {
|
|
9198
|
-
if (invocation.tempDir) {
|
|
9378
|
+
if (invocation.tempDir && !invocation.keepStaging) {
|
|
9199
9379
|
await fs2.rm(invocation.tempDir, { recursive: true, force: true });
|
|
9200
9380
|
}
|
|
9201
9381
|
}
|