@get-bb/plugin-sdk 0.4.17 → 0.4.20

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.
@@ -6802,1162 +6802,1190 @@ function resolveAcpPermissionDecision(args) {
6802
6802
 
6803
6803
  // ../provider-bridge-acp/src/session-params.ts
6804
6804
  import path4 from "node:path";
6805
- function sanitizeAcpSkillDescription(description) {
6806
- const sanitized = description.replace(/[\r\n]+/gu, " ").replace(/\s+/gu, " ").replace(/[<>]/gu, "").trim();
6807
- return sanitized.length > 0 ? sanitized : "(description unavailable)";
6808
- }
6809
- function buildAcpSkillsInstructions(skillRoots) {
6810
- if (!skillRoots || skillRoots.length === 0) {
6811
- return void 0;
6805
+
6806
+ // ../provider-bridge-acp/src/bridge/model-catalog.ts
6807
+ var ACP_NATIVE_REASONING_EFFORTS = [
6808
+ {
6809
+ reasoningEffort: "medium",
6810
+ description: "Reasoning effort is managed by the connected ACP agent."
6812
6811
  }
6813
- const skillLines = skillRoots.flatMap((skillRoot) => {
6814
- return skillRoot.skills.map((skill) => {
6815
- const skillFilePath = path4.join(
6816
- skillRoot.skillDirectoryRootPath,
6817
- skill.name,
6818
- "SKILL.md"
6819
- );
6820
- return `- ${skill.name}: ${sanitizeAcpSkillDescription(skill.description)} (SKILL.md: ${skillFilePath})`;
6821
- });
6822
- });
6823
- if (skillLines.length === 0) {
6824
- return void 0;
6812
+ ];
6813
+ var MODEL_LINE_PATTERN = /^(\S+) - (.+)$/;
6814
+ var BARE_PROVIDER_MODEL_LINE_PATTERN = /^\S+\/\S+$/;
6815
+ var BULLETED_MODEL_LINE_PATTERN = /^[*-]\s+(\S+)(?:\s+\([^)]*\))?$/u;
6816
+ var EFFORT_TOKENS = [
6817
+ ["extra-high", "xhigh"],
6818
+ ["medium", "medium"],
6819
+ ["xhigh", "xhigh"],
6820
+ ["high", "high"],
6821
+ ["low", "low"],
6822
+ ["max", "max"],
6823
+ ["none", "none"]
6824
+ ];
6825
+ var FAST_TAIL = "-fast";
6826
+ var THINKING_TOKEN = "thinking";
6827
+ function parseAgentModelLines(stdout) {
6828
+ const models = [];
6829
+ for (const line of stdout.split("\n")) {
6830
+ const trimmed = line.trim();
6831
+ const match = MODEL_LINE_PATTERN.exec(trimmed);
6832
+ if (!match) {
6833
+ const bulletMatch = BULLETED_MODEL_LINE_PATTERN.exec(trimmed);
6834
+ if (bulletMatch) {
6835
+ const [, id2] = bulletMatch;
6836
+ models.push({ id: id2, displayName: id2 });
6837
+ continue;
6838
+ }
6839
+ if (BARE_PROVIDER_MODEL_LINE_PATTERN.test(trimmed)) {
6840
+ models.push({ id: trimmed, displayName: trimmed });
6841
+ }
6842
+ continue;
6843
+ }
6844
+ const [, id, displayName] = match;
6845
+ models.push({ id, displayName });
6825
6846
  }
6826
- return [
6827
- "bb skills are reusable instruction folders. When the current task matches a listed skill description, read that skill's SKILL.md at the absolute path before proceeding; you may read supporting files in the same skill directory that SKILL.md references. If a listed path does not exist, the list is stale and should be ignored.",
6828
- "",
6829
- "Available bb skills:",
6830
- ...skillLines
6831
- ].join("\n");
6847
+ return models;
6832
6848
  }
6833
- function buildAcpSessionInstructions(options) {
6834
- const baseInstructions = options.instructions?.trim();
6835
- const skillsInstructions = buildAcpSkillsInstructions(options.skillRoots);
6836
- const instructions = [baseInstructions, skillsInstructions].filter(
6837
- (value) => value !== void 0 && value.length > 0
6849
+ function findAcpModelConfigOption(configOptions) {
6850
+ const options = configOptions ?? [];
6851
+ return options.find((option) => option.category === "model") ?? options.find((option) => option.id === "model");
6852
+ }
6853
+ function findAcpThoughtLevelConfigOption(configOptions) {
6854
+ return (configOptions ?? []).find(
6855
+ (option) => option.category === "thought_level"
6838
6856
  );
6839
- return instructions.length > 0 ? instructions.join("\n\n") : void 0;
6840
6857
  }
6841
- function launchEnvVars(launchSpec) {
6842
- return Object.keys(launchSpec.env).length > 0 ? { envVars: launchSpec.env } : {};
6858
+ var ACP_NATIVE_REASONING_LEVEL_BY_VALUE = {
6859
+ none: "none",
6860
+ minimal: "low",
6861
+ low: "low",
6862
+ medium: "medium",
6863
+ high: "high",
6864
+ xhigh: "xhigh",
6865
+ ultracode: "ultracode",
6866
+ max: "max",
6867
+ ultra: "ultra"
6868
+ };
6869
+ var ACP_NATIVE_REASONING_VALUE_CANDIDATES_BY_LEVEL = {
6870
+ none: ["none"],
6871
+ low: ["low", "minimal"],
6872
+ medium: ["medium"],
6873
+ high: ["high"],
6874
+ xhigh: ["xhigh"],
6875
+ ultracode: ["ultracode", "xhigh"],
6876
+ max: ["max", "xhigh"],
6877
+ ultra: ["ultra", "max"]
6878
+ };
6879
+ function acpNativeValueToReasoningLevel(value) {
6880
+ return value === void 0 ? void 0 : ACP_NATIVE_REASONING_LEVEL_BY_VALUE[value];
6843
6881
  }
6844
- function buildAcpModelListCommand(launchSpec) {
6845
- if (!launchSpec.modelCli || launchSpec.modelCli.listArgs.length === 0) {
6882
+ function acpNativeReasoningLevelToValue(level, thoughtLevelOption) {
6883
+ const candidateValues = ACP_NATIVE_REASONING_VALUE_CANDIDATES_BY_LEVEL[level];
6884
+ if (candidateValues === void 0) {
6846
6885
  return void 0;
6847
6886
  }
6848
- return {
6849
- command: launchSpec.command,
6850
- args: [...launchSpec.modelCli.listArgs],
6851
- ...launchSpec.cwd !== void 0 ? { cwd: launchSpec.cwd } : {},
6852
- ...launchEnvVars(launchSpec)
6853
- };
6887
+ const values = new Set(
6888
+ (thoughtLevelOption.options ?? []).map((o) => o.value)
6889
+ );
6890
+ return candidateValues.find((value) => values.has(value));
6854
6891
  }
6855
- function buildAcpModelDiscoveryAgentCommand(launchSpec) {
6856
- if (buildAcpModelListCommand(launchSpec) !== void 0) {
6857
- return void 0;
6892
+ function buildAcpNativeReasoningSupport(thoughtLevelOption) {
6893
+ const options = thoughtLevelOption?.options ?? [];
6894
+ const seen = /* @__PURE__ */ new Set();
6895
+ const matchedValueByLevel = /* @__PURE__ */ new Map();
6896
+ const supportedReasoningEfforts = [];
6897
+ for (const option of options) {
6898
+ const level = acpNativeValueToReasoningLevel(option.value);
6899
+ if (level === void 0) {
6900
+ continue;
6901
+ }
6902
+ if (seen.has(level)) {
6903
+ const previousValue = matchedValueByLevel.get(level);
6904
+ if (previousValue !== level && option.value === level) {
6905
+ const effort = supportedReasoningEfforts.find(
6906
+ (candidate) => candidate.reasoningEffort === level
6907
+ );
6908
+ if (effort) {
6909
+ effort.description = option.name ?? option.value;
6910
+ }
6911
+ matchedValueByLevel.set(level, option.value);
6912
+ }
6913
+ continue;
6914
+ }
6915
+ seen.add(level);
6916
+ matchedValueByLevel.set(level, option.value);
6917
+ supportedReasoningEfforts.push({
6918
+ reasoningEffort: level,
6919
+ description: option.name ?? option.value
6920
+ });
6858
6921
  }
6859
- return {
6860
- command: launchSpec.command,
6861
- args: [...launchSpec.args],
6862
- ...launchSpec.cwd !== void 0 ? { cwd: launchSpec.cwd } : {},
6863
- ...launchEnvVars(launchSpec)
6864
- };
6865
- }
6866
- function buildAcpModelListParams(launchSpec) {
6867
- if (launchSpec === null) {
6868
- return { primaryModels: [] };
6922
+ supportedReasoningEfforts.sort(
6923
+ (a, b) => reasoningLevelValues.indexOf(a.reasoningEffort) - reasoningLevelValues.indexOf(b.reasoningEffort)
6924
+ );
6925
+ if (supportedReasoningEfforts.length === 0) {
6926
+ return {
6927
+ // An omitted option preserves the legacy agent-managed fallback. A
6928
+ // declared option is authoritative, even when bb cannot map its values.
6929
+ supportedReasoningEfforts: thoughtLevelOption === void 0 ? ACP_NATIVE_REASONING_EFFORTS : [],
6930
+ defaultReasoningEffort: "medium"
6931
+ };
6869
6932
  }
6870
- const listCommand = buildAcpModelListCommand(launchSpec);
6871
- const agent = buildAcpModelDiscoveryAgentCommand(launchSpec);
6933
+ const currentLevel = acpNativeValueToReasoningLevel(
6934
+ thoughtLevelOption?.currentValue
6935
+ );
6936
+ const supportedLevels = supportedReasoningEfforts.map(
6937
+ (effort) => effort.reasoningEffort
6938
+ );
6872
6939
  return {
6873
- ...listCommand !== void 0 ? { listCommand } : {},
6874
- ...agent !== void 0 ? { agent } : {},
6875
- primaryModels: [...launchSpec.modelCli?.primaryModels ?? []],
6876
- ...launchSpec.reasoningCli !== void 0 ? { reasoningCli: launchSpec.reasoningCli } : {},
6877
- ...launchSpec.nativeReasoning !== void 0 ? { nativeReasoning: launchSpec.nativeReasoning } : {}
6940
+ supportedReasoningEfforts,
6941
+ defaultReasoningEffort: currentLevel !== void 0 && supportedLevels.includes(currentLevel) ? currentLevel : supportedReasoningEfforts[0].reasoningEffort
6878
6942
  };
6879
6943
  }
6880
- function buildAcpModelSelectionParam(launchSpec, options) {
6881
- const model = options.model;
6882
- const listCommand = buildAcpModelListCommand(launchSpec);
6883
- if (!model || model === ACP_DEFAULT_MODEL_ID) {
6884
- return {};
6944
+ function buildModelCatalogFromConfigOptions(modelOption, reasoningByModel) {
6945
+ const options = modelOption?.options ?? [];
6946
+ if (options.length === 0) {
6947
+ return [];
6885
6948
  }
6886
- if (!listCommand || !launchSpec.modelCli?.selectFlag) {
6949
+ const currentValue = modelOption?.currentValue;
6950
+ const models = options.map((option, index) => {
6951
+ const isDefault = currentValue !== void 0 ? option.value === currentValue : index === 0;
6952
+ const reasoning = reasoningByModel?.get(option.value) ?? {
6953
+ supportedReasoningEfforts: ACP_NATIVE_REASONING_EFFORTS,
6954
+ defaultReasoningEffort: "medium"
6955
+ };
6887
6956
  return {
6888
- modelSelection: {
6889
- modelId: model,
6890
- ...options.reasoningLevel !== void 0 ? { reasoningLevel: options.reasoningLevel } : {}
6891
- }
6957
+ id: option.value,
6958
+ model: option.value,
6959
+ displayName: option.name ?? option.value,
6960
+ description: "",
6961
+ supportedReasoningEfforts: reasoning.supportedReasoningEfforts,
6962
+ defaultReasoningEffort: reasoning.defaultReasoningEffort,
6963
+ isDefault
6892
6964
  };
6893
- }
6894
- return {
6895
- modelSelection: {
6896
- listCommand,
6897
- selectFlag: launchSpec.modelCli.selectFlag,
6898
- model,
6899
- ...options.reasoningLevel !== void 0 ? { reasoningLevel: options.reasoningLevel } : {},
6900
- // Only "fast" changes resolution; "default" is the catalog's normal id.
6901
- ...options.serviceTier === "fast" ? { serviceTier: options.serviceTier } : {}
6902
- }
6903
- };
6965
+ });
6966
+ return models.some((model) => model.isDefault) ? models : models.map(
6967
+ (model, index) => index === 0 ? { ...model, isDefault: true } : model
6968
+ );
6904
6969
  }
6905
- function buildAcpSessionParams(args) {
6906
- const { options, launchSpec } = args;
6907
- const instructions = buildAcpSessionInstructions(options);
6908
- const cwd = launchSpec.cwd ?? args.cwd;
6909
- const envVars = {
6910
- ...launchSpec.env,
6911
- ...options.envVars ?? {}
6912
- };
6913
- if (options.permissionMode === "auto") {
6914
- throw new Error(
6915
- `Provider "${args.providerLabel}" does not support permission mode "auto".`
6916
- );
6970
+ function buildModelCatalogFromSessionModels(sessionModels) {
6971
+ const availableModels = sessionModels?.availableModels ?? [];
6972
+ if (availableModels.length === 0) {
6973
+ return [];
6917
6974
  }
6918
- return {
6919
- threadId: args.threadId,
6920
- cwd,
6921
- agent: {
6922
- command: launchSpec.command,
6923
- args: [...launchSpec.args]
6924
- },
6925
- ...args.dialectId === void 0 ? {} : { dialectId: args.dialectId },
6926
- ...buildAcpModelSelectionParam(launchSpec, options),
6927
- ...launchSpec.reasoningCli !== void 0 ? { reasoningCli: launchSpec.reasoningCli } : {},
6928
- ...launchSpec.nativeReasoning !== void 0 ? { nativeReasoning: launchSpec.nativeReasoning } : {},
6929
- ...launchSpec.permissionCli !== void 0 ? { permissionCli: launchSpec.permissionCli } : {},
6930
- ...launchSpec.reasoningCli !== void 0 && options.reasoningLevel !== void 0 ? { launchReasoningLevel: options.reasoningLevel } : {},
6931
- permissionMode: options.permissionMode,
6932
- workspaceWriteRoots: [cwd, ...args.additionalWorkspaceWriteRoots],
6933
- ...Object.keys(envVars).length > 0 ? { envVars } : {},
6934
- ...instructions ? { instructions } : {},
6935
- ...args.dynamicTools && args.dynamicTools.length > 0 ? { dynamicTools: args.dynamicTools } : {}
6936
- };
6937
- }
6938
-
6939
- // ../provider-bridge-acp/src/bridge/agent-connection.ts
6940
- import { spawn } from "node:child_process";
6941
- import { createInterface } from "node:readline";
6942
- var STDERR_TAIL_MAX_CHUNKS = 40;
6943
- var CLOSED_STDIN_ERROR_CODES = /* @__PURE__ */ new Set(["EPIPE", "ERR_STREAM_DESTROYED"]);
6944
- var AcpAgentExitedError = class extends Error {
6945
- constructor(message) {
6946
- super(message);
6947
- this.name = "AcpAgentExitedError";
6948
- }
6949
- };
6950
- var AcpAgentResponseError = class extends Error {
6951
- code;
6952
- constructor(message, code) {
6953
- super(message);
6954
- this.name = "AcpAgentResponseError";
6955
- this.code = code;
6956
- }
6957
- };
6958
- function isClosedAgentStdinError(error) {
6959
- return "code" in error && typeof error.code === "string" && CLOSED_STDIN_ERROR_CODES.has(error.code);
6960
- }
6961
- function formatAgentError(error) {
6962
- const message = error.message ?? `ACP agent returned error code ${error.code ?? "unknown"}`;
6963
- const details = formatAgentErrorData(error.data);
6964
- return details === void 0 ? message : `${message}: ${details}`;
6975
+ const currentModelId = sessionModels?.currentModelId;
6976
+ const models = availableModels.map((model, index) => {
6977
+ const isDefault = currentModelId !== void 0 ? model.modelId === currentModelId : index === 0;
6978
+ return {
6979
+ id: model.modelId,
6980
+ model: model.modelId,
6981
+ displayName: model.name ?? model.modelId,
6982
+ description: model.description ?? "",
6983
+ supportedReasoningEfforts: ACP_NATIVE_REASONING_EFFORTS,
6984
+ defaultReasoningEffort: "medium",
6985
+ isDefault
6986
+ };
6987
+ });
6988
+ return models.some((model) => model.isDefault) ? models : models.map(
6989
+ (model, index) => index === 0 ? { ...model, isDefault: true } : model
6990
+ );
6965
6991
  }
6966
- function formatAgentErrorData(data) {
6967
- if (data === void 0 || data === null) {
6968
- return void 0;
6969
- }
6970
- if (typeof data === "string") {
6971
- return data.trim() === "" ? void 0 : data;
6992
+ function splitVariant(id) {
6993
+ let rest = id;
6994
+ let fast = false;
6995
+ if (rest.endsWith(FAST_TAIL)) {
6996
+ fast = true;
6997
+ rest = rest.slice(0, -FAST_TAIL.length);
6972
6998
  }
6973
- if (typeof data === "object" && "details" in data && typeof data.details === "string" && data.details.trim() !== "") {
6974
- return data.details;
6999
+ let thinking = false;
7000
+ if (rest.endsWith(`-${THINKING_TOKEN}`)) {
7001
+ thinking = true;
7002
+ rest = rest.slice(0, -(THINKING_TOKEN.length + 1));
7003
+ } else if (rest.includes(`-${THINKING_TOKEN}-`)) {
7004
+ thinking = true;
7005
+ rest = rest.replace(`-${THINKING_TOKEN}-`, "-");
6975
7006
  }
6976
- try {
6977
- return JSON.stringify(data);
6978
- } catch {
6979
- return void 0;
7007
+ for (const [token, effort] of EFFORT_TOKENS) {
7008
+ if (rest.endsWith(`-${token}`)) {
7009
+ return {
7010
+ familyKey: rest.slice(0, -(token.length + 1)),
7011
+ effort,
7012
+ effortToken: token,
7013
+ fast,
7014
+ thinking
7015
+ };
7016
+ }
6980
7017
  }
7018
+ return {
7019
+ familyKey: rest,
7020
+ effort: "medium",
7021
+ effortToken: void 0,
7022
+ fast,
7023
+ thinking
7024
+ };
6981
7025
  }
6982
- function parseAgentLine(line) {
6983
- const trimmed = line.trim();
6984
- if (!trimmed) {
6985
- return null;
7026
+ function agentModelFamilyId(id) {
7027
+ return splitVariant(id).familyKey;
7028
+ }
7029
+ var EFFORT_DISPLAY_WORDS = {
7030
+ "extra-high": "Extra High",
7031
+ medium: "Medium",
7032
+ xhigh: "Extra High",
7033
+ high: "High",
7034
+ low: "Low",
7035
+ max: "Max",
7036
+ ultra: "Ultra",
7037
+ none: "None"
7038
+ };
7039
+ function familyDisplayName(displayName, effortToken) {
7040
+ const word = effortToken ? EFFORT_DISPLAY_WORDS[effortToken] : void 0;
7041
+ if (!word) {
7042
+ return cleanDisplayName(displayName);
6986
7043
  }
6987
- let parsed;
6988
- try {
6989
- parsed = JSON.parse(trimmed);
6990
- } catch {
6991
- return null;
7044
+ return cleanDisplayName(
7045
+ displayName.replace(new RegExp(`(^|\\s)${word}(?=\\s|$)`), "$1")
7046
+ );
7047
+ }
7048
+ function cleanDisplayName(name) {
7049
+ return name.replace(/\s*\((?:NO ZDR|default|current)\)/gi, "").replace(/(^|\s)(?:1M|Thinking)(?=\s|$)/g, "$1").replace(/\s{2,}/g, " ").trim();
7050
+ }
7051
+ function buildAgentModelCatalog(rawModels) {
7052
+ const families = /* @__PURE__ */ new Map();
7053
+ for (const raw of rawModels) {
7054
+ const { familyKey, effort, effortToken, fast, thinking } = splitVariant(
7055
+ raw.id
7056
+ );
7057
+ const members = families.get(familyKey) ?? [];
7058
+ members.push({ ...raw, effort, effortToken, fast, thinking });
7059
+ families.set(familyKey, members);
6992
7060
  }
6993
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
7061
+ if (families.size === 0) {
6994
7062
  return null;
6995
7063
  }
6996
- return parsed;
6997
- }
6998
- function createAcpAgentConnection(options) {
6999
- const child = spawn(options.command, options.args, {
7000
- cwd: options.cwd,
7001
- env: options.env,
7002
- stdio: ["pipe", "pipe", "pipe"]
7003
- });
7004
- experimental_recordProviderChildIo(child, {
7005
- threadId: options.recordThreadId
7006
- });
7007
- const pending = /* @__PURE__ */ new Map();
7008
- const stderrChunks = [];
7009
- let nextRequestId = 1;
7010
- let exited = false;
7011
- function rejectAllPending(error) {
7012
- for (const [, request] of pending) {
7013
- request.reject(error);
7014
- }
7015
- pending.clear();
7016
- }
7017
- function closeForAgentStdin(error) {
7018
- if (exited) {
7019
- return;
7064
+ const models = [];
7065
+ const variantsByFamilyId = /* @__PURE__ */ new Map();
7066
+ const defaultEffortByFamilyId = /* @__PURE__ */ new Map();
7067
+ for (const members of families.values()) {
7068
+ const hasThinking = members.some((m) => m.thinking);
7069
+ const leveled = members.map((member) => ({
7070
+ member,
7071
+ level: member.thinking ? member.effort : hasThinking ? "none" : member.effort
7072
+ }));
7073
+ const byLevel = /* @__PURE__ */ new Map();
7074
+ const repEffortByCell = /* @__PURE__ */ new Map();
7075
+ for (const { member, level } of leveled) {
7076
+ const slot = member.fast ? "fast" : "normal";
7077
+ const tier = byLevel.get(level) ?? {};
7078
+ const cellKey = `${level}:${slot}`;
7079
+ const upgradesNoneRep = level === "none" && member.effort === "medium" && repEffortByCell.get(cellKey) !== "medium";
7080
+ if (tier[slot] === void 0 || upgradesNoneRep) {
7081
+ tier[slot] = member.id;
7082
+ repEffortByCell.set(cellKey, member.effort);
7083
+ byLevel.set(level, tier);
7084
+ }
7020
7085
  }
7021
- exited = true;
7022
- const code = "code" in error && typeof error.code === "string" ? ` (${error.code})` : "";
7023
- const detail = `stdin closed${code}: ${error.message}`;
7024
- rejectAllPending(
7025
- new AcpAgentExitedError(`ACP agent "${options.command}" ${detail}`)
7086
+ const nonFast = leveled.filter((entry) => !entry.member.fast);
7087
+ const pool = nonFast.length > 0 ? nonFast : leveled;
7088
+ const defaultEntry = pool.find((entry) => entry.level === "medium") ?? pool.find((entry) => entry.level !== "none") ?? pool[0];
7089
+ const defaultVariant = defaultEntry.member;
7090
+ const levelsInLadderOrder = [...byLevel.keys()].sort(
7091
+ (a, b) => reasoningLevelValues.indexOf(a) - reasoningLevelValues.indexOf(b)
7026
7092
  );
7027
- child.kill("SIGKILL");
7028
- const stderrTail = [...stderrChunks, detail].join("\n");
7029
- options.onExit({ code: null, signal: null, stderrTail });
7030
- }
7031
- function writeLine(message) {
7032
- const stdin = child.stdin;
7033
- if (!stdin || stdin.destroyed || !stdin.writable) {
7034
- closeForAgentStdin(new Error("stdin is not writable"));
7035
- return;
7093
+ const nameByLevel = /* @__PURE__ */ new Map();
7094
+ for (const { member, level } of leveled) {
7095
+ if (!nameByLevel.has(level)) {
7096
+ nameByLevel.set(level, member.displayName);
7097
+ }
7036
7098
  }
7037
- stdin.write(JSON.stringify(message) + "\n");
7099
+ models.push({
7100
+ id: defaultVariant.id,
7101
+ model: defaultVariant.id,
7102
+ displayName: familyDisplayName(
7103
+ defaultVariant.displayName,
7104
+ defaultVariant.effortToken
7105
+ ),
7106
+ description: "",
7107
+ supportedReasoningEfforts: levelsInLadderOrder.map((level) => ({
7108
+ reasoningEffort: level,
7109
+ description: nameByLevel.get(level) ?? ""
7110
+ })),
7111
+ defaultReasoningEffort: defaultEntry.level,
7112
+ // The agent lists its default model first.
7113
+ isDefault: models.length === 0
7114
+ });
7115
+ variantsByFamilyId.set(defaultVariant.id, byLevel);
7116
+ defaultEffortByFamilyId.set(defaultVariant.id, defaultEntry.level);
7038
7117
  }
7039
- child.stdin?.on("error", (error) => {
7040
- if (!isClosedAgentStdinError(error)) {
7041
- throw error;
7118
+ return {
7119
+ models,
7120
+ resolveVariant({ model, reasoningLevel, serviceTier }) {
7121
+ const byLevel = variantsByFamilyId.get(model);
7122
+ if (!byLevel) {
7123
+ return void 0;
7124
+ }
7125
+ const level = reasoningLevel ?? defaultEffortByFamilyId.get(model);
7126
+ const tier = level === void 0 ? void 0 : byLevel.get(level);
7127
+ if (!tier) {
7128
+ return void 0;
7129
+ }
7130
+ if (serviceTier === "fast" && tier.fast !== void 0) {
7131
+ return tier.fast;
7132
+ }
7133
+ return tier.normal ?? tier.fast;
7042
7134
  }
7043
- closeForAgentStdin(error);
7135
+ };
7136
+ }
7137
+ function splitPrimaryModels(catalogModels, primaryModels) {
7138
+ const primaryIds = new Set(primaryModels);
7139
+ const modelsById = new Map(catalogModels.map((model) => [model.id, model]));
7140
+ const models = primaryModels.flatMap((id) => {
7141
+ const model = modelsById.get(id);
7142
+ return model ? [model] : [];
7044
7143
  });
7045
- if (child.stdout) {
7046
- const stdoutLines = createInterface({
7047
- input: child.stdout,
7048
- terminal: false
7049
- });
7050
- stdoutLines.on("line", (line) => {
7051
- const message = parseAgentLine(line);
7052
- if (!message) {
7053
- return;
7054
- }
7055
- const id = message.id;
7056
- if ((typeof id === "string" || typeof id === "number") && message.method === void 0) {
7057
- const numericId = typeof id === "number" ? id : Number(id);
7058
- const request = pending.get(numericId);
7059
- if (!request) {
7060
- return;
7061
- }
7062
- pending.delete(numericId);
7063
- if (message.error) {
7064
- request.reject(
7065
- new AcpAgentResponseError(
7066
- formatAgentError(message.error),
7067
- message.error.code
7068
- )
7069
- );
7070
- } else {
7071
- request.resolve(message.result);
7072
- }
7073
- return;
7074
- }
7075
- if (typeof message.method !== "string") {
7076
- return;
7077
- }
7078
- if (typeof id === "string" || typeof id === "number") {
7079
- let settled = false;
7080
- options.onRequest(message.method, message.params, {
7081
- result(value) {
7082
- if (settled) return;
7083
- settled = true;
7084
- writeLine({ jsonrpc: "2.0", id, result: value ?? null });
7085
- },
7086
- error(code, errorMessage) {
7087
- if (settled) return;
7088
- settled = true;
7089
- writeLine({
7090
- jsonrpc: "2.0",
7091
- id,
7092
- error: { code, message: errorMessage }
7093
- });
7094
- }
7095
- });
7096
- return;
7097
- }
7098
- options.onNotification(message.method, message.params);
7099
- });
7100
- }
7101
- if (child.stderr) {
7102
- const stderrLines = createInterface({
7103
- input: child.stderr,
7104
- terminal: false
7105
- });
7106
- stderrLines.on("line", (line) => {
7107
- stderrChunks.push(line);
7108
- if (stderrChunks.length > STDERR_TAIL_MAX_CHUNKS) {
7109
- stderrChunks.shift();
7110
- }
7111
- });
7144
+ if (models.length === 0) {
7145
+ return { models: [...catalogModels], selectedOnlyModels: [] };
7112
7146
  }
7113
- child.on("error", (error) => {
7114
- if (exited) {
7115
- return;
7116
- }
7117
- exited = true;
7118
- rejectAllPending(
7119
- new AcpAgentExitedError(
7120
- `Failed to launch ACP agent "${options.command}": ${error.message}`
7121
- )
7122
- );
7123
- options.onExit({ code: null, signal: null, stderrTail: error.message });
7124
- });
7125
- child.on("exit", (code, signal) => {
7126
- if (exited) {
7127
- return;
7128
- }
7129
- exited = true;
7130
- const stderrTail = stderrChunks.join("\n");
7131
- rejectAllPending(
7132
- new AcpAgentExitedError(
7133
- `ACP agent "${options.command}" exited (code ${code ?? "null"}, signal ${signal ?? "null"})${stderrTail ? `: ${stderrTail}` : ""}`
7147
+ const selectedOnlyModels = catalogModels.filter(
7148
+ (model) => !primaryIds.has(model.id)
7149
+ );
7150
+ if (models.some((model) => model.isDefault)) {
7151
+ return {
7152
+ models,
7153
+ selectedOnlyModels: selectedOnlyModels.map(
7154
+ (model) => model.isDefault ? { ...model, isDefault: false } : model
7134
7155
  )
7135
- );
7136
- options.onExit({ code, signal, stderrTail });
7137
- });
7156
+ };
7157
+ }
7138
7158
  return {
7139
- get exited() {
7140
- return exited;
7141
- },
7142
- request({ method, params, resultSchema }) {
7143
- if (exited) {
7144
- return Promise.reject(
7145
- new AcpAgentExitedError(
7146
- `ACP agent "${options.command}" is not running`
7147
- )
7148
- );
7149
- }
7150
- const id = nextRequestId;
7151
- nextRequestId += 1;
7152
- return new Promise((resolve4, reject) => {
7153
- pending.set(id, {
7154
- resolve: (value) => {
7155
- const parsed = resultSchema.safeParse(value);
7156
- if (parsed.success) {
7157
- resolve4(parsed.data);
7158
- } else {
7159
- reject(
7160
- new Error(
7161
- `ACP agent returned an unexpected ${method} result: ${parsed.error.message}`
7162
- )
7163
- );
7164
- }
7165
- },
7166
- reject
7167
- });
7168
- writeLine({ jsonrpc: "2.0", id, method, params });
7169
- });
7170
- },
7171
- notify(method, params) {
7172
- if (exited) {
7173
- return;
7174
- }
7175
- writeLine({ jsonrpc: "2.0", method, params });
7176
- },
7177
- kill() {
7178
- if (exited) {
7179
- return;
7180
- }
7181
- child.kill("SIGTERM");
7182
- }
7159
+ models: models.map(
7160
+ (model, index) => index === 0 ? { ...model, isDefault: true } : model
7161
+ ),
7162
+ selectedOnlyModels: selectedOnlyModels.map(
7163
+ (model) => model.isDefault ? { ...model, isDefault: false } : model
7164
+ )
7183
7165
  };
7184
7166
  }
7185
7167
 
7186
- // ../provider-bridge-acp/src/bridge/cursor-mcp-approval.ts
7187
- import { execFile as execFile3 } from "node:child_process";
7188
- import { createHash, randomBytes } from "node:crypto";
7189
- import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
7190
- import { homedir } from "node:os";
7191
- import { basename as basename2, dirname, join as join2, resolve as resolve2 } from "node:path";
7192
-
7193
- // ../provider-bridge-acp/src/bridge/tool-proxy-mcp.ts
7194
- import { createConnection } from "node:net";
7195
- import { createInterface as createInterface2 } from "node:readline";
7196
- import { z as z38 } from "zod";
7197
- var ACP_BRIDGE_MCP_SERVER_NAME = "bb-bridge";
7198
- var ENV_HOST = "BB_ACP_DYNAMIC_TOOL_HOST";
7199
- var ENV_PORT = "BB_ACP_DYNAMIC_TOOL_PORT";
7200
- var ENV_TOKEN = "BB_ACP_DYNAMIC_TOOL_TOKEN";
7201
- var ENV_THREAD_ID = "BB_ACP_DYNAMIC_TOOL_THREAD_ID";
7202
- var ENV_TOOLS = "BB_ACP_DYNAMIC_TOOLS";
7203
- var ENV_PROGRESS_INTERVAL_MS = "BB_ACP_DYNAMIC_TOOL_PROGRESS_INTERVAL_MS";
7204
- var bridgeToolCallResponseSchema = z38.union([
7205
- z38.object({
7206
- ok: z38.literal(true),
7207
- content: z38.string(),
7208
- contentBlocks: z38.array(
7209
- z38.discriminatedUnion("type", [
7210
- z38.object({ type: z38.literal("text"), text: z38.string() }),
7211
- z38.object({
7212
- type: z38.literal("image"),
7213
- data: z38.string(),
7214
- mimeType: z38.string()
7215
- })
7216
- ])
7217
- ).optional(),
7218
- // The initialized response and older text-only responses omit images.
7219
- // Parsing them as an empty list keeps the re-executed packaged artifact
7220
- // compatible with that legacy socket shape.
7221
- images: z38.array(z38.object({ data: z38.string(), mimeType: z38.string() })).default([]),
7222
- isError: z38.boolean().optional()
7223
- }),
7224
- z38.object({ ok: z38.literal(false), error: z38.string() })
7225
- ]);
7226
- var nextMcpToolCallId = 0;
7227
- var TOOL_CALL_PROGRESS_INTERVAL_MS = 15e3;
7228
- function buildAcpMcpServerConfig(args) {
7229
- return {
7230
- name: ACP_BRIDGE_MCP_SERVER_NAME,
7231
- command: args.command,
7232
- args: args.bridgeArgs,
7233
- env: [
7234
- ...args.runtimeEnv,
7235
- { name: ENV_HOST, value: args.host },
7236
- { name: ENV_PORT, value: String(args.port) },
7237
- { name: ENV_TOKEN, value: args.token },
7238
- { name: ENV_THREAD_ID, value: args.threadId },
7239
- { name: ENV_TOOLS, value: JSON.stringify(args.dynamicTools) }
7240
- ]
7241
- };
7168
+ // ../provider-bridge-acp/src/session-params.ts
7169
+ function sanitizeAcpSkillDescription(description) {
7170
+ const sanitized = description.replace(/[\r\n]+/gu, " ").replace(/\s+/gu, " ").replace(/[<>]/gu, "").trim();
7171
+ return sanitized.length > 0 ? sanitized : "(description unavailable)";
7242
7172
  }
7243
- function readEnvironment() {
7244
- const port = Number(process.env[ENV_PORT]);
7245
- if (!Number.isInteger(port) || port <= 0) {
7246
- throw new Error(`${ENV_PORT} must be a positive integer`);
7173
+ function buildAcpSkillsInstructions(skillRoots) {
7174
+ if (!skillRoots || skillRoots.length === 0) {
7175
+ return void 0;
7247
7176
  }
7248
- const host = process.env[ENV_HOST];
7249
- const token = process.env[ENV_TOKEN];
7250
- const threadId = process.env[ENV_THREAD_ID];
7251
- const toolsJson = process.env[ENV_TOOLS];
7252
- if (!host || !token || !threadId || !toolsJson) {
7253
- throw new Error("Missing ACP dynamic tool MCP server environment");
7177
+ const skillLines = skillRoots.flatMap((skillRoot) => {
7178
+ return skillRoot.skills.map((skill) => {
7179
+ const skillFilePath = path4.join(
7180
+ skillRoot.skillDirectoryRootPath,
7181
+ skill.name,
7182
+ "SKILL.md"
7183
+ );
7184
+ return `- ${skill.name}: ${sanitizeAcpSkillDescription(skill.description)} (SKILL.md: ${skillFilePath})`;
7185
+ });
7186
+ });
7187
+ if (skillLines.length === 0) {
7188
+ return void 0;
7254
7189
  }
7255
- const parsedTools = JSON.parse(toolsJson);
7256
- const tools = dynamicToolSchema.array().parse(parsedTools);
7257
- const rawProgressInterval = process.env[ENV_PROGRESS_INTERVAL_MS];
7258
- const progressIntervalMs = rawProgressInterval !== void 0 && Number(rawProgressInterval) > 0 ? Number(rawProgressInterval) : void 0;
7259
- return {
7260
- host,
7261
- port,
7262
- progressIntervalMs,
7263
- threadId,
7264
- token,
7265
- tools
7266
- };
7190
+ return [
7191
+ "bb skills are reusable instruction folders. When the current task matches a listed skill description, read that skill's SKILL.md at the absolute path before proceeding; you may read supporting files in the same skill directory that SKILL.md references. If a listed path does not exist, the list is stale and should be ignored.",
7192
+ "",
7193
+ "Available bb skills:",
7194
+ ...skillLines
7195
+ ].join("\n");
7267
7196
  }
7268
- function writeJson(message) {
7269
- process.stdout.write(`${JSON.stringify(message)}
7270
- `);
7197
+ function buildAcpSessionInstructions(options) {
7198
+ const baseInstructions = options.instructions?.trim();
7199
+ const skillsInstructions = buildAcpSkillsInstructions(options.skillRoots);
7200
+ const instructions = [baseInstructions, skillsInstructions].filter(
7201
+ (value) => value !== void 0 && value.length > 0
7202
+ );
7203
+ return instructions.length > 0 ? instructions.join("\n\n") : void 0;
7271
7204
  }
7272
- function writeResult(id, result) {
7273
- writeJson({ jsonrpc: "2.0", id, result });
7274
- }
7275
- function writeError(id, code, message) {
7276
- writeJson({ jsonrpc: "2.0", id, error: { code, message } });
7277
- }
7278
- function mcpToolCallId(toolName) {
7279
- nextMcpToolCallId += 1;
7280
- return `acp-mcp-${toolName}-${Date.now()}-${nextMcpToolCallId}`;
7205
+ function launchEnvVars(launchSpec) {
7206
+ return Object.keys(launchSpec.env).length > 0 ? { envVars: launchSpec.env } : {};
7281
7207
  }
7282
- function callBridge(env, request) {
7283
- return new Promise((resolve4, reject) => {
7284
- const socket = createConnection({ host: env.host, port: env.port });
7285
- let buffer = "";
7286
- socket.setEncoding("utf8");
7287
- socket.on("connect", () => {
7288
- const payload = {
7289
- ...request,
7290
- threadId: env.threadId,
7291
- token: env.token
7292
- };
7293
- socket.write(`${JSON.stringify(payload)}
7294
- `);
7295
- });
7296
- socket.on("data", (chunk) => {
7297
- buffer += chunk;
7298
- const newlineIndex = buffer.indexOf("\n");
7299
- if (newlineIndex === -1) {
7300
- return;
7301
- }
7302
- const line = buffer.slice(0, newlineIndex);
7303
- socket.end();
7304
- try {
7305
- resolve4(bridgeToolCallResponseSchema.parse(JSON.parse(line)));
7306
- } catch (error) {
7307
- reject(error);
7308
- }
7309
- });
7310
- socket.on("error", reject);
7311
- socket.on("end", () => {
7312
- if (!buffer.includes("\n")) {
7313
- reject(new Error("ACP dynamic tool bridge closed without a response"));
7314
- }
7315
- });
7316
- });
7208
+ function buildAcpModelListCommand(launchSpec) {
7209
+ if (!launchSpec.modelCli || launchSpec.modelCli.listArgs.length === 0) {
7210
+ return void 0;
7211
+ }
7212
+ return {
7213
+ command: launchSpec.command,
7214
+ args: [...launchSpec.modelCli.listArgs],
7215
+ ...launchSpec.cwd !== void 0 ? { cwd: launchSpec.cwd } : {},
7216
+ ...launchEnvVars(launchSpec)
7217
+ };
7317
7218
  }
7318
- function objectParams(params) {
7319
- return params && typeof params === "object" && !Array.isArray(params) ? params : {};
7219
+ function buildAcpModelDiscoveryAgentCommand(launchSpec) {
7220
+ if (buildAcpModelListCommand(launchSpec) !== void 0) {
7221
+ return void 0;
7222
+ }
7223
+ return {
7224
+ command: launchSpec.command,
7225
+ args: [...launchSpec.args],
7226
+ ...launchSpec.cwd !== void 0 ? { cwd: launchSpec.cwd } : {},
7227
+ ...launchEnvVars(launchSpec)
7228
+ };
7320
7229
  }
7321
- function readProgressToken(params) {
7322
- const meta = objectParams(objectParams(params)._meta).progressToken;
7323
- return typeof meta === "string" || typeof meta === "number" ? meta : null;
7230
+ function buildAcpModelListParams(launchSpec, options) {
7231
+ const primaryModels = [
7232
+ ...options.primaryModels ?? launchSpec?.modelCli?.primaryModels ?? []
7233
+ ];
7234
+ const reasoningProbePriorityModelIds = [
7235
+ ...options.reasoningProbePriorityModelIds
7236
+ ];
7237
+ if (launchSpec === null) {
7238
+ return {
7239
+ primaryModels,
7240
+ reasoningProbePriorityModelIds,
7241
+ parameterizedModelPicker: options.parameterizedModelPicker
7242
+ };
7243
+ }
7244
+ const listCommand = buildAcpModelListCommand(launchSpec);
7245
+ const agent = buildAcpModelDiscoveryAgentCommand(launchSpec);
7246
+ return {
7247
+ ...listCommand !== void 0 ? { listCommand } : {},
7248
+ ...agent !== void 0 ? { agent } : {},
7249
+ primaryModels,
7250
+ reasoningProbePriorityModelIds,
7251
+ parameterizedModelPicker: options.parameterizedModelPicker,
7252
+ ...launchSpec.reasoningCli !== void 0 ? { reasoningCli: launchSpec.reasoningCli } : {},
7253
+ ...launchSpec.nativeReasoning !== void 0 ? { nativeReasoning: launchSpec.nativeReasoning } : {}
7254
+ };
7324
7255
  }
7325
- function startProgressHeartbeat(args) {
7326
- let progress = 0;
7327
- const timer = setInterval(() => {
7328
- progress += 1;
7329
- writeJson({
7330
- jsonrpc: "2.0",
7331
- method: "notifications/progress",
7332
- params: { progressToken: args.progressToken, progress }
7333
- });
7334
- }, args.intervalMs ?? TOOL_CALL_PROGRESS_INTERVAL_MS);
7335
- return () => clearInterval(timer);
7256
+ function cursorParameterizedModelId(model) {
7257
+ const familyId = model === "auto" ? "default" : agentModelFamilyId(model);
7258
+ return familyId.startsWith("cursor-") ? familyId.slice("cursor-".length) : familyId;
7336
7259
  }
7337
- async function handleRequest(env, message) {
7338
- if (message.id === void 0 || message.method === void 0) {
7339
- return;
7260
+ function buildAcpModelSelectionParam(launchSpec, options, parameterizedModelPicker, dialectId) {
7261
+ const model = options.model;
7262
+ const listCommand = buildAcpModelListCommand(launchSpec);
7263
+ if (!model || model === ACP_DEFAULT_MODEL_ID) {
7264
+ return {};
7340
7265
  }
7341
- switch (message.method) {
7342
- case "initialize":
7343
- writeResult(message.id, {
7344
- protocolVersion: typeof objectParams(message.params).protocolVersion === "string" ? objectParams(message.params).protocolVersion : "2024-11-05",
7345
- capabilities: { tools: {} },
7346
- serverInfo: { name: ACP_BRIDGE_MCP_SERVER_NAME, version: "1.0.0" }
7347
- });
7348
- void callBridge(env, {
7349
- kind: "initialized",
7350
- toolCount: env.tools.length
7351
- }).catch((error) => {
7352
- process.stderr.write(
7353
- `bb-bridge MCP: failed to report initialize: ${error instanceof Error ? error.message : String(error)}
7354
- `
7355
- );
7356
- });
7357
- return;
7358
- case "tools/list":
7359
- writeResult(message.id, {
7360
- tools: env.tools.map((tool) => ({
7361
- name: tool.name,
7362
- description: tool.description,
7363
- inputSchema: tool.inputSchema
7364
- }))
7365
- });
7366
- return;
7367
- case "tools/call": {
7368
- const params = objectParams(message.params);
7369
- const name = typeof params.name === "string" ? params.name : "";
7370
- const tool = env.tools.find((candidate) => candidate.name === name);
7371
- if (!tool) {
7372
- writeError(message.id, -32602, `Unknown tool: ${name}`);
7373
- return;
7374
- }
7375
- const rawArguments = params.arguments;
7376
- const toolArguments = rawArguments && typeof rawArguments === "object" && !Array.isArray(rawArguments) ? rawArguments : {};
7377
- const progressToken = readProgressToken(message.params);
7378
- const stopHeartbeat = progressToken === null ? () => {
7379
- } : startProgressHeartbeat({
7380
- intervalMs: env.progressIntervalMs,
7381
- progressToken
7382
- });
7383
- try {
7384
- const result = await callBridge(env, {
7385
- kind: "toolCall",
7386
- arguments: toolArguments,
7387
- callId: mcpToolCallId(tool.name),
7388
- tool: tool.name
7389
- });
7390
- stopHeartbeat();
7391
- if (!result.ok) {
7392
- writeResult(message.id, {
7393
- content: [{ type: "text", text: result.error }],
7394
- isError: true
7395
- });
7396
- return;
7397
- }
7398
- writeResult(message.id, {
7399
- content: buildBridgeToolCallContent(result),
7400
- ...result.isError ? { isError: true } : {}
7401
- });
7402
- } catch (error) {
7403
- stopHeartbeat();
7404
- writeResult(message.id, {
7405
- content: [
7406
- {
7407
- type: "text",
7408
- text: error instanceof Error ? error.message : String(error)
7409
- }
7410
- ],
7411
- isError: true
7412
- });
7266
+ if (parameterizedModelPicker || !listCommand || !launchSpec.modelCli?.selectFlag) {
7267
+ return {
7268
+ modelSelection: {
7269
+ modelId: parameterizedModelPicker && dialectId === "cursor" ? cursorParameterizedModelId(model) : model,
7270
+ ...options.reasoningLevel !== void 0 ? { reasoningLevel: options.reasoningLevel } : {},
7271
+ ...parameterizedModelPicker && options.serviceTier !== void 0 ? { serviceTier: options.serviceTier } : {}
7413
7272
  }
7414
- return;
7415
- }
7416
- default:
7417
- writeError(
7418
- message.id,
7419
- -32601,
7420
- `Unsupported MCP method: ${message.method}`
7421
- );
7273
+ };
7422
7274
  }
7423
- }
7424
- function runAcpDynamicToolMcpServer() {
7425
- const env = readEnvironment();
7426
- const rl = createInterface2({ input: process.stdin, terminal: false });
7427
- rl.on("line", (line) => {
7428
- const trimmed = line.trim();
7429
- if (!trimmed) {
7430
- return;
7431
- }
7432
- let message;
7433
- try {
7434
- message = JSON.parse(trimmed);
7435
- } catch {
7436
- return;
7275
+ return {
7276
+ modelSelection: {
7277
+ listCommand,
7278
+ selectFlag: launchSpec.modelCli.selectFlag,
7279
+ model,
7280
+ ...options.reasoningLevel !== void 0 ? { reasoningLevel: options.reasoningLevel } : {},
7281
+ // Only "fast" changes launch resolution; "default" is the normal id.
7282
+ ...options.serviceTier === "fast" ? { serviceTier: options.serviceTier } : {}
7437
7283
  }
7438
- void handleRequest(env, message);
7439
- });
7440
- }
7441
-
7442
- // ../provider-bridge-acp/src/bridge/cursor-mcp-approval.ts
7443
- var CURSOR_MCP_APPROVAL_FILE = "mcp-approvals.json";
7444
- var CURSOR_APPROVAL_LOCK_STALE_MS = 3e4;
7445
- var CURSOR_APPROVAL_LOCK_TIMEOUT_MS = 5e3;
7446
- function errorCode(error) {
7447
- return error instanceof Error && "code" in error ? error.code : void 0;
7448
- }
7449
- function cursorAgentCommand(command) {
7450
- return basename2(command).toLowerCase().replace(/\.(?:bat|cmd|exe)$/u, "") === "cursor-agent";
7451
- }
7452
- function cursorProjectSlug(projectRoot) {
7453
- return projectRoot.replace(/[^a-zA-Z0-9]/gu, "-").replace(/-+/gu, "-").replace(/^-+|-+$/gu, "");
7284
+ };
7454
7285
  }
7455
- function cursorDataDirectory(env) {
7456
- const configured = env.CURSOR_DATA_DIR?.trim();
7457
- if (configured) {
7458
- return configured;
7286
+ function buildAcpSessionParams(args) {
7287
+ const { options, launchSpec } = args;
7288
+ const instructions = buildAcpSessionInstructions(options);
7289
+ const cwd = launchSpec.cwd ?? args.cwd;
7290
+ const envVars = {
7291
+ ...launchSpec.env,
7292
+ ...options.envVars ?? {}
7293
+ };
7294
+ if (options.permissionMode === "auto") {
7295
+ throw new Error(
7296
+ `Provider "${args.providerLabel}" does not support permission mode "auto".`
7297
+ );
7459
7298
  }
7460
- const home = env.HOME?.trim() || env.USERPROFILE?.trim() || homedir();
7461
- return join2(home, ".cursor");
7462
- }
7463
- function cursorMcpServerConfig(config) {
7464
7299
  return {
7465
- command: config.command,
7466
- args: config.args,
7467
- env: Object.fromEntries(config.env.map(({ name, value }) => [name, value]))
7468
- };
7469
- }
7470
- function buildCursorMcpApprovalIdentifier(args) {
7471
- const fingerprint = createHash("sha256").update(
7472
- JSON.stringify({
7473
- path: args.projectRoot,
7474
- server: cursorMcpServerConfig(args.config)
7475
- })
7476
- ).digest("hex").slice(0, 16);
7477
- return `${args.config.name}-${fingerprint}`;
7478
- }
7479
- async function resolveCursorProjectRoot(args) {
7480
- return new Promise((resolveRoot) => {
7481
- execFile3(
7482
- "git",
7483
- ["rev-parse", "--show-toplevel"],
7484
- { cwd: args.cwd, env: args.env, windowsHide: true },
7485
- (error, stdout) => {
7486
- const root = stdout.trim();
7487
- resolveRoot(error === null && root !== "" ? root : resolve2(args.cwd));
7488
- }
7489
- );
7490
- });
7300
+ threadId: args.threadId,
7301
+ cwd,
7302
+ agent: {
7303
+ command: launchSpec.command,
7304
+ args: [...launchSpec.args]
7305
+ },
7306
+ ...args.dialectId === void 0 ? {} : { dialectId: args.dialectId },
7307
+ ...buildAcpModelSelectionParam(
7308
+ launchSpec,
7309
+ options,
7310
+ args.parameterizedModelPicker,
7311
+ args.dialectId
7312
+ ),
7313
+ parameterizedModelPicker: args.parameterizedModelPicker,
7314
+ ...launchSpec.reasoningCli !== void 0 ? { reasoningCli: launchSpec.reasoningCli } : {},
7315
+ ...launchSpec.nativeReasoning !== void 0 ? { nativeReasoning: launchSpec.nativeReasoning } : {},
7316
+ ...launchSpec.permissionCli !== void 0 ? { permissionCli: launchSpec.permissionCli } : {},
7317
+ ...launchSpec.reasoningCli !== void 0 && options.reasoningLevel !== void 0 ? { launchReasoningLevel: options.reasoningLevel } : {},
7318
+ permissionMode: options.permissionMode,
7319
+ workspaceWriteRoots: [cwd, ...args.additionalWorkspaceWriteRoots],
7320
+ ...Object.keys(envVars).length > 0 ? { envVars } : {},
7321
+ ...instructions ? { instructions } : {},
7322
+ ...args.dynamicTools && args.dynamicTools.length > 0 ? { dynamicTools: args.dynamicTools } : {}
7323
+ };
7491
7324
  }
7492
- async function readApprovals(path5) {
7493
- let text;
7494
- try {
7495
- text = await readFile(path5, "utf8");
7496
- } catch (error) {
7497
- if (errorCode(error) === "ENOENT") {
7498
- return [];
7499
- }
7500
- throw error;
7325
+
7326
+ // ../provider-bridge-acp/src/bridge/agent-connection.ts
7327
+ import { spawn } from "node:child_process";
7328
+ import { createInterface } from "node:readline";
7329
+ var STDERR_TAIL_MAX_CHUNKS = 40;
7330
+ var CLOSED_STDIN_ERROR_CODES = /* @__PURE__ */ new Set(["EPIPE", "ERR_STREAM_DESTROYED"]);
7331
+ var AcpAgentExitedError = class extends Error {
7332
+ constructor(message) {
7333
+ super(message);
7334
+ this.name = "AcpAgentExitedError";
7501
7335
  }
7502
- const value = JSON.parse(text);
7503
- if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) {
7504
- throw new Error(`Cursor MCP approval file is not a string array: ${path5}`);
7336
+ };
7337
+ var AcpAgentResponseError = class extends Error {
7338
+ code;
7339
+ constructor(message, code) {
7340
+ super(message);
7341
+ this.name = "AcpAgentResponseError";
7342
+ this.code = code;
7505
7343
  }
7506
- return value;
7344
+ };
7345
+ function isClosedAgentStdinError(error) {
7346
+ return "code" in error && typeof error.code === "string" && CLOSED_STDIN_ERROR_CODES.has(error.code);
7507
7347
  }
7508
- async function writeApprovals(path5, approvals) {
7509
- await mkdir(dirname(path5), { recursive: true });
7510
- const tempPath = `${path5}.bb-${process.pid}-${randomBytes(6).toString("hex")}.tmp`;
7348
+ function formatAgentError(error) {
7349
+ const message = error.message ?? `ACP agent returned error code ${error.code ?? "unknown"}`;
7350
+ const details = formatAgentErrorData(error.data);
7351
+ return details === void 0 ? message : `${message}: ${details}`;
7352
+ }
7353
+ function formatAgentErrorData(data) {
7354
+ if (data === void 0 || data === null) {
7355
+ return void 0;
7356
+ }
7357
+ if (typeof data === "string") {
7358
+ return data.trim() === "" ? void 0 : data;
7359
+ }
7360
+ if (typeof data === "object" && "details" in data && typeof data.details === "string" && data.details.trim() !== "") {
7361
+ return data.details;
7362
+ }
7511
7363
  try {
7512
- await writeFile(tempPath, `${JSON.stringify(approvals, null, 2)}
7513
- `, {
7514
- encoding: "utf8",
7515
- mode: 384
7516
- });
7517
- await rename(tempPath, path5);
7518
- } catch (error) {
7519
- await rm(tempPath, { force: true });
7520
- throw error;
7364
+ return JSON.stringify(data);
7365
+ } catch {
7366
+ return void 0;
7521
7367
  }
7522
7368
  }
7523
- async function acquireApprovalLock(path5) {
7524
- const lockPath = `${path5}.bb-lock`;
7525
- const deadline = Date.now() + CURSOR_APPROVAL_LOCK_TIMEOUT_MS;
7526
- await mkdir(dirname(path5), { recursive: true });
7527
- for (; ; ) {
7528
- try {
7529
- await mkdir(lockPath, { mode: 448 });
7530
- return () => rm(lockPath, { recursive: true, force: true });
7531
- } catch (error) {
7532
- if (errorCode(error) !== "EEXIST") {
7533
- throw error;
7534
- }
7535
- }
7536
- try {
7537
- const lockStat = await stat(lockPath);
7538
- if (Date.now() - lockStat.mtimeMs > CURSOR_APPROVAL_LOCK_STALE_MS) {
7539
- await rm(lockPath, { recursive: true, force: true });
7540
- continue;
7541
- }
7542
- } catch (error) {
7543
- if (errorCode(error) === "ENOENT") {
7544
- continue;
7545
- }
7546
- throw error;
7547
- }
7548
- if (Date.now() >= deadline) {
7549
- throw new Error(`Timed out updating Cursor MCP approvals: ${path5}`);
7550
- }
7551
- await new Promise((resolveDelay) => setTimeout(resolveDelay, 25));
7369
+ function parseAgentLine(line) {
7370
+ const trimmed = line.trim();
7371
+ if (!trimmed) {
7372
+ return null;
7552
7373
  }
7553
- }
7554
- async function mutateApprovals(path5, mutate) {
7555
- const releaseLock = await acquireApprovalLock(path5);
7374
+ let parsed;
7556
7375
  try {
7557
- const approvals = await readApprovals(path5);
7558
- const nextApprovals = mutate(approvals);
7559
- if (approvals.length !== nextApprovals.length || approvals.some((approval, index) => approval !== nextApprovals[index])) {
7560
- await writeApprovals(path5, nextApprovals);
7561
- }
7562
- } finally {
7563
- await releaseLock();
7376
+ parsed = JSON.parse(trimmed);
7377
+ } catch {
7378
+ return null;
7564
7379
  }
7565
- }
7566
- async function approveCursorSessionMcpServer(args) {
7567
- if (!cursorAgentCommand(args.agentCommand) || args.config.name !== ACP_BRIDGE_MCP_SERVER_NAME) {
7568
- return void 0;
7380
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
7381
+ return null;
7569
7382
  }
7570
- const projectRoot = await resolveCursorProjectRoot({
7571
- cwd: args.cwd,
7572
- env: args.env
7383
+ return parsed;
7384
+ }
7385
+ function createAcpAgentConnection(options) {
7386
+ const child = spawn(options.command, options.args, {
7387
+ cwd: options.cwd,
7388
+ env: options.env,
7389
+ stdio: ["pipe", "pipe", "pipe"]
7573
7390
  });
7574
- const path5 = join2(
7575
- cursorDataDirectory(args.env),
7576
- "projects",
7577
- cursorProjectSlug(projectRoot),
7578
- CURSOR_MCP_APPROVAL_FILE
7579
- );
7580
- const approval = buildCursorMcpApprovalIdentifier({
7581
- config: args.config,
7582
- projectRoot
7391
+ experimental_recordProviderChildIo(child, {
7392
+ threadId: options.recordThreadId
7583
7393
  });
7584
- let installedByBb = false;
7585
- await mutateApprovals(path5, (approvals) => {
7586
- if (approvals.includes(approval)) {
7587
- return approvals;
7394
+ const pending = /* @__PURE__ */ new Map();
7395
+ const stderrChunks = [];
7396
+ let nextRequestId = 1;
7397
+ let exited = false;
7398
+ function rejectAllPending(error) {
7399
+ for (const [, request] of pending) {
7400
+ request.reject(error);
7588
7401
  }
7589
- installedByBb = true;
7590
- return [...approvals, approval];
7591
- });
7592
- return { approval, installedByBb, path: path5 };
7593
- }
7594
- async function revokeCursorSessionMcpServer(approval) {
7595
- if (!approval.installedByBb) {
7596
- return;
7402
+ pending.clear();
7597
7403
  }
7598
- await mutateApprovals(
7599
- approval.path,
7600
- (approvals) => approvals.filter((candidate) => candidate !== approval.approval)
7601
- );
7602
- }
7603
-
7604
- // ../provider-bridge-acp/src/bridge/model-catalog.ts
7605
- var ACP_NATIVE_REASONING_EFFORTS = [
7606
- {
7607
- reasoningEffort: "medium",
7608
- description: "Reasoning effort is managed by the connected ACP agent."
7404
+ function closeForAgentStdin(error) {
7405
+ if (exited) {
7406
+ return;
7407
+ }
7408
+ exited = true;
7409
+ const code = "code" in error && typeof error.code === "string" ? ` (${error.code})` : "";
7410
+ const detail = `stdin closed${code}: ${error.message}`;
7411
+ rejectAllPending(
7412
+ new AcpAgentExitedError(`ACP agent "${options.command}" ${detail}`)
7413
+ );
7414
+ child.kill("SIGKILL");
7415
+ const stderrTail = [...stderrChunks, detail].join("\n");
7416
+ options.onExit({ code: null, signal: null, stderrTail });
7609
7417
  }
7610
- ];
7611
- var MODEL_LINE_PATTERN = /^(\S+) - (.+)$/;
7612
- var BARE_PROVIDER_MODEL_LINE_PATTERN = /^\S+\/\S+$/;
7613
- var BULLETED_MODEL_LINE_PATTERN = /^[*-]\s+(\S+)(?:\s+\([^)]*\))?$/u;
7614
- var EFFORT_TOKENS = [
7615
- ["extra-high", "xhigh"],
7616
- ["medium", "medium"],
7617
- ["xhigh", "xhigh"],
7618
- ["high", "high"],
7619
- ["low", "low"],
7620
- ["max", "max"],
7621
- ["none", "none"]
7622
- ];
7623
- var FAST_TAIL = "-fast";
7624
- var THINKING_TOKEN = "thinking";
7625
- function parseAgentModelLines(stdout) {
7626
- const models = [];
7627
- for (const line of stdout.split("\n")) {
7628
- const trimmed = line.trim();
7629
- const match = MODEL_LINE_PATTERN.exec(trimmed);
7630
- if (!match) {
7631
- const bulletMatch = BULLETED_MODEL_LINE_PATTERN.exec(trimmed);
7632
- if (bulletMatch) {
7633
- const [, id2] = bulletMatch;
7634
- models.push({ id: id2, displayName: id2 });
7635
- continue;
7636
- }
7637
- if (BARE_PROVIDER_MODEL_LINE_PATTERN.test(trimmed)) {
7638
- models.push({ id: trimmed, displayName: trimmed });
7639
- }
7640
- continue;
7418
+ function writeLine(message) {
7419
+ const stdin = child.stdin;
7420
+ if (!stdin || stdin.destroyed || !stdin.writable) {
7421
+ closeForAgentStdin(new Error("stdin is not writable"));
7422
+ return;
7641
7423
  }
7642
- const [, id, displayName] = match;
7643
- models.push({ id, displayName });
7424
+ stdin.write(JSON.stringify(message) + "\n");
7644
7425
  }
7645
- return models;
7646
- }
7647
- function findAcpModelConfigOption(configOptions) {
7648
- const options = configOptions ?? [];
7649
- return options.find((option) => option.category === "model") ?? options.find((option) => option.id === "model");
7650
- }
7651
- function findAcpThoughtLevelConfigOption(configOptions) {
7652
- return (configOptions ?? []).find(
7653
- (option) => option.category === "thought_level"
7654
- );
7655
- }
7656
- var ACP_NATIVE_REASONING_LEVEL_BY_VALUE = {
7657
- none: "none",
7658
- minimal: "low",
7659
- low: "low",
7660
- medium: "medium",
7661
- high: "high",
7662
- xhigh: "xhigh",
7663
- ultracode: "ultracode",
7664
- max: "max",
7665
- ultra: "ultra"
7666
- };
7667
- var ACP_NATIVE_REASONING_VALUE_CANDIDATES_BY_LEVEL = {
7668
- none: ["none"],
7669
- low: ["low", "minimal"],
7670
- medium: ["medium"],
7671
- high: ["high"],
7672
- xhigh: ["xhigh"],
7673
- ultracode: ["ultracode", "xhigh"],
7674
- max: ["max", "xhigh"],
7675
- ultra: ["ultra", "max"]
7676
- };
7677
- function acpNativeValueToReasoningLevel(value) {
7678
- return value === void 0 ? void 0 : ACP_NATIVE_REASONING_LEVEL_BY_VALUE[value];
7679
- }
7680
- function acpNativeReasoningLevelToValue(level, thoughtLevelOption) {
7681
- const candidateValues = ACP_NATIVE_REASONING_VALUE_CANDIDATES_BY_LEVEL[level];
7682
- if (candidateValues === void 0) {
7683
- return void 0;
7684
- }
7685
- const values = new Set(
7686
- (thoughtLevelOption.options ?? []).map((o) => o.value)
7687
- );
7688
- return candidateValues.find((value) => values.has(value));
7689
- }
7690
- function buildAcpNativeReasoningSupport(thoughtLevelOption) {
7691
- const options = thoughtLevelOption?.options ?? [];
7692
- const seen = /* @__PURE__ */ new Set();
7693
- const matchedValueByLevel = /* @__PURE__ */ new Map();
7694
- const supportedReasoningEfforts = [];
7695
- for (const option of options) {
7696
- const level = acpNativeValueToReasoningLevel(option.value);
7697
- if (level === void 0) {
7698
- continue;
7426
+ child.stdin?.on("error", (error) => {
7427
+ if (!isClosedAgentStdinError(error)) {
7428
+ throw error;
7699
7429
  }
7700
- if (seen.has(level)) {
7701
- const previousValue = matchedValueByLevel.get(level);
7702
- if (previousValue !== level && option.value === level) {
7703
- const effort = supportedReasoningEfforts.find(
7704
- (candidate) => candidate.reasoningEffort === level
7705
- );
7706
- if (effort) {
7707
- effort.description = option.name ?? option.value;
7430
+ closeForAgentStdin(error);
7431
+ });
7432
+ if (child.stdout) {
7433
+ const stdoutLines = createInterface({
7434
+ input: child.stdout,
7435
+ terminal: false
7436
+ });
7437
+ stdoutLines.on("line", (line) => {
7438
+ const message = parseAgentLine(line);
7439
+ if (!message) {
7440
+ return;
7441
+ }
7442
+ const id = message.id;
7443
+ if ((typeof id === "string" || typeof id === "number") && message.method === void 0) {
7444
+ const numericId = typeof id === "number" ? id : Number(id);
7445
+ const request = pending.get(numericId);
7446
+ if (!request) {
7447
+ return;
7708
7448
  }
7709
- matchedValueByLevel.set(level, option.value);
7449
+ pending.delete(numericId);
7450
+ if (message.error) {
7451
+ request.reject(
7452
+ new AcpAgentResponseError(
7453
+ formatAgentError(message.error),
7454
+ message.error.code
7455
+ )
7456
+ );
7457
+ } else {
7458
+ request.resolve(message.result);
7459
+ }
7460
+ return;
7710
7461
  }
7711
- continue;
7712
- }
7713
- seen.add(level);
7714
- matchedValueByLevel.set(level, option.value);
7715
- supportedReasoningEfforts.push({
7716
- reasoningEffort: level,
7717
- description: option.name ?? option.value
7462
+ if (typeof message.method !== "string") {
7463
+ return;
7464
+ }
7465
+ if (typeof id === "string" || typeof id === "number") {
7466
+ let settled = false;
7467
+ options.onRequest(message.method, message.params, {
7468
+ result(value) {
7469
+ if (settled) return;
7470
+ settled = true;
7471
+ writeLine({ jsonrpc: "2.0", id, result: value ?? null });
7472
+ },
7473
+ error(code, errorMessage) {
7474
+ if (settled) return;
7475
+ settled = true;
7476
+ writeLine({
7477
+ jsonrpc: "2.0",
7478
+ id,
7479
+ error: { code, message: errorMessage }
7480
+ });
7481
+ }
7482
+ });
7483
+ return;
7484
+ }
7485
+ options.onNotification(message.method, message.params);
7718
7486
  });
7719
7487
  }
7720
- supportedReasoningEfforts.sort(
7721
- (a, b) => reasoningLevelValues.indexOf(a.reasoningEffort) - reasoningLevelValues.indexOf(b.reasoningEffort)
7722
- );
7723
- if (supportedReasoningEfforts.length === 0) {
7724
- return {
7725
- // An omitted option preserves the legacy agent-managed fallback. A
7726
- // declared option is authoritative, even when bb cannot map its values.
7727
- supportedReasoningEfforts: thoughtLevelOption === void 0 ? ACP_NATIVE_REASONING_EFFORTS : [],
7728
- defaultReasoningEffort: "medium"
7729
- };
7730
- }
7731
- const currentLevel = acpNativeValueToReasoningLevel(
7732
- thoughtLevelOption?.currentValue
7733
- );
7734
- const supportedLevels = supportedReasoningEfforts.map(
7735
- (effort) => effort.reasoningEffort
7736
- );
7737
- return {
7738
- supportedReasoningEfforts,
7739
- defaultReasoningEffort: currentLevel !== void 0 && supportedLevels.includes(currentLevel) ? currentLevel : supportedReasoningEfforts[0].reasoningEffort
7740
- };
7741
- }
7742
- function buildModelCatalogFromConfigOptions(modelOption, reasoningByModel) {
7743
- const options = modelOption?.options ?? [];
7744
- if (options.length === 0) {
7745
- return [];
7746
- }
7747
- const currentValue = modelOption?.currentValue;
7748
- const models = options.map((option, index) => {
7749
- const isDefault = currentValue !== void 0 ? option.value === currentValue : index === 0;
7750
- const reasoning = reasoningByModel?.get(option.value) ?? {
7751
- supportedReasoningEfforts: ACP_NATIVE_REASONING_EFFORTS,
7752
- defaultReasoningEffort: "medium"
7753
- };
7754
- return {
7755
- id: option.value,
7756
- model: option.value,
7757
- displayName: option.name ?? option.value,
7758
- description: "",
7759
- supportedReasoningEfforts: reasoning.supportedReasoningEfforts,
7760
- defaultReasoningEffort: reasoning.defaultReasoningEffort,
7761
- isDefault
7762
- };
7763
- });
7764
- return models.some((model) => model.isDefault) ? models : models.map(
7765
- (model, index) => index === 0 ? { ...model, isDefault: true } : model
7766
- );
7767
- }
7768
- function buildModelCatalogFromSessionModels(sessionModels) {
7769
- const availableModels = sessionModels?.availableModels ?? [];
7770
- if (availableModels.length === 0) {
7771
- return [];
7488
+ if (child.stderr) {
7489
+ const stderrLines = createInterface({
7490
+ input: child.stderr,
7491
+ terminal: false
7492
+ });
7493
+ stderrLines.on("line", (line) => {
7494
+ stderrChunks.push(line);
7495
+ if (stderrChunks.length > STDERR_TAIL_MAX_CHUNKS) {
7496
+ stderrChunks.shift();
7497
+ }
7498
+ });
7772
7499
  }
7773
- const currentModelId = sessionModels?.currentModelId;
7774
- const models = availableModels.map((model, index) => {
7775
- const isDefault = currentModelId !== void 0 ? model.modelId === currentModelId : index === 0;
7776
- return {
7777
- id: model.modelId,
7778
- model: model.modelId,
7779
- displayName: model.name ?? model.modelId,
7780
- description: model.description ?? "",
7781
- supportedReasoningEfforts: ACP_NATIVE_REASONING_EFFORTS,
7782
- defaultReasoningEffort: "medium",
7783
- isDefault
7784
- };
7500
+ child.on("error", (error) => {
7501
+ if (exited) {
7502
+ return;
7503
+ }
7504
+ exited = true;
7505
+ rejectAllPending(
7506
+ new AcpAgentExitedError(
7507
+ `Failed to launch ACP agent "${options.command}": ${error.message}`
7508
+ )
7509
+ );
7510
+ options.onExit({ code: null, signal: null, stderrTail: error.message });
7785
7511
  });
7786
- return models.some((model) => model.isDefault) ? models : models.map(
7787
- (model, index) => index === 0 ? { ...model, isDefault: true } : model
7788
- );
7789
- }
7790
- function splitVariant(id) {
7791
- let rest = id;
7792
- let fast = false;
7793
- if (rest.endsWith(FAST_TAIL)) {
7794
- fast = true;
7795
- rest = rest.slice(0, -FAST_TAIL.length);
7796
- }
7797
- let thinking = false;
7798
- if (rest.endsWith(`-${THINKING_TOKEN}`)) {
7799
- thinking = true;
7800
- rest = rest.slice(0, -(THINKING_TOKEN.length + 1));
7801
- } else if (rest.includes(`-${THINKING_TOKEN}-`)) {
7802
- thinking = true;
7803
- rest = rest.replace(`-${THINKING_TOKEN}-`, "-");
7804
- }
7805
- for (const [token, effort] of EFFORT_TOKENS) {
7806
- if (rest.endsWith(`-${token}`)) {
7807
- return {
7808
- familyKey: rest.slice(0, -(token.length + 1)),
7809
- effort,
7810
- effortToken: token,
7811
- fast,
7812
- thinking
7813
- };
7512
+ child.on("exit", (code, signal) => {
7513
+ if (exited) {
7514
+ return;
7814
7515
  }
7815
- }
7516
+ exited = true;
7517
+ const stderrTail = stderrChunks.join("\n");
7518
+ rejectAllPending(
7519
+ new AcpAgentExitedError(
7520
+ `ACP agent "${options.command}" exited (code ${code ?? "null"}, signal ${signal ?? "null"})${stderrTail ? `: ${stderrTail}` : ""}`
7521
+ )
7522
+ );
7523
+ options.onExit({ code, signal, stderrTail });
7524
+ });
7816
7525
  return {
7817
- familyKey: rest,
7818
- effort: "medium",
7819
- effortToken: void 0,
7820
- fast,
7821
- thinking
7822
- };
7823
- }
7824
- var EFFORT_DISPLAY_WORDS = {
7825
- "extra-high": "Extra High",
7826
- medium: "Medium",
7827
- xhigh: "Extra High",
7828
- high: "High",
7829
- low: "Low",
7830
- max: "Max",
7831
- ultra: "Ultra",
7832
- none: "None"
7833
- };
7834
- function familyDisplayName(displayName, effortToken) {
7835
- const word = effortToken ? EFFORT_DISPLAY_WORDS[effortToken] : void 0;
7836
- if (!word) {
7837
- return cleanDisplayName(displayName);
7526
+ get exited() {
7527
+ return exited;
7528
+ },
7529
+ request({ method, params, resultSchema }) {
7530
+ if (exited) {
7531
+ return Promise.reject(
7532
+ new AcpAgentExitedError(
7533
+ `ACP agent "${options.command}" is not running`
7534
+ )
7535
+ );
7536
+ }
7537
+ const id = nextRequestId;
7538
+ nextRequestId += 1;
7539
+ return new Promise((resolve4, reject) => {
7540
+ pending.set(id, {
7541
+ resolve: (value) => {
7542
+ const parsed = resultSchema.safeParse(value);
7543
+ if (parsed.success) {
7544
+ resolve4(parsed.data);
7545
+ } else {
7546
+ reject(
7547
+ new Error(
7548
+ `ACP agent returned an unexpected ${method} result: ${parsed.error.message}`
7549
+ )
7550
+ );
7551
+ }
7552
+ },
7553
+ reject
7554
+ });
7555
+ writeLine({ jsonrpc: "2.0", id, method, params });
7556
+ });
7557
+ },
7558
+ notify(method, params) {
7559
+ if (exited) {
7560
+ return;
7561
+ }
7562
+ writeLine({ jsonrpc: "2.0", method, params });
7563
+ },
7564
+ kill() {
7565
+ if (exited) {
7566
+ return;
7567
+ }
7568
+ child.kill("SIGTERM");
7569
+ }
7570
+ };
7571
+ }
7572
+
7573
+ // ../provider-bridge-acp/src/bridge/cursor-mcp-approval.ts
7574
+ import { execFile as execFile3 } from "node:child_process";
7575
+ import { createHash, randomBytes } from "node:crypto";
7576
+ import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
7577
+ import { homedir } from "node:os";
7578
+ import { basename as basename2, dirname, join as join2, resolve as resolve2 } from "node:path";
7579
+
7580
+ // ../provider-bridge-acp/src/bridge/tool-proxy-mcp.ts
7581
+ import { createConnection } from "node:net";
7582
+ import { createInterface as createInterface2 } from "node:readline";
7583
+ import { z as z38 } from "zod";
7584
+ var ACP_BRIDGE_MCP_SERVER_NAME = "bb-bridge";
7585
+ var ENV_HOST = "BB_ACP_DYNAMIC_TOOL_HOST";
7586
+ var ENV_PORT = "BB_ACP_DYNAMIC_TOOL_PORT";
7587
+ var ENV_TOKEN = "BB_ACP_DYNAMIC_TOOL_TOKEN";
7588
+ var ENV_THREAD_ID = "BB_ACP_DYNAMIC_TOOL_THREAD_ID";
7589
+ var ENV_TOOLS = "BB_ACP_DYNAMIC_TOOLS";
7590
+ var ENV_PROGRESS_INTERVAL_MS = "BB_ACP_DYNAMIC_TOOL_PROGRESS_INTERVAL_MS";
7591
+ var bridgeToolCallResponseSchema = z38.union([
7592
+ z38.object({
7593
+ ok: z38.literal(true),
7594
+ content: z38.string(),
7595
+ contentBlocks: z38.array(
7596
+ z38.discriminatedUnion("type", [
7597
+ z38.object({ type: z38.literal("text"), text: z38.string() }),
7598
+ z38.object({
7599
+ type: z38.literal("image"),
7600
+ data: z38.string(),
7601
+ mimeType: z38.string()
7602
+ })
7603
+ ])
7604
+ ).optional(),
7605
+ // The initialized response and older text-only responses omit images.
7606
+ // Parsing them as an empty list keeps the re-executed packaged artifact
7607
+ // compatible with that legacy socket shape.
7608
+ images: z38.array(z38.object({ data: z38.string(), mimeType: z38.string() })).default([]),
7609
+ isError: z38.boolean().optional()
7610
+ }),
7611
+ z38.object({ ok: z38.literal(false), error: z38.string() })
7612
+ ]);
7613
+ var nextMcpToolCallId = 0;
7614
+ var TOOL_CALL_PROGRESS_INTERVAL_MS = 15e3;
7615
+ function buildAcpMcpServerConfig(args) {
7616
+ return {
7617
+ name: ACP_BRIDGE_MCP_SERVER_NAME,
7618
+ command: args.command,
7619
+ args: args.bridgeArgs,
7620
+ env: [
7621
+ ...args.runtimeEnv,
7622
+ { name: ENV_HOST, value: args.host },
7623
+ { name: ENV_PORT, value: String(args.port) },
7624
+ { name: ENV_TOKEN, value: args.token },
7625
+ { name: ENV_THREAD_ID, value: args.threadId },
7626
+ { name: ENV_TOOLS, value: JSON.stringify(args.dynamicTools) }
7627
+ ]
7628
+ };
7629
+ }
7630
+ function readEnvironment() {
7631
+ const port = Number(process.env[ENV_PORT]);
7632
+ if (!Number.isInteger(port) || port <= 0) {
7633
+ throw new Error(`${ENV_PORT} must be a positive integer`);
7838
7634
  }
7839
- return cleanDisplayName(
7840
- displayName.replace(new RegExp(`(^|\\s)${word}(?=\\s|$)`), "$1")
7841
- );
7635
+ const host = process.env[ENV_HOST];
7636
+ const token = process.env[ENV_TOKEN];
7637
+ const threadId = process.env[ENV_THREAD_ID];
7638
+ const toolsJson = process.env[ENV_TOOLS];
7639
+ if (!host || !token || !threadId || !toolsJson) {
7640
+ throw new Error("Missing ACP dynamic tool MCP server environment");
7641
+ }
7642
+ const parsedTools = JSON.parse(toolsJson);
7643
+ const tools = dynamicToolSchema.array().parse(parsedTools);
7644
+ const rawProgressInterval = process.env[ENV_PROGRESS_INTERVAL_MS];
7645
+ const progressIntervalMs = rawProgressInterval !== void 0 && Number(rawProgressInterval) > 0 ? Number(rawProgressInterval) : void 0;
7646
+ return {
7647
+ host,
7648
+ port,
7649
+ progressIntervalMs,
7650
+ threadId,
7651
+ token,
7652
+ tools
7653
+ };
7842
7654
  }
7843
- function cleanDisplayName(name) {
7844
- return name.replace(/\s*\((?:NO ZDR|default|current)\)/gi, "").replace(/(^|\s)(?:1M|Thinking)(?=\s|$)/g, "$1").replace(/\s{2,}/g, " ").trim();
7655
+ function writeJson(message) {
7656
+ process.stdout.write(`${JSON.stringify(message)}
7657
+ `);
7845
7658
  }
7846
- function buildAgentModelCatalog(rawModels) {
7847
- const families = /* @__PURE__ */ new Map();
7848
- for (const raw of rawModels) {
7849
- const { familyKey, effort, effortToken, fast, thinking } = splitVariant(
7850
- raw.id
7851
- );
7852
- const members = families.get(familyKey) ?? [];
7853
- members.push({ ...raw, effort, effortToken, fast, thinking });
7854
- families.set(familyKey, members);
7659
+ function writeResult(id, result) {
7660
+ writeJson({ jsonrpc: "2.0", id, result });
7661
+ }
7662
+ function writeError(id, code, message) {
7663
+ writeJson({ jsonrpc: "2.0", id, error: { code, message } });
7664
+ }
7665
+ function mcpToolCallId(toolName) {
7666
+ nextMcpToolCallId += 1;
7667
+ return `acp-mcp-${toolName}-${Date.now()}-${nextMcpToolCallId}`;
7668
+ }
7669
+ function callBridge(env, request) {
7670
+ return new Promise((resolve4, reject) => {
7671
+ const socket = createConnection({ host: env.host, port: env.port });
7672
+ let buffer = "";
7673
+ socket.setEncoding("utf8");
7674
+ socket.on("connect", () => {
7675
+ const payload = {
7676
+ ...request,
7677
+ threadId: env.threadId,
7678
+ token: env.token
7679
+ };
7680
+ socket.write(`${JSON.stringify(payload)}
7681
+ `);
7682
+ });
7683
+ socket.on("data", (chunk) => {
7684
+ buffer += chunk;
7685
+ const newlineIndex = buffer.indexOf("\n");
7686
+ if (newlineIndex === -1) {
7687
+ return;
7688
+ }
7689
+ const line = buffer.slice(0, newlineIndex);
7690
+ socket.end();
7691
+ try {
7692
+ resolve4(bridgeToolCallResponseSchema.parse(JSON.parse(line)));
7693
+ } catch (error) {
7694
+ reject(error);
7695
+ }
7696
+ });
7697
+ socket.on("error", reject);
7698
+ socket.on("end", () => {
7699
+ if (!buffer.includes("\n")) {
7700
+ reject(new Error("ACP dynamic tool bridge closed without a response"));
7701
+ }
7702
+ });
7703
+ });
7704
+ }
7705
+ function objectParams(params) {
7706
+ return params && typeof params === "object" && !Array.isArray(params) ? params : {};
7707
+ }
7708
+ function readProgressToken(params) {
7709
+ const meta = objectParams(objectParams(params)._meta).progressToken;
7710
+ return typeof meta === "string" || typeof meta === "number" ? meta : null;
7711
+ }
7712
+ function startProgressHeartbeat(args) {
7713
+ let progress = 0;
7714
+ const timer = setInterval(() => {
7715
+ progress += 1;
7716
+ writeJson({
7717
+ jsonrpc: "2.0",
7718
+ method: "notifications/progress",
7719
+ params: { progressToken: args.progressToken, progress }
7720
+ });
7721
+ }, args.intervalMs ?? TOOL_CALL_PROGRESS_INTERVAL_MS);
7722
+ return () => clearInterval(timer);
7723
+ }
7724
+ async function handleRequest(env, message) {
7725
+ if (message.id === void 0 || message.method === void 0) {
7726
+ return;
7855
7727
  }
7856
- if (families.size === 0) {
7857
- return null;
7728
+ switch (message.method) {
7729
+ case "initialize":
7730
+ writeResult(message.id, {
7731
+ protocolVersion: typeof objectParams(message.params).protocolVersion === "string" ? objectParams(message.params).protocolVersion : "2024-11-05",
7732
+ capabilities: { tools: {} },
7733
+ serverInfo: { name: ACP_BRIDGE_MCP_SERVER_NAME, version: "1.0.0" }
7734
+ });
7735
+ void callBridge(env, {
7736
+ kind: "initialized",
7737
+ toolCount: env.tools.length
7738
+ }).catch((error) => {
7739
+ process.stderr.write(
7740
+ `bb-bridge MCP: failed to report initialize: ${error instanceof Error ? error.message : String(error)}
7741
+ `
7742
+ );
7743
+ });
7744
+ return;
7745
+ case "tools/list":
7746
+ writeResult(message.id, {
7747
+ tools: env.tools.map((tool) => ({
7748
+ name: tool.name,
7749
+ description: tool.description,
7750
+ inputSchema: tool.inputSchema
7751
+ }))
7752
+ });
7753
+ return;
7754
+ case "tools/call": {
7755
+ const params = objectParams(message.params);
7756
+ const name = typeof params.name === "string" ? params.name : "";
7757
+ const tool = env.tools.find((candidate) => candidate.name === name);
7758
+ if (!tool) {
7759
+ writeError(message.id, -32602, `Unknown tool: ${name}`);
7760
+ return;
7761
+ }
7762
+ const rawArguments = params.arguments;
7763
+ const toolArguments = rawArguments && typeof rawArguments === "object" && !Array.isArray(rawArguments) ? rawArguments : {};
7764
+ const progressToken = readProgressToken(message.params);
7765
+ const stopHeartbeat = progressToken === null ? () => {
7766
+ } : startProgressHeartbeat({
7767
+ intervalMs: env.progressIntervalMs,
7768
+ progressToken
7769
+ });
7770
+ try {
7771
+ const result = await callBridge(env, {
7772
+ kind: "toolCall",
7773
+ arguments: toolArguments,
7774
+ callId: mcpToolCallId(tool.name),
7775
+ tool: tool.name
7776
+ });
7777
+ stopHeartbeat();
7778
+ if (!result.ok) {
7779
+ writeResult(message.id, {
7780
+ content: [{ type: "text", text: result.error }],
7781
+ isError: true
7782
+ });
7783
+ return;
7784
+ }
7785
+ writeResult(message.id, {
7786
+ content: buildBridgeToolCallContent(result),
7787
+ ...result.isError ? { isError: true } : {}
7788
+ });
7789
+ } catch (error) {
7790
+ stopHeartbeat();
7791
+ writeResult(message.id, {
7792
+ content: [
7793
+ {
7794
+ type: "text",
7795
+ text: error instanceof Error ? error.message : String(error)
7796
+ }
7797
+ ],
7798
+ isError: true
7799
+ });
7800
+ }
7801
+ return;
7802
+ }
7803
+ default:
7804
+ writeError(
7805
+ message.id,
7806
+ -32601,
7807
+ `Unsupported MCP method: ${message.method}`
7808
+ );
7858
7809
  }
7859
- const models = [];
7860
- const variantsByFamilyId = /* @__PURE__ */ new Map();
7861
- const defaultEffortByFamilyId = /* @__PURE__ */ new Map();
7862
- for (const members of families.values()) {
7863
- const hasThinking = members.some((m) => m.thinking);
7864
- const leveled = members.map((member) => ({
7865
- member,
7866
- level: member.thinking ? member.effort : hasThinking ? "none" : member.effort
7867
- }));
7868
- const byLevel = /* @__PURE__ */ new Map();
7869
- const repEffortByCell = /* @__PURE__ */ new Map();
7870
- for (const { member, level } of leveled) {
7871
- const slot = member.fast ? "fast" : "normal";
7872
- const tier = byLevel.get(level) ?? {};
7873
- const cellKey = `${level}:${slot}`;
7874
- const upgradesNoneRep = level === "none" && member.effort === "medium" && repEffortByCell.get(cellKey) !== "medium";
7875
- if (tier[slot] === void 0 || upgradesNoneRep) {
7876
- tier[slot] = member.id;
7877
- repEffortByCell.set(cellKey, member.effort);
7878
- byLevel.set(level, tier);
7810
+ }
7811
+ function runAcpDynamicToolMcpServer() {
7812
+ const env = readEnvironment();
7813
+ const rl = createInterface2({ input: process.stdin, terminal: false });
7814
+ rl.on("line", (line) => {
7815
+ const trimmed = line.trim();
7816
+ if (!trimmed) {
7817
+ return;
7818
+ }
7819
+ let message;
7820
+ try {
7821
+ message = JSON.parse(trimmed);
7822
+ } catch {
7823
+ return;
7824
+ }
7825
+ void handleRequest(env, message);
7826
+ });
7827
+ }
7828
+
7829
+ // ../provider-bridge-acp/src/bridge/cursor-mcp-approval.ts
7830
+ var CURSOR_MCP_APPROVAL_FILE = "mcp-approvals.json";
7831
+ var CURSOR_APPROVAL_LOCK_STALE_MS = 3e4;
7832
+ var CURSOR_APPROVAL_LOCK_TIMEOUT_MS = 5e3;
7833
+ function errorCode(error) {
7834
+ return error instanceof Error && "code" in error ? error.code : void 0;
7835
+ }
7836
+ function cursorAgentCommand(command) {
7837
+ return basename2(command).toLowerCase().replace(/\.(?:bat|cmd|exe)$/u, "") === "cursor-agent";
7838
+ }
7839
+ function cursorProjectSlug(projectRoot) {
7840
+ return projectRoot.replace(/[^a-zA-Z0-9]/gu, "-").replace(/-+/gu, "-").replace(/^-+|-+$/gu, "");
7841
+ }
7842
+ function cursorDataDirectory(env) {
7843
+ const configured = env.CURSOR_DATA_DIR?.trim();
7844
+ if (configured) {
7845
+ return configured;
7846
+ }
7847
+ const home = env.HOME?.trim() || env.USERPROFILE?.trim() || homedir();
7848
+ return join2(home, ".cursor");
7849
+ }
7850
+ function cursorMcpServerConfig(config) {
7851
+ return {
7852
+ command: config.command,
7853
+ args: config.args,
7854
+ env: Object.fromEntries(config.env.map(({ name, value }) => [name, value]))
7855
+ };
7856
+ }
7857
+ function buildCursorMcpApprovalIdentifier(args) {
7858
+ const fingerprint = createHash("sha256").update(
7859
+ JSON.stringify({
7860
+ path: args.projectRoot,
7861
+ server: cursorMcpServerConfig(args.config)
7862
+ })
7863
+ ).digest("hex").slice(0, 16);
7864
+ return `${args.config.name}-${fingerprint}`;
7865
+ }
7866
+ async function resolveCursorProjectRoot(args) {
7867
+ return new Promise((resolveRoot) => {
7868
+ execFile3(
7869
+ "git",
7870
+ ["rev-parse", "--show-toplevel"],
7871
+ { cwd: args.cwd, env: args.env, windowsHide: true },
7872
+ (error, stdout) => {
7873
+ const root = stdout.trim();
7874
+ resolveRoot(error === null && root !== "" ? root : resolve2(args.cwd));
7879
7875
  }
7880
- }
7881
- const nonFast = leveled.filter((entry) => !entry.member.fast);
7882
- const pool = nonFast.length > 0 ? nonFast : leveled;
7883
- const defaultEntry = pool.find((entry) => entry.level === "medium") ?? pool.find((entry) => entry.level !== "none") ?? pool[0];
7884
- const defaultVariant = defaultEntry.member;
7885
- const levelsInLadderOrder = [...byLevel.keys()].sort(
7886
- (a, b) => reasoningLevelValues.indexOf(a) - reasoningLevelValues.indexOf(b)
7887
7876
  );
7888
- const nameByLevel = /* @__PURE__ */ new Map();
7889
- for (const { member, level } of leveled) {
7890
- if (!nameByLevel.has(level)) {
7891
- nameByLevel.set(level, member.displayName);
7892
- }
7877
+ });
7878
+ }
7879
+ async function readApprovals(path5) {
7880
+ let text;
7881
+ try {
7882
+ text = await readFile(path5, "utf8");
7883
+ } catch (error) {
7884
+ if (errorCode(error) === "ENOENT") {
7885
+ return [];
7893
7886
  }
7894
- models.push({
7895
- id: defaultVariant.id,
7896
- model: defaultVariant.id,
7897
- displayName: familyDisplayName(
7898
- defaultVariant.displayName,
7899
- defaultVariant.effortToken
7900
- ),
7901
- description: "",
7902
- supportedReasoningEfforts: levelsInLadderOrder.map((level) => ({
7903
- reasoningEffort: level,
7904
- description: nameByLevel.get(level) ?? ""
7905
- })),
7906
- defaultReasoningEffort: defaultEntry.level,
7907
- // The agent lists its default model first.
7908
- isDefault: models.length === 0
7887
+ throw error;
7888
+ }
7889
+ const value = JSON.parse(text);
7890
+ if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) {
7891
+ throw new Error(`Cursor MCP approval file is not a string array: ${path5}`);
7892
+ }
7893
+ return value;
7894
+ }
7895
+ async function writeApprovals(path5, approvals) {
7896
+ await mkdir(dirname(path5), { recursive: true });
7897
+ const tempPath = `${path5}.bb-${process.pid}-${randomBytes(6).toString("hex")}.tmp`;
7898
+ try {
7899
+ await writeFile(tempPath, `${JSON.stringify(approvals, null, 2)}
7900
+ `, {
7901
+ encoding: "utf8",
7902
+ mode: 384
7909
7903
  });
7910
- variantsByFamilyId.set(defaultVariant.id, byLevel);
7911
- defaultEffortByFamilyId.set(defaultVariant.id, defaultEntry.level);
7904
+ await rename(tempPath, path5);
7905
+ } catch (error) {
7906
+ await rm(tempPath, { force: true });
7907
+ throw error;
7912
7908
  }
7913
- return {
7914
- models,
7915
- resolveVariant({ model, reasoningLevel, serviceTier }) {
7916
- const byLevel = variantsByFamilyId.get(model);
7917
- if (!byLevel) {
7918
- return void 0;
7909
+ }
7910
+ async function acquireApprovalLock(path5) {
7911
+ const lockPath = `${path5}.bb-lock`;
7912
+ const deadline = Date.now() + CURSOR_APPROVAL_LOCK_TIMEOUT_MS;
7913
+ await mkdir(dirname(path5), { recursive: true });
7914
+ for (; ; ) {
7915
+ try {
7916
+ await mkdir(lockPath, { mode: 448 });
7917
+ return () => rm(lockPath, { recursive: true, force: true });
7918
+ } catch (error) {
7919
+ if (errorCode(error) !== "EEXIST") {
7920
+ throw error;
7919
7921
  }
7920
- const level = reasoningLevel ?? defaultEffortByFamilyId.get(model);
7921
- const tier = level === void 0 ? void 0 : byLevel.get(level);
7922
- if (!tier) {
7923
- return void 0;
7922
+ }
7923
+ try {
7924
+ const lockStat = await stat(lockPath);
7925
+ if (Date.now() - lockStat.mtimeMs > CURSOR_APPROVAL_LOCK_STALE_MS) {
7926
+ await rm(lockPath, { recursive: true, force: true });
7927
+ continue;
7924
7928
  }
7925
- if (serviceTier === "fast" && tier.fast !== void 0) {
7926
- return tier.fast;
7929
+ } catch (error) {
7930
+ if (errorCode(error) === "ENOENT") {
7931
+ continue;
7927
7932
  }
7928
- return tier.normal ?? tier.fast;
7933
+ throw error;
7929
7934
  }
7930
- };
7935
+ if (Date.now() >= deadline) {
7936
+ throw new Error(`Timed out updating Cursor MCP approvals: ${path5}`);
7937
+ }
7938
+ await new Promise((resolveDelay) => setTimeout(resolveDelay, 25));
7939
+ }
7931
7940
  }
7932
- function splitPrimaryModels(catalogModels, primaryModels) {
7933
- const primaryIds = new Set(primaryModels);
7934
- const modelsById = new Map(catalogModels.map((model) => [model.id, model]));
7935
- const models = primaryModels.flatMap((id) => {
7936
- const model = modelsById.get(id);
7937
- return model ? [model] : [];
7938
- });
7939
- if (models.length === 0) {
7940
- return { models: [...catalogModels], selectedOnlyModels: [] };
7941
+ async function mutateApprovals(path5, mutate) {
7942
+ const releaseLock = await acquireApprovalLock(path5);
7943
+ try {
7944
+ const approvals = await readApprovals(path5);
7945
+ const nextApprovals = mutate(approvals);
7946
+ if (approvals.length !== nextApprovals.length || approvals.some((approval, index) => approval !== nextApprovals[index])) {
7947
+ await writeApprovals(path5, nextApprovals);
7948
+ }
7949
+ } finally {
7950
+ await releaseLock();
7941
7951
  }
7942
- const selectedOnlyModels = catalogModels.filter(
7943
- (model) => !primaryIds.has(model.id)
7952
+ }
7953
+ async function approveCursorSessionMcpServer(args) {
7954
+ if (!cursorAgentCommand(args.agentCommand) || args.config.name !== ACP_BRIDGE_MCP_SERVER_NAME) {
7955
+ return void 0;
7956
+ }
7957
+ const projectRoot = await resolveCursorProjectRoot({
7958
+ cwd: args.cwd,
7959
+ env: args.env
7960
+ });
7961
+ const path5 = join2(
7962
+ cursorDataDirectory(args.env),
7963
+ "projects",
7964
+ cursorProjectSlug(projectRoot),
7965
+ CURSOR_MCP_APPROVAL_FILE
7944
7966
  );
7945
- if (models.some((model) => model.isDefault)) {
7946
- return {
7947
- models,
7948
- selectedOnlyModels: selectedOnlyModels.map(
7949
- (model) => model.isDefault ? { ...model, isDefault: false } : model
7950
- )
7951
- };
7967
+ const approval = buildCursorMcpApprovalIdentifier({
7968
+ config: args.config,
7969
+ projectRoot
7970
+ });
7971
+ let installedByBb = false;
7972
+ await mutateApprovals(path5, (approvals) => {
7973
+ if (approvals.includes(approval)) {
7974
+ return approvals;
7975
+ }
7976
+ installedByBb = true;
7977
+ return [...approvals, approval];
7978
+ });
7979
+ return { approval, installedByBb, path: path5 };
7980
+ }
7981
+ async function revokeCursorSessionMcpServer(approval) {
7982
+ if (!approval.installedByBb) {
7983
+ return;
7952
7984
  }
7953
- return {
7954
- models: models.map(
7955
- (model, index) => index === 0 ? { ...model, isDefault: true } : model
7956
- ),
7957
- selectedOnlyModels: selectedOnlyModels.map(
7958
- (model) => model.isDefault ? { ...model, isDefault: false } : model
7959
- )
7960
- };
7985
+ await mutateApprovals(
7986
+ approval.path,
7987
+ (approvals) => approvals.filter((candidate) => candidate !== approval.approval)
7988
+ );
7961
7989
  }
7962
7990
 
7963
7991
  // ../provider-bridge-acp/src/bridge/bridge.ts
@@ -8315,6 +8343,13 @@ async function authenticateAcpAgent(args) {
8315
8343
  );
8316
8344
  }
8317
8345
  }
8346
+ function acpClientCapabilities(parameterizedModelPicker, fsAccess = false) {
8347
+ return {
8348
+ fs: { readTextFile: fsAccess, writeTextFile: fsAccess },
8349
+ terminal: false,
8350
+ ...parameterizedModelPicker === true ? { _meta: { parameterizedModelPicker: true } } : {}
8351
+ };
8352
+ }
8318
8353
  async function loadAgentModelCatalog(listCommand) {
8319
8354
  const stdout = await new Promise((resolveExec, rejectExec) => {
8320
8355
  execFile4(
@@ -8364,8 +8399,12 @@ async function loadAgentModelCatalog(listCommand) {
8364
8399
  cachedModelCatalog = { key, catalog };
8365
8400
  return catalog;
8366
8401
  }
8367
- async function loadSessionDiscoveredModels(agent) {
8368
- const key = JSON.stringify(agent);
8402
+ async function loadSessionDiscoveredModels(agent, reasoningProbePriorityModelIds, parameterizedModelPicker) {
8403
+ const key = JSON.stringify({
8404
+ agent,
8405
+ reasoningProbePriorityModelIds,
8406
+ parameterizedModelPicker
8407
+ });
8369
8408
  if (cachedSessionDiscoveredModels?.key === key && Date.now() - cachedSessionDiscoveredModels.fetchedAt < SESSION_MODEL_DISCOVERY_TTL_MS) {
8370
8409
  return cachedSessionDiscoveredModels.models;
8371
8410
  }
@@ -8406,10 +8445,7 @@ async function loadSessionDiscoveredModels(agent) {
8406
8445
  params: {
8407
8446
  protocolVersion: ACP_PROTOCOL_VERSION,
8408
8447
  clientInfo: { name: "bb", version: "1.0.0" },
8409
- clientCapabilities: {
8410
- fs: { readTextFile: false, writeTextFile: false },
8411
- terminal: false
8412
- }
8448
+ clientCapabilities: acpClientCapabilities(parameterizedModelPicker)
8413
8449
  },
8414
8450
  resultSchema: acpInitializeResultSchema
8415
8451
  });
@@ -8447,7 +8483,8 @@ async function loadSessionDiscoveredModels(agent) {
8447
8483
  const reasoningByModel = await discoverAcpNativeReasoningByModel({
8448
8484
  connection,
8449
8485
  sessionId: newSession.sessionId,
8450
- modelOption
8486
+ modelOption,
8487
+ reasoningProbePriorityModelIds
8451
8488
  });
8452
8489
  const models = reasoningByModel === null ? configOptionModels : buildModelCatalogFromConfigOptions(modelOption, reasoningByModel);
8453
8490
  cachedSessionDiscoveredModels = {
@@ -8475,6 +8512,23 @@ async function discoverAcpNativeReasoningByModel(args) {
8475
8512
  return null;
8476
8513
  }
8477
8514
  const modelOption = args.modelOption;
8515
+ const modelByValue = new Map(
8516
+ modelOptions.map((model) => [model.value, model])
8517
+ );
8518
+ const modelsToProbe = [];
8519
+ const addedModels = /* @__PURE__ */ new Set();
8520
+ for (const value of args.reasoningProbePriorityModelIds) {
8521
+ const model = modelByValue.get(value);
8522
+ if (model && !addedModels.has(model.value)) {
8523
+ modelsToProbe.push(model);
8524
+ addedModels.add(model.value);
8525
+ }
8526
+ }
8527
+ for (const model of modelOptions) {
8528
+ if (!addedModels.has(model.value)) {
8529
+ modelsToProbe.push(model);
8530
+ }
8531
+ }
8478
8532
  const supportByModel = /* @__PURE__ */ new Map();
8479
8533
  let timeout;
8480
8534
  const timeoutReached = new Promise((resolve4) => {
@@ -8486,7 +8540,7 @@ async function discoverAcpNativeReasoningByModel(args) {
8486
8540
  try {
8487
8541
  return await Promise.race([
8488
8542
  (async () => {
8489
- for (const model of modelOptions) {
8543
+ for (const model of modelsToProbe) {
8490
8544
  const configState = await args.connection.request({
8491
8545
  method: "session/set_config_option",
8492
8546
  params: {
@@ -8646,6 +8700,12 @@ async function selectAcpNativeModel(args) {
8646
8700
  modelSelection: selection,
8647
8701
  nativeReasoning: args.nativeReasoning
8648
8702
  });
8703
+ await selectAcpNativeServiceTier({
8704
+ connection: args.connection,
8705
+ sessionId: args.sessionId,
8706
+ configOptions,
8707
+ modelSelection: selection
8708
+ });
8649
8709
  }
8650
8710
  async function selectAcpNativeReasoning(args) {
8651
8711
  const reasoningLevel = args.modelSelection.reasoningLevel;
@@ -8676,6 +8736,28 @@ async function selectAcpNativeReasoning(args) {
8676
8736
  } catch {
8677
8737
  }
8678
8738
  }
8739
+ async function selectAcpNativeServiceTier(args) {
8740
+ const serviceTier = args.modelSelection.serviceTier;
8741
+ if (serviceTier === void 0) {
8742
+ return;
8743
+ }
8744
+ const fastOption = (args.configOptions ?? []).find(
8745
+ (option) => option.id === "fast" && option.type === "select"
8746
+ );
8747
+ const value = serviceTier === "fast" ? "true" : "false";
8748
+ if (!fastOption?.options?.some((option) => option.value === value)) {
8749
+ return;
8750
+ }
8751
+ await args.connection.request({
8752
+ method: "session/set_config_option",
8753
+ params: {
8754
+ sessionId: args.sessionId,
8755
+ configId: fastOption.id,
8756
+ value
8757
+ },
8758
+ resultSchema: acpConfigStateResultSchema
8759
+ });
8760
+ }
8679
8761
  function buildPromptContentBlocks(session, input) {
8680
8762
  const blocks = [];
8681
8763
  const instructions = session.pendingInstructions;
@@ -9041,10 +9123,10 @@ async function startAgentSession(request) {
9041
9123
  params: {
9042
9124
  protocolVersion: ACP_PROTOCOL_VERSION,
9043
9125
  clientInfo: { name: "bb", version: "1.0.0" },
9044
- clientCapabilities: {
9045
- fs: { readTextFile: true, writeTextFile: true },
9046
- terminal: false
9047
- }
9126
+ clientCapabilities: acpClientCapabilities(
9127
+ params.parameterizedModelPicker,
9128
+ true
9129
+ )
9048
9130
  },
9049
9131
  resultSchema: acpInitializeResultSchema
9050
9132
  });
@@ -9514,15 +9596,22 @@ async function handleModelList(id, params) {
9514
9596
  );
9515
9597
  return;
9516
9598
  }
9517
- const sessionDiscoveredModels = params.listCommand === void 0 && params.agent ? await loadSessionDiscoveredModels(params.agent) : null;
9599
+ const sessionDiscoveredModels = params.listCommand === void 0 && params.agent ? await loadSessionDiscoveredModels(
9600
+ params.agent,
9601
+ params.reasoningProbePriorityModelIds,
9602
+ params.parameterizedModelPicker
9603
+ ) : null;
9518
9604
  if (sessionDiscoveredModels) {
9519
- sendResult(id, {
9520
- models: applyConfiguredReasoningToModels(sessionDiscoveredModels, {
9521
- reasoningCli: params.reasoningCli,
9522
- nativeReasoning: params.nativeReasoning
9523
- }),
9524
- selectedOnlyModels: []
9525
- });
9605
+ sendResult(
9606
+ id,
9607
+ splitPrimaryModels(
9608
+ applyConfiguredReasoningToModels(sessionDiscoveredModels, {
9609
+ reasoningCli: params.reasoningCli,
9610
+ nativeReasoning: params.nativeReasoning
9611
+ }),
9612
+ params.primaryModels
9613
+ )
9614
+ );
9526
9615
  return;
9527
9616
  }
9528
9617
  sendResult(id, {
@@ -9554,8 +9643,24 @@ var acpProviderOptionsSchema = z39.object({
9554
9643
  * the provider, so a third-party registration of a known agent gets the
9555
9644
  * same reporting fidelity a first-party one does.
9556
9645
  */
9557
- acpDialect: z39.string().min(1).optional()
9646
+ acpDialect: z39.string().min(1).optional(),
9647
+ /** Enables an agent's separate model configuration options. */
9648
+ parameterizedModelPicker: z39.boolean().optional(),
9649
+ /** Bare model ids shown before the collapsed "More models" pool. */
9650
+ primaryModels: z39.array(z39.string().min(1)).optional(),
9651
+ /** Model ids to probe first during bounded native discovery. */
9652
+ reasoningProbePriorityModelIds: z39.array(z39.string().min(1)).optional()
9558
9653
  }).passthrough();
9654
+ function decodeAcpModelPickerOptions(providerOptions) {
9655
+ const parsed = acpProviderOptionsSchema.parse(providerOptions ?? {});
9656
+ return {
9657
+ parameterizedModelPicker: parsed.parameterizedModelPicker === true,
9658
+ ...parsed.primaryModels === void 0 ? {} : { primaryModels: [...parsed.primaryModels] },
9659
+ reasoningProbePriorityModelIds: [
9660
+ ...parsed.reasoningProbePriorityModelIds ?? []
9661
+ ]
9662
+ };
9663
+ }
9559
9664
  function decodeAdditionalWorkspaceWriteRoots(providerOptions) {
9560
9665
  return acpProviderOptionsSchema.parse(providerOptions ?? {}).additionalWorkspaceWriteRoots ?? [];
9561
9666
  }
@@ -9597,14 +9702,19 @@ async function handleRequest2(request) {
9597
9702
  };
9598
9703
  sendResult(request.id, result);
9599
9704
  return;
9600
- case "model/list":
9705
+ case "model/list": {
9706
+ const modelPicker = decodeAcpModelPickerOptions(
9707
+ request.params.providerOptions
9708
+ );
9601
9709
  await handleModelList(
9602
9710
  request.id,
9603
9711
  buildAcpModelListParams(
9604
- decodeLaunchSpec(request.params.providerOptions)
9712
+ decodeLaunchSpec(request.params.providerOptions),
9713
+ modelPicker
9605
9714
  )
9606
9715
  );
9607
9716
  return;
9717
+ }
9608
9718
  case "provider/health": {
9609
9719
  const launchSpec = decodeLaunchSpec(request.params.providerOptions);
9610
9720
  sendResult(
@@ -9683,6 +9793,9 @@ async function handleRequest2(request) {
9683
9793
  );
9684
9794
  return;
9685
9795
  }
9796
+ const modelPicker = decodeAcpModelPickerOptions(
9797
+ params.options.providerOptions
9798
+ );
9686
9799
  const sessionParams = buildAcpSessionParams({
9687
9800
  additionalWorkspaceWriteRoots: decodeAdditionalWorkspaceWriteRoots(
9688
9801
  params.options.providerOptions
@@ -9694,6 +9807,7 @@ async function handleRequest2(request) {
9694
9807
  ...params.options,
9695
9808
  skillRoots: configuredSkillRoots ?? void 0
9696
9809
  },
9810
+ parameterizedModelPicker: modelPicker.parameterizedModelPicker,
9697
9811
  launchSpec,
9698
9812
  providerLabel: launchSpec.displayName,
9699
9813
  threadId: params.threadId