@vedmalex/ai-connect 0.6.0 → 0.8.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 +24 -1
- package/dist/browser/index.js +5 -2
- package/dist/browser/index.js.map +2 -2
- package/dist/bun/index.js +176 -11
- package/dist/bun/index.js.map +2 -2
- package/dist/bun/local.js +176 -11
- package/dist/bun/local.js.map +2 -2
- package/dist/node/index.js +176 -11
- package/dist/node/index.js.map +2 -2
- package/dist/node/local.js +176 -11
- package/dist/node/local.js.map +2 -2
- package/dist/types/acp.d.ts +14 -0
- package/dist/types/acp.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/default-handlers.d.ts.map +1 -1
- package/dist/types/model-reference.d.ts.map +1 -1
- package/dist/types/types.d.ts +46 -0
- package/dist/types/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/bun/index.js
CHANGED
|
@@ -1984,6 +1984,8 @@ function fillConfiguredContextLength(route, models) {
|
|
|
1984
1984
|
return {
|
|
1985
1985
|
...entry,
|
|
1986
1986
|
contextLength: configured,
|
|
1987
|
+
// FT-002: typed provenance (canonical) + metadata alias (back-compat).
|
|
1988
|
+
contextWindowSource: "configured",
|
|
1987
1989
|
metadata: { ...entry.metadata ?? {}, contextWindowSource: "configured" }
|
|
1988
1990
|
};
|
|
1989
1991
|
});
|
|
@@ -3316,7 +3318,8 @@ function createClient(configOrInput, options = {}) {
|
|
|
3316
3318
|
...catalog.canonicalModelId ? { canonicalModelId: catalog.canonicalModelId } : {},
|
|
3317
3319
|
...catalog.resolvedModelId ? { resolvedModelId: catalog.resolvedModelId } : {},
|
|
3318
3320
|
...catalog.currentModelId ? { currentModelId: catalog.currentModelId } : {},
|
|
3319
|
-
availableModels: catalog.availableModels
|
|
3321
|
+
availableModels: catalog.availableModels,
|
|
3322
|
+
...catalog.warnings?.length ? { warnings: catalog.warnings } : {}
|
|
3320
3323
|
});
|
|
3321
3324
|
wideAttempts.push({
|
|
3322
3325
|
index: wideAttempts.length + 1,
|
|
@@ -5019,7 +5022,7 @@ function parseOpenAiModelCatalog(route, payload) {
|
|
|
5019
5022
|
modelId: entry.id,
|
|
5020
5023
|
name: typeof entry.display_name === "string" ? entry.display_name : typeof entry.name === "string" ? entry.name : entry.id,
|
|
5021
5024
|
...typeof entry.description === "string" ? { description: entry.description } : {},
|
|
5022
|
-
...contextLength !== void 0 ? { contextLength } : {},
|
|
5025
|
+
...contextLength !== void 0 ? { contextLength, contextWindowSource: "discovered" } : {},
|
|
5023
5026
|
...pricing ? { pricing } : {},
|
|
5024
5027
|
...free ? { free } : {},
|
|
5025
5028
|
...metadata ? {
|
|
@@ -6794,6 +6797,20 @@ var TEXT_EXTENSIONS2 = /* @__PURE__ */ new Set([
|
|
|
6794
6797
|
function isRecord2(value) {
|
|
6795
6798
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
6796
6799
|
}
|
|
6800
|
+
var ACP_HARNESS_NOISE_PATTERNS = [
|
|
6801
|
+
/^модель переключена на .+?\.?(?:\s*готов к работе[.!…]*)?$/iu,
|
|
6802
|
+
/^готов к работе[.!…]*$/iu,
|
|
6803
|
+
/^жду (?:вашего вопроса|вопроса или задачи|вашей задачи|вашего ответа|явного запроса)[^]*$/iu,
|
|
6804
|
+
/^это сообщение помечено как локальная команда[^]*$/iu,
|
|
6805
|
+
/^<local-command-(?:caveat|stdout|name|message|args)[^>]*>[^]*$/iu
|
|
6806
|
+
];
|
|
6807
|
+
function isHarnessNoiseLine(line) {
|
|
6808
|
+
const trimmed = line.trim();
|
|
6809
|
+
if (trimmed.length === 0) {
|
|
6810
|
+
return false;
|
|
6811
|
+
}
|
|
6812
|
+
return ACP_HARNESS_NOISE_PATTERNS.some((pattern) => pattern.test(trimmed));
|
|
6813
|
+
}
|
|
6797
6814
|
function splitCommandLine(commandLine) {
|
|
6798
6815
|
const matches = commandLine.match(/"[^"]*"|'[^']*'|[^\s]+/g) ?? [];
|
|
6799
6816
|
const parts = matches.map((part) => {
|
|
@@ -7630,6 +7647,7 @@ function buildAcpLifecycle(route, authRequest, mode) {
|
|
|
7630
7647
|
}
|
|
7631
7648
|
steps.push("session/new");
|
|
7632
7649
|
if (mode === "prompt") {
|
|
7650
|
+
steps.push("session/set_model");
|
|
7633
7651
|
steps.push("session/prompt");
|
|
7634
7652
|
}
|
|
7635
7653
|
return { steps, keys };
|
|
@@ -7717,6 +7735,9 @@ var AcpConnection = class {
|
|
|
7717
7735
|
env;
|
|
7718
7736
|
timeoutMs;
|
|
7719
7737
|
permissionMode;
|
|
7738
|
+
selectModel;
|
|
7739
|
+
suppressHarnessNoise;
|
|
7740
|
+
failOnHarnessOnlyTurn;
|
|
7720
7741
|
command;
|
|
7721
7742
|
args;
|
|
7722
7743
|
child;
|
|
@@ -7738,6 +7759,9 @@ var AcpConnection = class {
|
|
|
7738
7759
|
this.env = options.env;
|
|
7739
7760
|
this.timeoutMs = options.timeoutMs;
|
|
7740
7761
|
this.permissionMode = options.permissionMode;
|
|
7762
|
+
this.selectModel = options.selectModel;
|
|
7763
|
+
this.suppressHarnessNoise = options.suppressHarnessNoise;
|
|
7764
|
+
this.failOnHarnessOnlyTurn = options.failOnHarnessOnlyTurn;
|
|
7741
7765
|
const resolved = splitCommandLine(options.commandLine);
|
|
7742
7766
|
this.command = resolved.command;
|
|
7743
7767
|
this.args = resolved.args;
|
|
@@ -7770,8 +7794,18 @@ var AcpConnection = class {
|
|
|
7770
7794
|
usage: void 0,
|
|
7771
7795
|
attachments: [],
|
|
7772
7796
|
warnings: [],
|
|
7797
|
+
suppressNoise: this.suppressHarnessNoise,
|
|
7798
|
+
lineBuffer: "",
|
|
7799
|
+
harnessMarkers: [],
|
|
7773
7800
|
...onDelta ? { onDelta } : {}
|
|
7774
7801
|
};
|
|
7802
|
+
await this.selectSessionModel(
|
|
7803
|
+
sessionId,
|
|
7804
|
+
context.route,
|
|
7805
|
+
session,
|
|
7806
|
+
transport.phases ?? [],
|
|
7807
|
+
this.activePrompt.warnings
|
|
7808
|
+
);
|
|
7775
7809
|
let stopReason;
|
|
7776
7810
|
const abortSignal = context.abort.signal;
|
|
7777
7811
|
let abortListener;
|
|
@@ -7814,13 +7848,27 @@ var AcpConnection = class {
|
|
|
7814
7848
|
}
|
|
7815
7849
|
this.bumpIdleTimer();
|
|
7816
7850
|
}
|
|
7851
|
+
this.flushHarnessLineBuffer();
|
|
7817
7852
|
const stderrSlice = this.stderr.slice(stderrOffset).trim();
|
|
7818
7853
|
const warnings = [...this.activePrompt.warnings];
|
|
7854
|
+
if (this.activePrompt.harnessMarkers.length > 0) {
|
|
7855
|
+
warnings.push(
|
|
7856
|
+
`ACP: filtered ${this.activePrompt.harnessMarkers.length} interactive-harness line(s) from the response.`
|
|
7857
|
+
);
|
|
7858
|
+
}
|
|
7819
7859
|
if (stderrSlice.length > 0) {
|
|
7820
7860
|
warnings.push(stderrSlice);
|
|
7821
7861
|
}
|
|
7862
|
+
const text = this.activePrompt.text.trim();
|
|
7863
|
+
if (this.failOnHarnessOnlyTurn && this.activePrompt.harnessMarkers.length > 0 && text.length === 0) {
|
|
7864
|
+
this.activePrompt = void 0;
|
|
7865
|
+
throw new AiConnectError(
|
|
7866
|
+
"temporary_unavailable",
|
|
7867
|
+
"ACP turn returned only interactive-harness output (no task output)."
|
|
7868
|
+
);
|
|
7869
|
+
}
|
|
7822
7870
|
const result = {
|
|
7823
|
-
text
|
|
7871
|
+
text,
|
|
7824
7872
|
attachments: [...this.activePrompt.attachments],
|
|
7825
7873
|
warnings,
|
|
7826
7874
|
...this.activePrompt.usage ? { usage: this.activePrompt.usage } : {},
|
|
@@ -8127,6 +8175,108 @@ var AcpConnection = class {
|
|
|
8127
8175
|
}
|
|
8128
8176
|
});
|
|
8129
8177
|
}
|
|
8178
|
+
/**
|
|
8179
|
+
* Select the route's model through the ACP protocol (TASK-033 / UR-002).
|
|
8180
|
+
* Best-effort matrix: skip when disabled / no model / no advertised catalog /
|
|
8181
|
+
* already current; warn (not silently default) when the requested model is not
|
|
8182
|
+
* in the agent's `availableModels`; otherwise `session/set_model` and surface
|
|
8183
|
+
* any error as a warning. Never throws — a failed protocol selection must not
|
|
8184
|
+
* fail the generation.
|
|
8185
|
+
*/
|
|
8186
|
+
async selectSessionModel(sessionId, route, sessionResult, phases, warnings) {
|
|
8187
|
+
if (!this.selectModel) {
|
|
8188
|
+
return;
|
|
8189
|
+
}
|
|
8190
|
+
const desiredModel = route.model?.trim();
|
|
8191
|
+
if (!desiredModel) {
|
|
8192
|
+
return;
|
|
8193
|
+
}
|
|
8194
|
+
const catalog = modelCatalogFromSession(sessionResult);
|
|
8195
|
+
if (!catalog) {
|
|
8196
|
+
return;
|
|
8197
|
+
}
|
|
8198
|
+
if (catalog.currentModelId === desiredModel) {
|
|
8199
|
+
return;
|
|
8200
|
+
}
|
|
8201
|
+
const available = catalog.availableModels ?? [];
|
|
8202
|
+
if (available.length > 0 && !available.some((entry) => entry.modelId === desiredModel)) {
|
|
8203
|
+
warnings.push(
|
|
8204
|
+
`ACP agent's available models [${available.map((entry) => entry.modelId).join(", ")}] do not include route model "${desiredModel}"; model not switched via protocol.`
|
|
8205
|
+
);
|
|
8206
|
+
return;
|
|
8207
|
+
}
|
|
8208
|
+
try {
|
|
8209
|
+
await measurePhase(
|
|
8210
|
+
phases,
|
|
8211
|
+
"session/set_model",
|
|
8212
|
+
async () => this.request("session/set_model", {
|
|
8213
|
+
sessionId,
|
|
8214
|
+
modelId: desiredModel
|
|
8215
|
+
})
|
|
8216
|
+
);
|
|
8217
|
+
} catch (error) {
|
|
8218
|
+
const message = error instanceof Error ? error.message : isRecord2(error) && typeof error.message === "string" ? error.message : String(error);
|
|
8219
|
+
warnings.push(
|
|
8220
|
+
`ACP session/set_model("${desiredModel}") failed: ${message}`
|
|
8221
|
+
);
|
|
8222
|
+
}
|
|
8223
|
+
}
|
|
8224
|
+
/**
|
|
8225
|
+
* Append `agent_message_chunk` text (TASK-033 / UR-003). With suppression off
|
|
8226
|
+
* (legacy) the chunk streams + accumulates raw. With suppression on, the chunk
|
|
8227
|
+
* is line-buffered: each COMPLETE line is classified and either dropped (a
|
|
8228
|
+
* harness marker — no delta, no accumulation) or emitted; the trailing partial
|
|
8229
|
+
* line waits in `lineBuffer` for the next chunk or the end-of-turn flush.
|
|
8230
|
+
*/
|
|
8231
|
+
appendAgentText(chunk) {
|
|
8232
|
+
const state = this.activePrompt;
|
|
8233
|
+
if (!state) {
|
|
8234
|
+
return;
|
|
8235
|
+
}
|
|
8236
|
+
if (!state.suppressNoise) {
|
|
8237
|
+
state.onDelta?.(chunk);
|
|
8238
|
+
state.text += chunk;
|
|
8239
|
+
return;
|
|
8240
|
+
}
|
|
8241
|
+
state.lineBuffer += chunk;
|
|
8242
|
+
let newlineIndex = state.lineBuffer.indexOf("\n");
|
|
8243
|
+
while (newlineIndex !== -1) {
|
|
8244
|
+
const line = state.lineBuffer.slice(0, newlineIndex + 1);
|
|
8245
|
+
state.lineBuffer = state.lineBuffer.slice(newlineIndex + 1);
|
|
8246
|
+
this.emitAgentLine(line);
|
|
8247
|
+
newlineIndex = state.lineBuffer.indexOf("\n");
|
|
8248
|
+
}
|
|
8249
|
+
}
|
|
8250
|
+
/** Flush any trailing partial line through the harness-noise classifier. */
|
|
8251
|
+
flushHarnessLineBuffer() {
|
|
8252
|
+
const state = this.activePrompt;
|
|
8253
|
+
if (!state || !state.suppressNoise) {
|
|
8254
|
+
return;
|
|
8255
|
+
}
|
|
8256
|
+
const remaining = state.lineBuffer;
|
|
8257
|
+
state.lineBuffer = "";
|
|
8258
|
+
if (remaining.length > 0) {
|
|
8259
|
+
this.emitAgentLine(remaining);
|
|
8260
|
+
}
|
|
8261
|
+
}
|
|
8262
|
+
/**
|
|
8263
|
+
* Emit one classified line: a curated harness marker is recorded + dropped
|
|
8264
|
+
* (no delta, no accumulation); any other line streams + accumulates. `line`
|
|
8265
|
+
* may carry a trailing newline — classification runs on the content.
|
|
8266
|
+
*/
|
|
8267
|
+
emitAgentLine(line) {
|
|
8268
|
+
const state = this.activePrompt;
|
|
8269
|
+
if (!state) {
|
|
8270
|
+
return;
|
|
8271
|
+
}
|
|
8272
|
+
const content = line.endsWith("\n") ? line.slice(0, -1) : line;
|
|
8273
|
+
if (isHarnessNoiseLine(content)) {
|
|
8274
|
+
state.harnessMarkers.push(content.trim());
|
|
8275
|
+
return;
|
|
8276
|
+
}
|
|
8277
|
+
state.onDelta?.(line);
|
|
8278
|
+
state.text += line;
|
|
8279
|
+
}
|
|
8130
8280
|
handleNotification(message) {
|
|
8131
8281
|
if (message.method !== "session/update" || !this.activePrompt) {
|
|
8132
8282
|
return;
|
|
@@ -8141,8 +8291,7 @@ var AcpConnection = class {
|
|
|
8141
8291
|
}
|
|
8142
8292
|
const chunk = extractTextChunk(update);
|
|
8143
8293
|
if (update.sessionUpdate === "agent_message_chunk" && chunk) {
|
|
8144
|
-
this.
|
|
8145
|
-
this.activePrompt.text += chunk;
|
|
8294
|
+
this.appendAgentText(chunk);
|
|
8146
8295
|
}
|
|
8147
8296
|
if (update.sessionUpdate === "agent_thought_chunk" && chunk) {
|
|
8148
8297
|
this.activePrompt.warnings.push(chunk);
|
|
@@ -8329,7 +8478,10 @@ function createAcpTransportManager(options) {
|
|
|
8329
8478
|
cwd: runtime.cwd,
|
|
8330
8479
|
env: runtime.env,
|
|
8331
8480
|
timeoutMs: promptTimeout(options),
|
|
8332
|
-
permissionMode: options?.permissionMode ?? "deny-all"
|
|
8481
|
+
permissionMode: options?.permissionMode ?? "deny-all",
|
|
8482
|
+
selectModel: options?.selectModel ?? true,
|
|
8483
|
+
suppressHarnessNoise: options?.suppressHarnessNoise ?? true,
|
|
8484
|
+
failOnHarnessOnlyTurn: options?.failOnHarnessOnlyTurn ?? true
|
|
8333
8485
|
};
|
|
8334
8486
|
}
|
|
8335
8487
|
function getPool(key) {
|
|
@@ -9015,16 +9167,20 @@ function buildCliDiscoveryInvocation(route, command, options) {
|
|
|
9015
9167
|
parameterKeys
|
|
9016
9168
|
};
|
|
9017
9169
|
}
|
|
9018
|
-
function buildStaticCliCatalog(route) {
|
|
9170
|
+
function buildStaticCliCatalog(route, warning) {
|
|
9019
9171
|
const models = route.advertisedModels.map((id) => ({
|
|
9020
9172
|
modelId: id,
|
|
9021
9173
|
name: id
|
|
9022
9174
|
}));
|
|
9023
|
-
|
|
9175
|
+
const catalog = buildModelCatalog(
|
|
9024
9176
|
route,
|
|
9025
9177
|
models,
|
|
9026
9178
|
currentModelIdForRoute(route, route.advertisedModels)
|
|
9027
9179
|
);
|
|
9180
|
+
if (warning) {
|
|
9181
|
+
catalog.warnings = [...catalog.warnings ?? [], warning];
|
|
9182
|
+
}
|
|
9183
|
+
return catalog;
|
|
9028
9184
|
}
|
|
9029
9185
|
function statsToUsage(stats) {
|
|
9030
9186
|
if (!stats || typeof stats !== "object") {
|
|
@@ -9167,7 +9323,9 @@ function modelInfoFromRecord(record, selector) {
|
|
|
9167
9323
|
modelId,
|
|
9168
9324
|
name,
|
|
9169
9325
|
...description ? { description } : {},
|
|
9170
|
-
|
|
9326
|
+
// FT-002: a contextLength read from the list-command output is a discovered
|
|
9327
|
+
// value (consistent with the API parsers), so tag its provenance.
|
|
9328
|
+
...contextLength !== void 0 ? { contextLength, contextWindowSource: "discovered" } : {}
|
|
9171
9329
|
};
|
|
9172
9330
|
}
|
|
9173
9331
|
function parseCliModelList(stdout, parser, selector) {
|
|
@@ -9438,13 +9596,20 @@ function createCliTransportManager(options) {
|
|
|
9438
9596
|
throw error;
|
|
9439
9597
|
}
|
|
9440
9598
|
if (source.fallback === "static") {
|
|
9441
|
-
|
|
9599
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
9600
|
+
return buildStaticCliCatalog(
|
|
9601
|
+
context.route,
|
|
9602
|
+
`CLI discovery command for "${context.route.transport.id}" failed (${detail}); fell back to the static models[] catalog.`
|
|
9603
|
+
);
|
|
9442
9604
|
}
|
|
9443
9605
|
throw error;
|
|
9444
9606
|
}
|
|
9445
9607
|
if (models.length === 0) {
|
|
9446
9608
|
if (source.fallback === "static") {
|
|
9447
|
-
return buildStaticCliCatalog(
|
|
9609
|
+
return buildStaticCliCatalog(
|
|
9610
|
+
context.route,
|
|
9611
|
+
`CLI discovery command for "${context.route.transport.id}" returned no models; fell back to the static models[] catalog.`
|
|
9612
|
+
);
|
|
9448
9613
|
}
|
|
9449
9614
|
throw new AiConnectError(
|
|
9450
9615
|
"temporary_unavailable",
|