- 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
This commit is contained in:
@@ -12,10 +12,10 @@ describe("remapAgentKeysToDisplayNames", () => {
|
|||||||
// when remapping
|
// when remapping
|
||||||
const result = remapAgentKeysToDisplayNames(agents)
|
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["Sisyphus (Ultraworker)"]).toBeDefined()
|
||||||
expect(result["oracle"]).toBeDefined()
|
expect(result["oracle"]).toBeDefined()
|
||||||
expect(result["sisyphus"]).toBeUndefined()
|
expect(result["sisyphus"]).toBeDefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("preserves unknown agent keys unchanged", () => {
|
it("preserves unknown agent keys unchanged", () => {
|
||||||
@@ -46,20 +46,20 @@ describe("remapAgentKeysToDisplayNames", () => {
|
|||||||
// when remapping
|
// when remapping
|
||||||
const result = remapAgentKeysToDisplayNames(agents)
|
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 (Ultraworker)"]).toBeDefined()
|
||||||
expect(result["sisyphus"]).toBeUndefined()
|
expect(result["sisyphus"]).toBeDefined()
|
||||||
expect(result["Hephaestus (Deep Agent)"]).toBeDefined()
|
expect(result["Hephaestus (Deep Agent)"]).toBeDefined()
|
||||||
expect(result["hephaestus"]).toBeUndefined()
|
expect(result["hephaestus"]).toBeDefined()
|
||||||
expect(result["Prometheus (Plan Builder)"]).toBeDefined()
|
expect(result["Prometheus (Plan Builder)"]).toBeDefined()
|
||||||
expect(result["prometheus"]).toBeUndefined()
|
expect(result["prometheus"]).toBeDefined()
|
||||||
expect(result["Atlas (Plan Executor)"]).toBeDefined()
|
expect(result["Atlas (Plan Executor)"]).toBeDefined()
|
||||||
expect(result["atlas"]).toBeUndefined()
|
expect(result["atlas"]).toBeDefined()
|
||||||
expect(result["Metis (Plan Consultant)"]).toBeDefined()
|
expect(result["Metis (Plan Consultant)"]).toBeDefined()
|
||||||
expect(result["metis"]).toBeUndefined()
|
expect(result["metis"]).toBeDefined()
|
||||||
expect(result["Momus (Plan Critic)"]).toBeDefined()
|
expect(result["Momus (Plan Critic)"]).toBeDefined()
|
||||||
expect(result["momus"]).toBeUndefined()
|
expect(result["momus"]).toBeDefined()
|
||||||
expect(result["Sisyphus-Junior"]).toBeDefined()
|
expect(result["Sisyphus-Junior"]).toBeDefined()
|
||||||
expect(result["sisyphus-junior"]).toBeUndefined()
|
expect(result["sisyphus-junior"]).toBeDefined()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export function remapAgentKeysToDisplayNames(
|
|||||||
const displayName = AGENT_DISPLAY_NAMES[key]
|
const displayName = AGENT_DISPLAY_NAMES[key]
|
||||||
if (displayName && displayName !== key) {
|
if (displayName && displayName !== key) {
|
||||||
result[displayName] = value
|
result[displayName] = value
|
||||||
|
result[key] = value
|
||||||
} else {
|
} else {
|
||||||
result[key] = value
|
result[key] = value
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ export function applyToolConfig(params: {
|
|||||||
? { todowrite: "deny", todoread: "deny" }
|
? { todowrite: "deny", todoread: "deny" }
|
||||||
: {}
|
: {}
|
||||||
|
|
||||||
|
const existingPermission = params.config.permission as Record<string, unknown> | undefined;
|
||||||
|
const skillDeniedByHost = existingPermission?.skill === "deny";
|
||||||
|
|
||||||
params.config.tools = {
|
params.config.tools = {
|
||||||
...(params.config.tools as Record<string, unknown>),
|
...(params.config.tools as Record<string, unknown>),
|
||||||
"grep_app_*": false,
|
"grep_app_*": false,
|
||||||
@@ -40,6 +43,9 @@ export function applyToolConfig(params: {
|
|||||||
...(params.pluginConfig.experimental?.task_system
|
...(params.pluginConfig.experimental?.task_system
|
||||||
? { todowrite: false, todoread: false }
|
? { todowrite: false, todoread: false }
|
||||||
: {}),
|
: {}),
|
||||||
|
...(skillDeniedByHost
|
||||||
|
? { skill: false, skill_mcp: false }
|
||||||
|
: {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
const isCliRunMode = process.env.OPENCODE_CLI_RUN_MODE === "true";
|
const isCliRunMode = process.env.OPENCODE_CLI_RUN_MODE === "true";
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ describe("resolveActualContextLimit", () => {
|
|||||||
expect(actualLimit).toBe(1_000_000)
|
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
|
// given
|
||||||
delete process.env[ANTHROPIC_CONTEXT_ENV_KEY]
|
delete process.env[ANTHROPIC_CONTEXT_ENV_KEY]
|
||||||
delete process.env[VERTEX_CONTEXT_ENV_KEY]
|
delete process.env[VERTEX_CONTEXT_ENV_KEY]
|
||||||
@@ -59,7 +59,7 @@ describe("resolveActualContextLimit", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// then
|
// 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", () => {
|
it("returns default 200K for Anthropic models without cached limit and 1M mode disabled", () => {
|
||||||
|
|||||||
@@ -1,13 +1,6 @@
|
|||||||
import process from "node:process"
|
import process from "node:process"
|
||||||
|
|
||||||
const DEFAULT_ANTHROPIC_ACTUAL_LIMIT = 200_000
|
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 = {
|
export type ContextLimitModelCacheState = {
|
||||||
anthropicContext1MEnabled: boolean
|
anthropicContext1MEnabled: boolean
|
||||||
modelContextLimitsCache?: Map<string, number>
|
modelContextLimitsCache?: Map<string, number>
|
||||||
@@ -26,10 +19,6 @@ function getAnthropicActualLimit(modelCacheState?: ContextLimitModelCacheState):
|
|||||||
: DEFAULT_ANTHROPIC_ACTUAL_LIMIT
|
: DEFAULT_ANTHROPIC_ACTUAL_LIMIT
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAnthropicNoHeaderGaModel(modelID: string): boolean {
|
|
||||||
return ANTHROPIC_NO_HEADER_GA_MODEL_IDS.has(modelID.toLowerCase())
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resolveActualContextLimit(
|
export function resolveActualContextLimit(
|
||||||
providerID: string,
|
providerID: string,
|
||||||
modelID: string,
|
modelID: string,
|
||||||
@@ -40,7 +29,7 @@ export function resolveActualContextLimit(
|
|||||||
if (explicit1M === 1_000_000) return explicit1M
|
if (explicit1M === 1_000_000) return explicit1M
|
||||||
|
|
||||||
const cachedLimit = modelCacheState?.modelContextLimitsCache?.get(`${providerID}/${modelID}`)
|
const cachedLimit = modelCacheState?.modelContextLimitsCache?.get(`${providerID}/${modelID}`)
|
||||||
if (cachedLimit && isAnthropicNoHeaderGaModel(modelID)) return cachedLimit
|
if (cachedLimit) return cachedLimit
|
||||||
|
|
||||||
return DEFAULT_ANTHROPIC_ACTUAL_LIMIT
|
return DEFAULT_ANTHROPIC_ACTUAL_LIMIT
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user