refactor(model-core): move model family detectors
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
export * from "./model-requirements"
|
export * from "./model-requirements"
|
||||||
|
export * from "./model-family-detectors"
|
||||||
export * from "./model-capability-aliases"
|
export * from "./model-capability-aliases"
|
||||||
export * from "./model-capability-heuristics"
|
export * from "./model-capability-heuristics"
|
||||||
export * from "./model-capability-guardrails"
|
export * from "./model-capability-guardrails"
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { describe, expect, test } from "bun:test"
|
||||||
|
import {
|
||||||
|
isClaudeOpus47Model,
|
||||||
|
isGeminiModel,
|
||||||
|
isGlmModel,
|
||||||
|
isGptModel,
|
||||||
|
isKimiK2Model,
|
||||||
|
isMiniMaxModel,
|
||||||
|
} from "./model-family-detectors"
|
||||||
|
|
||||||
|
describe("model family detectors", () => {
|
||||||
|
test("#given GPT model ids #then detects GPT family only", () => {
|
||||||
|
expect(isGptModel("openai/gpt-5.5")).toBe(true)
|
||||||
|
expect(isGptModel("github-copilot/gpt-4o")).toBe(true)
|
||||||
|
expect(isGptModel("openai/o3-mini")).toBe(false)
|
||||||
|
expect(isGptModel("anthropic/claude-opus-4-7")).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("#given Gemini model ids #then detects Gemini family only", () => {
|
||||||
|
expect(isGeminiModel("google/gemini-3.1-pro")).toBe(true)
|
||||||
|
expect(isGeminiModel("google-vertex/gemini-3-flash")).toBe(true)
|
||||||
|
expect(isGeminiModel("github-copilot/gemini-3.1-pro")).toBe(true)
|
||||||
|
expect(isGeminiModel("openai/gpt-5.5")).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("#given Kimi K2 model ids #then detects Kimi K2 family only", () => {
|
||||||
|
expect(isKimiK2Model("moonshotai/kimi-k2.6")).toBe(true)
|
||||||
|
expect(isKimiK2Model("opencode/k2p5")).toBe(true)
|
||||||
|
expect(isKimiK2Model("opencode/k2-p6")).toBe(true)
|
||||||
|
expect(isKimiK2Model("anthropic/claude-opus-4-7")).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("#given GLM model ids #then detects GLM family only", () => {
|
||||||
|
expect(isGlmModel("z-ai/glm-5.1")).toBe(true)
|
||||||
|
expect(isGlmModel("opencode/glm-4.6v")).toBe(true)
|
||||||
|
expect(isGlmModel("google/gemini-3.1-pro")).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("#given Claude Opus 4.7 model ids #then detects Opus 4.7 only", () => {
|
||||||
|
expect(isClaudeOpus47Model("anthropic/claude-opus-4-7")).toBe(true)
|
||||||
|
expect(isClaudeOpus47Model("anthropic/claude-opus-4.7")).toBe(true)
|
||||||
|
expect(isClaudeOpus47Model("anthropic/claude-sonnet-4-6")).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("#given MiniMax model ids #then detects MiniMax family only", () => {
|
||||||
|
expect(isMiniMaxModel("opencode/minimax-m2.7")).toBe(true)
|
||||||
|
expect(isMiniMaxModel("minimax-m2.7-highspeed")).toBe(true)
|
||||||
|
expect(isMiniMaxModel("moonshotai/kimi-k2.6")).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
function extractModelName(model: string): string {
|
||||||
|
return model.includes("/") ? (model.split("/").pop() ?? model) : model
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isGptModel(model: string): boolean {
|
||||||
|
const modelName = extractModelName(model).toLowerCase()
|
||||||
|
return modelName.includes("gpt")
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isClaudeOpus47Model(model: string): boolean {
|
||||||
|
const modelName = extractModelName(model).toLowerCase().replaceAll(".", "-")
|
||||||
|
return modelName.includes("claude-opus-4-7")
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isKimiK2Model(model: string): boolean {
|
||||||
|
const modelName = extractModelName(model).toLowerCase()
|
||||||
|
if (modelName.includes("kimi")) return true
|
||||||
|
if (/k2[-.]?p[56]/.test(modelName)) return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isMiniMaxModel(model: string): boolean {
|
||||||
|
const modelName = extractModelName(model).toLowerCase()
|
||||||
|
return modelName.includes("minimax")
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isGlmModel(model: string): boolean {
|
||||||
|
const modelName = extractModelName(model).toLowerCase()
|
||||||
|
return modelName.includes("glm")
|
||||||
|
}
|
||||||
|
|
||||||
|
const GEMINI_PROVIDERS = ["google/", "google-vertex/"] as const
|
||||||
|
|
||||||
|
export function isGeminiModel(model: string): boolean {
|
||||||
|
if (GEMINI_PROVIDERS.some((prefix) => model.startsWith(prefix))) return true
|
||||||
|
|
||||||
|
if (
|
||||||
|
model.startsWith("github-copilot/") &&
|
||||||
|
extractModelName(model).toLowerCase().startsWith("gemini")
|
||||||
|
)
|
||||||
|
return true
|
||||||
|
|
||||||
|
const modelName = extractModelName(model).toLowerCase()
|
||||||
|
return modelName.startsWith("gemini-")
|
||||||
|
}
|
||||||
+9
-52
@@ -1,5 +1,14 @@
|
|||||||
import type { AgentConfig } from "@opencode-ai/sdk";
|
import type { AgentConfig } from "@opencode-ai/sdk";
|
||||||
|
|
||||||
|
export {
|
||||||
|
isClaudeOpus47Model,
|
||||||
|
isGeminiModel,
|
||||||
|
isGlmModel,
|
||||||
|
isGptModel,
|
||||||
|
isKimiK2Model,
|
||||||
|
isMiniMaxModel,
|
||||||
|
} from "@oh-my-opencode/model-core";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Agent mode determines UI model selection behavior:
|
* Agent mode determines UI model selection behavior:
|
||||||
* - "primary": Respects user's UI-selected model (sisyphus, atlas)
|
* - "primary": Respects user's UI-selected model (sisyphus, atlas)
|
||||||
@@ -74,11 +83,6 @@ function extractModelName(model: string): string {
|
|||||||
return model.includes("/") ? (model.split("/").pop() ?? model) : model;
|
return model.includes("/") ? (model.split("/").pop() ?? model) : model;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isGptModel(model: string): boolean {
|
|
||||||
const modelName = extractModelName(model).toLowerCase();
|
|
||||||
return modelName.includes("gpt");
|
|
||||||
}
|
|
||||||
|
|
||||||
const GPT_NATIVE_SISYPHUS_RE = /gpt-5[.-](?:[4-9]|\d{2,})/i;
|
const GPT_NATIVE_SISYPHUS_RE = /gpt-5[.-](?:[4-9]|\d{2,})/i;
|
||||||
|
|
||||||
export function isGptNativeSisyphusModel(model: string): boolean {
|
export function isGptNativeSisyphusModel(model: string): boolean {
|
||||||
@@ -101,53 +105,6 @@ export function isGpt5_2Model(model: string): boolean {
|
|||||||
return modelName.includes("gpt-5.2") || modelName.includes("gpt-5-2");
|
return modelName.includes("gpt-5.2") || modelName.includes("gpt-5-2");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isClaudeOpus47Model(model: string): boolean {
|
|
||||||
const modelName = extractModelName(model).toLowerCase().replaceAll(".", "-");
|
|
||||||
return modelName.includes("claude-opus-4-7");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Kimi K2.x model detection (K2.5 / K2.6 family).
|
|
||||||
*
|
|
||||||
* Matches model IDs containing any of:
|
|
||||||
* - "kimi" (provider/family signal — kimi-k2.6, moonshotai/Kimi-K2.6, etc.)
|
|
||||||
* - "k2p5" / "k2-p5" / "k2.p5"
|
|
||||||
* - "k2p6" / "k2-p6" / "k2.p6"
|
|
||||||
*
|
|
||||||
* Match is case-insensitive on the model name (last path segment).
|
|
||||||
*/
|
|
||||||
export function isKimiK2Model(model: string): boolean {
|
|
||||||
const modelName = extractModelName(model).toLowerCase();
|
|
||||||
if (modelName.includes("kimi")) return true;
|
|
||||||
if (/k2[-.]?p[56]/.test(modelName)) return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const GEMINI_PROVIDERS = ["google/", "google-vertex/"];
|
|
||||||
|
|
||||||
export function isMiniMaxModel(model: string): boolean {
|
|
||||||
const modelName = extractModelName(model).toLowerCase();
|
|
||||||
return modelName.includes("minimax");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isGlmModel(model: string): boolean {
|
|
||||||
const modelName = extractModelName(model).toLowerCase();
|
|
||||||
return modelName.includes("glm");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isGeminiModel(model: string): boolean {
|
|
||||||
if (GEMINI_PROVIDERS.some((prefix) => model.startsWith(prefix))) return true;
|
|
||||||
|
|
||||||
if (
|
|
||||||
model.startsWith("github-copilot/") &&
|
|
||||||
extractModelName(model).toLowerCase().startsWith("gemini")
|
|
||||||
)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
const modelName = extractModelName(model).toLowerCase();
|
|
||||||
return modelName.startsWith("gemini-");
|
|
||||||
}
|
|
||||||
|
|
||||||
export type BuiltinAgentName =
|
export type BuiltinAgentName =
|
||||||
| "sisyphus"
|
| "sisyphus"
|
||||||
| "hephaestus"
|
| "hephaestus"
|
||||||
|
|||||||
Reference in New Issue
Block a user