@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/local.js
CHANGED
|
@@ -1975,6 +1975,8 @@ function fillConfiguredContextLength(route, models) {
|
|
|
1975
1975
|
return {
|
|
1976
1976
|
...entry,
|
|
1977
1977
|
contextLength: configured,
|
|
1978
|
+
// FT-002: typed provenance (canonical) + metadata alias (back-compat).
|
|
1979
|
+
contextWindowSource: "configured",
|
|
1978
1980
|
metadata: { ...entry.metadata ?? {}, contextWindowSource: "configured" }
|
|
1979
1981
|
};
|
|
1980
1982
|
});
|
|
@@ -3304,7 +3306,8 @@ function createClient(configOrInput, options = {}) {
|
|
|
3304
3306
|
...catalog.canonicalModelId ? { canonicalModelId: catalog.canonicalModelId } : {},
|
|
3305
3307
|
...catalog.resolvedModelId ? { resolvedModelId: catalog.resolvedModelId } : {},
|
|
3306
3308
|
...catalog.currentModelId ? { currentModelId: catalog.currentModelId } : {},
|
|
3307
|
-
availableModels: catalog.availableModels
|
|
3309
|
+
availableModels: catalog.availableModels,
|
|
3310
|
+
...catalog.warnings?.length ? { warnings: catalog.warnings } : {}
|
|
3308
3311
|
});
|
|
3309
3312
|
wideAttempts.push({
|
|
3310
3313
|
index: wideAttempts.length + 1,
|
|
@@ -4989,7 +4992,7 @@ function parseOpenAiModelCatalog(route, payload) {
|
|
|
4989
4992
|
modelId: entry.id,
|
|
4990
4993
|
name: typeof entry.display_name === "string" ? entry.display_name : typeof entry.name === "string" ? entry.name : entry.id,
|
|
4991
4994
|
...typeof entry.description === "string" ? { description: entry.description } : {},
|
|
4992
|
-
...contextLength !== void 0 ? { contextLength } : {},
|
|
4995
|
+
...contextLength !== void 0 ? { contextLength, contextWindowSource: "discovered" } : {},
|
|
4993
4996
|
...pricing ? { pricing } : {},
|
|
4994
4997
|
...free ? { free } : {},
|
|
4995
4998
|
...metadata ? {
|
|
@@ -6544,6 +6547,20 @@ var TEXT_EXTENSIONS2 = /* @__PURE__ */ new Set([
|
|
|
6544
6547
|
function isRecord2(value) {
|
|
6545
6548
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
6546
6549
|
}
|
|
6550
|
+
var ACP_HARNESS_NOISE_PATTERNS = [
|
|
6551
|
+
/^модель переключена на .+?\.?(?:\s*готов к работе[.!…]*)?$/iu,
|
|
6552
|
+
/^готов к работе[.!…]*$/iu,
|
|
6553
|
+
/^жду (?:вашего вопроса|вопроса или задачи|вашей задачи|вашего ответа|явного запроса)[^]*$/iu,
|
|
6554
|
+
/^это сообщение помечено как локальная команда[^]*$/iu,
|
|
6555
|
+
/^<local-command-(?:caveat|stdout|name|message|args)[^>]*>[^]*$/iu
|
|
6556
|
+
];
|
|
6557
|
+
function isHarnessNoiseLine(line) {
|
|
6558
|
+
const trimmed = line.trim();
|
|
6559
|
+
if (trimmed.length === 0) {
|
|
6560
|
+
return false;
|
|
6561
|
+
}
|
|
6562
|
+
return ACP_HARNESS_NOISE_PATTERNS.some((pattern) => pattern.test(trimmed));
|
|
6563
|
+
}
|
|
6547
6564
|
function splitCommandLine(commandLine) {
|
|
6548
6565
|
const matches = commandLine.match(/"[^"]*"|'[^']*'|[^\s]+/g) ?? [];
|
|
6549
6566
|
const parts = matches.map((part) => {
|
|
@@ -7380,6 +7397,7 @@ function buildAcpLifecycle(route, authRequest, mode) {
|
|
|
7380
7397
|
}
|
|
7381
7398
|
steps.push("session/new");
|
|
7382
7399
|
if (mode === "prompt") {
|
|
7400
|
+
steps.push("session/set_model");
|
|
7383
7401
|
steps.push("session/prompt");
|
|
7384
7402
|
}
|
|
7385
7403
|
return { steps, keys };
|
|
@@ -7467,6 +7485,9 @@ var AcpConnection = class {
|
|
|
7467
7485
|
env;
|
|
7468
7486
|
timeoutMs;
|
|
7469
7487
|
permissionMode;
|
|
7488
|
+
selectModel;
|
|
7489
|
+
suppressHarnessNoise;
|
|
7490
|
+
failOnHarnessOnlyTurn;
|
|
7470
7491
|
command;
|
|
7471
7492
|
args;
|
|
7472
7493
|
child;
|
|
@@ -7488,6 +7509,9 @@ var AcpConnection = class {
|
|
|
7488
7509
|
this.env = options.env;
|
|
7489
7510
|
this.timeoutMs = options.timeoutMs;
|
|
7490
7511
|
this.permissionMode = options.permissionMode;
|
|
7512
|
+
this.selectModel = options.selectModel;
|
|
7513
|
+
this.suppressHarnessNoise = options.suppressHarnessNoise;
|
|
7514
|
+
this.failOnHarnessOnlyTurn = options.failOnHarnessOnlyTurn;
|
|
7491
7515
|
const resolved = splitCommandLine(options.commandLine);
|
|
7492
7516
|
this.command = resolved.command;
|
|
7493
7517
|
this.args = resolved.args;
|
|
@@ -7520,8 +7544,18 @@ var AcpConnection = class {
|
|
|
7520
7544
|
usage: void 0,
|
|
7521
7545
|
attachments: [],
|
|
7522
7546
|
warnings: [],
|
|
7547
|
+
suppressNoise: this.suppressHarnessNoise,
|
|
7548
|
+
lineBuffer: "",
|
|
7549
|
+
harnessMarkers: [],
|
|
7523
7550
|
...onDelta ? { onDelta } : {}
|
|
7524
7551
|
};
|
|
7552
|
+
await this.selectSessionModel(
|
|
7553
|
+
sessionId,
|
|
7554
|
+
context.route,
|
|
7555
|
+
session,
|
|
7556
|
+
transport.phases ?? [],
|
|
7557
|
+
this.activePrompt.warnings
|
|
7558
|
+
);
|
|
7525
7559
|
let stopReason;
|
|
7526
7560
|
const abortSignal = context.abort.signal;
|
|
7527
7561
|
let abortListener;
|
|
@@ -7564,13 +7598,27 @@ var AcpConnection = class {
|
|
|
7564
7598
|
}
|
|
7565
7599
|
this.bumpIdleTimer();
|
|
7566
7600
|
}
|
|
7601
|
+
this.flushHarnessLineBuffer();
|
|
7567
7602
|
const stderrSlice = this.stderr.slice(stderrOffset).trim();
|
|
7568
7603
|
const warnings = [...this.activePrompt.warnings];
|
|
7604
|
+
if (this.activePrompt.harnessMarkers.length > 0) {
|
|
7605
|
+
warnings.push(
|
|
7606
|
+
`ACP: filtered ${this.activePrompt.harnessMarkers.length} interactive-harness line(s) from the response.`
|
|
7607
|
+
);
|
|
7608
|
+
}
|
|
7569
7609
|
if (stderrSlice.length > 0) {
|
|
7570
7610
|
warnings.push(stderrSlice);
|
|
7571
7611
|
}
|
|
7612
|
+
const text = this.activePrompt.text.trim();
|
|
7613
|
+
if (this.failOnHarnessOnlyTurn && this.activePrompt.harnessMarkers.length > 0 && text.length === 0) {
|
|
7614
|
+
this.activePrompt = void 0;
|
|
7615
|
+
throw new AiConnectError(
|
|
7616
|
+
"temporary_unavailable",
|
|
7617
|
+
"ACP turn returned only interactive-harness output (no task output)."
|
|
7618
|
+
);
|
|
7619
|
+
}
|
|
7572
7620
|
const result = {
|
|
7573
|
-
text
|
|
7621
|
+
text,
|
|
7574
7622
|
attachments: [...this.activePrompt.attachments],
|
|
7575
7623
|
warnings,
|
|
7576
7624
|
...this.activePrompt.usage ? { usage: this.activePrompt.usage } : {},
|
|
@@ -7877,6 +7925,108 @@ var AcpConnection = class {
|
|
|
7877
7925
|
}
|
|
7878
7926
|
});
|
|
7879
7927
|
}
|
|
7928
|
+
/**
|
|
7929
|
+
* Select the route's model through the ACP protocol (TASK-033 / UR-002).
|
|
7930
|
+
* Best-effort matrix: skip when disabled / no model / no advertised catalog /
|
|
7931
|
+
* already current; warn (not silently default) when the requested model is not
|
|
7932
|
+
* in the agent's `availableModels`; otherwise `session/set_model` and surface
|
|
7933
|
+
* any error as a warning. Never throws — a failed protocol selection must not
|
|
7934
|
+
* fail the generation.
|
|
7935
|
+
*/
|
|
7936
|
+
async selectSessionModel(sessionId, route, sessionResult, phases, warnings) {
|
|
7937
|
+
if (!this.selectModel) {
|
|
7938
|
+
return;
|
|
7939
|
+
}
|
|
7940
|
+
const desiredModel = route.model?.trim();
|
|
7941
|
+
if (!desiredModel) {
|
|
7942
|
+
return;
|
|
7943
|
+
}
|
|
7944
|
+
const catalog = modelCatalogFromSession(sessionResult);
|
|
7945
|
+
if (!catalog) {
|
|
7946
|
+
return;
|
|
7947
|
+
}
|
|
7948
|
+
if (catalog.currentModelId === desiredModel) {
|
|
7949
|
+
return;
|
|
7950
|
+
}
|
|
7951
|
+
const available = catalog.availableModels ?? [];
|
|
7952
|
+
if (available.length > 0 && !available.some((entry) => entry.modelId === desiredModel)) {
|
|
7953
|
+
warnings.push(
|
|
7954
|
+
`ACP agent's available models [${available.map((entry) => entry.modelId).join(", ")}] do not include route model "${desiredModel}"; model not switched via protocol.`
|
|
7955
|
+
);
|
|
7956
|
+
return;
|
|
7957
|
+
}
|
|
7958
|
+
try {
|
|
7959
|
+
await measurePhase(
|
|
7960
|
+
phases,
|
|
7961
|
+
"session/set_model",
|
|
7962
|
+
async () => this.request("session/set_model", {
|
|
7963
|
+
sessionId,
|
|
7964
|
+
modelId: desiredModel
|
|
7965
|
+
})
|
|
7966
|
+
);
|
|
7967
|
+
} catch (error) {
|
|
7968
|
+
const message = error instanceof Error ? error.message : isRecord2(error) && typeof error.message === "string" ? error.message : String(error);
|
|
7969
|
+
warnings.push(
|
|
7970
|
+
`ACP session/set_model("${desiredModel}") failed: ${message}`
|
|
7971
|
+
);
|
|
7972
|
+
}
|
|
7973
|
+
}
|
|
7974
|
+
/**
|
|
7975
|
+
* Append `agent_message_chunk` text (TASK-033 / UR-003). With suppression off
|
|
7976
|
+
* (legacy) the chunk streams + accumulates raw. With suppression on, the chunk
|
|
7977
|
+
* is line-buffered: each COMPLETE line is classified and either dropped (a
|
|
7978
|
+
* harness marker — no delta, no accumulation) or emitted; the trailing partial
|
|
7979
|
+
* line waits in `lineBuffer` for the next chunk or the end-of-turn flush.
|
|
7980
|
+
*/
|
|
7981
|
+
appendAgentText(chunk) {
|
|
7982
|
+
const state = this.activePrompt;
|
|
7983
|
+
if (!state) {
|
|
7984
|
+
return;
|
|
7985
|
+
}
|
|
7986
|
+
if (!state.suppressNoise) {
|
|
7987
|
+
state.onDelta?.(chunk);
|
|
7988
|
+
state.text += chunk;
|
|
7989
|
+
return;
|
|
7990
|
+
}
|
|
7991
|
+
state.lineBuffer += chunk;
|
|
7992
|
+
let newlineIndex = state.lineBuffer.indexOf("\n");
|
|
7993
|
+
while (newlineIndex !== -1) {
|
|
7994
|
+
const line = state.lineBuffer.slice(0, newlineIndex + 1);
|
|
7995
|
+
state.lineBuffer = state.lineBuffer.slice(newlineIndex + 1);
|
|
7996
|
+
this.emitAgentLine(line);
|
|
7997
|
+
newlineIndex = state.lineBuffer.indexOf("\n");
|
|
7998
|
+
}
|
|
7999
|
+
}
|
|
8000
|
+
/** Flush any trailing partial line through the harness-noise classifier. */
|
|
8001
|
+
flushHarnessLineBuffer() {
|
|
8002
|
+
const state = this.activePrompt;
|
|
8003
|
+
if (!state || !state.suppressNoise) {
|
|
8004
|
+
return;
|
|
8005
|
+
}
|
|
8006
|
+
const remaining = state.lineBuffer;
|
|
8007
|
+
state.lineBuffer = "";
|
|
8008
|
+
if (remaining.length > 0) {
|
|
8009
|
+
this.emitAgentLine(remaining);
|
|
8010
|
+
}
|
|
8011
|
+
}
|
|
8012
|
+
/**
|
|
8013
|
+
* Emit one classified line: a curated harness marker is recorded + dropped
|
|
8014
|
+
* (no delta, no accumulation); any other line streams + accumulates. `line`
|
|
8015
|
+
* may carry a trailing newline — classification runs on the content.
|
|
8016
|
+
*/
|
|
8017
|
+
emitAgentLine(line) {
|
|
8018
|
+
const state = this.activePrompt;
|
|
8019
|
+
if (!state) {
|
|
8020
|
+
return;
|
|
8021
|
+
}
|
|
8022
|
+
const content = line.endsWith("\n") ? line.slice(0, -1) : line;
|
|
8023
|
+
if (isHarnessNoiseLine(content)) {
|
|
8024
|
+
state.harnessMarkers.push(content.trim());
|
|
8025
|
+
return;
|
|
8026
|
+
}
|
|
8027
|
+
state.onDelta?.(line);
|
|
8028
|
+
state.text += line;
|
|
8029
|
+
}
|
|
7880
8030
|
handleNotification(message) {
|
|
7881
8031
|
if (message.method !== "session/update" || !this.activePrompt) {
|
|
7882
8032
|
return;
|
|
@@ -7891,8 +8041,7 @@ var AcpConnection = class {
|
|
|
7891
8041
|
}
|
|
7892
8042
|
const chunk = extractTextChunk(update);
|
|
7893
8043
|
if (update.sessionUpdate === "agent_message_chunk" && chunk) {
|
|
7894
|
-
this.
|
|
7895
|
-
this.activePrompt.text += chunk;
|
|
8044
|
+
this.appendAgentText(chunk);
|
|
7896
8045
|
}
|
|
7897
8046
|
if (update.sessionUpdate === "agent_thought_chunk" && chunk) {
|
|
7898
8047
|
this.activePrompt.warnings.push(chunk);
|
|
@@ -8079,7 +8228,10 @@ function createAcpTransportManager(options) {
|
|
|
8079
8228
|
cwd: runtime.cwd,
|
|
8080
8229
|
env: runtime.env,
|
|
8081
8230
|
timeoutMs: promptTimeout(options),
|
|
8082
|
-
permissionMode: options?.permissionMode ?? "deny-all"
|
|
8231
|
+
permissionMode: options?.permissionMode ?? "deny-all",
|
|
8232
|
+
selectModel: options?.selectModel ?? true,
|
|
8233
|
+
suppressHarnessNoise: options?.suppressHarnessNoise ?? true,
|
|
8234
|
+
failOnHarnessOnlyTurn: options?.failOnHarnessOnlyTurn ?? true
|
|
8083
8235
|
};
|
|
8084
8236
|
}
|
|
8085
8237
|
function getPool(key) {
|
|
@@ -8765,16 +8917,20 @@ function buildCliDiscoveryInvocation(route, command, options) {
|
|
|
8765
8917
|
parameterKeys
|
|
8766
8918
|
};
|
|
8767
8919
|
}
|
|
8768
|
-
function buildStaticCliCatalog(route) {
|
|
8920
|
+
function buildStaticCliCatalog(route, warning) {
|
|
8769
8921
|
const models = route.advertisedModels.map((id) => ({
|
|
8770
8922
|
modelId: id,
|
|
8771
8923
|
name: id
|
|
8772
8924
|
}));
|
|
8773
|
-
|
|
8925
|
+
const catalog = buildModelCatalog(
|
|
8774
8926
|
route,
|
|
8775
8927
|
models,
|
|
8776
8928
|
currentModelIdForRoute(route, route.advertisedModels)
|
|
8777
8929
|
);
|
|
8930
|
+
if (warning) {
|
|
8931
|
+
catalog.warnings = [...catalog.warnings ?? [], warning];
|
|
8932
|
+
}
|
|
8933
|
+
return catalog;
|
|
8778
8934
|
}
|
|
8779
8935
|
function statsToUsage(stats) {
|
|
8780
8936
|
if (!stats || typeof stats !== "object") {
|
|
@@ -8917,7 +9073,9 @@ function modelInfoFromRecord(record, selector) {
|
|
|
8917
9073
|
modelId,
|
|
8918
9074
|
name,
|
|
8919
9075
|
...description ? { description } : {},
|
|
8920
|
-
|
|
9076
|
+
// FT-002: a contextLength read from the list-command output is a discovered
|
|
9077
|
+
// value (consistent with the API parsers), so tag its provenance.
|
|
9078
|
+
...contextLength !== void 0 ? { contextLength, contextWindowSource: "discovered" } : {}
|
|
8921
9079
|
};
|
|
8922
9080
|
}
|
|
8923
9081
|
function parseCliModelList(stdout, parser, selector) {
|
|
@@ -9188,13 +9346,20 @@ function createCliTransportManager(options) {
|
|
|
9188
9346
|
throw error;
|
|
9189
9347
|
}
|
|
9190
9348
|
if (source.fallback === "static") {
|
|
9191
|
-
|
|
9349
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
9350
|
+
return buildStaticCliCatalog(
|
|
9351
|
+
context.route,
|
|
9352
|
+
`CLI discovery command for "${context.route.transport.id}" failed (${detail}); fell back to the static models[] catalog.`
|
|
9353
|
+
);
|
|
9192
9354
|
}
|
|
9193
9355
|
throw error;
|
|
9194
9356
|
}
|
|
9195
9357
|
if (models.length === 0) {
|
|
9196
9358
|
if (source.fallback === "static") {
|
|
9197
|
-
return buildStaticCliCatalog(
|
|
9359
|
+
return buildStaticCliCatalog(
|
|
9360
|
+
context.route,
|
|
9361
|
+
`CLI discovery command for "${context.route.transport.id}" returned no models; fell back to the static models[] catalog.`
|
|
9362
|
+
);
|
|
9198
9363
|
}
|
|
9199
9364
|
throw new AiConnectError(
|
|
9200
9365
|
"temporary_unavailable",
|