@vedmalex/ai-connect 0.7.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.
@@ -6547,6 +6547,20 @@ var TEXT_EXTENSIONS2 = /* @__PURE__ */ new Set([
6547
6547
  function isRecord2(value) {
6548
6548
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
6549
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
+ }
6550
6564
  function splitCommandLine(commandLine) {
6551
6565
  const matches = commandLine.match(/"[^"]*"|'[^']*'|[^\s]+/g) ?? [];
6552
6566
  const parts = matches.map((part) => {
@@ -7383,6 +7397,7 @@ function buildAcpLifecycle(route, authRequest, mode) {
7383
7397
  }
7384
7398
  steps.push("session/new");
7385
7399
  if (mode === "prompt") {
7400
+ steps.push("session/set_model");
7386
7401
  steps.push("session/prompt");
7387
7402
  }
7388
7403
  return { steps, keys };
@@ -7470,6 +7485,9 @@ var AcpConnection = class {
7470
7485
  env;
7471
7486
  timeoutMs;
7472
7487
  permissionMode;
7488
+ selectModel;
7489
+ suppressHarnessNoise;
7490
+ failOnHarnessOnlyTurn;
7473
7491
  command;
7474
7492
  args;
7475
7493
  child;
@@ -7491,6 +7509,9 @@ var AcpConnection = class {
7491
7509
  this.env = options.env;
7492
7510
  this.timeoutMs = options.timeoutMs;
7493
7511
  this.permissionMode = options.permissionMode;
7512
+ this.selectModel = options.selectModel;
7513
+ this.suppressHarnessNoise = options.suppressHarnessNoise;
7514
+ this.failOnHarnessOnlyTurn = options.failOnHarnessOnlyTurn;
7494
7515
  const resolved = splitCommandLine(options.commandLine);
7495
7516
  this.command = resolved.command;
7496
7517
  this.args = resolved.args;
@@ -7523,8 +7544,18 @@ var AcpConnection = class {
7523
7544
  usage: void 0,
7524
7545
  attachments: [],
7525
7546
  warnings: [],
7547
+ suppressNoise: this.suppressHarnessNoise,
7548
+ lineBuffer: "",
7549
+ harnessMarkers: [],
7526
7550
  ...onDelta ? { onDelta } : {}
7527
7551
  };
7552
+ await this.selectSessionModel(
7553
+ sessionId,
7554
+ context.route,
7555
+ session,
7556
+ transport.phases ?? [],
7557
+ this.activePrompt.warnings
7558
+ );
7528
7559
  let stopReason;
7529
7560
  const abortSignal = context.abort.signal;
7530
7561
  let abortListener;
@@ -7567,13 +7598,27 @@ var AcpConnection = class {
7567
7598
  }
7568
7599
  this.bumpIdleTimer();
7569
7600
  }
7601
+ this.flushHarnessLineBuffer();
7570
7602
  const stderrSlice = this.stderr.slice(stderrOffset).trim();
7571
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
+ }
7572
7609
  if (stderrSlice.length > 0) {
7573
7610
  warnings.push(stderrSlice);
7574
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
+ }
7575
7620
  const result = {
7576
- text: this.activePrompt.text.trim(),
7621
+ text,
7577
7622
  attachments: [...this.activePrompt.attachments],
7578
7623
  warnings,
7579
7624
  ...this.activePrompt.usage ? { usage: this.activePrompt.usage } : {},
@@ -7880,6 +7925,108 @@ var AcpConnection = class {
7880
7925
  }
7881
7926
  });
7882
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
+ }
7883
8030
  handleNotification(message) {
7884
8031
  if (message.method !== "session/update" || !this.activePrompt) {
7885
8032
  return;
@@ -7894,8 +8041,7 @@ var AcpConnection = class {
7894
8041
  }
7895
8042
  const chunk = extractTextChunk(update);
7896
8043
  if (update.sessionUpdate === "agent_message_chunk" && chunk) {
7897
- this.activePrompt.onDelta?.(chunk);
7898
- this.activePrompt.text += chunk;
8044
+ this.appendAgentText(chunk);
7899
8045
  }
7900
8046
  if (update.sessionUpdate === "agent_thought_chunk" && chunk) {
7901
8047
  this.activePrompt.warnings.push(chunk);
@@ -8082,7 +8228,10 @@ function createAcpTransportManager(options) {
8082
8228
  cwd: runtime.cwd,
8083
8229
  env: runtime.env,
8084
8230
  timeoutMs: promptTimeout(options),
8085
- 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
8086
8235
  };
8087
8236
  }
8088
8237
  function getPool(key) {