From 40a92138eae75573bb811b8c3a85bfe3d432a1b4 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Fri, 27 Mar 2026 13:32:18 +0900 Subject: [PATCH] fix: resolve three open bugs (#2836, #2858, #2873) - fix(context-limit): check modelContextLimitsCache for all Anthropic models, not just GA-model set; user config/cache wins over 200K default (fixes #2836) - fix(agent-key-remapper): preserve config key aliases alongside display names so `opencode run --agent sisyphus` resolves correctly (fixes #2858) - fix(tool-config): respect host permission.skill=deny by disabling skill/skill_mcp tools when host denies them (fixes #2873) - test: update context-limit and agent-key-remapper tests to match new behavior --- .../agent-key-remapper.test.ts | 20 +++++++++---------- src/plugin-handlers/agent-key-remapper.ts | 1 + src/plugin-handlers/tool-config-handler.ts | 6 ++++++ src/shared/context-limit-resolver.test.ts | 4 ++-- src/shared/context-limit-resolver.ts | 13 +----------- 5 files changed, 20 insertions(+), 24 deletions(-) diff --git a/src/plugin-handlers/agent-key-remapper.test.ts b/src/plugin-handlers/agent-key-remapper.test.ts index 34e1f08e7..49818276c 100644 --- a/src/plugin-handlers/agent-key-remapper.test.ts +++ b/src/plugin-handlers/agent-key-remapper.test.ts @@ -12,10 +12,10 @@ describe("remapAgentKeysToDisplayNames", () => { // when remapping const result = remapAgentKeysToDisplayNames(agents) - // then known agents get display name keys only + // then known agents get display name keys and config key aliases expect(result["Sisyphus (Ultraworker)"]).toBeDefined() expect(result["oracle"]).toBeDefined() - expect(result["sisyphus"]).toBeUndefined() + expect(result["sisyphus"]).toBeDefined() }) it("preserves unknown agent keys unchanged", () => { @@ -46,20 +46,20 @@ describe("remapAgentKeysToDisplayNames", () => { // when remapping const result = remapAgentKeysToDisplayNames(agents) - // then all get display name keys without lowercase duplicates + // then all get display name keys with config key aliases preserved expect(result["Sisyphus (Ultraworker)"]).toBeDefined() - expect(result["sisyphus"]).toBeUndefined() + expect(result["sisyphus"]).toBeDefined() expect(result["Hephaestus (Deep Agent)"]).toBeDefined() - expect(result["hephaestus"]).toBeUndefined() + expect(result["hephaestus"]).toBeDefined() expect(result["Prometheus (Plan Builder)"]).toBeDefined() - expect(result["prometheus"]).toBeUndefined() + expect(result["prometheus"]).toBeDefined() expect(result["Atlas (Plan Executor)"]).toBeDefined() - expect(result["atlas"]).toBeUndefined() + expect(result["atlas"]).toBeDefined() expect(result["Metis (Plan Consultant)"]).toBeDefined() - expect(result["metis"]).toBeUndefined() + expect(result["metis"]).toBeDefined() expect(result["Momus (Plan Critic)"]).toBeDefined() - expect(result["momus"]).toBeUndefined() + expect(result["momus"]).toBeDefined() expect(result["Sisyphus-Junior"]).toBeDefined() - expect(result["sisyphus-junior"]).toBeUndefined() + expect(result["sisyphus-junior"]).toBeDefined() }) }) diff --git a/src/plugin-handlers/agent-key-remapper.ts b/src/plugin-handlers/agent-key-remapper.ts index dd2a127e0..c60bcfcb9 100644 --- a/src/plugin-handlers/agent-key-remapper.ts +++ b/src/plugin-handlers/agent-key-remapper.ts @@ -9,6 +9,7 @@ export function remapAgentKeysToDisplayNames( const displayName = AGENT_DISPLAY_NAMES[key] if (displayName && displayName !== key) { result[displayName] = value + result[key] = value } else { result[key] = value } diff --git a/src/plugin-handlers/tool-config-handler.ts b/src/plugin-handlers/tool-config-handler.ts index 4be1f2937..633d32577 100644 --- a/src/plugin-handlers/tool-config-handler.ts +++ b/src/plugin-handlers/tool-config-handler.ts @@ -29,6 +29,9 @@ export function applyToolConfig(params: { ? { todowrite: "deny", todoread: "deny" } : {} + const existingPermission = params.config.permission as Record | undefined; + const skillDeniedByHost = existingPermission?.skill === "deny"; + params.config.tools = { ...(params.config.tools as Record), "grep_app_*": false, @@ -40,6 +43,9 @@ export function applyToolConfig(params: { ...(params.pluginConfig.experimental?.task_system ? { todowrite: false, todoread: false } : {}), + ...(skillDeniedByHost + ? { skill: false, skill_mcp: false } + : {}), }; const isCliRunMode = process.env.OPENCODE_CLI_RUN_MODE === "true"; diff --git a/src/shared/context-limit-resolver.test.ts b/src/shared/context-limit-resolver.test.ts index 749eef287..a55209a5e 100644 --- a/src/shared/context-limit-resolver.test.ts +++ b/src/shared/context-limit-resolver.test.ts @@ -45,7 +45,7 @@ describe("resolveActualContextLimit", () => { expect(actualLimit).toBe(1_000_000) }) - it("returns default 200K for older Anthropic models even when cached limit is higher", () => { + it("returns cached limit for Anthropic models when modelContextLimitsCache has entry", () => { // given delete process.env[ANTHROPIC_CONTEXT_ENV_KEY] delete process.env[VERTEX_CONTEXT_ENV_KEY] @@ -59,7 +59,7 @@ describe("resolveActualContextLimit", () => { }) // then - expect(actualLimit).toBe(200_000) + expect(actualLimit).toBe(500_000) }) it("returns default 200K for Anthropic models without cached limit and 1M mode disabled", () => { diff --git a/src/shared/context-limit-resolver.ts b/src/shared/context-limit-resolver.ts index bc19a4191..e714989a5 100644 --- a/src/shared/context-limit-resolver.ts +++ b/src/shared/context-limit-resolver.ts @@ -1,13 +1,6 @@ import process from "node:process" const DEFAULT_ANTHROPIC_ACTUAL_LIMIT = 200_000 -const ANTHROPIC_NO_HEADER_GA_MODEL_IDS = new Set([ - "claude-opus-4-6", - "claude-opus-4.6", - "claude-sonnet-4-6", - "claude-sonnet-4.6", -]) - export type ContextLimitModelCacheState = { anthropicContext1MEnabled: boolean modelContextLimitsCache?: Map @@ -26,10 +19,6 @@ function getAnthropicActualLimit(modelCacheState?: ContextLimitModelCacheState): : DEFAULT_ANTHROPIC_ACTUAL_LIMIT } -function isAnthropicNoHeaderGaModel(modelID: string): boolean { - return ANTHROPIC_NO_HEADER_GA_MODEL_IDS.has(modelID.toLowerCase()) -} - export function resolveActualContextLimit( providerID: string, modelID: string, @@ -40,7 +29,7 @@ export function resolveActualContextLimit( if (explicit1M === 1_000_000) return explicit1M const cachedLimit = modelCacheState?.modelContextLimitsCache?.get(`${providerID}/${modelID}`) - if (cachedLimit && isAnthropicNoHeaderGaModel(modelID)) return cachedLimit + if (cachedLimit) return cachedLimit return DEFAULT_ANTHROPIC_ACTUAL_LIMIT }