@vedmalex/ai-connect 0.2.1 → 0.5.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 +130 -67
- package/dist/browser/index.js +114 -104
- package/dist/browser/index.js.map +2 -2
- package/dist/bun/index.js +338 -412
- package/dist/bun/index.js.map +3 -3
- package/dist/bun/local.js +330 -374
- package/dist/bun/local.js.map +3 -3
- package/dist/node/index.js +338 -412
- package/dist/node/index.js.map +3 -3
- package/dist/node/local.js +330 -374
- package/dist/node/local.js.map +3 -3
- package/dist/types/acp-presets.d.ts.map +1 -1
- package/dist/types/acp.d.ts.map +1 -1
- package/dist/types/catalog.d.ts.map +1 -1
- package/dist/types/cli-presets.d.ts.map +1 -1
- package/dist/types/cli.d.ts +8 -1
- package/dist/types/cli.d.ts.map +1 -1
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/default-handlers.d.ts +3 -1
- package/dist/types/default-handlers.d.ts.map +1 -1
- package/dist/types/local-handlers.d.ts.map +1 -1
- package/dist/types/model-reference.d.ts +19 -1
- package/dist/types/model-reference.d.ts.map +1 -1
- package/dist/types/types.d.ts +86 -5
- package/dist/types/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
It models routes as `provider + transport + account + credential + model`, so one client can combine:
|
|
6
6
|
|
|
7
7
|
- direct APIs for `OpenAI`, `Anthropic`, and `Gemini`
|
|
8
|
-
- local-only ACP harness routes for `Claude Code
|
|
8
|
+
- local-only ACP harness routes for `Claude Code` and `Codex`
|
|
9
9
|
- key rotation, account rotation, cooldowns, retries, and fallback chains
|
|
10
10
|
- portable file, PDF document, and image inputs across direct API and ACP paths
|
|
11
11
|
- cooperative cancellation, pause-with-partial, and per-operation timeouts
|
|
@@ -17,8 +17,8 @@ It models routes as `provider + transport + account + credential + model`, so on
|
|
|
17
17
|
Implemented today:
|
|
18
18
|
|
|
19
19
|
- `OpenAI API`, `Anthropic API`, `Gemini API`
|
|
20
|
-
- `Claude Code ACP`, `Codex ACP
|
|
21
|
-
- `
|
|
20
|
+
- `Claude Code ACP`, `Codex ACP`
|
|
21
|
+
- `agy CLI`, `pi CLI`, `Claude/OpenClaude CLI`, `Codex CLI`
|
|
22
22
|
- `OpenCode Server`
|
|
23
23
|
- browser and local client factories
|
|
24
24
|
- env-backed key pools with delimiter-based rotation
|
|
@@ -219,51 +219,11 @@ Dedicated provider-specific ACP examples:
|
|
|
219
219
|
|
|
220
220
|
- [examples/acp-claude.ts](/Users/vedmalex/work/ai-connect/examples/acp-claude.ts)
|
|
221
221
|
- [examples/acp-codex.ts](/Users/vedmalex/work/ai-connect/examples/acp-codex.ts)
|
|
222
|
-
- [examples/acp-gemini.ts](/Users/vedmalex/work/ai-connect/examples/acp-gemini.ts)
|
|
223
|
-
|
|
224
222
|
Dedicated `clientTools` examples:
|
|
225
223
|
|
|
226
224
|
- [examples/browser-client-tools.ts](/Users/vedmalex/work/ai-connect/examples/browser-client-tools.ts)
|
|
227
225
|
- [examples/local-client-tools.ts](/Users/vedmalex/work/ai-connect/examples/local-client-tools.ts)
|
|
228
226
|
|
|
229
|
-
For Gemini ACP you can choose the harness auth mode per connection by creating separate accounts:
|
|
230
|
-
|
|
231
|
-
```ts
|
|
232
|
-
const client = createLocalClient(
|
|
233
|
-
defineConfig({
|
|
234
|
-
providers: {
|
|
235
|
-
gemini: {
|
|
236
|
-
accounts: [
|
|
237
|
-
{
|
|
238
|
-
id: "gemini-default",
|
|
239
|
-
transport: { kind: "acp", id: "gemini-acp" },
|
|
240
|
-
models: ["auto-gemini-3"],
|
|
241
|
-
},
|
|
242
|
-
{
|
|
243
|
-
id: "gemini-oauth",
|
|
244
|
-
transport: {
|
|
245
|
-
kind: "acp",
|
|
246
|
-
id: "gemini-acp",
|
|
247
|
-
auth: {
|
|
248
|
-
methodId: "oauth-personal",
|
|
249
|
-
},
|
|
250
|
-
},
|
|
251
|
-
models: ["auto-gemini-3"],
|
|
252
|
-
},
|
|
253
|
-
],
|
|
254
|
-
},
|
|
255
|
-
},
|
|
256
|
-
routing: {
|
|
257
|
-
operations: {
|
|
258
|
-
text: ["gemini:gemini-acp:gemini-oauth:auto-gemini-3"],
|
|
259
|
-
},
|
|
260
|
-
},
|
|
261
|
-
}),
|
|
262
|
-
);
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
This keeps ACP harness auth explicit at connection selection time without injecting provider keys or `baseUrl` from `ai-connect`.
|
|
266
|
-
|
|
267
227
|
## Local CLI And Server Presets
|
|
268
228
|
|
|
269
229
|
Built-in local transport presets are available both as catalog entries and as exported preset metadata:
|
|
@@ -279,7 +239,6 @@ import {
|
|
|
279
239
|
const localCatalog = listTextProviderCatalog({ runtime: "local" });
|
|
280
240
|
const codexCli = getTextTransportPresetById("openai", "codex-cli");
|
|
281
241
|
const opencodeServer = AI_CONNECT_DEFAULT_SERVER_PRESETS.opencode;
|
|
282
|
-
const geminiCli = AI_CONNECT_DEFAULT_CLI_PRESETS.gemini;
|
|
283
242
|
```
|
|
284
243
|
|
|
285
244
|
For built-in CLI routes the shortest form is still the route `id`:
|
|
@@ -287,7 +246,7 @@ For built-in CLI routes the shortest form is still the route `id`:
|
|
|
287
246
|
```ts
|
|
288
247
|
transport: {
|
|
289
248
|
kind: "cli",
|
|
290
|
-
id: "
|
|
249
|
+
id: "codex-cli",
|
|
291
250
|
}
|
|
292
251
|
```
|
|
293
252
|
|
|
@@ -296,9 +255,9 @@ If you want a custom route id but still want the built-in argv/parser/command de
|
|
|
296
255
|
```ts
|
|
297
256
|
transport: {
|
|
298
257
|
kind: "cli",
|
|
299
|
-
id: "my-
|
|
258
|
+
id: "my-codex-wrapper",
|
|
300
259
|
cli: {
|
|
301
|
-
preset: "
|
|
260
|
+
preset: "codex",
|
|
302
261
|
},
|
|
303
262
|
}
|
|
304
263
|
```
|
|
@@ -315,11 +274,8 @@ Known local presets now include:
|
|
|
315
274
|
- `openai:codex-cli`
|
|
316
275
|
- `anthropic:claude-cli`
|
|
317
276
|
- `openclaude:openclaude-cli`
|
|
277
|
+
- `pi:pi-cli`
|
|
318
278
|
- `anthropic:claude-code-acp`
|
|
319
|
-
- `gemini:gemini-cli`
|
|
320
|
-
- `gemini:gemini-acp`
|
|
321
|
-
- `qwen:qwen-cli`
|
|
322
|
-
- `qwen:qwen-acp`
|
|
323
279
|
- `opencode:opencode-server`
|
|
324
280
|
- `opencode:opencode-acp`
|
|
325
281
|
|
|
@@ -368,6 +324,70 @@ const client = createLocalClient(
|
|
|
368
324
|
);
|
|
369
325
|
```
|
|
370
326
|
|
|
327
|
+
The parser supports three kinds:
|
|
328
|
+
|
|
329
|
+
- `kind: "json"` — parse stdout as a single JSON object; read the answer from `textPath` (plus optional `usagePath` / `errorPath`).
|
|
330
|
+
- `kind: "jsonl"` — parse stdout as newline-delimited JSON; select the answer/usage/error lines with `{ path, wherePath, whereEquals }` selectors.
|
|
331
|
+
- `kind: "text"` — treat stdout as **raw plain text** and return it as `result.text`. For **print-mode coding-agent CLIs that emit plain text, not JSON** (no `--output-format json` flag, no ACP mode). Options:
|
|
332
|
+
- `trim` (default `true`) — trim leading/trailing whitespace.
|
|
333
|
+
- `stripAnsi` (default `false`) — strip ANSI escape sequences (spinner/color noise) before returning.
|
|
334
|
+
|
|
335
|
+
Print-mode plain text carries no token information, so `result.usage` is absent (none is fabricated). An empty stdout on a non-zero exit still rejects with `temporary_unavailable`.
|
|
336
|
+
|
|
337
|
+
```ts
|
|
338
|
+
import { createLocalClient, defineConfig } from "@vedmalex/ai-connect";
|
|
339
|
+
|
|
340
|
+
// A custom print-mode coding-agent CLI ("agy") that writes the answer as raw text.
|
|
341
|
+
const client = createLocalClient(
|
|
342
|
+
defineConfig({
|
|
343
|
+
providers: {
|
|
344
|
+
agy: {
|
|
345
|
+
accounts: [
|
|
346
|
+
{
|
|
347
|
+
id: "local",
|
|
348
|
+
transport: {
|
|
349
|
+
kind: "cli",
|
|
350
|
+
id: "agy-cli",
|
|
351
|
+
command: "agy",
|
|
352
|
+
cli: {
|
|
353
|
+
argsTemplate: ["-p", "{prompt}", "--model", "{model}"],
|
|
354
|
+
parser: { kind: "text" }, // raw stdout -> result.text
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
models: ["default"],
|
|
358
|
+
},
|
|
359
|
+
],
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
}),
|
|
363
|
+
);
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
`pi` has a built-in CLI preset (`pi-cli`). The preset supplies the default command (`pi`), argsTemplate (`["--print","--model","{model}","{prompt}"]`), parser (`kind: "text"`), and discovery (`via: "none"`). The minimal config is therefore:
|
|
367
|
+
|
|
368
|
+
```ts
|
|
369
|
+
import { createLocalClient, defineConfig } from "@vedmalex/ai-connect";
|
|
370
|
+
|
|
371
|
+
const client = createLocalClient(
|
|
372
|
+
defineConfig({
|
|
373
|
+
providers: {
|
|
374
|
+
pi: {
|
|
375
|
+
accounts: [
|
|
376
|
+
{
|
|
377
|
+
id: "local",
|
|
378
|
+
transport: {
|
|
379
|
+
kind: "cli",
|
|
380
|
+
id: "pi-cli", // selects the built-in pi-cli preset
|
|
381
|
+
},
|
|
382
|
+
models: ["gemini-3.1-pro-low"],
|
|
383
|
+
},
|
|
384
|
+
],
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
}),
|
|
388
|
+
);
|
|
389
|
+
```
|
|
390
|
+
|
|
371
391
|
Supported placeholders in `argsTemplate`:
|
|
372
392
|
|
|
373
393
|
- `{prompt}`
|
|
@@ -376,9 +396,40 @@ Supported placeholders in `argsTemplate`:
|
|
|
376
396
|
|
|
377
397
|
`{output_file}` is useful for CLIs like `codex exec` that stream JSONL to stdout but write the final assistant message to a file.
|
|
378
398
|
|
|
399
|
+
### CLI Model Discovery
|
|
400
|
+
|
|
401
|
+
A `cli` route exposes the same management interfaces as other providers — `discoverModels` / `checkHealth` / `probeModels` / `listCandidateModels`. The discovery **source** is resolved from `transport.cli.discovery.via`; when `via` is omitted it is chosen by the chain **`command` → `acp` → `static` → `none`**:
|
|
402
|
+
|
|
403
|
+
- **`command`** — run a configured CLI sub-command that lists models and parse its stdout. Reuses the same `json | jsonl | text` formats; model fields are mapped with a `models` selector. Falls back to the static source on empty/failed output (`fallback: "static"` by default when `models[]` is present; set `fallback: "none"` to fail loud):
|
|
404
|
+
|
|
405
|
+
```ts
|
|
406
|
+
transport: {
|
|
407
|
+
kind: "cli",
|
|
408
|
+
command: "agy",
|
|
409
|
+
cli: {
|
|
410
|
+
argsTemplate: ["-p", "{prompt}", "--model", "{model}"],
|
|
411
|
+
parser: { kind: "text" },
|
|
412
|
+
discovery: {
|
|
413
|
+
command: {
|
|
414
|
+
argsTemplate: ["models", "list", "--json"],
|
|
415
|
+
parser: { kind: "json" },
|
|
416
|
+
models: { path: "data", idPath: "id", namePath: "display_name", contextLengthPath: "context_window" },
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
models: ["agy-pro", { id: "agy-fast", contextWindow: 200_000 }],
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
- **`acp`** — delegate discovery to an ACP sidecar (the default for the built-in coding-agent presets).
|
|
425
|
+
- **`static`** — build the catalog from the account's configured `models[]` (+ `contextWindow`). This is the default for a preset-less custom CLI that declares `models[]` (it previously reported `not_supported`); opt out with `discovery: { via: "none" }`.
|
|
426
|
+
- **`none`** — no discovery.
|
|
427
|
+
|
|
428
|
+
**Configured context window (GAP-A).** A model entry's `contextLength` is surfaced from the route's configured `contextWindow` only when discovery did not already report one (monotonic — a live discovered value always wins). Such entries are tagged `metadata.contextWindowSource: "configured"`. A consumer mapping `catalog.contextLength` into `resolveModelContextWindow`'s `discovered` slot should gate on `metadata.contextWindowSource !== "configured"` (otherwise feed it as the `configured` input) so the documented precedence `discovered > reference > configured > default` stays honest.
|
|
429
|
+
|
|
379
430
|
Current local transport scope:
|
|
380
431
|
|
|
381
|
-
- `cli`: text generation, plus
|
|
432
|
+
- `cli`: text generation, plus model discovery via a list `command`, a `static` config catalog, or an ACP sidecar
|
|
382
433
|
- `server`: text generation plus provider-native model discovery
|
|
383
434
|
|
|
384
435
|
## Mock Gateway
|
|
@@ -933,7 +984,7 @@ When consuming `ai-connect` from `bs-search`:
|
|
|
933
984
|
| Context-window resolution | yes | yes | yes |
|
|
934
985
|
| Local file paths | no | yes | yes |
|
|
935
986
|
| Local command/session verification | no | yes | yes |
|
|
936
|
-
| Claude/Codex
|
|
987
|
+
| Claude/Codex ACP | no | yes | yes |
|
|
937
988
|
|
|
938
989
|
## Runtime Entry Points
|
|
939
990
|
|
|
@@ -1017,8 +1068,6 @@ Current discovery support matrix:
|
|
|
1017
1068
|
|
|
1018
1069
|
Built-in CLI discovery defaults:
|
|
1019
1070
|
|
|
1020
|
-
- `gemini-cli` -> `gemini-acp`
|
|
1021
|
-
- `qwen-cli` -> `qwen-acp`
|
|
1022
1071
|
- `claude-cli` -> `claude-code-acp`
|
|
1023
1072
|
- `codex-cli` -> `codex-acp`
|
|
1024
1073
|
- `openclaude-cli` -> no default discovery bridge
|
|
@@ -1029,22 +1078,38 @@ CLI discovery through ACP adds ACP-side prerequisites:
|
|
|
1029
1078
|
- the ACP harness must be authenticated if that provider requires auth
|
|
1030
1079
|
- `verify()` checks route plausibility and handler presence, but it does not perform a live discovery/auth handshake up front
|
|
1031
1080
|
|
|
1032
|
-
For custom CLI wrappers you can make the public API stay uniform by delegating discovery to ACP
|
|
1081
|
+
For custom CLI wrappers you can make the public API stay uniform by delegating discovery to an ACP sidecar. For example, a Codex wrapper that delegates to `codex-acp`:
|
|
1033
1082
|
|
|
1034
1083
|
```ts
|
|
1035
1084
|
transport: {
|
|
1036
1085
|
kind: "cli",
|
|
1037
|
-
id: "my-
|
|
1038
|
-
command: "/opt/bin/
|
|
1086
|
+
id: "my-codex-wrapper",
|
|
1087
|
+
command: "/opt/bin/codex-wrapper",
|
|
1039
1088
|
cli: {
|
|
1040
1089
|
discovery: {
|
|
1041
1090
|
via: "acp",
|
|
1042
1091
|
acp: {
|
|
1043
|
-
providerId: "
|
|
1044
|
-
transportId: "
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1092
|
+
providerId: "openai",
|
|
1093
|
+
transportId: "codex-acp",
|
|
1094
|
+
},
|
|
1095
|
+
},
|
|
1096
|
+
},
|
|
1097
|
+
}
|
|
1098
|
+
```
|
|
1099
|
+
|
|
1100
|
+
Or a Claude wrapper delegating to `claude-code-acp`:
|
|
1101
|
+
|
|
1102
|
+
```ts
|
|
1103
|
+
transport: {
|
|
1104
|
+
kind: "cli",
|
|
1105
|
+
id: "my-claude-wrapper",
|
|
1106
|
+
command: "/opt/bin/claude-wrapper",
|
|
1107
|
+
cli: {
|
|
1108
|
+
discovery: {
|
|
1109
|
+
via: "acp",
|
|
1110
|
+
acp: {
|
|
1111
|
+
providerId: "anthropic",
|
|
1112
|
+
transportId: "claude-code-acp",
|
|
1048
1113
|
},
|
|
1049
1114
|
},
|
|
1050
1115
|
},
|
|
@@ -1156,9 +1221,7 @@ Server routes:
|
|
|
1156
1221
|
|
|
1157
1222
|
ACP usage statistics are exposed on `result.usage` when the harness provides them. `ai-connect` currently normalizes:
|
|
1158
1223
|
|
|
1159
|
-
- Gemini ACP `_meta.quota.token_count` and `_meta.quota.model_usage`
|
|
1160
1224
|
- OpenCode ACP `usage_update` (`used`, `size`, `cost`)
|
|
1161
|
-
- Qwen ACP `_meta.usage` (`inputTokens`, `outputTokens`, `totalTokens`, `thoughtTokens`, `cachedReadTokens`)
|
|
1162
1225
|
|
|
1163
1226
|
## Examples
|
|
1164
1227
|
|
|
@@ -1166,7 +1229,6 @@ See:
|
|
|
1166
1229
|
|
|
1167
1230
|
- [examples/acp-claude.ts](/Users/vedmalex/work/ai-connect/examples/acp-claude.ts)
|
|
1168
1231
|
- [examples/acp-codex.ts](/Users/vedmalex/work/ai-connect/examples/acp-codex.ts)
|
|
1169
|
-
- [examples/acp-gemini.ts](/Users/vedmalex/work/ai-connect/examples/acp-gemini.ts)
|
|
1170
1232
|
- [examples/browser-basic.ts](/Users/vedmalex/work/ai-connect/examples/browser-basic.ts)
|
|
1171
1233
|
- [examples/local-acp.ts](/Users/vedmalex/work/ai-connect/examples/local-acp.ts)
|
|
1172
1234
|
- [examples/local-test-server.ts](/Users/vedmalex/work/ai-connect/examples/local-test-server.ts)
|
|
@@ -1189,7 +1251,8 @@ If you are targeting the local gateway at `127.0.0.1:8045`, configure direct API
|
|
|
1189
1251
|
```ts
|
|
1190
1252
|
import { createLocalClient, defineConfig } from "@vedmalex/ai-connect";
|
|
1191
1253
|
|
|
1192
|
-
|
|
1254
|
+
// Read the local gateway key from the environment — never hardcode a key.
|
|
1255
|
+
const LOCAL_TEST_API_KEY = process.env.LOCAL_TEST_API_KEY ?? "";
|
|
1193
1256
|
|
|
1194
1257
|
const client = createLocalClient(
|
|
1195
1258
|
defineConfig({
|
package/dist/browser/index.js
CHANGED
|
@@ -219,11 +219,7 @@ function normalizeTransport(providerId, input) {
|
|
|
219
219
|
...selector.whereEquals !== void 0 ? { whereEquals: selector.whereEquals } : {}
|
|
220
220
|
};
|
|
221
221
|
};
|
|
222
|
-
const
|
|
223
|
-
if (!descriptor.cli?.parser) {
|
|
224
|
-
return void 0;
|
|
225
|
-
}
|
|
226
|
-
const cliParser = descriptor.cli.parser;
|
|
222
|
+
const normalizeCliParser = (cliParser) => {
|
|
227
223
|
if (cliParser.kind === "json") {
|
|
228
224
|
assert(
|
|
229
225
|
cliParser.textPath.trim().length > 0,
|
|
@@ -236,13 +232,22 @@ function normalizeTransport(providerId, input) {
|
|
|
236
232
|
...cliParser.usagePath?.trim() ? { usagePath: cliParser.usagePath.trim() } : {}
|
|
237
233
|
};
|
|
238
234
|
}
|
|
235
|
+
if (cliParser.kind === "text") {
|
|
236
|
+
return {
|
|
237
|
+
kind: "text",
|
|
238
|
+
// Default trim=true, stripAnsi=false; only persist explicit overrides.
|
|
239
|
+
...cliParser.trim === false ? { trim: false } : {},
|
|
240
|
+
...cliParser.stripAnsi === true ? { stripAnsi: true } : {}
|
|
241
|
+
};
|
|
242
|
+
}
|
|
239
243
|
return {
|
|
240
244
|
kind: "jsonl",
|
|
241
245
|
text: normalizeSelector(cliParser.text, "text"),
|
|
242
246
|
...cliParser.error ? { error: normalizeSelector(cliParser.error, "error") } : {},
|
|
243
247
|
...cliParser.usage ? { usage: normalizeSelector(cliParser.usage, "usage") } : {}
|
|
244
248
|
};
|
|
245
|
-
}
|
|
249
|
+
};
|
|
250
|
+
const parser = descriptor.cli?.parser ? normalizeCliParser(descriptor.cli.parser) : void 0;
|
|
246
251
|
const normalized = {
|
|
247
252
|
...descriptor.cli.preset ? { preset: descriptor.cli.preset } : {},
|
|
248
253
|
...descriptor.cli.argsTemplate ? {
|
|
@@ -254,11 +259,13 @@ function normalizeTransport(providerId, input) {
|
|
|
254
259
|
if (!descriptor.cli?.discovery) {
|
|
255
260
|
return void 0;
|
|
256
261
|
}
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
+
const explicitVia = descriptor.cli.discovery.via;
|
|
263
|
+
if (explicitVia !== void 0) {
|
|
264
|
+
assert(
|
|
265
|
+
explicitVia === "none" || explicitVia === "acp" || explicitVia === "command" || explicitVia === "static",
|
|
266
|
+
`Unsupported CLI discovery mode "${String(explicitVia)}" for provider "${providerId}".`
|
|
267
|
+
);
|
|
268
|
+
}
|
|
262
269
|
const launch = descriptor.cli.discovery.acp?.launch ? (() => {
|
|
263
270
|
const contextMode = descriptor.cli.discovery?.acp?.launch?.contextMode ?? "workspace";
|
|
264
271
|
const skillsMode = descriptor.cli.discovery?.acp?.launch?.skillsMode ?? "default";
|
|
@@ -279,7 +286,7 @@ function normalizeTransport(providerId, input) {
|
|
|
279
286
|
methodId: descriptor.cli.discovery.acp.auth.methodId.trim(),
|
|
280
287
|
params: descriptor.cli.discovery.acp.auth.params ?? {}
|
|
281
288
|
} : void 0;
|
|
282
|
-
const acp =
|
|
289
|
+
const acp = descriptor.cli.discovery.acp ? {
|
|
283
290
|
...descriptor.cli.discovery.acp?.providerId?.trim() ? { providerId: descriptor.cli.discovery.acp.providerId.trim() } : {},
|
|
284
291
|
...descriptor.cli.discovery.acp?.transportId?.trim() ? {
|
|
285
292
|
transportId: descriptor.cli.discovery.acp.transportId.trim()
|
|
@@ -287,9 +294,55 @@ function normalizeTransport(providerId, input) {
|
|
|
287
294
|
...auth ? { auth } : {},
|
|
288
295
|
...launch ? { launch } : {}
|
|
289
296
|
} : void 0;
|
|
297
|
+
const commandInput = descriptor.cli.discovery.command;
|
|
298
|
+
const command = commandInput ? (() => {
|
|
299
|
+
assert(
|
|
300
|
+
Array.isArray(commandInput.argsTemplate) && commandInput.argsTemplate.length > 0,
|
|
301
|
+
`CLI discovery command.argsTemplate must be a non-empty array for provider "${providerId}".`
|
|
302
|
+
);
|
|
303
|
+
const cmdParserInput = commandInput.parser ?? { kind: "text" };
|
|
304
|
+
const cmdParser = cmdParserInput.kind === "text" ? {
|
|
305
|
+
kind: "text",
|
|
306
|
+
...cmdParserInput.trim === false ? { trim: false } : {},
|
|
307
|
+
...cmdParserInput.stripAnsi === true ? { stripAnsi: true } : {}
|
|
308
|
+
} : { kind: cmdParserInput.kind };
|
|
309
|
+
if (cmdParser.kind !== "text") {
|
|
310
|
+
assert(
|
|
311
|
+
Boolean(commandInput.models?.idPath?.trim()),
|
|
312
|
+
`CLI discovery command.models.idPath is required for a ${cmdParser.kind} parser for provider "${providerId}".`
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
const m = commandInput.models;
|
|
316
|
+
const models = m?.idPath?.trim() ? {
|
|
317
|
+
...m.path?.trim() ? { path: m.path.trim() } : {},
|
|
318
|
+
idPath: m.idPath.trim(),
|
|
319
|
+
...m.namePath?.trim() ? { namePath: m.namePath.trim() } : {},
|
|
320
|
+
...m.descriptionPath?.trim() ? { descriptionPath: m.descriptionPath.trim() } : {},
|
|
321
|
+
...m.contextLengthPath?.trim() ? { contextLengthPath: m.contextLengthPath.trim() } : {}
|
|
322
|
+
} : void 0;
|
|
323
|
+
return {
|
|
324
|
+
...commandInput.command?.trim() ? { command: commandInput.command.trim() } : {},
|
|
325
|
+
argsTemplate: commandInput.argsTemplate.map((part) => String(part)),
|
|
326
|
+
parser: cmdParser,
|
|
327
|
+
...models ? { models } : {}
|
|
328
|
+
};
|
|
329
|
+
})() : void 0;
|
|
330
|
+
assert(
|
|
331
|
+
explicitVia !== "command" || command !== void 0,
|
|
332
|
+
`CLI discovery via:"command" requires a discovery.command block for provider "${providerId}".`
|
|
333
|
+
);
|
|
334
|
+
const fallback = descriptor.cli.discovery.fallback;
|
|
335
|
+
if (fallback !== void 0) {
|
|
336
|
+
assert(
|
|
337
|
+
fallback === "static" || fallback === "none",
|
|
338
|
+
`Unsupported CLI discovery fallback "${String(fallback)}" for provider "${providerId}".`
|
|
339
|
+
);
|
|
340
|
+
}
|
|
290
341
|
return {
|
|
291
|
-
via,
|
|
292
|
-
...acp ? { acp } : {}
|
|
342
|
+
...explicitVia !== void 0 ? { via: explicitVia } : {},
|
|
343
|
+
...acp ? { acp } : {},
|
|
344
|
+
...command ? { command } : {},
|
|
345
|
+
...fallback !== void 0 ? { fallback } : {}
|
|
293
346
|
};
|
|
294
347
|
})();
|
|
295
348
|
return {
|
|
@@ -330,7 +383,7 @@ function normalizeTransport(providerId, input) {
|
|
|
330
383
|
};
|
|
331
384
|
}
|
|
332
385
|
if (descriptor.kind === "cli") {
|
|
333
|
-
const inferredId2 = providerId === "anthropic" ? "claude-cli" : providerId === "openclaude" ? "openclaude-cli" : providerId === "openai" ? "codex-cli" : providerId === "
|
|
386
|
+
const inferredId2 = providerId === "anthropic" ? "claude-cli" : providerId === "openclaude" ? "openclaude-cli" : providerId === "openai" ? "codex-cli" : providerId === "pi" ? "pi-cli" : "cli";
|
|
334
387
|
return {
|
|
335
388
|
kind: "cli",
|
|
336
389
|
id: descriptor.id?.trim() || inferredId2,
|
|
@@ -347,7 +400,7 @@ function normalizeTransport(providerId, input) {
|
|
|
347
400
|
...descriptor.baseUrl?.trim() ? { baseUrl: descriptor.baseUrl.trim() } : {}
|
|
348
401
|
};
|
|
349
402
|
}
|
|
350
|
-
const inferredId = providerId === "anthropic" ? "claude-code-acp" : providerId === "openai" ? "codex-acp" :
|
|
403
|
+
const inferredId = providerId === "anthropic" ? "claude-code-acp" : providerId === "openai" ? "codex-acp" : "acp";
|
|
351
404
|
const normalizedLaunch = descriptor.launch ? (() => {
|
|
352
405
|
const contextMode = descriptor.launch?.contextMode ?? "workspace";
|
|
353
406
|
const skillsMode = descriptor.launch?.skillsMode ?? "default";
|
|
@@ -1585,18 +1638,14 @@ var MODEL_REFERENCE = {
|
|
|
1585
1638
|
key: "gemini-3.1-flash-lite",
|
|
1586
1639
|
contextLength: 1048576
|
|
1587
1640
|
},
|
|
1641
|
+
"gemini-3.1-pro": { key: "gemini-3.1-pro", contextLength: 1048576 },
|
|
1588
1642
|
"gemini-3-pro": { key: "gemini-3-pro", contextLength: 2097152 },
|
|
1589
1643
|
"auto-gemini-3": { key: "auto-gemini-3", contextLength: 1048576 },
|
|
1590
1644
|
"gemini-2.5-flash": { key: "gemini-2.5-flash", contextLength: 1048576 },
|
|
1591
1645
|
"gemini-2.5-pro": { key: "gemini-2.5-pro", contextLength: 2097152 },
|
|
1592
1646
|
"gemini-2.0-flash": { key: "gemini-2.0-flash", contextLength: 1048576 },
|
|
1593
1647
|
"gemini-1.5-flash": { key: "gemini-1.5-flash", contextLength: 1048576 },
|
|
1594
|
-
"gemini-1.5-pro": { key: "gemini-1.5-pro", contextLength: 2097152 }
|
|
1595
|
-
// --- Qwen (catalog: qwen3-coder-plus) ---
|
|
1596
|
-
"qwen3-coder-plus": { key: "qwen3-coder-plus", contextLength: 1048576 },
|
|
1597
|
-
"qwen3-coder": { key: "qwen3-coder", contextLength: 262144 },
|
|
1598
|
-
"qwen-max": { key: "qwen-max", contextLength: 32768 },
|
|
1599
|
-
"qwen-plus": { key: "qwen-plus", contextLength: 131072 }
|
|
1648
|
+
"gemini-1.5-pro": { key: "gemini-1.5-pro", contextLength: 2097152 }
|
|
1600
1649
|
};
|
|
1601
1650
|
function normalizeModelKey(model) {
|
|
1602
1651
|
let key = model.trim().toLowerCase();
|
|
@@ -1730,6 +1779,25 @@ function resolveModelContextWindow(input) {
|
|
|
1730
1779
|
const fallback = isUsableContextLength(input.defaultContextWindow) ? input.defaultContextWindow : DEFAULT_CONTEXT_WINDOW;
|
|
1731
1780
|
return { contextWindow: fallback, source: "default" };
|
|
1732
1781
|
}
|
|
1782
|
+
function fillConfiguredContextLength(route, models) {
|
|
1783
|
+
if (!isUsableContextLength(route.contextWindow)) {
|
|
1784
|
+
return models;
|
|
1785
|
+
}
|
|
1786
|
+
const configured = route.contextWindow;
|
|
1787
|
+
return models.map((entry) => {
|
|
1788
|
+
if (isUsableContextLength(entry.contextLength)) {
|
|
1789
|
+
return entry;
|
|
1790
|
+
}
|
|
1791
|
+
if (route.model !== void 0 && entry.modelId !== route.model) {
|
|
1792
|
+
return entry;
|
|
1793
|
+
}
|
|
1794
|
+
return {
|
|
1795
|
+
...entry,
|
|
1796
|
+
contextLength: configured,
|
|
1797
|
+
metadata: { ...entry.metadata ?? {}, contextWindowSource: "configured" }
|
|
1798
|
+
};
|
|
1799
|
+
});
|
|
1800
|
+
}
|
|
1733
1801
|
function modelContextCacheKey(input) {
|
|
1734
1802
|
if (typeof input === "string") {
|
|
1735
1803
|
return { key: `::${input}`, model: input };
|
|
@@ -4723,18 +4791,19 @@ function canonicalGeminiImageModelId(modelId) {
|
|
|
4723
4791
|
}
|
|
4724
4792
|
function buildModelCatalog(route, availableModels, currentModelId) {
|
|
4725
4793
|
const requestedModelId = route.model;
|
|
4726
|
-
const
|
|
4794
|
+
const filledModels = fillConfiguredContextLength(route, availableModels);
|
|
4795
|
+
const requestedModelAdvertised = filledModels.some(
|
|
4727
4796
|
(model) => model.modelId === requestedModelId
|
|
4728
4797
|
);
|
|
4729
4798
|
const canonicalModelId = route.provider === "gemini" ? canonicalGeminiImageModelId(requestedModelId) : void 0;
|
|
4730
|
-
const resolvedModelId = requestedModelAdvertised ? requestedModelId : canonicalModelId &&
|
|
4799
|
+
const resolvedModelId = requestedModelAdvertised ? requestedModelId : canonicalModelId && filledModels.some((model) => model.modelId === canonicalModelId) ? canonicalModelId : void 0;
|
|
4731
4800
|
return {
|
|
4732
4801
|
requestedModelId,
|
|
4733
4802
|
requestedModelAdvertised,
|
|
4734
4803
|
...canonicalModelId ? { canonicalModelId } : {},
|
|
4735
4804
|
...resolvedModelId ? { resolvedModelId } : {},
|
|
4736
4805
|
...currentModelId ? { currentModelId } : {},
|
|
4737
|
-
availableModels
|
|
4806
|
+
availableModels: filledModels
|
|
4738
4807
|
};
|
|
4739
4808
|
}
|
|
4740
4809
|
function parseOpenAiModelCatalog(route, payload) {
|
|
@@ -6267,54 +6336,26 @@ function createDefaultRouteHandlers(options = {}) {
|
|
|
6267
6336
|
var AI_CONNECT_DEFAULT_ACP_COMMANDS = {
|
|
6268
6337
|
"anthropic:claude-code-acp": "npx -y @agentclientprotocol/claude-agent-acp@^0.25.0",
|
|
6269
6338
|
"openai:codex-acp": "npx @zed-industries/codex-acp@^0.11.1",
|
|
6270
|
-
"gemini:gemini-acp": "gemini --acp",
|
|
6271
|
-
"qwen:qwen-acp": "qwen --acp",
|
|
6272
6339
|
"opencode:opencode-acp": "opencode acp"
|
|
6273
6340
|
};
|
|
6274
6341
|
|
|
6275
6342
|
// src/cli-presets.ts
|
|
6276
6343
|
var AI_CONNECT_DEFAULT_CLI_PRESETS = {
|
|
6277
|
-
|
|
6278
|
-
id: "
|
|
6279
|
-
label: "
|
|
6280
|
-
command: "
|
|
6281
|
-
transportId: "
|
|
6344
|
+
pi: {
|
|
6345
|
+
id: "pi",
|
|
6346
|
+
label: "pi (coding agent)",
|
|
6347
|
+
command: "pi",
|
|
6348
|
+
transportId: "pi-cli",
|
|
6282
6349
|
options: {
|
|
6283
|
-
preset: "
|
|
6284
|
-
|
|
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}"],
|
|
6285
6354
|
discovery: {
|
|
6286
|
-
via: "
|
|
6287
|
-
acp: {
|
|
6288
|
-
transportId: "gemini-acp"
|
|
6289
|
-
}
|
|
6290
|
-
},
|
|
6291
|
-
parser: {
|
|
6292
|
-
kind: "json",
|
|
6293
|
-
textPath: "response",
|
|
6294
|
-
usagePath: "stats",
|
|
6295
|
-
errorPath: "error"
|
|
6296
|
-
}
|
|
6297
|
-
}
|
|
6298
|
-
},
|
|
6299
|
-
qwen: {
|
|
6300
|
-
id: "qwen",
|
|
6301
|
-
label: "Qwen CLI",
|
|
6302
|
-
command: "qwen",
|
|
6303
|
-
transportId: "qwen-cli",
|
|
6304
|
-
options: {
|
|
6305
|
-
preset: "qwen",
|
|
6306
|
-
argsTemplate: ["-p", "{prompt}", "--output-format", "json", "--model", "{model}"],
|
|
6307
|
-
discovery: {
|
|
6308
|
-
via: "acp",
|
|
6309
|
-
acp: {
|
|
6310
|
-
transportId: "qwen-acp"
|
|
6311
|
-
}
|
|
6355
|
+
via: "none"
|
|
6312
6356
|
},
|
|
6313
6357
|
parser: {
|
|
6314
|
-
kind: "
|
|
6315
|
-
textPath: "__preset__:qwen.result",
|
|
6316
|
-
usagePath: "__preset__:qwen.stats",
|
|
6317
|
-
errorPath: "__preset__:qwen.error"
|
|
6358
|
+
kind: "text"
|
|
6318
6359
|
}
|
|
6319
6360
|
}
|
|
6320
6361
|
},
|
|
@@ -6398,8 +6439,7 @@ var AI_CONNECT_DEFAULT_CLI_COMMANDS = {
|
|
|
6398
6439
|
"anthropic:openclaude-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.openclaude.command,
|
|
6399
6440
|
"openclaude:openclaude-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.openclaude.command,
|
|
6400
6441
|
"openai:codex-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.codex.command,
|
|
6401
|
-
"
|
|
6402
|
-
"qwen:qwen-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.qwen.command
|
|
6442
|
+
"pi:pi-cli": AI_CONNECT_DEFAULT_CLI_PRESETS.pi.command
|
|
6403
6443
|
};
|
|
6404
6444
|
function getCliTransportPreset(presetId) {
|
|
6405
6445
|
return AI_CONNECT_DEFAULT_CLI_PRESETS[presetId];
|
|
@@ -6521,52 +6561,22 @@ var TEXT_PROVIDER_CATALOG = [
|
|
|
6521
6561
|
runtime: "universal",
|
|
6522
6562
|
defaultModel: "gemini-3.1-flash-lite",
|
|
6523
6563
|
defaultBaseUrl: ""
|
|
6524
|
-
},
|
|
6525
|
-
{
|
|
6526
|
-
providerId: "gemini",
|
|
6527
|
-
providerLabel: "Gemini",
|
|
6528
|
-
transportKind: "cli",
|
|
6529
|
-
transportId: "gemini-cli",
|
|
6530
|
-
transportLabel: "Gemini CLI",
|
|
6531
|
-
runtime: "local",
|
|
6532
|
-
defaultModel: "gemini-2.5-flash",
|
|
6533
|
-
defaultCommand: AI_CONNECT_DEFAULT_CLI_COMMANDS["gemini:gemini-cli"]
|
|
6534
|
-
},
|
|
6535
|
-
{
|
|
6536
|
-
providerId: "gemini",
|
|
6537
|
-
providerLabel: "Gemini",
|
|
6538
|
-
transportKind: "acp",
|
|
6539
|
-
transportId: "gemini-acp",
|
|
6540
|
-
transportLabel: "Gemini ACP",
|
|
6541
|
-
runtime: "local",
|
|
6542
|
-
defaultModel: "auto-gemini-3",
|
|
6543
|
-
defaultCommand: AI_CONNECT_DEFAULT_ACP_COMMANDS["gemini:gemini-acp"]
|
|
6544
6564
|
}
|
|
6545
6565
|
]
|
|
6546
6566
|
},
|
|
6547
6567
|
{
|
|
6548
|
-
providerId: "
|
|
6549
|
-
label: "
|
|
6568
|
+
providerId: "pi",
|
|
6569
|
+
label: "pi",
|
|
6550
6570
|
transports: [
|
|
6551
6571
|
{
|
|
6552
|
-
providerId: "
|
|
6553
|
-
providerLabel: "
|
|
6572
|
+
providerId: "pi",
|
|
6573
|
+
providerLabel: "pi",
|
|
6554
6574
|
transportKind: "cli",
|
|
6555
|
-
transportId: "
|
|
6556
|
-
transportLabel: "
|
|
6575
|
+
transportId: "pi-cli",
|
|
6576
|
+
transportLabel: "pi CLI",
|
|
6557
6577
|
runtime: "local",
|
|
6558
|
-
defaultModel: "
|
|
6559
|
-
defaultCommand: AI_CONNECT_DEFAULT_CLI_COMMANDS["
|
|
6560
|
-
},
|
|
6561
|
-
{
|
|
6562
|
-
providerId: "qwen",
|
|
6563
|
-
providerLabel: "Qwen",
|
|
6564
|
-
transportKind: "acp",
|
|
6565
|
-
transportId: "qwen-acp",
|
|
6566
|
-
transportLabel: "Qwen ACP",
|
|
6567
|
-
runtime: "local",
|
|
6568
|
-
defaultModel: "default",
|
|
6569
|
-
defaultCommand: AI_CONNECT_DEFAULT_ACP_COMMANDS["qwen:qwen-acp"]
|
|
6578
|
+
defaultModel: "gemini-3.1-pro-low",
|
|
6579
|
+
defaultCommand: AI_CONNECT_DEFAULT_CLI_COMMANDS["pi:pi-cli"]
|
|
6570
6580
|
}
|
|
6571
6581
|
]
|
|
6572
6582
|
},
|