@ai-sdk/harness-opencode 1.0.11 → 1.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @ai-sdk/harness-opencode
2
2
 
3
+ ## 1.0.12
4
+
5
+ ### Patch Changes
6
+
7
+ - 7859cea: feat(harness): add tool filtering via `activeTools` and `inactiveTools`
8
+ - c857346: feat(harness): add utility functions for certain duplicated layers in harnesses
9
+ - Updated dependencies [7859cea]
10
+ - Updated dependencies [c857346]
11
+ - @ai-sdk/harness@1.0.12
12
+
3
13
  ## 1.0.11
4
14
 
5
15
  ### Patch Changes
@@ -736,6 +736,19 @@ var OPENCODE_TO_WIRE = {
736
736
  agent: "agent",
737
737
  subtask: "agent"
738
738
  };
739
+ var PUBLIC_TO_NATIVE = {
740
+ read: "view",
741
+ write: "write",
742
+ edit: "edit",
743
+ bash: "bash",
744
+ glob: "glob",
745
+ grep: "grep",
746
+ ls: "list",
747
+ webfetch: "webfetch",
748
+ skill: "skill",
749
+ todowrite: "todowrite",
750
+ agent: "agent"
751
+ };
739
752
  var TOOL_KIND = {
740
753
  read: "readonly",
741
754
  glob: "readonly",
@@ -837,6 +850,18 @@ function buildOpenCodeConfig({
837
850
  };
838
851
  if (start.model) config.model = start.model;
839
852
  if (skillsDir) config.skills = { paths: [skillsDir] };
853
+ const inactiveToolNames = resolveInactiveBuiltinToolNames(start);
854
+ const permission = config.permission;
855
+ for (const toolName of inactiveToolNames) {
856
+ const permissionName = toPermissionToolName(
857
+ PUBLIC_TO_NATIVE[toolName] ?? toolName
858
+ );
859
+ if (permissionName === "ls") {
860
+ permission.list = "ask";
861
+ } else {
862
+ permission[permissionName] = "ask";
863
+ }
864
+ }
840
865
  const provider = buildProviderConfig(start);
841
866
  if (provider) config.provider = provider;
842
867
  if (relayPort && start.tools && start.tools.length > 0) {
@@ -1063,6 +1088,7 @@ async function runPrompt({
1063
1088
  client,
1064
1089
  sessionId,
1065
1090
  permissionMode: start.permissionMode,
1091
+ builtinToolFiltering: start.builtinToolFiltering,
1066
1092
  turn,
1067
1093
  emit: (msg) => {
1068
1094
  if (msg.type === "text-delta" || msg.type === "reasoning-delta") {
@@ -1176,6 +1202,7 @@ async function runCompaction({
1176
1202
  client,
1177
1203
  sessionId,
1178
1204
  permissionMode: start.permissionMode,
1205
+ builtinToolFiltering: start.builtinToolFiltering,
1179
1206
  turn,
1180
1207
  emit: (msg) => {
1181
1208
  if (msg.type === "compaction") sawCompaction = true;
@@ -1237,6 +1264,7 @@ async function consumeEvents({
1237
1264
  client,
1238
1265
  sessionId,
1239
1266
  permissionMode,
1267
+ builtinToolFiltering,
1240
1268
  turn,
1241
1269
  emit,
1242
1270
  signal,
@@ -1255,6 +1283,7 @@ async function consumeEvents({
1255
1283
  state,
1256
1284
  sessionId,
1257
1285
  permissionMode,
1286
+ builtinToolFiltering,
1258
1287
  client,
1259
1288
  turn,
1260
1289
  emit
@@ -1283,6 +1312,7 @@ async function translateAndEmit({
1283
1312
  state,
1284
1313
  sessionId,
1285
1314
  permissionMode,
1315
+ builtinToolFiltering,
1286
1316
  client,
1287
1317
  turn,
1288
1318
  emit
@@ -1520,6 +1550,7 @@ async function translateAndEmit({
1520
1550
  client,
1521
1551
  sessionId,
1522
1552
  permissionMode,
1553
+ builtinToolFiltering,
1523
1554
  turn,
1524
1555
  emit,
1525
1556
  event
@@ -1531,6 +1562,7 @@ async function translateAndEmit({
1531
1562
  client,
1532
1563
  sessionId,
1533
1564
  permissionMode,
1565
+ builtinToolFiltering,
1534
1566
  turn,
1535
1567
  emit,
1536
1568
  event
@@ -1698,6 +1730,7 @@ async function handlePermissionV2({
1698
1730
  client,
1699
1731
  sessionId,
1700
1732
  permissionMode,
1733
+ builtinToolFiltering,
1701
1734
  turn,
1702
1735
  emit,
1703
1736
  event
@@ -1711,6 +1744,7 @@ async function handlePermissionV2({
1711
1744
  requestID,
1712
1745
  toolCallId: typeof props.source === "object" && props.source !== null && "callID" in props.source ? String(props.source.callID) : requestID,
1713
1746
  permissionMode,
1747
+ builtinToolFiltering,
1714
1748
  turn,
1715
1749
  emit
1716
1750
  });
@@ -1725,6 +1759,7 @@ async function handlePermission({
1725
1759
  client,
1726
1760
  sessionId,
1727
1761
  permissionMode,
1762
+ builtinToolFiltering,
1728
1763
  turn,
1729
1764
  emit,
1730
1765
  event
@@ -1738,6 +1773,7 @@ async function handlePermission({
1738
1773
  requestID,
1739
1774
  toolCallId: typeof props.tool === "object" && props.tool !== null && "callID" in props.tool ? String(props.tool.callID) : requestID,
1740
1775
  permissionMode,
1776
+ builtinToolFiltering,
1741
1777
  turn,
1742
1778
  emit
1743
1779
  });
@@ -1755,6 +1791,7 @@ async function selectPermissionReply({
1755
1791
  requestID,
1756
1792
  toolCallId,
1757
1793
  permissionMode,
1794
+ builtinToolFiltering,
1758
1795
  turn,
1759
1796
  emit
1760
1797
  }) {
@@ -1762,6 +1799,18 @@ async function selectPermissionReply({
1762
1799
  if (resources.some((resource) => isExternalPath(resource))) {
1763
1800
  return { reply: "reject", message: "External directory access rejected." };
1764
1801
  }
1802
+ if (isBuiltinToolInactive({ toolName, toolFiltering: builtinToolFiltering })) {
1803
+ emit({
1804
+ type: "tool-approval-request",
1805
+ approvalId: requestID,
1806
+ toolCallId
1807
+ });
1808
+ const decision2 = await turn.requestToolApproval(requestID);
1809
+ return decision2.approved ? { reply: "once" } : {
1810
+ reply: "reject",
1811
+ ...decision2.reason ? { message: decision2.reason } : {}
1812
+ };
1813
+ }
1765
1814
  if (!permissionMode || permissionMode === "allow-all") {
1766
1815
  return { reply: "always" };
1767
1816
  }
@@ -1794,6 +1843,17 @@ function toPermissionToolName(action) {
1794
1843
  if (normalized.includes("read")) return "read";
1795
1844
  return toWireToolName(normalized);
1796
1845
  }
1846
+ function resolveInactiveBuiltinToolNames(start) {
1847
+ const toolFiltering = start.builtinToolFiltering;
1848
+ if (toolFiltering == null) return [];
1849
+ return toolFiltering.mode === "allow" ? Object.keys(PUBLIC_TO_NATIVE).filter(
1850
+ (name) => !toolFiltering.toolNames.includes(name)
1851
+ ) : toolFiltering.toolNames;
1852
+ }
1853
+ function isBuiltinToolInactive(input) {
1854
+ if (input.toolFiltering == null) return false;
1855
+ return input.toolFiltering.mode === "allow" ? !input.toolFiltering.toolNames.includes(input.toolName) : input.toolFiltering.toolNames.includes(input.toolName);
1856
+ }
1797
1857
  function isExternalPath(resource) {
1798
1858
  if (!path2.isAbsolute(resource)) return false;
1799
1859
  const normalized = path2.resolve(resource);