@vedmalex/ai-connect 0.5.0 → 0.7.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 +62 -1
- package/dist/browser/index.js +195 -112
- package/dist/browser/index.js.map +3 -3
- package/dist/bun/index.js +338 -142
- package/dist/bun/index.js.map +3 -3
- package/dist/bun/local.js +338 -144
- package/dist/bun/local.js.map +3 -3
- package/dist/node/index.js +338 -142
- package/dist/node/index.js.map +3 -3
- package/dist/node/local.js +338 -144
- 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/client.d.ts.map +1 -1
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/default-handlers.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/model-reference.d.ts.map +1 -1
- package/dist/types/types.d.ts +60 -0
- package/dist/types/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/node/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);
|
|
@@ -1785,6 +1975,8 @@ function fillConfiguredContextLength(route, models) {
|
|
|
1785
1975
|
return {
|
|
1786
1976
|
...entry,
|
|
1787
1977
|
contextLength: configured,
|
|
1978
|
+
// FT-002: typed provenance (canonical) + metadata alias (back-compat).
|
|
1979
|
+
contextWindowSource: "configured",
|
|
1788
1980
|
metadata: { ...entry.metadata ?? {}, contextWindowSource: "configured" }
|
|
1789
1981
|
};
|
|
1790
1982
|
});
|
|
@@ -3114,7 +3306,8 @@ function createClient(configOrInput, options = {}) {
|
|
|
3114
3306
|
...catalog.canonicalModelId ? { canonicalModelId: catalog.canonicalModelId } : {},
|
|
3115
3307
|
...catalog.resolvedModelId ? { resolvedModelId: catalog.resolvedModelId } : {},
|
|
3116
3308
|
...catalog.currentModelId ? { currentModelId: catalog.currentModelId } : {},
|
|
3117
|
-
availableModels: catalog.availableModels
|
|
3309
|
+
availableModels: catalog.availableModels,
|
|
3310
|
+
...catalog.warnings?.length ? { warnings: catalog.warnings } : {}
|
|
3118
3311
|
});
|
|
3119
3312
|
wideAttempts.push({
|
|
3120
3313
|
index: wideAttempts.length + 1,
|
|
@@ -4799,7 +4992,7 @@ function parseOpenAiModelCatalog(route, payload) {
|
|
|
4799
4992
|
modelId: entry.id,
|
|
4800
4993
|
name: typeof entry.display_name === "string" ? entry.display_name : typeof entry.name === "string" ? entry.name : entry.id,
|
|
4801
4994
|
...typeof entry.description === "string" ? { description: entry.description } : {},
|
|
4802
|
-
...contextLength !== void 0 ? { contextLength } : {},
|
|
4995
|
+
...contextLength !== void 0 ? { contextLength, contextWindowSource: "discovered" } : {},
|
|
4803
4996
|
...pricing ? { pricing } : {},
|
|
4804
4997
|
...free ? { free } : {},
|
|
4805
4998
|
...metadata ? {
|
|
@@ -8175,114 +8368,6 @@ import { spawn as spawn2 } from "node:child_process";
|
|
|
8175
8368
|
import fs2 from "node:fs/promises";
|
|
8176
8369
|
import os2 from "node:os";
|
|
8177
8370
|
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
8371
|
var CLI_PRESET_ACP_DISCOVERY_DEFAULTS = {
|
|
8287
8372
|
claude: {
|
|
8288
8373
|
transportId: "claude-code-acp",
|
|
@@ -8313,25 +8398,26 @@ function splitCommandLine2(commandLine) {
|
|
|
8313
8398
|
function quoteArg(value) {
|
|
8314
8399
|
return /\s/.test(value) ? JSON.stringify(value) : value;
|
|
8315
8400
|
}
|
|
8316
|
-
function buildCliPrompt(context) {
|
|
8401
|
+
function buildCliPrompt(context, fileInputEnabled) {
|
|
8317
8402
|
if (context.request.operation !== "text") {
|
|
8318
8403
|
throw new AiConnectError(
|
|
8319
8404
|
"not_supported",
|
|
8320
8405
|
`CLI transport "${context.route.transport.id}" only supports text requests.`
|
|
8321
8406
|
);
|
|
8322
8407
|
}
|
|
8323
|
-
if (context.request.image
|
|
8324
|
-
(file) => portableFileCategory(file) === "image" || portableFileCategory(file) === "document"
|
|
8325
|
-
)) {
|
|
8408
|
+
if (context.request.image) {
|
|
8326
8409
|
throw new AiConnectError(
|
|
8327
8410
|
"unsupported_capability",
|
|
8328
|
-
`CLI transport "${context.route.transport.id}" does not support image
|
|
8411
|
+
`CLI transport "${context.route.transport.id}" does not support image output.`
|
|
8329
8412
|
);
|
|
8330
8413
|
}
|
|
8331
|
-
if (context.request.attachments.length > 0) {
|
|
8414
|
+
if (!fileInputEnabled && context.request.attachments.length > 0) {
|
|
8415
|
+
const hasBinary = context.request.attachments.some(
|
|
8416
|
+
(file) => portableFileCategory(file) === "image" || portableFileCategory(file) === "document"
|
|
8417
|
+
);
|
|
8332
8418
|
throw new AiConnectError(
|
|
8333
|
-
"not_supported",
|
|
8334
|
-
`CLI transport "${context.route.transport.id}" does not support attachments.`
|
|
8419
|
+
hasBinary ? "unsupported_capability" : "not_supported",
|
|
8420
|
+
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
8421
|
);
|
|
8336
8422
|
}
|
|
8337
8423
|
return context.request.messages.map((message) => {
|
|
@@ -8476,11 +8562,16 @@ function createCliDiscoveryAcpRoute(route) {
|
|
|
8476
8562
|
function resolveCliTransportOptions(route) {
|
|
8477
8563
|
const presetId = route.transport.cli?.preset ?? defaultCliPresetIdForRoute(route);
|
|
8478
8564
|
const preset = presetId ? getCliTransportPreset(presetId) : void 0;
|
|
8565
|
+
const mergedFileInput = preset?.options.fileInput || route.transport.cli?.fileInput ? {
|
|
8566
|
+
...preset?.options.fileInput ?? {},
|
|
8567
|
+
...route.transport.cli?.fileInput ?? {}
|
|
8568
|
+
} : void 0;
|
|
8479
8569
|
const merged = {
|
|
8480
8570
|
...preset?.options ?? {},
|
|
8481
8571
|
...route.transport.cli ?? {},
|
|
8482
8572
|
...route.transport.cli?.argsTemplate ? { argsTemplate: route.transport.cli.argsTemplate } : {},
|
|
8483
8573
|
...route.transport.cli?.parser ? { parser: route.transport.cli.parser } : {},
|
|
8574
|
+
...mergedFileInput ? { fileInput: mergedFileInput } : {},
|
|
8484
8575
|
...presetId ? { preset: presetId } : {}
|
|
8485
8576
|
};
|
|
8486
8577
|
if (!merged.argsTemplate?.length) {
|
|
@@ -8491,15 +8582,73 @@ function resolveCliTransportOptions(route) {
|
|
|
8491
8582
|
}
|
|
8492
8583
|
return merged;
|
|
8493
8584
|
}
|
|
8494
|
-
async function
|
|
8585
|
+
async function createCliTempDir(baseDir, prefix) {
|
|
8495
8586
|
const tempDir = await fs2.mkdtemp(
|
|
8496
|
-
path2.join(os2.tmpdir(), "ai-connect-cli-
|
|
8587
|
+
path2.join(baseDir ?? os2.tmpdir(), prefix ?? "ai-connect-cli-")
|
|
8497
8588
|
);
|
|
8498
8589
|
return {
|
|
8499
8590
|
tempDir,
|
|
8500
8591
|
outputFile: path2.join(tempDir, "last-message.txt")
|
|
8501
8592
|
};
|
|
8502
8593
|
}
|
|
8594
|
+
function safeStagedBasename(name, index, mimeType) {
|
|
8595
|
+
const base = path2.basename(name.replace(/\\/g, "/"));
|
|
8596
|
+
if (base === "" || base === "." || base === "..") {
|
|
8597
|
+
return `attachment-${index}.${extensionForMime(mimeType)}`;
|
|
8598
|
+
}
|
|
8599
|
+
return base;
|
|
8600
|
+
}
|
|
8601
|
+
async function stageCliAttachments(attachments, attachDir, fileInput) {
|
|
8602
|
+
const categories = new Set(
|
|
8603
|
+
fileInput.categories ?? ["image", "document", "text", "other"]
|
|
8604
|
+
);
|
|
8605
|
+
await fs2.mkdir(attachDir, { recursive: true });
|
|
8606
|
+
const refs = [];
|
|
8607
|
+
const usedNames = /* @__PURE__ */ new Map();
|
|
8608
|
+
for (let i = 0; i < attachments.length; i += 1) {
|
|
8609
|
+
const file = preparePortableFile(attachments[i]);
|
|
8610
|
+
const payload = await materializePortableFile(file);
|
|
8611
|
+
if (!categories.has(payload.category)) {
|
|
8612
|
+
continue;
|
|
8613
|
+
}
|
|
8614
|
+
let destName = safeStagedBasename(payload.name, i, payload.mimeType);
|
|
8615
|
+
const seen = usedNames.get(destName);
|
|
8616
|
+
if (seen !== void 0) {
|
|
8617
|
+
destName = `${i}-${destName}`;
|
|
8618
|
+
}
|
|
8619
|
+
usedNames.set(destName, i);
|
|
8620
|
+
const dest = path2.join(attachDir, destName);
|
|
8621
|
+
if (payload.category === "text") {
|
|
8622
|
+
if (payload.text !== void 0) {
|
|
8623
|
+
await fs2.writeFile(dest, payload.text, "utf8");
|
|
8624
|
+
refs.push(dest);
|
|
8625
|
+
continue;
|
|
8626
|
+
}
|
|
8627
|
+
} else {
|
|
8628
|
+
const bytes = payload.base64 ? Buffer.from(payload.base64, "base64") : await portableFileToBytes(file);
|
|
8629
|
+
if (bytes !== void 0) {
|
|
8630
|
+
await fs2.writeFile(dest, bytes);
|
|
8631
|
+
refs.push(dest);
|
|
8632
|
+
continue;
|
|
8633
|
+
}
|
|
8634
|
+
}
|
|
8635
|
+
if (payload.uri) {
|
|
8636
|
+
refs.push(payload.uri);
|
|
8637
|
+
}
|
|
8638
|
+
}
|
|
8639
|
+
return refs;
|
|
8640
|
+
}
|
|
8641
|
+
function expandFileArgs(staged, fileInput) {
|
|
8642
|
+
const perFileArgs = fileInput?.perFileArgs ?? ["@{path}"];
|
|
8643
|
+
if (fileInput?.placement === "prompt") {
|
|
8644
|
+
return [];
|
|
8645
|
+
}
|
|
8646
|
+
return staged.flatMap(
|
|
8647
|
+
(ref) => perFileArgs.map(
|
|
8648
|
+
(tpl) => tpl.replace("{path}", ref).replace("{name}", path2.basename(ref))
|
|
8649
|
+
)
|
|
8650
|
+
);
|
|
8651
|
+
}
|
|
8503
8652
|
function materializeTemplateArg(templateArg, values, parameterKeys) {
|
|
8504
8653
|
switch (templateArg) {
|
|
8505
8654
|
case "{prompt}":
|
|
@@ -8526,31 +8675,62 @@ function materializeTemplateArg(templateArg, values, parameterKeys) {
|
|
|
8526
8675
|
}
|
|
8527
8676
|
async function buildCliInvocation(context, options) {
|
|
8528
8677
|
const route = context.route;
|
|
8529
|
-
const prompt = buildCliPrompt(context);
|
|
8530
8678
|
const cliOptions = resolveCliTransportOptions(route);
|
|
8679
|
+
const fileInput = cliOptions.fileInput;
|
|
8680
|
+
const attachments = context.request.attachments;
|
|
8681
|
+
const hasStageable = Boolean(fileInput) && attachments.length > 0;
|
|
8682
|
+
let prompt = buildCliPrompt(context, Boolean(fileInput));
|
|
8531
8683
|
const commandLine = resolveCliCommand(route, options);
|
|
8532
8684
|
const resolved = splitCommandLine2(commandLine);
|
|
8533
8685
|
const parameterKeys = [];
|
|
8534
8686
|
let tempDir;
|
|
8535
8687
|
let outputFile;
|
|
8536
|
-
|
|
8537
|
-
|
|
8688
|
+
const needsOutputFile = cliOptions.argsTemplate.includes("{output_file}");
|
|
8689
|
+
if (needsOutputFile || hasStageable) {
|
|
8690
|
+
const created = await createCliTempDir(
|
|
8691
|
+
fileInput?.stagingDir ?? options?.staging?.dir,
|
|
8692
|
+
options?.staging?.prefix
|
|
8693
|
+
);
|
|
8538
8694
|
tempDir = created.tempDir;
|
|
8539
8695
|
outputFile = created.outputFile;
|
|
8540
8696
|
}
|
|
8697
|
+
let staged = [];
|
|
8698
|
+
if (hasStageable && tempDir && fileInput) {
|
|
8699
|
+
staged = await stageCliAttachments(
|
|
8700
|
+
attachments,
|
|
8701
|
+
path2.join(tempDir, "attachments"),
|
|
8702
|
+
fileInput
|
|
8703
|
+
);
|
|
8704
|
+
if (fileInput.placement === "prompt" && staged.length > 0) {
|
|
8705
|
+
const separator = fileInput.separator ?? " ";
|
|
8706
|
+
const mentionTemplate = fileInput.mentionTemplate ?? "@{path}";
|
|
8707
|
+
const mentions = staged.map(
|
|
8708
|
+
(ref) => mentionTemplate.replace("{path}", ref).replace("{name}", path2.basename(ref))
|
|
8709
|
+
).join(separator);
|
|
8710
|
+
prompt = `${prompt}${separator}${mentions}`;
|
|
8711
|
+
}
|
|
8712
|
+
}
|
|
8541
8713
|
const args = [
|
|
8542
8714
|
...resolved.args,
|
|
8543
|
-
...cliOptions.argsTemplate.
|
|
8544
|
-
(part)
|
|
8545
|
-
|
|
8546
|
-
|
|
8547
|
-
|
|
8548
|
-
|
|
8549
|
-
|
|
8550
|
-
|
|
8551
|
-
|
|
8552
|
-
|
|
8553
|
-
|
|
8715
|
+
...cliOptions.argsTemplate.flatMap((part) => {
|
|
8716
|
+
if (part === "{files}") {
|
|
8717
|
+
if (staged.length > 0) {
|
|
8718
|
+
parameterKeys.push("{files}");
|
|
8719
|
+
}
|
|
8720
|
+
return expandFileArgs(staged, fileInput);
|
|
8721
|
+
}
|
|
8722
|
+
return [
|
|
8723
|
+
materializeTemplateArg(
|
|
8724
|
+
part,
|
|
8725
|
+
{
|
|
8726
|
+
prompt,
|
|
8727
|
+
model: route.model,
|
|
8728
|
+
...outputFile ? { outputFile } : {}
|
|
8729
|
+
},
|
|
8730
|
+
parameterKeys
|
|
8731
|
+
)
|
|
8732
|
+
];
|
|
8733
|
+
})
|
|
8554
8734
|
];
|
|
8555
8735
|
return {
|
|
8556
8736
|
command: resolved.command,
|
|
@@ -8559,6 +8739,7 @@ async function buildCliInvocation(context, options) {
|
|
|
8559
8739
|
env: buildCliEnvironment(options),
|
|
8560
8740
|
...tempDir ? { tempDir } : {},
|
|
8561
8741
|
...outputFile ? { outputFile } : {},
|
|
8742
|
+
...options?.staging?.keep ? { keepStaging: true } : {},
|
|
8562
8743
|
parameterKeys
|
|
8563
8744
|
};
|
|
8564
8745
|
}
|
|
@@ -8587,16 +8768,20 @@ function buildCliDiscoveryInvocation(route, command, options) {
|
|
|
8587
8768
|
parameterKeys
|
|
8588
8769
|
};
|
|
8589
8770
|
}
|
|
8590
|
-
function buildStaticCliCatalog(route) {
|
|
8771
|
+
function buildStaticCliCatalog(route, warning) {
|
|
8591
8772
|
const models = route.advertisedModels.map((id) => ({
|
|
8592
8773
|
modelId: id,
|
|
8593
8774
|
name: id
|
|
8594
8775
|
}));
|
|
8595
|
-
|
|
8776
|
+
const catalog = buildModelCatalog(
|
|
8596
8777
|
route,
|
|
8597
8778
|
models,
|
|
8598
8779
|
currentModelIdForRoute(route, route.advertisedModels)
|
|
8599
8780
|
);
|
|
8781
|
+
if (warning) {
|
|
8782
|
+
catalog.warnings = [...catalog.warnings ?? [], warning];
|
|
8783
|
+
}
|
|
8784
|
+
return catalog;
|
|
8600
8785
|
}
|
|
8601
8786
|
function statsToUsage(stats) {
|
|
8602
8787
|
if (!stats || typeof stats !== "object") {
|
|
@@ -8739,7 +8924,9 @@ function modelInfoFromRecord(record, selector) {
|
|
|
8739
8924
|
modelId,
|
|
8740
8925
|
name,
|
|
8741
8926
|
...description ? { description } : {},
|
|
8742
|
-
|
|
8927
|
+
// FT-002: a contextLength read from the list-command output is a discovered
|
|
8928
|
+
// value (consistent with the API parsers), so tag its provenance.
|
|
8929
|
+
...contextLength !== void 0 ? { contextLength, contextWindowSource: "discovered" } : {}
|
|
8743
8930
|
};
|
|
8744
8931
|
}
|
|
8745
8932
|
function parseCliModelList(stdout, parser, selector) {
|
|
@@ -8947,7 +9134,7 @@ async function executeCliInvocation(invocation, timeoutMs, phases, signal) {
|
|
|
8947
9134
|
});
|
|
8948
9135
|
}
|
|
8949
9136
|
async function cleanupCliInvocation(invocation) {
|
|
8950
|
-
if (invocation.tempDir) {
|
|
9137
|
+
if (invocation.tempDir && !invocation.keepStaging) {
|
|
8951
9138
|
await fs2.rm(invocation.tempDir, { recursive: true, force: true });
|
|
8952
9139
|
}
|
|
8953
9140
|
}
|
|
@@ -9010,13 +9197,20 @@ function createCliTransportManager(options) {
|
|
|
9010
9197
|
throw error;
|
|
9011
9198
|
}
|
|
9012
9199
|
if (source.fallback === "static") {
|
|
9013
|
-
|
|
9200
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
9201
|
+
return buildStaticCliCatalog(
|
|
9202
|
+
context.route,
|
|
9203
|
+
`CLI discovery command for "${context.route.transport.id}" failed (${detail}); fell back to the static models[] catalog.`
|
|
9204
|
+
);
|
|
9014
9205
|
}
|
|
9015
9206
|
throw error;
|
|
9016
9207
|
}
|
|
9017
9208
|
if (models.length === 0) {
|
|
9018
9209
|
if (source.fallback === "static") {
|
|
9019
|
-
return buildStaticCliCatalog(
|
|
9210
|
+
return buildStaticCliCatalog(
|
|
9211
|
+
context.route,
|
|
9212
|
+
`CLI discovery command for "${context.route.transport.id}" returned no models; fell back to the static models[] catalog.`
|
|
9213
|
+
);
|
|
9020
9214
|
}
|
|
9021
9215
|
throw new AiConnectError(
|
|
9022
9216
|
"temporary_unavailable",
|