Split 1021-line index.ts into 10 focused modules per project conventions. New structure: - error-classifier.ts: error analysis with dynamic status code extraction - agent-resolver.ts: agent detection utilities - fallback-state.ts: state management and cooldown logic - fallback-models.ts: model resolution from config - auto-retry.ts: retry helpers with mutual recursion support - event-handler.ts: session lifecycle events - message-update-handler.ts: message.updated event handling - chat-message-handler.ts: chat message interception - hook.ts: main factory with proper cleanup - types.ts: updated with HookDeps interface - index.ts: 2-line barrel re-export Embedded fixes: - Fix setInterval leak with .unref() - Replace require() with ESM import - Add log warning on invalid model format - Update sessionLastAccess on normal traffic - Make extractStatusCode dynamic from config - Remove unused SessionErrorInfo type All 61 tests pass without modification. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
68 lines
2.5 KiB
TypeScript
68 lines
2.5 KiB
TypeScript
import type { PluginInput } from "@opencode-ai/plugin"
|
|
import type { HookDeps, RuntimeFallbackHook, RuntimeFallbackOptions } from "./types"
|
|
import { DEFAULT_CONFIG, HOOK_NAME } from "./constants"
|
|
import { log } from "../../shared/logger"
|
|
import { loadPluginConfig } from "../../plugin-config"
|
|
import { createAutoRetryHelpers } from "./auto-retry"
|
|
import { createEventHandler } from "./event-handler"
|
|
import { createMessageUpdateHandler } from "./message-update-handler"
|
|
import { createChatMessageHandler } from "./chat-message-handler"
|
|
|
|
export function createRuntimeFallbackHook(
|
|
ctx: PluginInput,
|
|
options?: RuntimeFallbackOptions
|
|
): RuntimeFallbackHook {
|
|
const config = {
|
|
enabled: options?.config?.enabled ?? DEFAULT_CONFIG.enabled,
|
|
retry_on_errors: options?.config?.retry_on_errors ?? DEFAULT_CONFIG.retry_on_errors,
|
|
max_fallback_attempts: options?.config?.max_fallback_attempts ?? DEFAULT_CONFIG.max_fallback_attempts,
|
|
cooldown_seconds: options?.config?.cooldown_seconds ?? DEFAULT_CONFIG.cooldown_seconds,
|
|
timeout_seconds: options?.config?.timeout_seconds ?? DEFAULT_CONFIG.timeout_seconds,
|
|
notify_on_fallback: options?.config?.notify_on_fallback ?? DEFAULT_CONFIG.notify_on_fallback,
|
|
}
|
|
|
|
let pluginConfig = options?.pluginConfig
|
|
if (!pluginConfig) {
|
|
try {
|
|
pluginConfig = loadPluginConfig(ctx.directory, ctx)
|
|
} catch {
|
|
log(`[${HOOK_NAME}] Plugin config not available`)
|
|
}
|
|
}
|
|
|
|
const deps: HookDeps = {
|
|
ctx,
|
|
config,
|
|
options,
|
|
pluginConfig,
|
|
sessionStates: new Map(),
|
|
sessionLastAccess: new Map(),
|
|
sessionRetryInFlight: new Set(),
|
|
sessionAwaitingFallbackResult: new Set(),
|
|
sessionFallbackTimeouts: new Map(),
|
|
}
|
|
|
|
const helpers = createAutoRetryHelpers(deps)
|
|
const baseEventHandler = createEventHandler(deps, helpers)
|
|
const messageUpdateHandler = createMessageUpdateHandler(deps, helpers)
|
|
const chatMessageHandler = createChatMessageHandler(deps)
|
|
|
|
const cleanupInterval = setInterval(helpers.cleanupStaleSessions, 5 * 60 * 1000)
|
|
cleanupInterval.unref()
|
|
|
|
const eventHandler = async ({ event }: { event: { type: string; properties?: unknown } }) => {
|
|
if (event.type === "message.updated") {
|
|
if (!config.enabled) return
|
|
const props = event.properties as Record<string, unknown> | undefined
|
|
await messageUpdateHandler(props)
|
|
return
|
|
}
|
|
await baseEventHandler({ event })
|
|
}
|
|
|
|
return {
|
|
event: eventHandler,
|
|
"chat.message": chatMessageHandler,
|
|
} as RuntimeFallbackHook
|
|
}
|