@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/README.md
CHANGED
|
@@ -432,6 +432,65 @@ Current local transport scope:
|
|
|
432
432
|
- `cli`: text generation, plus model discovery via a list `command`, a `static` config catalog, or an ACP sidecar
|
|
433
433
|
- `server`: text generation plus provider-native model discovery
|
|
434
434
|
|
|
435
|
+
### CLI File and Image Input
|
|
436
|
+
|
|
437
|
+
CLI routes can stage local attachment files into a temp directory and pass them to the subprocess as argv tokens or inline prompt references.
|
|
438
|
+
|
|
439
|
+
**Client-level staging** is configured under `cli.staging`:
|
|
440
|
+
|
|
441
|
+
```ts
|
|
442
|
+
const client = createLocalClient(config, {
|
|
443
|
+
cli: {
|
|
444
|
+
staging: {
|
|
445
|
+
dir: "/tmp/my-staging", // default: os.tmpdir()
|
|
446
|
+
prefix: "ai-connect-", // temp dir name prefix
|
|
447
|
+
keep: true, // retain per-invocation temp dir (debug only)
|
|
448
|
+
},
|
|
449
|
+
},
|
|
450
|
+
});
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
Staged files are written under `<stagingDir>/attachments/` with sanitized basenames (path-traversal safe) and removed after the call completes (unless `keep: true`). Attachments that carry only a remote URI and no bytes degrade to the raw URI reference.
|
|
454
|
+
|
|
455
|
+
**Per-route file input** is declared under `transport.cli.fileInput`:
|
|
456
|
+
|
|
457
|
+
```ts
|
|
458
|
+
transport: {
|
|
459
|
+
kind: "cli",
|
|
460
|
+
id: "my-route",
|
|
461
|
+
cli: {
|
|
462
|
+
fileInput: {
|
|
463
|
+
placement: "args", // "args" (default) | "prompt"
|
|
464
|
+
perFileArgs: ["@{path}"], // argv tokens per file; {path} and {name} placeholders
|
|
465
|
+
// mentionTemplate: "@{path}", // prompt placement: inserted per file (default "@{path}")
|
|
466
|
+
// separator: " ",
|
|
467
|
+
categories: ["image", "document", "text", "other"], // accepted categories (default: all)
|
|
468
|
+
stagingDir: "/custom/dir", // per-route override of cli.staging.dir
|
|
469
|
+
},
|
|
470
|
+
},
|
|
471
|
+
}
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
A `{files}` placeholder in `argsTemplate` expands to the full per-file argv block; it records a single telemetry key, not the staged absolute paths.
|
|
475
|
+
|
|
476
|
+
**Capability gate.** A route advertises `supportsImageInput` only when its `fileInput` stages the `image` category, and `supportsFileUpload` only when it stages the `document` category. A route that does not accept a category rejects the attachment with `unsupported_capability` at routing time, before any subprocess is spawned.
|
|
477
|
+
|
|
478
|
+
**Built-in preset behavior:**
|
|
479
|
+
|
|
480
|
+
- **`pi` preset** — accepts attachments from all categories (`image`, `document`, `text`, `other`). Each file is passed as an `@{path}` argv token (`perFileArgs: ["@{path}"]`). Pass an image attachment and `pi` receives `@/tmp/.../attachments/photo.png` as an argument. PDFs are staged as-is with no document extraction; `pi` receives raw bytes — prefer images or text files for reliable results.
|
|
481
|
+
|
|
482
|
+
```ts
|
|
483
|
+
const result = await client.generate({
|
|
484
|
+
messages: [{ role: "user", content: "Describe this image." }],
|
|
485
|
+
attachments: ["/path/to/photo.png"],
|
|
486
|
+
// route is pi:cli:local or routeHints selects pi-cli
|
|
487
|
+
});
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
- **`codex` preset** — accepts images only (`categories: ["image"]`). Each image is passed as `--image {path}` (`perFileArgs: ["--image", "{path}"]`). A non-image attachment (e.g. a PDF) on a codex route is rejected with `unsupported_capability` before any spawn.
|
|
491
|
+
|
|
492
|
+
- **`claude` / `openclaude` presets** — do not accept CLI file input by default. File input is opt-in: add an explicit `transport.cli.fileInput` block to your route config.
|
|
493
|
+
|
|
435
494
|
## Mock Gateway
|
|
436
495
|
|
|
437
496
|
For API-level debugging you can run a local mock backend that behaves like a small OpenAI/Anthropic/Gemini proxy and captures the real finalized wire payloads:
|
package/dist/browser/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,
|
|
@@ -6339,112 +6525,6 @@ var AI_CONNECT_DEFAULT_ACP_COMMANDS = {
|
|
|
6339
6525
|
"opencode:opencode-acp": "opencode acp"
|
|
6340
6526
|
};
|
|
6341
6527
|
|
|
6342
|
-
// src/cli-presets.ts
|
|
6343
|
-
var AI_CONNECT_DEFAULT_CLI_PRESETS = {
|
|
6344
|
-
pi: {
|
|
6345
|
-
id: "pi",
|
|
6346
|
-
label: "pi (coding agent)",
|
|
6347
|
-
command: "pi",
|
|
6348
|
-
transportId: "pi-cli",
|
|
6349
|
-
options: {
|
|
6350
|
-
preset: "pi",
|
|
6351
|
-
// pi print mode emits the answer as plain text on stdout (no JSON wrapper),
|
|
6352
|
-
// and has no ACP mode — so it uses the raw `text` parser and no discovery sidecar.
|
|
6353
|
-
argsTemplate: ["--print", "--model", "{model}", "{prompt}"],
|
|
6354
|
-
discovery: {
|
|
6355
|
-
via: "none"
|
|
6356
|
-
},
|
|
6357
|
-
parser: {
|
|
6358
|
-
kind: "text"
|
|
6359
|
-
}
|
|
6360
|
-
}
|
|
6361
|
-
},
|
|
6362
|
-
claude: {
|
|
6363
|
-
id: "claude",
|
|
6364
|
-
label: "Claude CLI",
|
|
6365
|
-
command: "claude",
|
|
6366
|
-
transportId: "claude-cli",
|
|
6367
|
-
options: {
|
|
6368
|
-
preset: "claude",
|
|
6369
|
-
argsTemplate: ["--print", "--output-format", "json", "--model", "{model}", "{prompt}"],
|
|
6370
|
-
discovery: {
|
|
6371
|
-
via: "acp",
|
|
6372
|
-
acp: {
|
|
6373
|
-
transportId: "claude-code-acp"
|
|
6374
|
-
}
|
|
6375
|
-
},
|
|
6376
|
-
parser: {
|
|
6377
|
-
kind: "json",
|
|
6378
|
-
textPath: "__preset__:claude.result",
|
|
6379
|
-
usagePath: "usage",
|
|
6380
|
-
errorPath: "__preset__:claude.error"
|
|
6381
|
-
}
|
|
6382
|
-
}
|
|
6383
|
-
},
|
|
6384
|
-
openclaude: {
|
|
6385
|
-
id: "openclaude",
|
|
6386
|
-
label: "OpenClaude CLI",
|
|
6387
|
-
command: "openclaude",
|
|
6388
|
-
transportId: "openclaude-cli",
|
|
6389
|
-
options: {
|
|
6390
|
-
preset: "openclaude",
|
|
6391
|
-
argsTemplate: ["--print", "--output-format", "json", "--model", "{model}", "{prompt}"],
|
|
6392
|
-
parser: {
|
|
6393
|
-
kind: "json",
|
|
6394
|
-
textPath: "__preset__:claude.result",
|
|
6395
|
-
usagePath: "usage",
|
|
6396
|
-
errorPath: "__preset__:claude.error"
|
|
6397
|
-
}
|
|
6398
|
-
}
|
|
6399
|
-
},
|
|
6400
|
-
codex: {
|
|
6401
|
-
id: "codex",
|
|
6402
|
-
label: "Codex CLI",
|
|
6403
|
-
command: "codex",
|
|
6404
|
-
transportId: "codex-cli",
|
|
6405
|
-
options: {
|
|
6406
|
-
preset: "codex",
|
|
6407
|
-
argsTemplate: [
|
|
6408
|
-
"exec",
|
|
6409
|
-
"--json",
|
|
6410
|
-
"--output-last-message",
|
|
6411
|
-
"{output_file}",
|
|
6412
|
-
"--model",
|
|
6413
|
-
"{model}",
|
|
6414
|
-
"{prompt}"
|
|
6415
|
-
],
|
|
6416
|
-
discovery: {
|
|
6417
|
-
via: "acp",
|
|
6418
|
-
acp: {
|
|
6419
|
-
transportId: "codex-acp"
|
|
6420
|
-
}
|
|
6421
|
-
},
|
|
6422
|
-
parser: {
|
|
6423
|
-
kind: "jsonl",
|
|
6424
|
-
text: {
|
|
6425
|
-
path: "__preset__:codex.text"
|
|
6426
|
-
},
|
|
6427
|
-
usage: {
|
|
6428
|
-
path: "__preset__:codex.usage"
|
|
6429
|
-
},
|
|
6430
|
-
error: {
|
|
6431
|
-
path: "__preset__:codex.error"
|
|
6432
|
-
}
|
|
6433
|
-
}
|
|
6434
|
-
}
|
|
6435
|
-
}
|
|
6436
|
-
};
|
|
6437
|
-
var AI_CONNECT_DEFAULT_CLI_COMMANDS = {
|
|
6438
|
-
"anthropic:claude-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.claude.command,
|
|
6439
|
-
"anthropic:openclaude-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.openclaude.command,
|
|
6440
|
-
"openclaude:openclaude-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.openclaude.command,
|
|
6441
|
-
"openai:codex-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.codex.command,
|
|
6442
|
-
"pi:pi-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.pi.command
|
|
6443
|
-
};
|
|
6444
|
-
function getCliTransportPreset(presetId) {
|
|
6445
|
-
return AI_CONNECT_DEFAULT_CLI_PRESETS[presetId];
|
|
6446
|
-
}
|
|
6447
|
-
|
|
6448
6528
|
// src/server-presets.ts
|
|
6449
6529
|
var AI_CONNECT_DEFAULT_SERVER_PRESETS = {
|
|
6450
6530
|
opencode: {
|