mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-12 23:01:21 +00:00
Add advisor functionality and tests
This commit is contained in:
parent
7adbfec2a4
commit
d59fa64cfd
7 changed files with 217 additions and 1 deletions
|
|
@ -98,6 +98,7 @@ export const modelInfoSchema = z.object({
|
|||
outputPrice: z.number().optional(),
|
||||
cacheWritesPrice: z.number().optional(),
|
||||
cacheReadsPrice: z.number().optional(),
|
||||
supportsAdvisorTool: z.boolean().optional(),
|
||||
longContextPricing: z
|
||||
.object({
|
||||
thresholdTokens: z.number(),
|
||||
|
|
|
|||
|
|
@ -207,6 +207,9 @@ const anthropicSchema = apiModelIdProviderModelSchema.extend({
|
|||
anthropicBaseUrl: z.string().optional(),
|
||||
anthropicUseAuthToken: z.boolean().optional(),
|
||||
anthropicBeta1MContext: z.boolean().optional(), // Enable 'context-1m-2025-08-07' beta for 1M context window.
|
||||
anthropicAdvisorEnabled: z.boolean().optional(),
|
||||
anthropicAdvisorModel: z.string().optional(),
|
||||
anthropicAdvisorMaxUses: z.number().int().min(1).optional(),
|
||||
})
|
||||
|
||||
const openRouterSchema = baseProviderSettingsSchema.extend({
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ export const anthropicModels = {
|
|||
cacheWritesPrice: 3.75, // $3.75 per million tokens
|
||||
cacheReadsPrice: 0.3, // $0.30 per million tokens
|
||||
supportsReasoningBudget: true,
|
||||
supportsAdvisorTool: true,
|
||||
// Tiered pricing for extended context (requires beta flag 'context-1m-2025-08-07')
|
||||
tiers: [
|
||||
{
|
||||
|
|
@ -80,6 +81,7 @@ export const anthropicModels = {
|
|||
cacheWritesPrice: 6.25, // $6.25 per million tokens
|
||||
cacheReadsPrice: 0.5, // $0.50 per million tokens
|
||||
supportsReasoningBudget: true,
|
||||
supportsAdvisorTool: true,
|
||||
// Tiered pricing for extended context (requires beta flag)
|
||||
tiers: [
|
||||
{
|
||||
|
|
@ -196,6 +198,7 @@ export const anthropicModels = {
|
|||
cacheWritesPrice: 1.25,
|
||||
cacheReadsPrice: 0.1,
|
||||
supportsReasoningBudget: true,
|
||||
supportsAdvisorTool: true,
|
||||
description:
|
||||
"Claude Haiku 4.5 delivers near-frontier intelligence at lightning speeds with extended thinking, vision, and multilingual support.",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -209,6 +209,141 @@ describe("AnthropicHandler", () => {
|
|||
const requestOptions = mockCreate.mock.calls[mockCreate.mock.calls.length - 1]?.[1]
|
||||
expect(requestOptions?.headers?.["anthropic-beta"]).toContain("context-1m-2025-08-07")
|
||||
})
|
||||
|
||||
describe("advisor tool feature", () => {
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: [{ type: "text" as const, text: "Hello" }],
|
||||
},
|
||||
]
|
||||
|
||||
it("should include advisor tool beta header when advisor tool is enabled", async () => {
|
||||
const advisorHandler = new AnthropicHandler({
|
||||
apiKey: "test-api-key",
|
||||
apiModelId: "claude-sonnet-4-6",
|
||||
anthropicAdvisorEnabled: true,
|
||||
})
|
||||
|
||||
const stream = advisorHandler.createMessage(systemPrompt, messages)
|
||||
|
||||
for await (const _chunk of stream) {
|
||||
// Consume stream
|
||||
}
|
||||
|
||||
const requestOptions = mockCreate.mock.calls[mockCreate.mock.calls.length - 1]?.[1]
|
||||
expect(requestOptions?.headers?.["anthropic-beta"]).toContain("advisor-tool-2026-03-01")
|
||||
})
|
||||
|
||||
it("should inject advisor tool definition when advisor tool is enabled", async () => {
|
||||
const advisorHandler = new AnthropicHandler({
|
||||
apiKey: "test-api-key",
|
||||
apiModelId: "claude-sonnet-4-6",
|
||||
anthropicAdvisorEnabled: true,
|
||||
})
|
||||
|
||||
const stream = advisorHandler.createMessage(systemPrompt, messages)
|
||||
|
||||
for await (const _chunk of stream) {
|
||||
// Consume stream
|
||||
}
|
||||
|
||||
const callArgs = mockCreate.mock.calls[mockCreate.mock.calls.length - 1]?.[0]
|
||||
expect(callArgs?.tools).toBeDefined()
|
||||
expect(callArgs?.tools).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
type: "advisor_20260301",
|
||||
name: "advisor",
|
||||
model: "claude-opus-4-6",
|
||||
}),
|
||||
]),
|
||||
)
|
||||
})
|
||||
|
||||
it("should include max_uses in advisor tool definition when configured", async () => {
|
||||
const customMaxUses = 5
|
||||
const advisorHandler = new AnthropicHandler({
|
||||
apiKey: "test-api-key",
|
||||
apiModelId: "claude-sonnet-4-6",
|
||||
anthropicAdvisorEnabled: true,
|
||||
anthropicAdvisorMaxUses: customMaxUses,
|
||||
})
|
||||
|
||||
const stream = advisorHandler.createMessage(systemPrompt, messages)
|
||||
|
||||
for await (const _chunk of stream) {
|
||||
// Consume stream
|
||||
}
|
||||
|
||||
const callArgs = mockCreate.mock.calls[mockCreate.mock.calls.length - 1]?.[0]
|
||||
const advisorTool = callArgs?.tools?.find((tool: any) => tool.type === "advisor_20260301")
|
||||
expect(advisorTool).toBeDefined()
|
||||
expect(advisorTool?.max_uses).toBe(customMaxUses)
|
||||
})
|
||||
|
||||
it("should use custom advisor model when configured", async () => {
|
||||
const customModel = "claude-opus-4-5-20251101"
|
||||
const advisorHandler = new AnthropicHandler({
|
||||
apiKey: "test-api-key",
|
||||
apiModelId: "claude-sonnet-4-6",
|
||||
anthropicAdvisorEnabled: true,
|
||||
anthropicAdvisorModel: customModel,
|
||||
})
|
||||
|
||||
const stream = advisorHandler.createMessage(systemPrompt, messages)
|
||||
|
||||
for await (const _chunk of stream) {
|
||||
// Consume stream
|
||||
}
|
||||
|
||||
const callArgs = mockCreate.mock.calls[mockCreate.mock.calls.length - 1]?.[0]
|
||||
const advisorTool = callArgs?.tools?.find((tool: any) => tool.type === "advisor_20260301")
|
||||
expect(advisorTool).toBeDefined()
|
||||
expect(advisorTool?.model).toBe(customModel)
|
||||
})
|
||||
|
||||
it("should not include advisor tool when advisor tool is disabled", async () => {
|
||||
const stream = handler.createMessage(systemPrompt, messages)
|
||||
|
||||
for await (const _chunk of stream) {
|
||||
// Consume stream
|
||||
}
|
||||
|
||||
const callArgs = mockCreate.mock.calls[mockCreate.mock.calls.length - 1]?.[0]
|
||||
const advisorTool = callArgs?.tools?.find((tool: any) => tool.type === "advisor_20260301")
|
||||
expect(advisorTool).toBeUndefined()
|
||||
|
||||
// Also verify beta header is not present
|
||||
const requestOptions = mockCreate.mock.calls[mockCreate.mock.calls.length - 1]?.[1]
|
||||
const betaHeader = requestOptions?.headers?.["anthropic-beta"]
|
||||
if (betaHeader && typeof betaHeader === "string") {
|
||||
expect(betaHeader).not.toContain("advisor-tool-2026-03-01")
|
||||
}
|
||||
})
|
||||
|
||||
it("should use default advisor model and max_uses when not configured", async () => {
|
||||
const advisorHandler = new AnthropicHandler({
|
||||
apiKey: "test-api-key",
|
||||
apiModelId: "claude-sonnet-4-6",
|
||||
anthropicAdvisorEnabled: true,
|
||||
// No anthropicAdvisorModel or anthropicAdvisorMaxUses provided
|
||||
})
|
||||
|
||||
const stream = advisorHandler.createMessage(systemPrompt, messages)
|
||||
|
||||
for await (const _chunk of stream) {
|
||||
// Consume stream
|
||||
}
|
||||
|
||||
const callArgs = mockCreate.mock.calls[mockCreate.mock.calls.length - 1]?.[0]
|
||||
const advisorTool = callArgs?.tools?.find((tool: any) => tool.type === "advisor_20260301")
|
||||
expect(advisorTool).toBeDefined()
|
||||
expect(advisorTool?.model).toBe("claude-opus-4-6") // default
|
||||
expect(advisorTool?.max_uses).toBe(3) // default
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("completePrompt", () => {
|
||||
|
|
|
|||
|
|
@ -75,8 +75,30 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa
|
|||
betas.push("context-1m-2025-08-07")
|
||||
}
|
||||
|
||||
// Add advisor tool beta flag if enabled
|
||||
if (this.options.anthropicAdvisorEnabled) {
|
||||
betas.push("advisor-tool-2026-03-01")
|
||||
}
|
||||
|
||||
let tools = convertOpenAIToolsToAnthropic(metadata?.tools ?? [])
|
||||
|
||||
// Add advisor tool if enabled
|
||||
if (this.options.anthropicAdvisorEnabled) {
|
||||
const advisorModel = this.options.anthropicAdvisorModel ?? "claude-opus-4-6"
|
||||
const maxUses = this.options.anthropicAdvisorMaxUses ?? 3
|
||||
const advisorToolDef: { type: string; name: string; model: string; max_uses?: number } = {
|
||||
type: "advisor_20260301",
|
||||
name: "advisor",
|
||||
model: advisorModel,
|
||||
}
|
||||
if (maxUses >= 1) {
|
||||
advisorToolDef.max_uses = maxUses
|
||||
}
|
||||
tools.push(advisorToolDef as unknown as Anthropic.Tool)
|
||||
}
|
||||
|
||||
const nativeToolParams = {
|
||||
tools: convertOpenAIToolsToAnthropic(metadata?.tools ?? []),
|
||||
tools,
|
||||
tool_choice: convertOpenAIToolChoiceToAnthropic(metadata?.tool_choice, metadata?.parallelToolCalls),
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ type AnthropicProps = {
|
|||
export const Anthropic = ({ apiConfiguration, setApiConfigurationField }: AnthropicProps) => {
|
||||
const { t } = useAppTranslation()
|
||||
const selectedModel = useSelectedModel(apiConfiguration)
|
||||
const modelInfo = selectedModel?.info
|
||||
|
||||
const [anthropicBaseUrlSelected, setAnthropicBaseUrlSelected] = useState(!!apiConfiguration?.anthropicBaseUrl)
|
||||
|
||||
|
|
@ -103,6 +104,53 @@ export const Anthropic = ({ apiConfiguration, setApiConfigurationField }: Anthro
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
{modelInfo?.supportsAdvisorTool && (
|
||||
<div>
|
||||
<Checkbox
|
||||
checked={apiConfiguration?.anthropicAdvisorEnabled ?? false}
|
||||
onChange={(checked: boolean) => {
|
||||
setApiConfigurationField("anthropicAdvisorEnabled", checked)
|
||||
}}>
|
||||
{t("settings:providers.anthropicAdvisorToolLabel")}
|
||||
</Checkbox>
|
||||
{apiConfiguration?.anthropicAdvisorEnabled && (
|
||||
<div className="mt-2 ml-6 space-y-2">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">
|
||||
{t("settings:providers.anthropicAdvisorModelLabel")}
|
||||
</label>
|
||||
<select
|
||||
value={apiConfiguration?.anthropicAdvisorModel ?? "claude-opus-4-6"}
|
||||
onChange={(event) => {
|
||||
setApiConfigurationField("anthropicAdvisorModel", event.target.value)
|
||||
}}
|
||||
className="w-full px-2 py-1 rounded border border-vscode-input-border bg-vscode-input-background text-vscode-foreground">
|
||||
<option value="claude-opus-4-6">claude-opus-4-6</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">
|
||||
{t("settings:providers.anthropicAdvisorMaxUsesLabel")}
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
value={apiConfiguration?.anthropicAdvisorMaxUses ?? 3}
|
||||
onChange={(event) => {
|
||||
const value = event.target.value
|
||||
setApiConfigurationField(
|
||||
"anthropicAdvisorMaxUses",
|
||||
value === "" ? undefined : parseInt(value, 10),
|
||||
)
|
||||
}}
|
||||
placeholder={t("settings:providers.anthropicAdvisorMaxUsesPlaceholder")}
|
||||
className="w-full px-2 py-1 rounded border border-vscode-input-border bg-vscode-input-background text-vscode-foreground"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -437,6 +437,10 @@
|
|||
"anthropicUseAuthToken": "Pass Anthropic API Key as Authorization header instead of X-Api-Key",
|
||||
"anthropic1MContextBetaLabel": "Enable 1M context window (Beta)",
|
||||
"anthropic1MContextBetaDescription": "Extends context window to 1 million tokens for Claude Sonnet 4.x / Claude Opus 4.6",
|
||||
"anthropicAdvisorToolLabel": "Enable Advisor Tool (Beta)",
|
||||
"anthropicAdvisorModelLabel": "Advisor Model",
|
||||
"anthropicAdvisorMaxUsesLabel": "Max Uses",
|
||||
"anthropicAdvisorMaxUsesPlaceholder": "No limit",
|
||||
"awsBedrock1MContextBetaLabel": "Enable 1M context window (Beta)",
|
||||
"awsBedrock1MContextBetaDescription": "Extends context window to 1 million tokens for Claude Sonnet 4.x / Claude Opus 4.6",
|
||||
"vertex1MContextBetaLabel": "Enable 1M context window (Beta)",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue