185 lines
6.6 KiB
TypeScript
185 lines
6.6 KiB
TypeScript
import type { ModelFallbackInfo } from "../../features/task-toast-manager/types"
|
|
import type { DelegateTaskArgs } from "./types"
|
|
import type { ExecutorContext } from "./executor-types"
|
|
import type { FallbackEntry } from "../../shared/model-requirements"
|
|
import { mergeCategories } from "../../shared/merge-categories"
|
|
import { SISYPHUS_JUNIOR_AGENT } from "./sisyphus-junior-agent"
|
|
import { resolveCategoryConfig } from "./categories"
|
|
import { parseModelString } from "./model-string-parser"
|
|
import { CATEGORY_MODEL_REQUIREMENTS } from "../../shared/model-requirements"
|
|
import { getAvailableModelsForDelegateTask } from "./available-models"
|
|
import { resolveModelForDelegateTask } from "./model-selection"
|
|
|
|
export interface CategoryResolutionResult {
|
|
agentToUse: string
|
|
categoryModel: { providerID: string; modelID: string; variant?: string } | undefined
|
|
categoryPromptAppend: string | undefined
|
|
modelInfo: ModelFallbackInfo | undefined
|
|
actualModel: string | undefined
|
|
isUnstableAgent: boolean
|
|
fallbackChain?: FallbackEntry[] // For runtime retry on model errors
|
|
error?: string
|
|
}
|
|
|
|
export async function resolveCategoryExecution(
|
|
args: DelegateTaskArgs,
|
|
executorCtx: ExecutorContext,
|
|
inheritedModel: string | undefined,
|
|
systemDefaultModel: string | undefined
|
|
): Promise<CategoryResolutionResult> {
|
|
const { client, userCategories, sisyphusJuniorModel } = executorCtx
|
|
|
|
const availableModels = await getAvailableModelsForDelegateTask(client)
|
|
|
|
const categoryName = args.category!
|
|
const enabledCategories = mergeCategories(userCategories)
|
|
const categoryExists = enabledCategories[categoryName] !== undefined
|
|
|
|
const resolved = resolveCategoryConfig(categoryName, {
|
|
userCategories,
|
|
inheritedModel,
|
|
systemDefaultModel,
|
|
availableModels,
|
|
})
|
|
|
|
if (!resolved) {
|
|
const requirement = CATEGORY_MODEL_REQUIREMENTS[categoryName]
|
|
const allCategoryNames = Object.keys(enabledCategories).join(", ")
|
|
|
|
if (categoryExists && requirement?.requiresModel) {
|
|
return {
|
|
agentToUse: "",
|
|
categoryModel: undefined,
|
|
categoryPromptAppend: undefined,
|
|
modelInfo: undefined,
|
|
actualModel: undefined,
|
|
isUnstableAgent: false,
|
|
error: `Category "${categoryName}" requires model "${requirement.requiresModel}" which is not available.
|
|
|
|
To use this category:
|
|
1. Connect a provider with this model: ${requirement.requiresModel}
|
|
2. Or configure an alternative model in your oh-my-opencode.json for this category
|
|
|
|
Available categories: ${allCategoryNames}`,
|
|
}
|
|
}
|
|
|
|
return {
|
|
agentToUse: "",
|
|
categoryModel: undefined,
|
|
categoryPromptAppend: undefined,
|
|
modelInfo: undefined,
|
|
actualModel: undefined,
|
|
isUnstableAgent: false,
|
|
error: `Unknown category: "${categoryName}". Available: ${allCategoryNames}`,
|
|
}
|
|
}
|
|
|
|
const requirement = CATEGORY_MODEL_REQUIREMENTS[args.category!]
|
|
let actualModel: string | undefined
|
|
let modelInfo: ModelFallbackInfo | undefined
|
|
let categoryModel: { providerID: string; modelID: string; variant?: string } | undefined
|
|
|
|
const overrideModel = sisyphusJuniorModel
|
|
const explicitCategoryModel = userCategories?.[args.category!]?.model
|
|
|
|
if (!requirement) {
|
|
// Precedence: explicit category model > sisyphus-junior default > category resolved model
|
|
// This keeps `sisyphus-junior.model` useful as a global default while allowing
|
|
// per-category overrides via `categories[category].model`.
|
|
actualModel = explicitCategoryModel ?? overrideModel ?? resolved.model
|
|
if (actualModel) {
|
|
modelInfo = explicitCategoryModel || overrideModel
|
|
? { model: actualModel, type: "user-defined", source: "override" }
|
|
: { model: actualModel, type: "system-default", source: "system-default" }
|
|
}
|
|
} else {
|
|
const resolution = resolveModelForDelegateTask({
|
|
userModel: explicitCategoryModel ?? overrideModel,
|
|
categoryDefaultModel: resolved.model,
|
|
fallbackChain: requirement.fallbackChain,
|
|
availableModels,
|
|
systemDefaultModel,
|
|
})
|
|
|
|
if (resolution) {
|
|
const { model: resolvedModel, variant: resolvedVariant } = resolution
|
|
actualModel = resolvedModel
|
|
|
|
if (!parseModelString(actualModel)) {
|
|
return {
|
|
agentToUse: "",
|
|
categoryModel: undefined,
|
|
categoryPromptAppend: undefined,
|
|
modelInfo: undefined,
|
|
actualModel: undefined,
|
|
isUnstableAgent: false,
|
|
error: `Invalid model format "${actualModel}". Expected "provider/model" format (e.g., "anthropic/claude-sonnet-4-6").`,
|
|
}
|
|
}
|
|
|
|
const type: "user-defined" | "inherited" | "category-default" | "system-default" =
|
|
(explicitCategoryModel || overrideModel)
|
|
? "user-defined"
|
|
: (systemDefaultModel && actualModel === systemDefaultModel)
|
|
? "system-default"
|
|
: "category-default"
|
|
|
|
const source: "override" | "category-default" | "system-default" =
|
|
type === "user-defined"
|
|
? "override"
|
|
: type === "system-default"
|
|
? "system-default"
|
|
: "category-default"
|
|
|
|
modelInfo = { model: actualModel, type, source }
|
|
|
|
const parsedModel = parseModelString(actualModel)
|
|
const variantToUse = userCategories?.[args.category!]?.variant ?? resolvedVariant ?? resolved.config.variant
|
|
categoryModel = parsedModel
|
|
? (variantToUse ? { ...parsedModel, variant: variantToUse } : parsedModel)
|
|
: undefined
|
|
}
|
|
}
|
|
|
|
if (!categoryModel && actualModel) {
|
|
const parsedModel = parseModelString(actualModel)
|
|
categoryModel = parsedModel ?? undefined
|
|
}
|
|
const categoryPromptAppend = resolved.promptAppend || undefined
|
|
|
|
if (!categoryModel && !actualModel) {
|
|
const categoryNames = Object.keys(enabledCategories)
|
|
return {
|
|
agentToUse: "",
|
|
categoryModel: undefined,
|
|
categoryPromptAppend: undefined,
|
|
modelInfo: undefined,
|
|
actualModel: undefined,
|
|
isUnstableAgent: false,
|
|
error: `Model not configured for category "${args.category}".
|
|
|
|
Configure in one of:
|
|
1. OpenCode: Set "model" in opencode.json
|
|
2. Oh-My-OpenCode: Set category model in oh-my-opencode.json
|
|
3. Provider: Connect a provider with available models
|
|
|
|
Current category: ${args.category}
|
|
Available categories: ${categoryNames.join(", ")}`,
|
|
}
|
|
}
|
|
|
|
const unstableModel = actualModel?.toLowerCase()
|
|
const isUnstableAgent = resolved.config.is_unstable_agent === true || (unstableModel ? unstableModel.includes("gemini") || unstableModel.includes("minimax") : false)
|
|
|
|
return {
|
|
agentToUse: SISYPHUS_JUNIOR_AGENT,
|
|
categoryModel,
|
|
categoryPromptAppend,
|
|
modelInfo,
|
|
actualModel,
|
|
isUnstableAgent,
|
|
fallbackChain: requirement?.fallbackChain,
|
|
}
|
|
}
|