@vedmalex/ai-connect 0.2.0 → 0.4.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 +137 -67
- package/dist/browser/index.js +113 -17
- package/dist/browser/index.js.map +2 -2
- package/dist/bun/index.js +345 -38
- package/dist/bun/index.js.map +2 -2
- package/dist/bun/local.js +345 -38
- package/dist/bun/local.js.map +2 -2
- package/dist/node/index.js +345 -38
- package/dist/node/index.js.map +2 -2
- package/dist/node/local.js +345 -38
- package/dist/node/local.js.map +2 -2
- package/dist/types/acp.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/client.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 +90 -4
- package/dist/types/types.d.ts.map +1 -1
- package/package.json +3 -2
package/dist/bun/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 {
|
|
@@ -1730,6 +1783,25 @@ function resolveModelContextWindow(input) {
|
|
|
1730
1783
|
const fallback = isUsableContextLength(input.defaultContextWindow) ? input.defaultContextWindow : DEFAULT_CONTEXT_WINDOW;
|
|
1731
1784
|
return { contextWindow: fallback, source: "default" };
|
|
1732
1785
|
}
|
|
1786
|
+
function fillConfiguredContextLength(route, models) {
|
|
1787
|
+
if (!isUsableContextLength(route.contextWindow)) {
|
|
1788
|
+
return models;
|
|
1789
|
+
}
|
|
1790
|
+
const configured = route.contextWindow;
|
|
1791
|
+
return models.map((entry) => {
|
|
1792
|
+
if (isUsableContextLength(entry.contextLength)) {
|
|
1793
|
+
return entry;
|
|
1794
|
+
}
|
|
1795
|
+
if (route.model !== void 0 && entry.modelId !== route.model) {
|
|
1796
|
+
return entry;
|
|
1797
|
+
}
|
|
1798
|
+
return {
|
|
1799
|
+
...entry,
|
|
1800
|
+
contextLength: configured,
|
|
1801
|
+
metadata: { ...entry.metadata ?? {}, contextWindowSource: "configured" }
|
|
1802
|
+
};
|
|
1803
|
+
});
|
|
1804
|
+
}
|
|
1733
1805
|
function modelContextCacheKey(input) {
|
|
1734
1806
|
if (typeof input === "string") {
|
|
1735
1807
|
return { key: `::${input}`, model: input };
|
|
@@ -2175,6 +2247,7 @@ function normalizeResult(route, output, attempts) {
|
|
|
2175
2247
|
attempts,
|
|
2176
2248
|
...output.toolCalls ? { toolCalls: output.toolCalls } : {},
|
|
2177
2249
|
...output.text !== void 0 ? { text: output.text } : {},
|
|
2250
|
+
...output.reasoning !== void 0 ? { reasoning: output.reasoning } : {},
|
|
2178
2251
|
...output.data !== void 0 ? { data: output.data } : {},
|
|
2179
2252
|
...output.usage !== void 0 ? { usage: output.usage } : {}
|
|
2180
2253
|
};
|
|
@@ -4725,18 +4798,19 @@ function canonicalGeminiImageModelId(modelId) {
|
|
|
4725
4798
|
}
|
|
4726
4799
|
function buildModelCatalog(route, availableModels, currentModelId) {
|
|
4727
4800
|
const requestedModelId = route.model;
|
|
4728
|
-
const
|
|
4801
|
+
const filledModels = fillConfiguredContextLength(route, availableModels);
|
|
4802
|
+
const requestedModelAdvertised = filledModels.some(
|
|
4729
4803
|
(model) => model.modelId === requestedModelId
|
|
4730
4804
|
);
|
|
4731
4805
|
const canonicalModelId = route.provider === "gemini" ? canonicalGeminiImageModelId(requestedModelId) : void 0;
|
|
4732
|
-
const resolvedModelId = requestedModelAdvertised ? requestedModelId : canonicalModelId &&
|
|
4806
|
+
const resolvedModelId = requestedModelAdvertised ? requestedModelId : canonicalModelId && filledModels.some((model) => model.modelId === canonicalModelId) ? canonicalModelId : void 0;
|
|
4733
4807
|
return {
|
|
4734
4808
|
requestedModelId,
|
|
4735
4809
|
requestedModelAdvertised,
|
|
4736
4810
|
...canonicalModelId ? { canonicalModelId } : {},
|
|
4737
4811
|
...resolvedModelId ? { resolvedModelId } : {},
|
|
4738
4812
|
...currentModelId ? { currentModelId } : {},
|
|
4739
|
-
availableModels
|
|
4813
|
+
availableModels: filledModels
|
|
4740
4814
|
};
|
|
4741
4815
|
}
|
|
4742
4816
|
function parseOpenAiModelCatalog(route, payload) {
|
|
@@ -5551,6 +5625,9 @@ function extractText(value) {
|
|
|
5551
5625
|
return [part];
|
|
5552
5626
|
}
|
|
5553
5627
|
if (part && typeof part === "object") {
|
|
5628
|
+
if (part.thought === true) {
|
|
5629
|
+
return [];
|
|
5630
|
+
}
|
|
5554
5631
|
const candidate = part.text;
|
|
5555
5632
|
if (typeof candidate === "string") {
|
|
5556
5633
|
return [candidate];
|
|
@@ -5561,6 +5638,22 @@ function extractText(value) {
|
|
|
5561
5638
|
}
|
|
5562
5639
|
return "";
|
|
5563
5640
|
}
|
|
5641
|
+
function extractThoughts(value) {
|
|
5642
|
+
if (!Array.isArray(value)) {
|
|
5643
|
+
return void 0;
|
|
5644
|
+
}
|
|
5645
|
+
const thoughts = value.flatMap((part) => {
|
|
5646
|
+
if (part && typeof part === "object" && part.thought === true) {
|
|
5647
|
+
const candidate = part.text;
|
|
5648
|
+
if (typeof candidate === "string") {
|
|
5649
|
+
return [candidate];
|
|
5650
|
+
}
|
|
5651
|
+
}
|
|
5652
|
+
return [];
|
|
5653
|
+
});
|
|
5654
|
+
const joined = thoughts.join("\n").trim();
|
|
5655
|
+
return joined.length > 0 ? joined : void 0;
|
|
5656
|
+
}
|
|
5564
5657
|
function imageAttachmentsFromPayload(payload, ...sources) {
|
|
5565
5658
|
return extractImagePayloads(payload, ...sources).map(
|
|
5566
5659
|
(item) => preparePortableFile(item)
|
|
@@ -6103,8 +6196,11 @@ async function runGemini(fetchImpl, context) {
|
|
|
6103
6196
|
throw classifyApiError(provider, response, payload);
|
|
6104
6197
|
}
|
|
6105
6198
|
const data = payload;
|
|
6199
|
+
const geminiThoughts = extractThoughts(data.candidates?.[0]?.content?.parts);
|
|
6106
6200
|
return {
|
|
6107
6201
|
text: extractText(data.candidates?.[0]?.content?.parts),
|
|
6202
|
+
// The model's `{ thought: true }` parts, separated from the answer (consumers route this to a thinking UI).
|
|
6203
|
+
...geminiThoughts ? { reasoning: geminiThoughts } : {},
|
|
6108
6204
|
data,
|
|
6109
6205
|
...geminiToolCallsFromPayload(data).length > 0 ? { toolCalls: geminiToolCallsFromPayload(data) } : {},
|
|
6110
6206
|
...(() => {
|
|
@@ -7943,6 +8039,10 @@ var AcpConnection = class {
|
|
|
7943
8039
|
`ACP route "${context.route.id}" did not return an ACP model catalog in session/new.`
|
|
7944
8040
|
);
|
|
7945
8041
|
}
|
|
8042
|
+
catalog.availableModels = fillConfiguredContextLength(
|
|
8043
|
+
context.route,
|
|
8044
|
+
catalog.availableModels
|
|
8045
|
+
);
|
|
7946
8046
|
return catalog;
|
|
7947
8047
|
} finally {
|
|
7948
8048
|
this.bumpIdleTimer();
|
|
@@ -8884,14 +8984,26 @@ function normalizeCliDiscoveryAcpSource(route, discovery) {
|
|
|
8884
8984
|
};
|
|
8885
8985
|
}
|
|
8886
8986
|
function resolveCliDiscoverySource(route) {
|
|
8887
|
-
const
|
|
8888
|
-
|
|
8889
|
-
|
|
8987
|
+
const discovery = route.transport.cli?.discovery;
|
|
8988
|
+
const hasModels = (route.advertisedModels?.length ?? 0) > 0;
|
|
8989
|
+
const hasAcpDefault = Boolean(defaultCliDiscoveryTransportIdForRoute(route));
|
|
8990
|
+
const via = discovery?.via ?? (discovery?.command ? "command" : hasAcpDefault ? "acp" : hasModels ? "static" : "none");
|
|
8991
|
+
switch (via) {
|
|
8992
|
+
case "none":
|
|
8993
|
+
return { via: "none" };
|
|
8994
|
+
case "static":
|
|
8995
|
+
return { via: "static" };
|
|
8996
|
+
case "command": {
|
|
8997
|
+
const command = discovery?.command;
|
|
8998
|
+
if (!command) {
|
|
8999
|
+
return { via: "none" };
|
|
9000
|
+
}
|
|
9001
|
+
const fallback = discovery?.fallback ?? (hasModels ? "static" : "none");
|
|
9002
|
+
return { via: "command", command, fallback };
|
|
9003
|
+
}
|
|
9004
|
+
default:
|
|
9005
|
+
return normalizeCliDiscoveryAcpSource(route, discovery?.acp);
|
|
8890
9006
|
}
|
|
8891
|
-
return normalizeCliDiscoveryAcpSource(
|
|
8892
|
-
route,
|
|
8893
|
-
route.transport.cli?.discovery?.acp
|
|
8894
|
-
);
|
|
8895
9007
|
}
|
|
8896
9008
|
function createCliDiscoveryAcpRoute(route) {
|
|
8897
9009
|
const discovery = resolveCliDiscoverySource(route);
|
|
@@ -9000,6 +9112,42 @@ async function buildCliInvocation(context, options) {
|
|
|
9000
9112
|
parameterKeys
|
|
9001
9113
|
};
|
|
9002
9114
|
}
|
|
9115
|
+
function buildCliDiscoveryInvocation(route, command, options) {
|
|
9116
|
+
const commandLine = command.command?.trim() ? command.command : resolveCliCommand(route, options);
|
|
9117
|
+
const resolved = splitCommandLine2(commandLine);
|
|
9118
|
+
const parameterKeys = [];
|
|
9119
|
+
const args = [
|
|
9120
|
+
...resolved.args,
|
|
9121
|
+
...command.argsTemplate.map((part) => {
|
|
9122
|
+
if (part === "{model}") {
|
|
9123
|
+
parameterKeys.push("model");
|
|
9124
|
+
return route.model;
|
|
9125
|
+
}
|
|
9126
|
+
if (part.startsWith("--")) {
|
|
9127
|
+
parameterKeys.push(part);
|
|
9128
|
+
}
|
|
9129
|
+
return part;
|
|
9130
|
+
})
|
|
9131
|
+
];
|
|
9132
|
+
return {
|
|
9133
|
+
command: resolved.command,
|
|
9134
|
+
args,
|
|
9135
|
+
cwd: path2.resolve(options?.cwd ?? process.cwd()),
|
|
9136
|
+
env: buildCliEnvironment(options),
|
|
9137
|
+
parameterKeys
|
|
9138
|
+
};
|
|
9139
|
+
}
|
|
9140
|
+
function buildStaticCliCatalog(route) {
|
|
9141
|
+
const models = route.advertisedModels.map((id) => ({
|
|
9142
|
+
modelId: id,
|
|
9143
|
+
name: id
|
|
9144
|
+
}));
|
|
9145
|
+
return buildModelCatalog(
|
|
9146
|
+
route,
|
|
9147
|
+
models,
|
|
9148
|
+
currentModelIdForRoute(route, route.advertisedModels)
|
|
9149
|
+
);
|
|
9150
|
+
}
|
|
9003
9151
|
function statsToUsage(stats) {
|
|
9004
9152
|
if (!stats || typeof stats !== "object") {
|
|
9005
9153
|
return void 0;
|
|
@@ -9037,6 +9185,9 @@ function getValueByPath(value, dotPath) {
|
|
|
9037
9185
|
}
|
|
9038
9186
|
return current;
|
|
9039
9187
|
}
|
|
9188
|
+
function splitJsonlLines(stdout) {
|
|
9189
|
+
return stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => JSON.parse(line));
|
|
9190
|
+
}
|
|
9040
9191
|
function normalizeErrorMessage(value) {
|
|
9041
9192
|
if (typeof value === "string" && value.trim()) {
|
|
9042
9193
|
return value.trim();
|
|
@@ -9084,7 +9235,7 @@ function parseGenericJsonCli(stdout, parser) {
|
|
|
9084
9235
|
};
|
|
9085
9236
|
}
|
|
9086
9237
|
function parseGenericJsonlCli(stdout, parser) {
|
|
9087
|
-
const entries = stdout
|
|
9238
|
+
const entries = splitJsonlLines(stdout);
|
|
9088
9239
|
const errorValue = parser.error ? findJsonlSelection(entries, parser.error) : void 0;
|
|
9089
9240
|
const errorMessage = normalizeErrorMessage(errorValue);
|
|
9090
9241
|
if (errorMessage) {
|
|
@@ -9105,6 +9256,70 @@ function parseGenericJsonlCli(stdout, parser) {
|
|
|
9105
9256
|
data: entries
|
|
9106
9257
|
};
|
|
9107
9258
|
}
|
|
9259
|
+
var ANSI_ESCAPE_PATTERN = /\[[0-?]*[ -/]*[@-~]/g;
|
|
9260
|
+
function parseTextCli(stdout, parser) {
|
|
9261
|
+
let text = stdout;
|
|
9262
|
+
if (parser.stripAnsi) {
|
|
9263
|
+
text = text.replace(ANSI_ESCAPE_PATTERN, "");
|
|
9264
|
+
}
|
|
9265
|
+
if (parser.trim !== false) {
|
|
9266
|
+
text = text.trim();
|
|
9267
|
+
}
|
|
9268
|
+
if (!text) {
|
|
9269
|
+
throw new AiConnectError(
|
|
9270
|
+
"temporary_unavailable",
|
|
9271
|
+
"CLI text parser produced no output."
|
|
9272
|
+
);
|
|
9273
|
+
}
|
|
9274
|
+
return { text, data: stdout };
|
|
9275
|
+
}
|
|
9276
|
+
function modelInfoFromRecord(record, selector) {
|
|
9277
|
+
const rawId = getValueByPath(record, selector.idPath);
|
|
9278
|
+
const modelId = typeof rawId === "string" ? rawId.trim() : "";
|
|
9279
|
+
if (!modelId) {
|
|
9280
|
+
return void 0;
|
|
9281
|
+
}
|
|
9282
|
+
const rawName = selector.namePath ? getValueByPath(record, selector.namePath) : void 0;
|
|
9283
|
+
const name = typeof rawName === "string" && rawName.trim().length > 0 ? rawName : modelId;
|
|
9284
|
+
const rawDescription = selector.descriptionPath ? getValueByPath(record, selector.descriptionPath) : void 0;
|
|
9285
|
+
const description = typeof rawDescription === "string" && rawDescription.trim().length > 0 ? rawDescription : void 0;
|
|
9286
|
+
const rawContext = selector.contextLengthPath ? getValueByPath(record, selector.contextLengthPath) : void 0;
|
|
9287
|
+
const contextLength = typeof rawContext === "number" && Number.isFinite(rawContext) && rawContext > 0 ? Math.floor(rawContext) : void 0;
|
|
9288
|
+
return {
|
|
9289
|
+
modelId,
|
|
9290
|
+
name,
|
|
9291
|
+
...description ? { description } : {},
|
|
9292
|
+
...contextLength !== void 0 ? { contextLength } : {}
|
|
9293
|
+
};
|
|
9294
|
+
}
|
|
9295
|
+
function parseCliModelList(stdout, parser, selector) {
|
|
9296
|
+
if (parser.kind === "text") {
|
|
9297
|
+
let text = stdout;
|
|
9298
|
+
if (parser.stripAnsi) {
|
|
9299
|
+
text = text.replace(ANSI_ESCAPE_PATTERN, "");
|
|
9300
|
+
}
|
|
9301
|
+
return text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => ({ modelId: line, name: line }));
|
|
9302
|
+
}
|
|
9303
|
+
if (!selector) {
|
|
9304
|
+
throw new AiConnectError(
|
|
9305
|
+
"validation_error",
|
|
9306
|
+
`CLI ${parser.kind} discovery parser requires a models selector with idPath.`
|
|
9307
|
+
);
|
|
9308
|
+
}
|
|
9309
|
+
const records = parser.kind === "jsonl" ? splitJsonlLines(stdout) : (() => {
|
|
9310
|
+
const payload = JSON.parse(stdout);
|
|
9311
|
+
const arr = selector.path ? getValueByPath(payload, selector.path) : payload;
|
|
9312
|
+
return Array.isArray(arr) ? arr : [];
|
|
9313
|
+
})();
|
|
9314
|
+
const models = [];
|
|
9315
|
+
for (const record of records) {
|
|
9316
|
+
const info = modelInfoFromRecord(record, selector);
|
|
9317
|
+
if (info) {
|
|
9318
|
+
models.push(info);
|
|
9319
|
+
}
|
|
9320
|
+
}
|
|
9321
|
+
return models;
|
|
9322
|
+
}
|
|
9108
9323
|
function parseGeminiCli(stdout) {
|
|
9109
9324
|
const payload = JSON.parse(stdout);
|
|
9110
9325
|
if (typeof payload.error === "string") {
|
|
@@ -9176,8 +9391,7 @@ function parseClaudeCli(stdout) {
|
|
|
9176
9391
|
};
|
|
9177
9392
|
}
|
|
9178
9393
|
function parseCodexCli(stdout, outputFileContent) {
|
|
9179
|
-
const
|
|
9180
|
-
const events = lines.map((line) => JSON.parse(line));
|
|
9394
|
+
const events = splitJsonlLines(stdout);
|
|
9181
9395
|
let text = outputFileContent?.trim() || void 0;
|
|
9182
9396
|
let usage;
|
|
9183
9397
|
for (const event of events) {
|
|
@@ -9235,7 +9449,14 @@ function parseCliResult(route, result, outputFileContent) {
|
|
|
9235
9449
|
`CLI route "${route.id}" declared a parser override but normalization did not preserve it.`
|
|
9236
9450
|
);
|
|
9237
9451
|
}
|
|
9238
|
-
|
|
9452
|
+
switch (parser.kind) {
|
|
9453
|
+
case "text":
|
|
9454
|
+
return parseTextCli(result.stdout, parser);
|
|
9455
|
+
case "json":
|
|
9456
|
+
return parseGenericJsonCli(stdout, parser);
|
|
9457
|
+
default:
|
|
9458
|
+
return parseGenericJsonlCli(stdout, parser);
|
|
9459
|
+
}
|
|
9239
9460
|
}
|
|
9240
9461
|
switch (cliOptions.preset) {
|
|
9241
9462
|
case "gemini":
|
|
@@ -9338,6 +9559,88 @@ async function cleanupCliInvocation(invocation) {
|
|
|
9338
9559
|
}
|
|
9339
9560
|
function createCliTransportManager(options) {
|
|
9340
9561
|
return {
|
|
9562
|
+
async discoverModels(context) {
|
|
9563
|
+
const source = resolveCliDiscoverySource(context.route);
|
|
9564
|
+
if (source.via === "none") {
|
|
9565
|
+
throw new AiConnectError(
|
|
9566
|
+
"not_supported",
|
|
9567
|
+
`CLI transport "${context.route.transport.id}" does not support model discovery (no list command, no ACP sidecar, and no configured models[]).`
|
|
9568
|
+
);
|
|
9569
|
+
}
|
|
9570
|
+
if (source.via === "acp") {
|
|
9571
|
+
throw new AiConnectError(
|
|
9572
|
+
"not_supported",
|
|
9573
|
+
`CLI route "${context.route.id}" resolves discovery via acp; dispatch to the acp discovery route instead.`
|
|
9574
|
+
);
|
|
9575
|
+
}
|
|
9576
|
+
if (source.via === "static") {
|
|
9577
|
+
return buildStaticCliCatalog(context.route);
|
|
9578
|
+
}
|
|
9579
|
+
const phases = [];
|
|
9580
|
+
const invocation = buildCliDiscoveryInvocation(
|
|
9581
|
+
context.route,
|
|
9582
|
+
source.command,
|
|
9583
|
+
options
|
|
9584
|
+
);
|
|
9585
|
+
context.telemetry?.captureTransport({
|
|
9586
|
+
protocol: "cli",
|
|
9587
|
+
endpoint: invocation.command,
|
|
9588
|
+
method: "process",
|
|
9589
|
+
bodyKeys: ["argv"],
|
|
9590
|
+
parameterKeys: invocation.parameterKeys,
|
|
9591
|
+
phases,
|
|
9592
|
+
stream: false
|
|
9593
|
+
});
|
|
9594
|
+
try {
|
|
9595
|
+
let models;
|
|
9596
|
+
try {
|
|
9597
|
+
const execution = await executeCliInvocation(
|
|
9598
|
+
invocation,
|
|
9599
|
+
options?.timeoutMs ?? 6e4,
|
|
9600
|
+
phases,
|
|
9601
|
+
context.abort.signal
|
|
9602
|
+
);
|
|
9603
|
+
if (execution.exitCode !== 0 || !execution.stdout.trim()) {
|
|
9604
|
+
throw new AiConnectError(
|
|
9605
|
+
"temporary_unavailable",
|
|
9606
|
+
execution.stderr.trim() || `CLI discovery command for "${context.route.transport.id}" exited with code ${execution.exitCode ?? "null"}.`
|
|
9607
|
+
);
|
|
9608
|
+
}
|
|
9609
|
+
models = parseCliModelList(
|
|
9610
|
+
execution.stdout,
|
|
9611
|
+
source.command.parser,
|
|
9612
|
+
source.command.models
|
|
9613
|
+
);
|
|
9614
|
+
} catch (error) {
|
|
9615
|
+
if (error instanceof AiConnectError && error.code === "aborted") {
|
|
9616
|
+
throw error;
|
|
9617
|
+
}
|
|
9618
|
+
if (source.fallback === "static") {
|
|
9619
|
+
return buildStaticCliCatalog(context.route);
|
|
9620
|
+
}
|
|
9621
|
+
throw error;
|
|
9622
|
+
}
|
|
9623
|
+
if (models.length === 0) {
|
|
9624
|
+
if (source.fallback === "static") {
|
|
9625
|
+
return buildStaticCliCatalog(context.route);
|
|
9626
|
+
}
|
|
9627
|
+
throw new AiConnectError(
|
|
9628
|
+
"temporary_unavailable",
|
|
9629
|
+
`CLI discovery command for "${context.route.transport.id}" returned no models.`
|
|
9630
|
+
);
|
|
9631
|
+
}
|
|
9632
|
+
return buildModelCatalog(
|
|
9633
|
+
context.route,
|
|
9634
|
+
models,
|
|
9635
|
+
currentModelIdForRoute(
|
|
9636
|
+
context.route,
|
|
9637
|
+
models.map((model) => model.modelId)
|
|
9638
|
+
)
|
|
9639
|
+
);
|
|
9640
|
+
} finally {
|
|
9641
|
+
await cleanupCliInvocation(invocation);
|
|
9642
|
+
}
|
|
9643
|
+
},
|
|
9341
9644
|
async runPrompt(context) {
|
|
9342
9645
|
const invocation = await buildCliInvocation(context, options);
|
|
9343
9646
|
const phases = [];
|
|
@@ -9857,17 +10160,21 @@ function createLocalRouteHandlers(options = {}) {
|
|
|
9857
10160
|
return cliTransport.runPrompt(context);
|
|
9858
10161
|
},
|
|
9859
10162
|
async discoverModels(context) {
|
|
9860
|
-
const
|
|
9861
|
-
if (
|
|
9862
|
-
|
|
9863
|
-
|
|
9864
|
-
|
|
9865
|
-
|
|
10163
|
+
const source = resolveCliDiscoverySource(context.route);
|
|
10164
|
+
if (source.via === "acp") {
|
|
10165
|
+
const discoveryRoute = createCliDiscoveryAcpRoute(context.route);
|
|
10166
|
+
if (!discoveryRoute) {
|
|
10167
|
+
throw new AiConnectError(
|
|
10168
|
+
"not_supported",
|
|
10169
|
+
`CLI route "${context.route.id}" does not define a model discovery backend.`
|
|
10170
|
+
);
|
|
10171
|
+
}
|
|
10172
|
+
return acpTransport.discoverModels({
|
|
10173
|
+
...context,
|
|
10174
|
+
route: discoveryRoute
|
|
10175
|
+
});
|
|
9866
10176
|
}
|
|
9867
|
-
return
|
|
9868
|
-
...context,
|
|
9869
|
-
route: discoveryRoute
|
|
9870
|
-
});
|
|
10177
|
+
return cliTransport.discoverModels(context);
|
|
9871
10178
|
},
|
|
9872
10179
|
async verify({ route, runtime }) {
|
|
9873
10180
|
try {
|