8af6774ae8
* fix(models): update model names to match OpenCode Zen catalog OpenCode Zen recently updated their official model catalog, deprecating several preview and free model variants: DEPRECATED → NEW (Official Zen Names): - gemini-3-pro-preview → gemini-3-pro - gemini-3-flash-preview → gemini-3-flash - grok-code → gpt-5-nano (FREE tier maintained) - glm-4.7-free → big-pickle (FREE tier maintained) - glm-4.6v → glm-4.6 Changes: - Updated 6 source files (model-requirements, delegate-task, think-mode, etc.) - Updated 9 documentation files (installation, configurations, features, etc.) - Updated 14 test files with new model references - Regenerated snapshots to reflect catalog changes - Removed duplicate think-mode entries for preview variants Impact: - FREE tier access preserved via gpt-5-nano and big-pickle - All 55 model-related tests passing - Zero breaking changes - pure string replacement - Aligns codebase with official OpenCode Zen model catalog Verified: - Zero deprecated model names in codebase - All model-related tests pass (55/55) - Snapshots regenerated and validated Affects: 30 files (6 source, 9 docs, 14 tests, 1 snapshot) * fix(multimodal-looker): update fallback chain with glm-4.6v and gpt-5-nano - Change glm-4.6 to glm-4.6v for zai-coding-plan provider - Add opencode/gpt-5-nano as 4th fallback (FREE tier) - Push gpt-5.2 to 5th position Fallback chain now: 1. gemini-3-flash (google, github-copilot, opencode) 2. claude-haiku-4-5 (anthropic, github-copilot, opencode) 3. glm-4.6v (zai-coding-plan) 4. gpt-5-nano (opencode) - FREE 5. gpt-5.2 (openai, github-copilot, opencode) * chore: update bun.lock --------- Co-authored-by: justsisyphus <justsisyphus@users.noreply.github.com>
354 lines
12 KiB
TypeScript
354 lines
12 KiB
TypeScript
import { describe, expect, it, beforeEach } from "bun:test"
|
|
import type { ThinkModeInput } from "./types"
|
|
|
|
const { createThinkModeHook, clearThinkModeState } = await import("./index")
|
|
|
|
/**
|
|
* Helper to create a mock ThinkModeInput for testing
|
|
*/
|
|
function createMockInput(
|
|
providerID: string,
|
|
modelID: string,
|
|
promptText: string
|
|
): ThinkModeInput {
|
|
return {
|
|
parts: [{ type: "text", text: promptText }],
|
|
message: {
|
|
model: {
|
|
providerID,
|
|
modelID,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Type helper for accessing dynamically injected properties on message
|
|
*/
|
|
type MessageWithInjectedProps = Record<string, unknown>
|
|
|
|
describe("createThinkModeHook integration", () => {
|
|
const sessionID = "test-session-id"
|
|
|
|
beforeEach(() => {
|
|
clearThinkModeState(sessionID)
|
|
})
|
|
|
|
describe("GitHub Copilot provider integration", () => {
|
|
describe("Claude models", () => {
|
|
it("should activate thinking mode for github-copilot Claude with think keyword", async () => {
|
|
// #given a github-copilot Claude model and prompt with "think" keyword
|
|
const hook = createThinkModeHook()
|
|
const input = createMockInput(
|
|
"github-copilot",
|
|
"claude-opus-4-5",
|
|
"Please think deeply about this problem"
|
|
)
|
|
|
|
// #when the chat.params hook is called
|
|
await hook["chat.params"](input, sessionID)
|
|
|
|
// #then should upgrade to high variant and inject thinking config
|
|
const message = input.message as MessageWithInjectedProps
|
|
expect(input.message.model?.modelID).toBe("claude-opus-4-5-high")
|
|
expect(message.thinking).toBeDefined()
|
|
expect((message.thinking as Record<string, unknown>)?.type).toBe(
|
|
"enabled"
|
|
)
|
|
expect(
|
|
(message.thinking as Record<string, unknown>)?.budgetTokens
|
|
).toBe(64000)
|
|
})
|
|
|
|
it("should handle github-copilot Claude with dots in version", async () => {
|
|
// #given a github-copilot Claude model with dot format (claude-opus-4.5)
|
|
const hook = createThinkModeHook()
|
|
const input = createMockInput(
|
|
"github-copilot",
|
|
"claude-opus-4.5",
|
|
"ultrathink mode"
|
|
)
|
|
|
|
// #when the chat.params hook is called
|
|
await hook["chat.params"](input, sessionID)
|
|
|
|
// #then should upgrade to high variant (hyphen format)
|
|
const message = input.message as MessageWithInjectedProps
|
|
expect(input.message.model?.modelID).toBe("claude-opus-4-5-high")
|
|
expect(message.thinking).toBeDefined()
|
|
})
|
|
|
|
it("should handle github-copilot Claude Sonnet", async () => {
|
|
// #given a github-copilot Claude Sonnet model
|
|
const hook = createThinkModeHook()
|
|
const input = createMockInput(
|
|
"github-copilot",
|
|
"claude-sonnet-4-5",
|
|
"think about this"
|
|
)
|
|
|
|
// #when the chat.params hook is called
|
|
await hook["chat.params"](input, sessionID)
|
|
|
|
// #then should upgrade to high variant
|
|
const message = input.message as MessageWithInjectedProps
|
|
expect(input.message.model?.modelID).toBe("claude-sonnet-4-5-high")
|
|
expect(message.thinking).toBeDefined()
|
|
})
|
|
})
|
|
|
|
describe("Gemini models", () => {
|
|
it("should activate thinking mode for github-copilot Gemini Pro", async () => {
|
|
// #given a github-copilot Gemini Pro model
|
|
const hook = createThinkModeHook()
|
|
const input = createMockInput(
|
|
"github-copilot",
|
|
"gemini-3-pro",
|
|
"think about this"
|
|
)
|
|
|
|
// #when the chat.params hook is called
|
|
await hook["chat.params"](input, sessionID)
|
|
|
|
// #then should upgrade to high variant and inject google thinking config
|
|
const message = input.message as MessageWithInjectedProps
|
|
expect(input.message.model?.modelID).toBe("gemini-3-pro-high")
|
|
expect(message.providerOptions).toBeDefined()
|
|
const googleOptions = (
|
|
message.providerOptions as Record<string, unknown>
|
|
)?.google as Record<string, unknown>
|
|
expect(googleOptions?.thinkingConfig).toBeDefined()
|
|
})
|
|
|
|
it("should activate thinking mode for github-copilot Gemini Flash", async () => {
|
|
// #given a github-copilot Gemini Flash model
|
|
const hook = createThinkModeHook()
|
|
const input = createMockInput(
|
|
"github-copilot",
|
|
"gemini-3-flash",
|
|
"ultrathink"
|
|
)
|
|
|
|
// #when the chat.params hook is called
|
|
await hook["chat.params"](input, sessionID)
|
|
|
|
// #then should upgrade to high variant
|
|
const message = input.message as MessageWithInjectedProps
|
|
expect(input.message.model?.modelID).toBe("gemini-3-flash-high")
|
|
expect(message.providerOptions).toBeDefined()
|
|
})
|
|
})
|
|
|
|
describe("GPT models", () => {
|
|
it("should activate thinking mode for github-copilot GPT-5.2", async () => {
|
|
// #given a github-copilot GPT-5.2 model
|
|
const hook = createThinkModeHook()
|
|
const input = createMockInput(
|
|
"github-copilot",
|
|
"gpt-5.2",
|
|
"please think"
|
|
)
|
|
|
|
// #when the chat.params hook is called
|
|
await hook["chat.params"](input, sessionID)
|
|
|
|
// #then should upgrade to high variant and inject openai thinking config
|
|
const message = input.message as MessageWithInjectedProps
|
|
expect(input.message.model?.modelID).toBe("gpt-5-2-high")
|
|
expect(message.reasoning_effort).toBe("high")
|
|
})
|
|
|
|
it("should activate thinking mode for github-copilot GPT-5", async () => {
|
|
// #given a github-copilot GPT-5 model
|
|
const hook = createThinkModeHook()
|
|
const input = createMockInput("github-copilot", "gpt-5", "think deeply")
|
|
|
|
// #when the chat.params hook is called
|
|
await hook["chat.params"](input, sessionID)
|
|
|
|
// #then should upgrade to high variant
|
|
const message = input.message as MessageWithInjectedProps
|
|
expect(input.message.model?.modelID).toBe("gpt-5-high")
|
|
expect(message.reasoning_effort).toBe("high")
|
|
})
|
|
})
|
|
|
|
describe("No think keyword", () => {
|
|
it("should NOT activate for github-copilot without think keyword", async () => {
|
|
// #given a prompt without any think keyword
|
|
const hook = createThinkModeHook()
|
|
const input = createMockInput(
|
|
"github-copilot",
|
|
"claude-opus-4-5",
|
|
"Just do this task"
|
|
)
|
|
const originalModelID = input.message.model?.modelID
|
|
|
|
// #when the chat.params hook is called
|
|
await hook["chat.params"](input, sessionID)
|
|
|
|
// #then should NOT change model or inject config
|
|
const message = input.message as MessageWithInjectedProps
|
|
expect(input.message.model?.modelID).toBe(originalModelID)
|
|
expect(message.thinking).toBeUndefined()
|
|
})
|
|
})
|
|
})
|
|
|
|
describe("Backwards compatibility with direct providers", () => {
|
|
it("should still work for direct anthropic provider", async () => {
|
|
// #given direct anthropic provider
|
|
const hook = createThinkModeHook()
|
|
const input = createMockInput(
|
|
"anthropic",
|
|
"claude-sonnet-4-5",
|
|
"think about this"
|
|
)
|
|
|
|
// #when the chat.params hook is called
|
|
await hook["chat.params"](input, sessionID)
|
|
|
|
// #then should work as before
|
|
const message = input.message as MessageWithInjectedProps
|
|
expect(input.message.model?.modelID).toBe("claude-sonnet-4-5-high")
|
|
expect(message.thinking).toBeDefined()
|
|
})
|
|
|
|
it("should still work for direct google provider", async () => {
|
|
// #given direct google provider
|
|
const hook = createThinkModeHook()
|
|
const input = createMockInput(
|
|
"google",
|
|
"gemini-3-pro",
|
|
"think about this"
|
|
)
|
|
|
|
// #when the chat.params hook is called
|
|
await hook["chat.params"](input, sessionID)
|
|
|
|
// #then should work as before
|
|
const message = input.message as MessageWithInjectedProps
|
|
expect(input.message.model?.modelID).toBe("gemini-3-pro-high")
|
|
expect(message.providerOptions).toBeDefined()
|
|
})
|
|
|
|
it("should still work for direct openai provider", async () => {
|
|
// #given direct openai provider
|
|
const hook = createThinkModeHook()
|
|
const input = createMockInput("openai", "gpt-5", "think about this")
|
|
|
|
// #when the chat.params hook is called
|
|
await hook["chat.params"](input, sessionID)
|
|
|
|
// #then should work
|
|
const message = input.message as MessageWithInjectedProps
|
|
expect(input.message.model?.modelID).toBe("gpt-5-high")
|
|
expect(message.reasoning_effort).toBe("high")
|
|
})
|
|
|
|
it("should still work for amazon-bedrock provider", async () => {
|
|
// #given amazon-bedrock provider
|
|
const hook = createThinkModeHook()
|
|
const input = createMockInput(
|
|
"amazon-bedrock",
|
|
"claude-sonnet-4-5",
|
|
"think"
|
|
)
|
|
|
|
// #when the chat.params hook is called
|
|
await hook["chat.params"](input, sessionID)
|
|
|
|
// #then should inject bedrock thinking config
|
|
const message = input.message as MessageWithInjectedProps
|
|
expect(input.message.model?.modelID).toBe("claude-sonnet-4-5-high")
|
|
expect(message.reasoningConfig).toBeDefined()
|
|
})
|
|
})
|
|
|
|
describe("Already-high variants", () => {
|
|
it("should NOT re-upgrade already-high variants", async () => {
|
|
// #given an already-high variant model
|
|
const hook = createThinkModeHook()
|
|
const input = createMockInput(
|
|
"github-copilot",
|
|
"claude-opus-4-5-high",
|
|
"think deeply"
|
|
)
|
|
|
|
// #when the chat.params hook is called
|
|
await hook["chat.params"](input, sessionID)
|
|
|
|
// #then should NOT modify the model (already high)
|
|
const message = input.message as MessageWithInjectedProps
|
|
expect(input.message.model?.modelID).toBe("claude-opus-4-5-high")
|
|
// No additional thinking config should be injected
|
|
expect(message.thinking).toBeUndefined()
|
|
})
|
|
|
|
it("should NOT re-upgrade already-high GPT variants", async () => {
|
|
// #given an already-high GPT variant
|
|
const hook = createThinkModeHook()
|
|
const input = createMockInput(
|
|
"github-copilot",
|
|
"gpt-5.2-high",
|
|
"ultrathink"
|
|
)
|
|
|
|
// #when the chat.params hook is called
|
|
await hook["chat.params"](input, sessionID)
|
|
|
|
// #then should NOT modify the model
|
|
const message = input.message as MessageWithInjectedProps
|
|
expect(input.message.model?.modelID).toBe("gpt-5.2-high")
|
|
expect(message.reasoning_effort).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
describe("Unknown models", () => {
|
|
it("should not crash for unknown models via github-copilot", async () => {
|
|
// #given an unknown model type
|
|
const hook = createThinkModeHook()
|
|
const input = createMockInput(
|
|
"github-copilot",
|
|
"llama-3-70b",
|
|
"think about this"
|
|
)
|
|
|
|
// #when the chat.params hook is called
|
|
await hook["chat.params"](input, sessionID)
|
|
|
|
// #then should not crash and model should remain unchanged
|
|
expect(input.message.model?.modelID).toBe("llama-3-70b")
|
|
})
|
|
})
|
|
|
|
describe("Edge cases", () => {
|
|
it("should handle missing model gracefully", async () => {
|
|
// #given input without a model
|
|
const hook = createThinkModeHook()
|
|
const input: ThinkModeInput = {
|
|
parts: [{ type: "text", text: "think about this" }],
|
|
message: {},
|
|
}
|
|
|
|
// #when the chat.params hook is called
|
|
// #then should not crash
|
|
await expect(
|
|
hook["chat.params"](input, sessionID)
|
|
).resolves.toBeUndefined()
|
|
})
|
|
|
|
it("should handle empty prompt gracefully", async () => {
|
|
// #given empty prompt
|
|
const hook = createThinkModeHook()
|
|
const input = createMockInput("github-copilot", "claude-opus-4-5", "")
|
|
|
|
// #when the chat.params hook is called
|
|
await hook["chat.params"](input, sessionID)
|
|
|
|
// #then should not upgrade (no think keyword)
|
|
expect(input.message.model?.modelID).toBe("claude-opus-4-5")
|
|
})
|
|
})
|
|
})
|