2025-12-27 17:56:40 +08:00
import { tool , type PluginInput , type ToolDefinition } from "@opencode-ai/plugin"
2025-12-12 18:39:53 +09:00
import { ALLOWED_AGENTS , CALL_OMO_AGENT_DESCRIPTION } from "./constants"
2026-04-18 01:51:26 +09:00
import type { CallOmoAgentArgs , ToolContextWithMetadata } from "./types"
2025-12-12 18:39:53 +09:00
import type { BackgroundManager } from "../../features/background-agent"
2026-04-18 02:35:46 +09:00
import type { ModelFallbackControllerAccessor } from "../../hooks/model-fallback"
2026-03-04 20:43:55 +01:00
import type { CategoriesConfig , AgentOverrides } from "../../config/schema"
2026-03-27 13:59:06 +09:00
import type { DelegatedModelConfig } from "../../shared/model-resolution-types"
2026-03-04 20:43:55 +01:00
import type { FallbackEntry } from "../../shared/model-requirements"
import { AGENT_MODEL_REQUIREMENTS } from "../../shared/model-requirements"
2026-04-15 10:46:41 +09:00
import { getAgentConfigKey , stripInvisibleAgentCharacters } from "../../shared/agent-display-names"
2026-03-04 20:43:55 +01:00
import { normalizeFallbackModels } from "../../shared/model-resolver"
import { buildFallbackChainFromModels } from "../../shared/fallback-chain-from-models"
2026-02-08 16:24:13 +09:00
import { log } from "../../shared"
2026-04-10 11:16:16 +09:00
import { CONFIG_BASENAME } from "../../shared/plugin-identity"
2026-04-18 01:51:26 +09:00
import { parseModelString } from "../../shared"
2026-02-08 21:11:07 +09:00
import { executeBackground } from "./background-executor"
import { executeSync } from "./sync-executor"
2026-03-08 13:50:32 -04:00
import { resolveCallableAgents } from "./agent-resolver"
2026-04-18 02:35:46 +09:00
import { createOrGetSession } from "./session-creator"
import { processMessages } from "./message-processor"
import { waitForCompletion } from "./completion-poller"
function createSyncExecutorDeps ( modelFallbackControllerAccessor? : ModelFallbackControllerAccessor ) {
return {
createOrGetSession ,
waitForCompletion ,
processMessages ,
setSessionFallbackChain : ( sessionID : string , fallbackChain : FallbackEntry [ ] | undefined ) = > {
modelFallbackControllerAccessor ? . setSessionFallbackChain ( sessionID , fallbackChain )
} ,
clearSessionFallbackChain : ( sessionID : string ) = > {
modelFallbackControllerAccessor ? . clearSessionFallbackChain ( sessionID )
} ,
}
}
2025-12-28 17:00:32 +09:00
2026-03-27 13:59:06 +09:00
function resolveModelAndFallbackChain ( args : {
2026-03-04 20:43:55 +01:00
subagentType : string
agentOverrides? : AgentOverrides
userCategories? : CategoriesConfig
2026-03-27 13:59:06 +09:00
} ) : { model : DelegatedModelConfig | undefined ; fallbackChain : FallbackEntry [ ] | undefined } {
2026-03-04 20:43:55 +01:00
const { subagentType , agentOverrides , userCategories } = args
const agentConfigKey = getAgentConfigKey ( subagentType )
const agentRequirement = AGENT_MODEL_REQUIREMENTS [ agentConfigKey ]
const agentOverride = agentOverrides ? . [ agentConfigKey as keyof AgentOverrides ]
? ? ( agentOverrides
? Object . entries ( agentOverrides ) . find ( ( [ key ] ) = > key . toLowerCase ( ) === agentConfigKey ) ? . [ 1 ]
: undefined )
2026-03-31 17:03:42 -07:00
const agentCategoryModel = agentOverride ? . category
? userCategories ? . [ agentOverride . category ] ? . model
: undefined
const agentCategoryVariant = agentOverride ? . category
? userCategories ? . [ agentOverride . category ] ? . variant
: undefined
2026-03-04 20:43:55 +01:00
2026-03-27 13:59:06 +09:00
let model : DelegatedModelConfig | undefined
if ( agentOverride ? . model ) {
2026-04-01 18:17:15 -07:00
const normalized = parseModelString ( agentOverride . model )
2026-03-27 13:59:06 +09:00
if ( normalized ) {
model = agentOverride . variant ? { . . . normalized , variant : agentOverride.variant } : normalized
log ( "[call_omo_agent] Resolved model override from agent config" , {
agent : subagentType ,
model : agentOverride.model ,
variant : agentOverride.variant ,
} )
}
2026-03-31 17:03:42 -07:00
} else if ( agentCategoryModel ) {
2026-04-01 18:17:15 -07:00
const normalized = parseModelString ( agentCategoryModel )
2026-03-31 17:03:42 -07:00
if ( normalized ) {
const variantToUse = agentOverride ? . variant ? ? agentCategoryVariant
model = variantToUse ? { . . . normalized , variant : variantToUse } : normalized
log ( "[call_omo_agent] Resolved model override from agent category" , {
agent : subagentType ,
category : agentOverride?.category ,
model : agentCategoryModel ,
variant : variantToUse ,
} )
}
2026-03-27 13:59:06 +09:00
}
2026-03-04 20:43:55 +01:00
const normalizedFallbackModels = normalizeFallbackModels (
agentOverride ? . fallback_models
? ? ( agentOverride ? . category ? userCategories ? . [ agentOverride . category ] ? . fallback_models : undefined )
)
2026-03-27 13:59:06 +09:00
const defaultProviderID = model ? . providerID
? ? agentRequirement ? . fallbackChain ? . [ 0 ] ? . providers ? . [ 0 ]
? ? "opencode"
2026-03-04 20:43:55 +01:00
const configuredFallbackChain = buildFallbackChainFromModels ( normalizedFallbackModels , defaultProviderID )
2026-03-27 13:59:06 +09:00
return {
model ,
fallbackChain : configuredFallbackChain ? ? agentRequirement ? . fallbackChain ,
}
2026-03-04 20:43:55 +01:00
}
2025-12-12 18:39:53 +09:00
export function createCallOmoAgent (
ctx : PluginInput ,
2026-02-10 18:44:45 +01:00
backgroundManager : BackgroundManager ,
2026-03-04 20:43:55 +01:00
disabledAgents : string [ ] = [ ] ,
agentOverrides? : AgentOverrides ,
userCategories? : CategoriesConfig ,
2026-04-18 02:35:46 +09:00
modelFallbackControllerAccessor? : ModelFallbackControllerAccessor ,
2025-12-27 17:56:40 +08:00
) : ToolDefinition {
2025-12-12 18:39:53 +09:00
const agentDescriptions = ALLOWED_AGENTS . map (
2026-03-08 13:50:32 -04:00
( name ) = > ` - ${ name } : Specialized agent for ${ name } tasks ` ,
) . join ( "\n" ) ;
const description = CALL_OMO_AGENT_DESCRIPTION . replace (
"{agents}" ,
agentDescriptions ,
) ;
2025-12-12 18:39:53 +09:00
return tool ( {
description ,
args : {
2026-03-08 13:50:32 -04:00
description : tool.schema
. string ( )
. describe ( "A short (3-5 words) description of the task" ) ,
prompt : tool.schema
. string ( )
. describe ( "The task for the agent to perform" ) ,
2025-12-12 18:39:53 +09:00
subagent_type : tool.schema
2026-01-23 00:55:01 +09:00
. string ( )
2026-03-08 13:50:32 -04:00
. describe (
"The agent to invoke. Supports built-in agents and any custom agents registered at runtime." ,
) ,
2025-12-12 18:39:53 +09:00
run_in_background : tool.schema
. boolean ( )
2026-03-08 13:50:32 -04:00
. describe (
"REQUIRED. true: run asynchronously (use background_output to get results), false: run synchronously and wait for completion" ,
) ,
session_id : tool.schema
. string ( )
. describe ( "Existing Task session to continue" )
. optional ( ) ,
2025-12-12 18:39:53 +09:00
} ,
async execute ( args : CallOmoAgentArgs , toolContext ) {
2026-03-08 13:50:32 -04:00
const toolCtx = toolContext as ToolContextWithMetadata ;
log (
` [call_omo_agent] Starting with agent: ${ args . subagent_type } , background: ${ args . run_in_background } ` ,
) ;
2026-05-01 18:55:47 +09:00
if ( typeof args . subagent_type !== "string" || args . subagent_type . trim ( ) === "" ) {
return "Error: subagent_type is required."
}
2026-03-08 13:50:32 -04:00
const callableAgents = await resolveCallableAgents ( ctx . client ) ;
2025-12-12 18:39:53 +09:00
2026-04-15 10:46:41 +09:00
// Strip ZWSP and case-insensitive agent validation - allows "Explore", "EXPLORE", "explore" etc.
const strippedAgentType = stripInvisibleAgentCharacters ( args . subagent_type )
2026-02-08 21:11:07 +09:00
if (
2026-03-08 13:50:32 -04:00
! callableAgents . some (
2026-04-15 10:46:41 +09:00
( name ) = > name . toLowerCase ( ) === strippedAgentType . toLowerCase ( ) ,
2026-02-08 21:11:07 +09:00
)
) {
2026-03-08 13:50:32 -04:00
return ` Error: Invalid agent type " ${ args . subagent_type } ". Only ${ callableAgents . join ( ", " ) } are allowed. ` ;
2025-12-12 18:39:53 +09:00
}
2026-02-08 16:24:13 +09:00
2026-03-08 13:50:32 -04:00
const normalizedAgent = strippedAgentType . toLowerCase ( ) ;
args = { . . . args , subagent_type : normalizedAgent } ;
2025-12-12 18:39:53 +09:00
2026-02-10 18:44:45 +01:00
// Check if agent is disabled
2026-04-15 10:46:41 +09:00
if ( disabledAgents . some ( ( disabled ) = > stripInvisibleAgentCharacters ( disabled ) . toLowerCase ( ) === normalizedAgent ) ) {
2026-04-10 11:16:16 +09:00
return ` Error: Agent " ${ normalizedAgent } " is disabled via disabled_agents configuration. Remove it from disabled_agents in your ${ CONFIG_BASENAME } .json to use it. `
2026-02-10 18:44:45 +01:00
}
2026-03-27 13:59:06 +09:00
const { model : resolvedModel , fallbackChain } = resolveModelAndFallbackChain ( {
2026-03-09 13:11:03 +09:00
subagentType : args.subagent_type ,
agentOverrides ,
userCategories ,
} )
2025-12-12 18:39:53 +09:00
if ( args . run_in_background ) {
if ( args . session_id ) {
2026-03-08 13:50:32 -04:00
return ` Error: session_id is not supported in background mode. Use run_in_background=false to continue an existing session. ` ;
2025-12-12 18:39:53 +09:00
}
2026-03-27 13:59:06 +09:00
return await executeBackground ( args , toolCtx , backgroundManager , ctx . client , fallbackChain , resolvedModel )
2025-12-12 18:39:53 +09:00
}
2026-03-11 17:46:04 +09:00
if ( ! args . session_id ) {
2026-03-11 18:44:20 +09:00
let spawnReservation : Awaited < ReturnType < BackgroundManager [ "reserveSubagentSpawn" ] > > | undefined
2026-03-11 17:46:04 +09:00
try {
2026-03-11 18:44:20 +09:00
spawnReservation = await backgroundManager . reserveSubagentSpawn ( toolCtx . sessionID )
2026-04-18 02:35:46 +09:00
return await executeSync (
args ,
toolCtx ,
ctx ,
createSyncExecutorDeps ( modelFallbackControllerAccessor ) ,
fallbackChain ,
spawnReservation ,
resolvedModel ,
)
2026-03-11 17:46:04 +09:00
} catch ( error ) {
2026-03-11 18:44:20 +09:00
spawnReservation ? . rollback ( )
2026-03-11 17:46:04 +09:00
return ` Error: ${ error instanceof Error ? error.message : String ( error ) } `
}
}
2026-04-18 02:35:46 +09:00
return await executeSync (
args ,
toolCtx ,
ctx ,
createSyncExecutorDeps ( modelFallbackControllerAccessor ) ,
fallbackChain ,
undefined ,
resolvedModel ,
)
2025-12-12 18:39:53 +09:00
} ,
2026-03-08 13:50:32 -04:00
} ) ;
2025-12-12 18:39:53 +09:00
}