@openma/deepseek-harness-acp 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/bridge.js +255 -55
  2. package/dist/index.js +258 -55
  3. package/package.json +1 -1
package/dist/bridge.js CHANGED
@@ -23,8 +23,11 @@ import { SessionId } from "@deepseek-ai/dsh-session";
23
23
  import { foldSessionTitle } from "@deepseek-ai/dsh-session-title";
24
24
  import { SANDBOX_MODES, setSandboxMode } from "@deepseek-ai/dsh-sandbox-policy";
25
25
  import { credentialRef } from "@deepseek-ai/dsh-credentials";
26
+ import * as agentModule from "@deepseek-ai/dsh-agent";
26
27
  import * as mcpClient from "@deepseek-ai/dsh-mcp-client";
27
28
  function selfHarness() {
29
+ const agents = agentModule;
30
+ const installModelSelection = agents.installModelSelection ?? agents.ln;
28
31
  return {
29
32
  createUserMessage,
30
33
  errorChain,
@@ -33,6 +36,7 @@ function selfHarness() {
33
36
  setSandboxMode,
34
37
  sandboxModes: SANDBOX_MODES,
35
38
  credentialRef,
39
+ ...installModelSelection !== void 0 ? { installModelSelection } : {},
36
40
  mcpClient
37
41
  };
38
42
  }
@@ -610,6 +614,7 @@ async function apply(ctx, config = {}) {
610
614
  return { provider: value.slice(0, i), model: value.slice(i + 2) };
611
615
  };
612
616
  let liveCatalog;
617
+ const effortCache = /* @__PURE__ */ new Map();
613
618
  const discoverModels = async () => {
614
619
  if (liveCatalog !== void 0) return liveCatalog;
615
620
  const llm = ctx.get("llm");
@@ -640,7 +645,136 @@ async function apply(ctx, config = {}) {
640
645
  };
641
646
  ctx.on("llm/adapters-updated", () => {
642
647
  liveCatalog = void 0;
648
+ effortCache.clear();
643
649
  });
650
+ const routeOf = (record) => {
651
+ const logged = loggedConfig(record);
652
+ const provider = record.provider ?? defaultProvider() ?? logged?.provider;
653
+ const model = record.model ?? config.model ?? defaultSelection().model ?? logged?.model ?? record.agent.options.model;
654
+ return {
655
+ ...provider !== void 0 ? { provider } : {},
656
+ ...model !== void 0 ? { model } : {}
657
+ };
658
+ };
659
+ const loggedConfig = (record) => {
660
+ try {
661
+ const header = record.agent.session.requestHeader?.();
662
+ const cfg = header?.config;
663
+ if (cfg === void 0) return void 0;
664
+ return {
665
+ provider: cfg.provider,
666
+ model: cfg.model,
667
+ ...cfg.reasoningEffort !== void 0 ? { reasoningEffort: String(cfg.reasoningEffort) } : {}
668
+ };
669
+ } catch {
670
+ return void 0;
671
+ }
672
+ };
673
+ const effortCatalog = async (provider, model) => {
674
+ if (provider === void 0 || model === void 0) return void 0;
675
+ const key = `${provider}::${model}`;
676
+ if (effortCache.has(key)) return effortCache.get(key);
677
+ const llm = ctx.get("llm");
678
+ let catalog;
679
+ try {
680
+ const reasoning = (await llm?.resolveModelInfo?.(provider, model))?.reasoning;
681
+ if (reasoning !== void 0 && reasoning.efforts.length > 0) {
682
+ catalog = {
683
+ efforts: reasoning.efforts.map((effort) => ({
684
+ id: String(effort.id),
685
+ name: effort.name,
686
+ ...effort.description !== void 0 ? { description: effort.description } : {}
687
+ })),
688
+ ...reasoning.defaultEffort !== void 0 ? { defaultEffort: String(reasoning.defaultEffort) } : {}
689
+ };
690
+ }
691
+ } catch (error) {
692
+ logDebug(`resolveModelInfo(${key}) failed: ${String(error)}`);
693
+ }
694
+ effortCache.set(key, catalog);
695
+ return catalog;
696
+ };
697
+ const ensureSelection = (record) => {
698
+ const install = harness.installModelSelection;
699
+ if (install === void 0) return void 0;
700
+ if (record.selection !== void 0) return record.selection;
701
+ let picked;
702
+ const selection = {
703
+ get current() {
704
+ if (picked !== void 0) return picked;
705
+ const logged = loggedConfig(record);
706
+ if (logged !== void 0) return logged;
707
+ const { provider, model } = routeOf(record);
708
+ if (provider === void 0 || model === void 0) return void 0;
709
+ return { provider, model };
710
+ },
711
+ set current(next) {
712
+ picked = next;
713
+ },
714
+ assembled: void 0
715
+ };
716
+ try {
717
+ install(record.agent.ctx, selection);
718
+ } catch (error) {
719
+ logWarn(`installModelSelection failed: ${String(error)}`);
720
+ return void 0;
721
+ }
722
+ record.selection = selection;
723
+ return selection;
724
+ };
725
+ const switchModel = async (record, acpSessionId, value) => {
726
+ if (record.inflight !== void 0) {
727
+ throw invalidParams("cannot switch models while a prompt is running");
728
+ }
729
+ const choice = decodeChoice(value);
730
+ const known = /* @__PURE__ */ new Set([
731
+ ...modelCandidates().map((model) => encodeChoice(void 0, model)),
732
+ ...(await discoverModels()).map((entry) => encodeChoice(entry.provider, entry.model)),
733
+ encodeChoice(record.provider, record.model ?? "")
734
+ ]);
735
+ if (!known.has(value)) throw invalidParams(`unknown model: ${value}`);
736
+ if (choice.model === record.model && choice.provider === (record.provider ?? defaultProvider())) return;
737
+ const sessionId = record.agent.session.id;
738
+ await record.dispose().catch((error) => {
739
+ logWarn(`dispose during model switch failed: ${String(error)}`);
740
+ });
741
+ let handle;
742
+ try {
743
+ handle = await agents.resume({
744
+ resumeSessionId: sessionId,
745
+ agentOptions: agentOptionsFor(choice.model, choice.provider)
746
+ });
747
+ } catch (error) {
748
+ sessions.delete(acpSessionId);
749
+ throw internalError(`model switch failed: ${errorChain2(error)}`);
750
+ }
751
+ record.agent = handle.agent;
752
+ record.dispose = () => handle.dispose();
753
+ record.model = choice.model;
754
+ if (choice.provider !== void 0 && choice.provider !== defaultProvider()) {
755
+ record.provider = choice.provider;
756
+ } else {
757
+ delete record.provider;
758
+ }
759
+ delete record.selection;
760
+ if (record.effort !== void 0) {
761
+ const route = routeOf(record);
762
+ const catalog = await effortCatalog(route.provider, route.model);
763
+ if (catalog?.efforts.some((effort) => effort.id === record.effort) === true && route.provider !== void 0 && route.model !== void 0) {
764
+ const selection = ensureSelection(record);
765
+ if (selection !== void 0) {
766
+ selection.current = {
767
+ provider: route.provider,
768
+ model: route.model,
769
+ reasoningEffort: record.effort
770
+ };
771
+ }
772
+ } else {
773
+ delete record.effort;
774
+ }
775
+ }
776
+ setApprovalPolicy(record, record.approvals);
777
+ };
644
778
  const credentialPresent = async () => {
645
779
  if (envCredentialPresent()) return true;
646
780
  const brand = harness.credentialRef;
@@ -698,7 +832,23 @@ async function apply(ctx, config = {}) {
698
832
  return { id: mode, name: label.name, description: label.description };
699
833
  })
700
834
  });
