@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/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);
|
|
@@ -1794,6 +1984,8 @@ function fillConfiguredContextLength(route, models) {
|
|
|
1794
1984
|
return {
|
|
1795
1985
|
...entry,
|
|
1796
1986
|
contextLength: configured,
|
|
1987
|
+
// FT-002: typed provenance (canonical) + metadata alias (back-compat).
|
|
1988
|
+
contextWindowSource: "configured",
|
|
1797
1989
|
metadata: { ...entry.metadata ?? {}, contextWindowSource: "configured" }
|
|
1798
1990
|
};
|
|
1799
1991
|
});
|
|
@@ -3126,7 +3318,8 @@ function createClient(configOrInput, options = {}) {
|
|
|
3126
3318
|
...catalog.canonicalModelId ? { canonicalModelId: catalog.canonicalModelId } : {},
|
|
3127
3319
|
...catalog.resolvedModelId ? { resolvedModelId: catalog.resolvedModelId } : {},
|
|
3128
3320
|
...catalog.currentModelId ? { currentModelId: catalog.currentModelId } : {},
|
|
3129
|
-
availableModels: catalog.availableModels
|
|
3321
|
+
availableModels: catalog.availableModels,
|
|
3322
|
+
...catalog.warnings?.length ? { warnings: catalog.warnings } : {}
|
|
3130
3323
|
});
|
|
3131
3324
|
wideAttempts.push({
|
|
3132
3325
|
index: wideAttempts.length + 1,
|
|
@@ -4829,7 +5022,7 @@ function parseOpenAiModelCatalog(route, payload) {
|
|
|
4829
5022
|
modelId: entry.id,
|
|
4830
5023
|
name: typeof entry.display_name === "string" ? entry.display_name : typeof entry.name === "string" ? entry.name : entry.id,
|
|
4831
5024
|
...typeof entry.description === "string" ? { description: entry.description } : {},
|
|
4832
|
-
...contextLength !== void 0 ? { contextLength } : {},
|
|
5025
|
+
...contextLength !== void 0 ? { contextLength, contextWindowSource: "discovered" } : {},
|
|
4833
5026
|
...pricing ? { pricing } : {},
|
|
4834
5027
|
...free ? { free } : {},
|
|
4835
5028
|
...metadata ? {
|
|
@@ -6342,112 +6535,6 @@ var AI_CONNECT_DEFAULT_ACP_COMMANDS = {
|
|
|
6342
6535
|
"opencode:opencode-acp": "opencode acp"
|
|
6343
6536
|
};
|
|
6344
6537
|
|
|
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
6538
|
// src/server-presets.ts
|
|
6452
6539
|
var AI_CONNECT_DEFAULT_SERVER_PRESETS = {
|
|
6453
6540
|
opencode: {
|
|
@@ -8561,25 +8648,26 @@ function splitCommandLine2(commandLine) {
|
|
|
8561
8648
|
function quoteArg(value) {
|
|
8562
8649
|
return /\s/.test(value) ? JSON.stringify(value) : value;
|
|
8563
8650
|
}
|
|
8564
|
-
function buildCliPrompt(context) {
|
|
8651
|
+
function buildCliPrompt(context, fileInputEnabled) {
|
|
8565
8652
|
if (context.request.operation !== "text") {
|
|
8566
8653
|
throw new AiConnectError(
|
|
8567
8654
|
"not_supported",
|
|
8568
8655
|
`CLI transport "${context.route.transport.id}" only supports text requests.`
|
|
8569
8656
|
);
|
|
8570
8657
|
}
|
|
8571
|
-
if (context.request.image
|
|
8572
|
-
(file) => portableFileCategory(file) === "image" || portableFileCategory(file) === "document"
|
|
8573
|
-
)) {
|
|
8658
|
+
if (context.request.image) {
|
|
8574
8659
|
throw new AiConnectError(
|
|
8575
8660
|
"unsupported_capability",
|
|
8576
|
-
`CLI transport "${context.route.transport.id}" does not support image
|
|
8661
|
+
`CLI transport "${context.route.transport.id}" does not support image output.`
|
|
8577
8662
|
);
|
|
8578
8663
|
}
|
|
8579
|
-
if (context.request.attachments.length > 0) {
|
|
8664
|
+
if (!fileInputEnabled && context.request.attachments.length > 0) {
|
|
8665
|
+
const hasBinary = context.request.attachments.some(
|
|
8666
|
+
(file) => portableFileCategory(file) === "image" || portableFileCategory(file) === "document"
|
|
8667
|
+
);
|
|
8580
8668
|
throw new AiConnectError(
|
|
8581
|
-
"not_supported",
|
|
8582
|
-
`CLI transport "${context.route.transport.id}" does not support attachments.`
|
|
8669
|
+
hasBinary ? "unsupported_capability" : "not_supported",
|
|
8670
|
+
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
8671
|
);
|
|
8584
8672
|
}
|
|
8585
8673
|
return context.request.messages.map((message) => {
|
|
@@ -8724,11 +8812,16 @@ function createCliDiscoveryAcpRoute(route) {
|
|
|
8724
8812
|
function resolveCliTransportOptions(route) {
|
|
8725
8813
|
const presetId = route.transport.cli?.preset ?? defaultCliPresetIdForRoute(route);
|
|
8726
8814
|
const preset = presetId ? getCliTransportPreset(presetId) : void 0;
|
|
8815
|
+
const mergedFileInput = preset?.options.fileInput || route.transport.cli?.fileInput ? {
|
|
8816
|
+
...preset?.options.fileInput ?? {},
|
|
8817
|
+
...route.transport.cli?.fileInput ?? {}
|
|
8818
|
+
} : void 0;
|
|
8727
8819
|
const merged = {
|
|
8728
8820
|
...preset?.options ?? {},
|
|
8729
8821
|
...route.transport.cli ?? {},
|
|
8730
8822
|
...route.transport.cli?.argsTemplate ? { argsTemplate: route.transport.cli.argsTemplate } : {},
|
|
8731
8823
|
...route.transport.cli?.parser ? { parser: route.transport.cli.parser } : {},
|
|
8824
|
+
...mergedFileInput ? { fileInput: mergedFileInput } : {},
|
|
8732
8825
|
...presetId ? { preset: presetId } : {}
|
|
8733
8826
|
};
|
|
8734
8827
|
if (!merged.argsTemplate?.length) {
|
|
@@ -8739,15 +8832,73 @@ function resolveCliTransportOptions(route) {
|
|
|
8739
8832
|
}
|
|
8740
8833
|
return merged;
|
|
8741
8834
|
}
|
|
8742
|
-
async function
|
|
8835
|
+
async function createCliTempDir(baseDir, prefix) {
|
|
8743
8836
|
const tempDir = await fs2.mkdtemp(
|
|
8744
|
-
path2.join(os2.tmpdir(), "ai-connect-cli-
|
|
8837
|
+
path2.join(baseDir ?? os2.tmpdir(), prefix ?? "ai-connect-cli-")
|
|
8745
8838
|
);
|
|
8746
8839
|
return {
|
|
8747
8840
|
tempDir,
|
|
8748
8841
|
outputFile: path2.join(tempDir, "last-message.txt")
|
|
8749
8842
|
};
|
|
8750
8843
|
}
|
|
8844
|
+
function safeStagedBasename(name, index, mimeType) {
|
|
8845
|
+
const base = path2.basename(name.replace(/\\/g, "/"));
|
|
8846
|
+
if (base === "" || base === "." || base === "..") {
|
|
8847
|
+
return `attachment-${index}.${extensionForMime(mimeType)}`;
|
|
8848
|
+
}
|
|
8849
|
+
return base;
|
|
8850
|
+
}
|
|
8851
|
+
async function stageCliAttachments(attachments, attachDir, fileInput) {
|
|
8852
|
+
const categories = new Set(
|
|
8853
|
+
fileInput.categories ?? ["image", "document", "text", "other"]
|
|
8854
|
+
);
|
|
8855
|
+
await fs2.mkdir(attachDir, { recursive: true });
|
|
8856
|
+
const refs = [];
|
|
8857
|
+
const usedNames = /* @__PURE__ */ new Map();
|
|
8858
|
+
for (let i = 0; i < attachments.length; i += 1) {
|
|
8859
|
+
const file = preparePortableFile(attachments[i]);
|
|
8860
|
+
const payload = await materializePortableFile(file);
|
|
8861
|
+
if (!categories.has(payload.category)) {
|
|
8862
|
+
continue;
|
|
8863
|
+
}
|
|
8864
|
+
let destName = safeStagedBasename(payload.name, i, payload.mimeType);
|
|
8865
|
+
const seen = usedNames.get(destName);
|
|
8866
|
+
if (seen !== void 0) {
|
|
8867
|
+
destName = `${i}-${destName}`;
|
|
8868
|
+
}
|
|
8869
|
+
usedNames.set(destName, i);
|
|
8870
|
+
const dest = path2.join(attachDir, destName);
|
|
8871
|
+
if (payload.category === "text") {
|
|
8872
|
+
if (payload.text !== void 0) {
|
|
8873
|
+
await fs2.writeFile(dest, payload.text, "utf8");
|
|
8874
|
+
refs.push(dest);
|
|
8875
|
+
continue;
|
|
8876
|
+
}
|
|
8877
|
+
} else {
|
|
8878
|
+
const bytes = payload.base64 ? Buffer.from(payload.base64, "base64") : await portableFileToBytes(file);
|
|
8879
|
+
if (bytes !== void 0) {
|
|
8880
|
+
await fs2.writeFile(dest, bytes);
|
|
8881
|
+
refs.push(dest);
|
|
8882
|
+
continue;
|
|
8883
|
+
}
|
|
8884
|
+
}
|
|
8885
|
+
if (payload.uri) {
|
|
8886
|
+
refs.push(payload.uri);
|
|
8887
|
+
}
|
|
8888
|
+
}
|
|
8889
|
+
return refs;
|
|
8890
|
+
}
|
|
8891
|
+
function expandFileArgs(staged, fileInput) {
|
|
8892
|
+
const perFileArgs = fileInput?.perFileArgs ?? ["@{path}"];
|
|
8893
|
+
if (fileInput?.placement === "prompt") {
|
|
8894
|
+
return [];
|
|
8895
|
+
}
|
|
8896
|
+
return staged.flatMap(
|
|
8897
|
+
(ref) => perFileArgs.map(
|
|
8898
|
+
(tpl) => tpl.replace("{path}", ref).replace("{name}", path2.basename(ref))
|
|
8899
|
+
)
|
|
8900
|
+
);
|
|
8901
|
+
}
|
|
8751
8902
|
function materializeTemplateArg(templateArg, values, parameterKeys) {
|
|
8752
8903
|
switch (templateArg) {
|
|
8753
8904
|
case "{prompt}":
|
|
@@ -8774,31 +8925,62 @@ function materializeTemplateArg(templateArg, values, parameterKeys) {
|
|
|
8774
8925
|
}
|
|
8775
8926
|
async function buildCliInvocation(context, options) {
|
|
8776
8927
|
const route = context.route;
|
|
8777
|
-
const prompt = buildCliPrompt(context);
|
|
8778
8928
|
const cliOptions = resolveCliTransportOptions(route);
|
|
8929
|
+
const fileInput = cliOptions.fileInput;
|
|
8930
|
+
const attachments = context.request.attachments;
|
|
8931
|
+
const hasStageable = Boolean(fileInput) && attachments.length > 0;
|
|
8932
|
+
let prompt = buildCliPrompt(context, Boolean(fileInput));
|
|
8779
8933
|
const commandLine = resolveCliCommand(route, options);
|
|
8780
8934
|
const resolved = splitCommandLine2(commandLine);
|
|
8781
8935
|
const parameterKeys = [];
|
|
8782
8936
|
let tempDir;
|
|
8783
8937
|
let outputFile;
|
|
8784
|
-
|
|
8785
|
-
|
|
8938
|
+
const needsOutputFile = cliOptions.argsTemplate.includes("{output_file}");
|
|
8939
|
+
if (needsOutputFile || hasStageable) {
|
|
8940
|
+
const created = await createCliTempDir(
|
|
8941
|
+
fileInput?.stagingDir ?? options?.staging?.dir,
|
|
8942
|
+
options?.staging?.prefix
|
|
8943
|
+
);
|
|
8786
8944
|
tempDir = created.tempDir;
|
|
8787
8945
|
outputFile = created.outputFile;
|
|
8788
8946
|
}
|
|
8947
|
+
let staged = [];
|
|
8948
|
+
if (hasStageable && tempDir && fileInput) {
|
|
8949
|
+
staged = await stageCliAttachments(
|
|
8950
|
+
attachments,
|
|
8951
|
+
path2.join(tempDir, "attachments"),
|
|
8952
|
+
fileInput
|
|
8953
|
+
);
|
|
8954
|
+
if (fileInput.placement === "prompt" && staged.length > 0) {
|
|
8955
|
+
const separator = fileInput.separator ?? " ";
|
|
8956
|
+
const mentionTemplate = fileInput.mentionTemplate ?? "@{path}";
|
|
8957
|
+
const mentions = staged.map(
|
|
8958
|
+
(ref) => mentionTemplate.replace("{path}", ref).replace("{name}", path2.basename(ref))
|
|
8959
|
+
).join(separator);
|
|
8960
|
+
prompt = `${prompt}${separator}${mentions}`;
|
|
8961
|
+
}
|
|
8962
|
+
}
|
|
8789
8963
|
const args = [
|
|
8790
8964
|
...resolved.args,
|
|
8791
|
-
...cliOptions.argsTemplate.
|
|
8792
|
-
(part)
|
|
8793
|
-
|
|
8794
|
-
|
|
8795
|
-
|
|
8796
|
-
|
|
8797
|
-
|
|
8798
|
-
|
|
8799
|
-
|
|
8800
|
-
|
|
8801
|
-
|
|
8965
|
+
...cliOptions.argsTemplate.flatMap((part) => {
|
|
8966
|
+
if (part === "{files}") {
|
|
8967
|
+
if (staged.length > 0) {
|
|
8968
|
+
parameterKeys.push("{files}");
|
|
8969
|
+
}
|
|
8970
|
+
return expandFileArgs(staged, fileInput);
|
|
8971
|
+
}
|
|
8972
|
+
return [
|
|
8973
|
+
materializeTemplateArg(
|
|
8974
|
+
part,
|
|
8975
|
+
{
|
|
8976
|
+
prompt,
|
|
8977
|
+
model: route.model,
|
|
8978
|
+
...outputFile ? { outputFile } : {}
|
|
8979
|
+
},
|
|
8980
|
+
parameterKeys
|
|
8981
|
+
)
|
|
8982
|
+
];
|
|
8983
|
+
})
|
|
8802
8984
|
];
|
|
8803
8985
|
return {
|
|
8804
8986
|
command: resolved.command,
|
|
@@ -8807,6 +8989,7 @@ async function buildCliInvocation(context, options) {
|
|
|
8807
8989
|
env: buildCliEnvironment(options),
|
|
8808
8990
|
...tempDir ? { tempDir } : {},
|
|
8809
8991
|
...outputFile ? { outputFile } : {},
|
|
8992
|
+
...options?.staging?.keep ? { keepStaging: true } : {},
|
|
8810
8993
|
parameterKeys
|
|
8811
8994
|
};
|
|
8812
8995
|
}
|
|
@@ -8835,16 +9018,20 @@ function buildCliDiscoveryInvocation(route, command, options) {
|
|
|
8835
9018
|
parameterKeys
|
|
8836
9019
|
};
|
|
8837
9020
|
}
|
|
8838
|
-
function buildStaticCliCatalog(route) {
|
|
9021
|
+
function buildStaticCliCatalog(route, warning) {
|
|
8839
9022
|
const models = route.advertisedModels.map((id) => ({
|
|
8840
9023
|
modelId: id,
|
|
8841
9024
|
name: id
|
|
8842
9025
|
}));
|
|
8843
|
-
|
|
9026
|
+
const catalog = buildModelCatalog(
|
|
8844
9027
|
route,
|
|
8845
9028
|
models,
|
|
8846
9029
|
currentModelIdForRoute(route, route.advertisedModels)
|
|
8847
9030
|
);
|
|
9031
|
+
if (warning) {
|
|
9032
|
+
catalog.warnings = [...catalog.warnings ?? [], warning];
|
|
9033
|
+
}
|
|
9034
|
+
return catalog;
|
|
8848
9035
|
}
|
|
8849
9036
|
function statsToUsage(stats) {
|
|
8850
9037
|
if (!stats || typeof stats !== "object") {
|
|
@@ -8987,7 +9174,9 @@ function modelInfoFromRecord(record, selector) {
|
|
|
8987
9174
|
modelId,
|
|
8988
9175
|
name,
|
|
8989
9176
|
...description ? { description } : {},
|
|
8990
|
-
|
|
9177
|
+
// FT-002: a contextLength read from the list-command output is a discovered
|
|
9178
|
+
// value (consistent with the API parsers), so tag its provenance.
|
|
9179
|
+
...contextLength !== void 0 ? { contextLength, contextWindowSource: "discovered" } : {}
|
|
8991
9180
|
};
|
|
8992
9181
|
}
|
|
8993
9182
|
function parseCliModelList(stdout, parser, selector) {
|
|
@@ -9195,7 +9384,7 @@ async function executeCliInvocation(invocation, timeoutMs, phases, signal) {
|
|
|
9195
9384
|
});
|
|
9196
9385
|
}
|
|
9197
9386
|
async function cleanupCliInvocation(invocation) {
|
|
9198
|
-
if (invocation.tempDir) {
|
|
9387
|
+
if (invocation.tempDir && !invocation.keepStaging) {
|
|
9199
9388
|
await fs2.rm(invocation.tempDir, { recursive: true, force: true });
|
|
9200
9389
|
}
|
|
9201
9390
|
}
|
|
@@ -9258,13 +9447,20 @@ function createCliTransportManager(options) {
|
|
|
9258
9447
|
throw error;
|
|
9259
9448
|
}
|
|
9260
9449
|
if (source.fallback === "static") {
|
|
9261
|
-
|
|
9450
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
9451
|
+
return buildStaticCliCatalog(
|
|
9452
|
+
context.route,
|
|
9453
|
+
`CLI discovery command for "${context.route.transport.id}" failed (${detail}); fell back to the static models[] catalog.`
|
|
9454
|
+
);
|
|
9262
9455
|
}
|
|
9263
9456
|
throw error;
|
|
9264
9457
|
}
|
|
9265
9458
|
if (models.length === 0) {
|
|
9266
9459
|
if (source.fallback === "static") {
|
|
9267
|
-
return buildStaticCliCatalog(
|
|
9460
|
+
return buildStaticCliCatalog(
|
|
9461
|
+
context.route,
|
|
9462
|
+
`CLI discovery command for "${context.route.transport.id}" returned no models; fell back to the static models[] catalog.`
|
|
9463
|
+
);
|
|
9268
9464
|
}
|
|
9269
9465
|
throw new AiConnectError(
|
|
9270
9466
|
"temporary_unavailable",
|