fix(runtime-fallback): handle object-shaped event models
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -18,6 +18,27 @@ function resolveEventModel(props: Record<string, unknown> | undefined): string |
|
|||||||
return model
|
return model
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (model && typeof model === "object") {
|
||||||
|
const modelRecord = model as Record<string, unknown>
|
||||||
|
const provider = modelRecord.provider
|
||||||
|
const providerID = modelRecord.providerID
|
||||||
|
const modelName = modelRecord.model
|
||||||
|
const modelID = modelRecord.modelID
|
||||||
|
const id = modelRecord.id
|
||||||
|
|
||||||
|
if (typeof provider === "string" && typeof modelName === "string") {
|
||||||
|
return `${provider}/${modelName}`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof providerID === "string" && typeof modelID === "string") {
|
||||||
|
return `${providerID}/${modelID}`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof id === "string") {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const providerID = props?.providerID
|
const providerID = props?.providerID
|
||||||
const modelID = props?.modelID
|
const modelID = props?.modelID
|
||||||
if (typeof providerID === "string" && typeof modelID === "string") {
|
if (typeof providerID === "string" && typeof modelID === "string") {
|
||||||
@@ -45,9 +66,9 @@ export function createEventHandler(deps: HookDeps, helpers: AutoRetryHelpers) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleSessionCreated = (props: Record<string, unknown> | undefined) => {
|
const handleSessionCreated = (props: Record<string, unknown> | undefined) => {
|
||||||
const sessionInfo = props?.info as { id?: string; model?: string } | undefined
|
const sessionInfo = props?.info as Record<string, unknown> | undefined
|
||||||
const sessionID = resolveSessionEventID(props)
|
const sessionID = resolveSessionEventID(props)
|
||||||
const model = sessionInfo?.model
|
const model = resolveEventModel(sessionInfo)
|
||||||
|
|
||||||
if (sessionID && model) {
|
if (sessionID && model) {
|
||||||
log(`[${HOOK_NAME}] Session created with model`, { sessionID, model })
|
log(`[${HOOK_NAME}] Session created with model`, { sessionID, model })
|
||||||
@@ -209,7 +230,7 @@ export function createEventHandler(deps: HookDeps, helpers: AutoRetryHelpers) {
|
|||||||
const initialModel = resolveFallbackBootstrapModel({
|
const initialModel = resolveFallbackBootstrapModel({
|
||||||
sessionID,
|
sessionID,
|
||||||
source: "session.error",
|
source: "session.error",
|
||||||
eventModel: props?.model as string | undefined,
|
eventModel: resolveEventModel(props),
|
||||||
resolvedAgent,
|
resolvedAgent,
|
||||||
pluginConfig,
|
pluginConfig,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ function parseCanonicalModel(model: string): { providerID: string; modelID: stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isEquivalentModel(candidate: string, current: string): boolean {
|
function isEquivalentModel(candidate: string, current: string): boolean {
|
||||||
|
if (typeof current !== "string") return false
|
||||||
|
|
||||||
const parsedCandidate = parseCanonicalModel(candidate)
|
const parsedCandidate = parseCanonicalModel(candidate)
|
||||||
const parsedCurrent = parseCanonicalModel(current)
|
const parsedCurrent = parseCanonicalModel(current)
|
||||||
|
|
||||||
|
|||||||
@@ -1024,6 +1024,50 @@ describe("runtime-fallback", () => {
|
|||||||
expect(createLog?.data).toMatchObject({ sessionID, model })
|
expect(createLog?.data).toMatchObject({ sessionID, model })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("#given session.created carries an object-shaped model #when fallback is prepared #then it does not crash", async () => {
|
||||||
|
//#given
|
||||||
|
const promptCalls: unknown[] = []
|
||||||
|
const sessionID = "test-session-object-model"
|
||||||
|
const hook = createRuntimeFallbackHook(
|
||||||
|
createMockPluginInput({
|
||||||
|
session: {
|
||||||
|
messages: async () => ({
|
||||||
|
data: [{ info: { role: "user" }, parts: [{ type: "text", text: "continue" }] }],
|
||||||
|
}),
|
||||||
|
promptAsync: async (args: unknown) => {
|
||||||
|
promptCalls.push(args)
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
config: createMockConfig({ notify_on_fallback: false }),
|
||||||
|
pluginConfig: createMockPluginConfigWithCategoryFallback(["openai/gpt-5.4"]),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
SessionCategoryRegistry.register(sessionID, "test")
|
||||||
|
|
||||||
|
await hook.event({
|
||||||
|
event: {
|
||||||
|
type: "session.created",
|
||||||
|
properties: { info: { id: sessionID, model: { provider: "openai", model: "gpt-5.5-fast" } } },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
//#when
|
||||||
|
await hook.event({
|
||||||
|
event: {
|
||||||
|
type: "session.error",
|
||||||
|
properties: { sessionID, error: { statusCode: 429, message: "Rate limit" } },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(promptCalls).toHaveLength(1)
|
||||||
|
const createLog = logCalls.find((c) => c.msg.includes("Session created with model"))
|
||||||
|
expect(createLog?.data).toMatchObject({ sessionID, model: "openai/gpt-5.5-fast" })
|
||||||
|
})
|
||||||
|
|
||||||
test("should cleanup state on session.deleted", async () => {
|
test("should cleanup state on session.deleted", async () => {
|
||||||
const hook = createRuntimeFallbackHook(createMockPluginInput(), { config: createMockConfig() })
|
const hook = createRuntimeFallbackHook(createMockPluginInput(), { config: createMockConfig() })
|
||||||
const sessionID = "test-session-delete"
|
const sessionID = "test-session-delete"
|
||||||
|
|||||||
Reference in New Issue
Block a user