701
- const configOptions = async (record) => {
835
+ const setApprovalPolicy = (record, policy) => {
836
+ try {
837
+ ctx.get("approval")?.setPolicy?.(record.agent, policy);
838
+ } catch (error) {
839
+ logWarn(`approval policy switch failed: ${String(error)}`);
840
+ }
841
+ record.approvals = policy;
842
+ };
843
+ const applyMode = (record, sessionId, modeId) => {
844
+ const mode = SANDBOX_MODES2.find((candidate) => candidate === modeId);
845
+ if (mode === void 0) throw invalidParams(`unknown mode: ${modeId}`);
846
+ setSandboxMode2(record.agent.session, mode);
847
+ setApprovalPolicy(record, mode === "danger-full-access" ? "never" : "ask");
848
+ record.modeId = mode;
849
+ notify(sessionId, { sessionUpdate: "current_mode_update", currentModeId: mode });
850
+ };
851
+ const modelChoices = async (record) => {
702
852
  const seen = /* @__PURE__ */ new Set();
703
853
  const options = [];
704
854
  const push = (value, name3) => {
@@ -717,17 +867,67 @@ async function apply(ctx, config = {}) {
717
867
  push(encodeChoice(choice.provider, choice.model), choice.label);
718
868
  }
719
869
  for (const model of modelCandidates()) push(encodeChoice(void 0, model), model);
720
- if (options.length < 2) return [];
721
- return [
722
- {
870
+ return options;
871
+ };
872
+ const configOptions = async (record) => {
873
+ const result = [];
874
+ result.push({
875
+ type: "select",
876
+ id: "mode",
877
+ name: "Mode",
878
+ category: "mode",
879
+ currentValue: record.modeId,
880
+ options: SANDBOX_MODES2.map((mode) => {
881
+ const label = MODE_LABELS[mode] ?? { name: mode, description: "" };
882
+ return {
883
+ value: mode,
884
+ name: label.name,
885
+ ...label.description.length > 0 ? { description: label.description } : {}
886
+ };
887
+ })
888
+ });
889
+ const models = await modelChoices(record);
890
+ if (models.length >= 2) {
891
+ result.push({
723
892
  type: "select",
724
893
  id: "model",
725
894
  name: "Model",
726
895
  category: "model",
727
- currentValue: current,
728
- options
729
- }
730
- ];
896
+ currentValue: models[0].value,
897
+ options: models
898
+ });
899
+ }
900
+ const route = routeOf(record);
901
+ const efforts = await effortCatalog(route.provider, route.model);
902
+ if (efforts !== void 0 && efforts.efforts.length >= 2) {
903
+ const known = new Set(efforts.efforts.map((effort) => effort.id));
904
+ const preferred = [record.effort, loggedConfig(record)?.reasoningEffort, efforts.defaultEffort].find(
905
+ (candidate) => candidate !== void 0 && known.has(candidate)
906
+ );
907
+ result.push({
908
+ type: "select",
909
+ id: "effort",
910
+ name: "Reasoning",
911
+ category: "thought_level",
912
+ currentValue: preferred ?? efforts.efforts[0].id,
913
+ options: efforts.efforts.map((effort) => ({
914
+ value: effort.id,
915
+ name: effort.name,
916
+ ...effort.description !== void 0 ? { description: effort.description } : {}
917
+ }))
918
+ });
919
+ }
920
+ result.push({
921
+ type: "select",
922
+ id: "approvals",
923
+ name: "Approvals",
924
+ currentValue: record.approvals,
925
+ options: [
926
+ { value: "ask", name: "Ask", description: "Ask before actions beyond the sandbox mode" },
927
+ { value: "never", name: "Never ask", description: "Auto-approve every permission request" }
928
+ ]
929
+ });
930
+ return result;
731
931
  };
732
932
  const publishCommands = (sessionId) => {
733
933
  notify(sessionId, {
@@ -809,15 +1009,19 @@ async function apply(ctx, config = {}) {
809
1009
  };
810
1010
  const statusText = async (record) => {
811
1011
  const used = record.projection.contextWindow;
1012
+ const route = routeOf(record);
1013
+ const effort = record.effort ?? loggedConfig(record)?.reasoningEffort;
812
1014
  const lines = [
813
1015
  `**dsh-acp** ${VERSION} \u2014 DeepSeek Harness ACP bridge`,
814
1016
  "",
815
1017
  `| | |`,
816
1018
  `|---|---|`,
817
- `| Provider | ${record.provider ?? defaultProvider() ?? "(composition default)"} |`,
818
- `| Model | ${record.model ?? config.model ?? defaultSelection().model ?? record.agent.options.model ?? "(composition default)"} |`,
1019
+ `| Provider | ${route.provider ?? "(composition default)"} |`,
1020
+ `| Model | ${route.model ?? "(composition default)"} |`,
1021
+ `| Reasoning | ${effort ?? "(adapter default)"} |`,
819
1022
  `| Credential | ${await describeCredential()} |`,
820
1023
  `| Permission mode | ${record.modeId} |`,
1024
+ `| Approvals | ${record.approvals} |`,
821
1025
  `| Workspace | ${record.agent.session.header.cwd ?? process.cwd()} |`,
822
1026
  `| Session | ${String(record.agent.session.id)} |`,
823
1027
  ...used !== void 0 ? [`| Context window | ${used.toLocaleString()} tokens |`] : []
@@ -831,6 +1035,7 @@ async function apply(ctx, config = {}) {
831
1035
  projection: projection ?? new SessionProjection(),
832
1036
  modeId,
833
1037
  model,
1038
+ approvals: modeId === "danger-full-access" ? "never" : "ask",
834
1039
  cancelled: false,
835
1040
  inflight: void 0
836
1041
  };
@@ -1095,58 +1300,53 @@ async function apply(ctx, config = {}) {
1095
1300
  const usage = record.projection.promptUsage();
1096
1301
  return { stopReason, ...usage !== void 0 ? { usage } : {} };
1097
1302
  },
1098
- setSessionMode(params) {
1303
+ async setSessionMode(params) {
1099
1304
  const record = requireSession(params.sessionId);
1100
- const mode = SANDBOX_MODES2.find((candidate) => candidate === params.modeId);
1101
- if (mode === void 0) throw invalidParams(`unknown mode: ${params.modeId}`);
1102
- setSandboxMode2(record.agent.session, mode);
1103
- try {
1104
- ctx.get("approval")?.setPolicy?.(record.agent, mode === "danger-full-access" ? "never" : "ask");
1105
- } catch (error) {
1106
- logWarn(`approval policy switch failed: ${String(error)}`);
1107
- }
1108
- record.modeId = mode;
1109
- notify(params.sessionId, { sessionUpdate: "current_mode_update", currentModeId: mode });
1110
- return Promise.resolve({});
1305
+ applyMode(record, params.sessionId, params.modeId);
1306
+ notify(params.sessionId, {
1307
+ sessionUpdate: "config_option_update",
1308
+ configOptions: await configOptions(record)
1309
+ });
1310
+ return {};
1111
1311
  },
1112
1312
  async setSessionConfigOption(params) {
1113
1313
  const record = requireSession(params.sessionId);
1114
- if (params.configId !== "model") throw invalidParams(`unknown config option: ${params.configId}`);
1115
- if (record.inflight !== void 0) {
1116
- throw invalidParams("cannot switch models while a prompt is running");
1117
- }
1118
1314
  const value = typeof params.value === "string" ? params.value : void 0;
1119
- if (value === void 0) throw invalidParams(`unknown model: ${String(params.value)}`);
1120
- const choice = decodeChoice(value);
1121
- const known = /* @__PURE__ */ new Set([
1122
- ...modelCandidates().map((model) => encodeChoice(void 0, model)),
1123
- ...(await discoverModels()).map((entry) => encodeChoice(entry.provider, entry.model)),
1124
- encodeChoice(record.provider, record.model ?? "")
1125
- ]);
1126
- if (!known.has(value)) throw invalidParams(`unknown model: ${value}`);
1127
- if (choice.model !== record.model || choice.provider !== (record.provider ?? defaultProvider())) {
1128
- const sessionId = record.agent.session.id;
1129
- await record.dispose().catch((error) => {
1130
- logWarn(`dispose during model switch failed: ${String(error)}`);
1131
- });
1132
- let handle;
1133
- try {
1134
- handle = await agents.resume({
1135
- resumeSessionId: sessionId,
1136
- agentOptions: agentOptionsFor(choice.model, choice.provider)
1137
- });
1138
- } catch (error) {
1139
- sessions.delete(params.sessionId);
1140
- throw internalError(`model switch failed: ${errorChain2(error)}`);
1315
+ if (value === void 0) throw invalidParams(`invalid value: ${String(params.value)}`);
1316
+ switch (params.configId) {
1317
+ case "mode": {
1318
+ applyMode(record, params.sessionId, value);
1319
+ break;
1320
+ }
1321
+ case "approvals": {
1322
+ if (value !== "ask" && value !== "never") throw invalidParams(`unknown approvals: ${value}`);
1323
+ setApprovalPolicy(record, value);
1324
+ break;
1325
+ }
1326
+ case "effort": {
1327
+ const route = routeOf(record);
1328
+ const catalog = await effortCatalog(route.provider, route.model);
1329
+ if (catalog === void 0 || !catalog.efforts.some((effort) => effort.id === value)) {
1330
+ throw invalidParams(`unknown effort: ${value}`);
1331
+ }
1332
+ const selection = ensureSelection(record);
1333
+ if (selection === void 0 || route.provider === void 0 || route.model === void 0) {
1334
+ throw invalidParams("reasoning effort switching is unavailable on this host");
1335
+ }
1336
+ selection.current = {
1337
+ provider: route.provider,
1338
+ model: route.model,
1339
+ reasoningEffort: value
1340
+ };
1341
+ record.effort = value;
1342
+ break;
1141
1343
  }
1142
- record.agent = handle.agent;
1143
- record.dispose = () => handle.dispose();
1144
- record.model = choice.model;
1145
- if (choice.provider !== void 0 && choice.provider !== defaultProvider()) {
1146
- record.provider = choice.provider;
1147
- } else {
1148
- delete record.provider;
1344
+ case "model": {
1345
+ await switchModel(record, params.sessionId, value);
1346
+ break;
1149
1347
  }
1348
+ default:
1349
+ throw invalidParams(`unknown config option: ${params.configId}`);
1150
1350
  }
1151
1351
  return { configOptions: await configOptions(record) };
1152
1352
  },
package/dist/index.js CHANGED
@@ -24,8 +24,11 @@ import { SessionId } from "@deepseek-ai/dsh-session";
24
24
  import { foldSessionTitle } from "@deepseek-ai/dsh-session-title";
25
25
  import { SANDBOX_MODES, setSandboxMode } from "@deepseek-ai/dsh-sandbox-policy";
26
26
  import { credentialRef } from "@deepseek-ai/dsh-credentials";
27
+ import * as agentModule from "@deepseek-ai/dsh-agent";
27
28
  import * as mcpClient from "@deepseek-ai/dsh-mcp-client";
28
29
  function selfHarness() {
30
+ const agents = agentModule;
31
+ const installModelSelection = agents.installModelSelection ?? agents.ln;
29
32
  return {
30
33
  createUserMessage,
31
34
  errorChain,
@@ -34,6 +37,7 @@ function selfHarness() {
34
37
  setSandboxMode,
35
38
  sandboxModes: SANDBOX_MODES,
36
39
  credentialRef,
40
+ ...installModelSelection !== void 0 ? { installModelSelection } : {},
37
41
  mcpClient
38
42
  };
39
43
  }
@@ -628,6 +632,7 @@ async function apply(ctx, config = {}) {
628
632
  return { provider: value.slice(0, i), model: value.slice(i + 2) };
629
633
  };
630
634
  let liveCatalog;
635
+ const effortCache = /* @__PURE__ */ new Map();
631
636
  const discoverModels = async () => {
632
637
  if (liveCatalog !== void 0) return liveCatalog;
633
638
  const llm = ctx.get("llm");
@@ -658,7 +663,136 @@ async function apply(ctx, config = {}) {
658
663
  };
659
664
  ctx.on("llm/adapters-updated", () => {
660
665
  liveCatalog = void 0;
666
+ effortCache.clear();
661
667
  });
668
+ const routeOf = (record) => {
669
+ const logged = loggedConfig(record);
670
+ const provider = record.provider ?? defaultProvider() ?? logged?.provider;
671
+ const model = record.model ?? config.model ?? defaultSelection().model ?? logged?.model ?? record.agent.options.model;
672
+ return {
673
+ ...provider !== void 0 ? { provider } : {},
674
+ ...model !== void 0 ? { model } : {}
675
+ };
676
+ };
677
+ const loggedConfig = (record) => {
678
+ try {
679
+ const header = record.agent.session.requestHeader?.();
680
+ const cfg = header?.config;
681
+ if (cfg === void 0) return void 0;
682
+ return {
683
+ provider: cfg.provider,
684
+ model: cfg.model,
685
+ ...cfg.reasoningEffort !== void 0 ? { reasoningEffort: String(cfg.reasoningEffort) } : {}
686
+ };
687
+ } catch {
688
+ return void 0;
689
+ }
690
+ };
691
+ const effortCatalog = async (provider, model) => {
692
+ if (provider === void 0 || model === void 0) return void 0;
693
+ const key = `${provider}::${model}`;
694
+ if (effortCache.has(key)) return effortCache.get(key);
695
+ const llm = ctx.get("llm");
696
+ let catalog;
697
+ try {
698
+ const reasoning = (await llm?.resolveModelInfo?.(provider, model))?.reasoning;
699
+ if (reasoning !== void 0 && reasoning.efforts.length > 0) {
700
+ catalog = {
701
+ efforts: reasoning.efforts.map((effort) => ({
702
+ id: String(effort.id),
703
+ name: effort.name,
704
+ ...effort.description !== void 0 ? { description: effort.description } : {}
705
+ })),
706
+ ...reasoning.defaultEffort !== void 0 ? { defaultEffort: String(reasoning.defaultEffort) } : {}
707
+ };
708
+ }
709
+ } catch (error) {
710
+ logDebug(`resolveModelInfo(${key}) failed: ${String(error)}`);
711
+ }
712
+ effortCache.set(key, catalog);
713
+ return catalog;
714
+ };
715
+ const ensureSelection = (record) => {
716
+ const install = harness.installModelSelection;
717
+ if (install === void 0) return void 0;
718
+ if (record.selection !== void 0) return record.selection;
719
+ let picked;
720
+ const selection = {
721
+ get current() {
722
+ if (picked !== void 0) return picked;
723
+ const logged = loggedConfig(record);
724
+ if (logged !== void 0) return logged;
725
+ const { provider, model } = routeOf(record);
726
+ if (provider === void 0 || model === void 0) return void 0;
727
+ return { provider, model };
728
+ },
729
+ set current(next) {
730
+ picked = next;
731
+ },
732
+ assembled: void 0
733
+ };
734
+ try {
735
+ install(record.agent.ctx, selection);
736
+ } catch (error) {
737
+ logWarn(`installModelSelection failed: ${String(error)}`);
738
+ return void 0;
739
+ }
740
+ record.selection = selection;
741
+ return selection;
742
+ };
743
+ const switchModel = async (record, acpSessionId, value) => {
744
+ if (record.inflight !== void 0) {
745
+ throw invalidParams("cannot switch models while a prompt is running");
746
+ }
747
+ const choice = decodeChoice(value);
748
+ const known = /* @__PURE__ */ new Set([
749
+ ...modelCandidates().map((model) => encodeChoice(void 0, model)),
750
+ ...(await discoverModels()).map((entry) => encodeChoice(entry.provider, entry.model)),
751
+ encodeChoice(record.provider, record.model ?? "")
752
+ ]);
753
+ if (!known.has(value)) throw invalidParams(`unknown model: ${value}`);
754
+ if (choice.model === record.model && choice.provider === (record.provider ?? defaultProvider())) return;
755
+ const sessionId = record.agent.session.id;
756
+ await record.dispose().catch((error) => {
757
+ logWarn(`dispose during model switch failed: ${String(error)}`);
758
+ });
759
+ let handle;
760
+ try {
761
+ handle = await agents.resume({
762
+ resumeSessionId: sessionId,
763
+ agentOptions: agentOptionsFor(choice.model, choice.provider)
764
+ });
765
+ } catch (error) {
766
+ sessions.delete(acpSessionId);
767
+ throw internalError(`model switch failed: ${errorChain2(error)}`);
768
+ }
769
+ record.agent = handle.agent;
770
+ record.dispose = () => handle.dispose();
771
+ record.model = choice.model;
772
+ if (choice.provider !== void 0 && choice.provider !== defaultProvider()) {
773
+ record.provider = choice.provider;
774
+ } else {
775
+ delete record.provider;
776
+ }
777
+ delete record.selection;
778
+ if (record.effort !== void 0) {
779
+ const route = routeOf(record);
780
+ const catalog = await effortCatalog(route.provider, route.model);
781
+ if (catalog?.efforts.some((effort) => effort.id === record.effort) === true && route.provider !== void 0 && route.model !== void 0) {
782
+ const selection = ensureSelection(record);
783
+ if (selection !== void 0) {
784
+ selection.current = {
785
+ provider: route.provider,
786
+ model: route.model,
787
+ reasoningEffort: record.effort
788
+ };
789
+ }
790
+ } else {
791
+ delete record.effort;
792
+ }
793
+ }
794
+ setApprovalPolicy(record, record.approvals);
795
+ };
662
796
  const credentialPresent = async () => {
663
797
  if (envCredentialPresent()) return true;
664
798
  const brand = harness.credentialRef;
@@ -716,7 +850,23 @@ async function apply(ctx, config = {}) {
716
850
  return { id: mode, name: label.name, description: label.description };
717
851
  })
718
852
  });
719
- const configOptions = async (record) => {
853
+ const setApprovalPolicy = (record, policy) => {
854
+ try {
855
+ ctx.get("approval")?.setPolicy?.(record.agent, policy);
856
+ } catch (error) {
857
+ logWarn(`approval policy switch failed: ${String(error)}`);
858
+ }
859
+ record.approvals = policy;
860
+ };
861
+ const applyMode = (record, sessionId, modeId) => {
862
+ const mode = SANDBOX_MODES2.find((candidate) => candidate === modeId);
863
+ if (mode === void 0) throw invalidParams(`unknown mode: ${modeId}`);
864
+ setSandboxMode2(record.agent.session, mode);
865
+ setApprovalPolicy(record, mode === "danger-full-access" ? "never" : "ask");
866
+ record.modeId = mode;
867
+ notify(sessionId, { sessionUpdate: "current_mode_update", currentModeId: mode });
868
+ };
869
+ const modelChoices = async (record) => {
720
870
  const seen = /* @__PURE__ */ new Set();
721
871
  const options = [];
722
872
  const push = (value, name3) => {
@@ -735,17 +885,67 @@ async function apply(ctx, config = {}) {
735
885
  push(encodeChoice(choice.provider, choice.model), choice.label);
736
886
  }
737
887
  for (const model of modelCandidates()) push(encodeChoice(void 0, model), model);
738
- if (options.length < 2) return [];
739
- return [
740
- {
888
+ return options;
889
+ };
890
+ const configOptions = async (record) => {
891
+ const result = [];
892
+ result.push({
893
+ type: "select",
894
+ id: "mode",
895
+ name: "Mode",
896
+ category: "mode",
897
+ currentValue: record.modeId,
898
+ options: SANDBOX_MODES2.map((mode) => {
899
+ const label = MODE_LABELS[mode] ?? { name: mode, description: "" };
900
+ return {
901
+ value: mode,
902
+ name: label.name,
903
+ ...label.description.length > 0 ? { description: label.description } : {}
904
+ };
905
+ })
906
+ });
907
+ const models = await modelChoices(record);
908
+ if (models.length >= 2) {
909
+ result.push({
741
910
  type: "select",
742
911
  id: "model",
743
912
  name: "Model",
744
913
  category: "model",
745
- currentValue: current,
746
- options
747
- }
748
- ];
914
+ currentValue: models[0].value,
915
+ options: models
916
+ });
917
+ }
918
+ const route = routeOf(record);
919
+ const efforts = await effortCatalog(route.provider, route.model);
920
+ if (efforts !== void 0 && efforts.efforts.length >= 2) {
921
+ const known = new Set(efforts.efforts.map((effort) => effort.id));
922
+ const preferred = [record.effort, loggedConfig(record)?.reasoningEffort, efforts.defaultEffort].find(
923
+ (candidate) => candidate !== void 0 && known.has(candidate)
924
+ );
925
+ result.push({
926
+ type: "select",
927
+ id: "effort",
928
+ name: "Reasoning",
929
+ category: "thought_level",
930
+ currentValue: preferred ?? efforts.efforts[0].id,
931
+ options: efforts.efforts.map((effort) => ({
932
+ value: effort.id,
933
+ name: effort.name,
934
+ ...effort.description !== void 0 ? { description: effort.description } : {}
935
+ }))
936
+ });
937
+ }
938
+ result.push({
939
+ type: "select",
940
+ id: "approvals",
941
+ name: "Approvals",
942
+ currentValue: record.approvals,
943
+ options: [
944
+ { value: "ask", name: "Ask", description: "Ask before actions beyond the sandbox mode" },
945
+ { value: "never", name: "Never ask", description: "Auto-approve every permission request" }
946
+ ]
947
+ });
948
+ return result;
749
949
  };
750
950
  const publishCommands = (sessionId) => {
751
951
  notify(sessionId, {
@@ -827,15 +1027,19 @@ async function apply(ctx, config = {}) {
827
1027
  };
828
1028
  const statusText = async (record) => {
829
1029
  const used = record.projection.contextWindow;
1030
+ const route = routeOf(record);
1031
+ const effort = record.effort ?? loggedConfig(record)?.reasoningEffort;
830
1032
  const lines = [
831
1033
  `**dsh-acp** ${VERSION} \u2014 DeepSeek Harness ACP bridge`,
832
1034
  "",
833
1035
  `| | |`,
834
1036
  `|---|---|`,
835
- `| Provider | ${record.provider ?? defaultProvider() ?? "(composition default)"} |`,
836
- `| Model | ${record.model ?? config.model ?? defaultSelection().model ?? record.agent.options.model ?? "(composition default)"} |`,
1037
+ `| Provider | ${route.provider ?? "(composition default)"} |`,
1038
+ `| Model | ${route.model ?? "(composition default)"} |`,
1039
+ `| Reasoning | ${effort ?? "(adapter default)"} |`,
837
1040
  `| Credential | ${await describeCredential()} |`,
838
1041
  `| Permission mode | ${record.modeId} |`,
1042
+ `| Approvals | ${record.approvals} |`,
839
1043
  `| Workspace | ${record.agent.session.header.cwd ?? process.cwd()} |`,
840
1044
  `| Session | ${String(record.agent.session.id)} |`,
841
1045
  ...used !== void 0 ? [`| Context window | ${used.toLocaleString()} tokens |`] : []
@@ -849,6 +1053,7 @@ async function apply(ctx, config = {}) {
849
1053
  projection: projection ?? new SessionProjection(),
850
1054
  modeId,
851
1055
  model,
1056
+ approvals: modeId === "danger-full-access" ? "never" : "ask",
852
1057
  cancelled: false,
853
1058
  inflight: void 0
854
1059
  };
@@ -1113,58 +1318,53 @@ async function apply(ctx, config = {}) {
1113
1318
  const usage = record.projection.promptUsage();
1114
1319
  return { stopReason, ...usage !== void 0 ? { usage } : {} };
1115
1320
  },
1116
- setSessionMode(params) {
1321
+ async setSessionMode(params) {
1117
1322
  const record = requireSession(params.sessionId);
1118
- const mode = SANDBOX_MODES2.find((candidate) => candidate === params.modeId);
1119
- if (mode === void 0) throw invalidParams(`unknown mode: ${params.modeId}`);
1120
- setSandboxMode2(record.agent.session, mode);
1121
- try {
1122
- ctx.get("approval")?.setPolicy?.(record.agent, mode === "danger-full-access" ? "never" : "ask");
1123
- } catch (error) {
1124
- logWarn(`approval policy switch failed: ${String(error)}`);
1125
- }
1126
- record.modeId = mode;
1127
- notify(params.sessionId, { sessionUpdate: "current_mode_update", currentModeId: mode });
1128
- return Promise.resolve({});
1323
+ applyMode(record, params.sessionId, params.modeId);
1324
+ notify(params.sessionId, {
1325
+ sessionUpdate: "config_option_update",
1326
+ configOptions: await configOptions(record)
1327
+ });
1328
+ return {};
1129
1329
  },
1130
1330
  async setSessionConfigOption(params) {
1131
1331
  const record = requireSession(params.sessionId);
1132
- if (params.configId !== "model") throw invalidParams(`unknown config option: ${params.configId}`);
1133
- if (record.inflight !== void 0) {
1134
- throw invalidParams("cannot switch models while a prompt is running");
1135
- }
1136
1332
  const value = typeof params.value === "string" ? params.value : void 0;
1137
- if (value === void 0) throw invalidParams(`unknown model: ${String(params.value)}`);
1138
- const choice = decodeChoice(value);
1139
- const known = /* @__PURE__ */ new Set([
1140
- ...modelCandidates().map((model) => encodeChoice(void 0, model)),
1141
- ...(await discoverModels()).map((entry) => encodeChoice(entry.provider, entry.model)),
1142
- encodeChoice(record.provider, record.model ?? "")
1143
- ]);
1144
- if (!known.has(value)) throw invalidParams(`unknown model: ${value}`);
1145
- if (choice.model !== record.model || choice.provider !== (record.provider ?? defaultProvider())) {
1146
- const sessionId = record.agent.session.id;
1147
- await record.dispose().catch((error) => {
1148
- logWarn(`dispose during model switch failed: ${String(error)}`);
1149
- });
1150
- let handle;
1151
- try {
1152
- handle = await agents.resume({
1153
- resumeSessionId: sessionId,
1154
- agentOptions: agentOptionsFor(choice.model, choice.provider)
1155
- });
1156
- } catch (error) {
1157
- sessions.delete(params.sessionId);
1158
- throw internalError(`model switch failed: ${errorChain2(error)}`);
1333
+ if (value === void 0) throw invalidParams(`invalid value: ${String(params.value)}`);
1334
+ switch (params.configId) {
1335
+ case "mode": {
1336
+ applyMode(record, params.sessionId, value);
1337
+ break;
1338
+ }
1339
+ case "approvals": {
1340
+ if (value !== "ask" && value !== "never") throw invalidParams(`unknown approvals: ${value}`);
1341
+ setApprovalPolicy(record, value);
1342
+ break;
1343
+ }
1344
+ case "effort": {
1345
+ const route = routeOf(record);
1346
+ const catalog = await effortCatalog(route.provider, route.model);
1347
+ if (catalog === void 0 || !catalog.efforts.some((effort) => effort.id === value)) {
1348
+ throw invalidParams(`unknown effort: ${value}`);
1349
+ }
1350
+ const selection = ensureSelection(record);
1351
+ if (selection === void 0 || route.provider === void 0 || route.model === void 0) {
1352
+ throw invalidParams("reasoning effort switching is unavailable on this host");
1353
+ }
1354
+ selection.current = {
1355
+ provider: route.provider,
1356
+ model: route.model,
1357
+ reasoningEffort: value
1358
+ };
1359
+ record.effort = value;
1360
+ break;
1159
1361
  }
1160
- record.agent = handle.agent;
1161
- record.dispose = () => handle.dispose();
1162
- record.model = choice.model;
1163
- if (choice.provider !== void 0 && choice.provider !== defaultProvider()) {
1164
- record.provider = choice.provider;
1165
- } else {
1166
- delete record.provider;
1362
+ case "model": {
1363
+ await switchModel(record, params.sessionId, value);
1364
+ break;
1167
1365
  }
1366
+ default:
1367
+ throw invalidParams(`unknown config option: ${params.configId}`);
1168
1368
  }
1169
1369
  return { configOptions: await configOptions(record) };
1170
1370
  },
@@ -1600,6 +1800,8 @@ async function apply2(ctx, config) {
1600
1800
  const p = kit.plugins;
1601
1801
  const anyCtx = ctx;
1602
1802
  const credentialsSeam = p["credentialsSeam"];
1803
+ const agentsModule = p["agents"];
1804
+ const installModelSelection = agentsModule.installModelSelection ?? agentsModule.ln;
1603
1805
  const harness = {
1604
1806
  createUserMessage: kit.llm.createUserMessage,
1605
1807
  errorChain: kit.llm.errorChain,
@@ -1607,6 +1809,7 @@ async function apply2(ctx, config) {
1607
1809
  foldSessionTitle: kit.sessionTitle.foldSessionTitle,
1608
1810
  setSandboxMode: kit.sandboxPolicy.setSandboxMode,
1609
1811
  sandboxModes: kit.sandboxPolicy.SANDBOX_MODES,
1812
+ ...installModelSelection !== void 0 ? { installModelSelection } : {},
1610
1813
  ...credentialsSeam?.credentialRef !== void 0 ? { credentialRef: credentialsSeam.credentialRef } : {},
1611
1814
  ...p["mcpClient"] !== void 0 ? { mcpClient: p["mcpClient"] } : {}
1612
1815
  };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.1.0",
6
+ "version": "0.2.0",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "git+https://github.com/openma-ai/deepseek-harness-acp.git"