2026-03-18 20:37:42 +01:00
|
|
|
import { normalizeSDKResponse } from "../shared/normalize-sdk-response"
|
2026-03-18 14:21:27 +01:00
|
|
|
import { getSessionPromptParams } from "../shared/session-prompt-params-state"
|
2026-03-18 20:37:42 +01:00
|
|
|
import { resolveCompatibleModelSettings } from "../shared"
|
2026-03-18 14:21:27 +01:00
|
|
|
|
2026-02-19 04:41:00 +02:00
|
|
|
export type ChatParamsInput = {
|
2026-02-08 16:25:25 +09:00
|
|
|
sessionID: string
|
|
|
|
|
agent: { name?: string }
|
|
|
|
|
model: { providerID: string; modelID: string }
|
|
|
|
|
provider: { id: string }
|
|
|
|
|
message: { variant?: string }
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-17 11:57:56 +01:00
|
|
|
type ChatParamsHookInput = ChatParamsInput & {
|
|
|
|
|
rawMessage?: Record<string, unknown>
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-19 04:41:00 +02:00
|
|
|
export type ChatParamsOutput = {
|
2026-02-08 16:25:25 +09:00
|
|
|
temperature?: number
|
|
|
|
|
topP?: number
|
|
|
|
|
topK?: number
|
|
|
|
|
options: Record<string, unknown>
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-18 20:37:42 +01:00
|
|
|
type ProviderListClient = {
|
|
|
|
|
provider?: {
|
|
|
|
|
list?: () => Promise<unknown>
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type ProviderModelMetadata = {
|
|
|
|
|
variants?: Record<string, unknown>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type ProviderListEntry = {
|
|
|
|
|
id?: string
|
|
|
|
|
models?: Record<string, ProviderModelMetadata>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type ProviderListData = {
|
|
|
|
|
all?: ProviderListEntry[]
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-08 16:25:25 +09:00
|
|
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
|
|
return typeof value === "object" && value !== null
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-17 11:57:56 +01:00
|
|
|
function buildChatParamsInput(raw: unknown): ChatParamsHookInput | null {
|
2026-02-08 16:25:25 +09:00
|
|
|
if (!isRecord(raw)) return null
|
|
|
|
|
|
|
|
|
|
const sessionID = raw.sessionID
|
|
|
|
|
const agent = raw.agent
|
|
|
|
|
const model = raw.model
|
|
|
|
|
const provider = raw.provider
|
|
|
|
|
const message = raw.message
|
|
|
|
|
|
|
|
|
|
if (typeof sessionID !== "string") return null
|
|
|
|
|
if (!isRecord(model)) return null
|
|
|
|
|
if (!isRecord(provider)) return null
|
|
|
|
|
if (!isRecord(message)) return null
|
|
|
|
|
|
2026-02-19 04:41:00 +02:00
|
|
|
let agentName: string | undefined
|
|
|
|
|
if (typeof agent === "string") {
|
|
|
|
|
agentName = agent
|
|
|
|
|
} else if (isRecord(agent)) {
|
|
|
|
|
const name = agent.name
|
|
|
|
|
if (typeof name === "string") {
|
|
|
|
|
agentName = name
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!agentName) return null
|
|
|
|
|
|
2026-02-08 16:25:25 +09:00
|
|
|
const providerID = model.providerID
|
2026-03-18 20:37:42 +01:00
|
|
|
const modelID = typeof model.modelID === "string"
|
|
|
|
|
? model.modelID
|
|
|
|
|
: typeof model.id === "string"
|
|
|
|
|
? model.id
|
|
|
|
|
: undefined
|
2026-02-08 16:25:25 +09:00
|
|
|
const providerId = provider.id
|
|
|
|
|
const variant = message.variant
|
|
|
|
|
|
|
|
|
|
if (typeof providerID !== "string") return null
|
|
|
|
|
if (typeof modelID !== "string") return null
|
|
|
|
|
if (typeof providerId !== "string") return null
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
sessionID,
|
2026-02-19 04:41:00 +02:00
|
|
|
agent: { name: agentName },
|
2026-02-08 16:25:25 +09:00
|
|
|
model: { providerID, modelID },
|
|
|
|
|
provider: { id: providerId },
|
2026-03-17 11:57:56 +01:00
|
|
|
message,
|
|
|
|
|
rawMessage: message,
|
|
|
|
|
...(typeof variant === "string" ? {} : {}),
|
2026-02-08 16:25:25 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isChatParamsOutput(raw: unknown): raw is ChatParamsOutput {
|
|
|
|
|
if (!isRecord(raw)) return false
|
|
|
|
|
if (!isRecord(raw.options)) {
|
|
|
|
|
raw.options = {}
|
|
|
|
|
}
|
|
|
|
|
return isRecord(raw.options)
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-18 20:37:42 +01:00
|
|
|
async function getVariantCapabilities(
|
|
|
|
|
client: ProviderListClient | undefined,
|
|
|
|
|
model: { providerID: string; modelID: string },
|
|
|
|
|
): Promise<string[] | undefined> {
|
|
|
|
|
const providerList = client?.provider?.list
|
|
|
|
|
if (typeof providerList !== "function") {
|
|
|
|
|
return undefined
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const response = await providerList()
|
|
|
|
|
const data = normalizeSDKResponse<ProviderListData>(response, {})
|
|
|
|
|
const providerEntry = data.all?.find((entry) => entry.id === model.providerID)
|
|
|
|
|
const variants = providerEntry?.models?.[model.modelID]?.variants
|
|
|
|
|
if (!variants) {
|
|
|
|
|
return undefined
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Object.keys(variants)
|
|
|
|
|
} catch {
|
|
|
|
|
return undefined
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-08 16:25:25 +09:00
|
|
|
export function createChatParamsHandler(args: {
|
2026-03-17 11:57:56 +01:00
|
|
|
anthropicEffort: { "chat.params"?: (input: ChatParamsHookInput, output: ChatParamsOutput) => Promise<void> } | null
|
2026-03-18 20:37:42 +01:00
|
|
|
client?: ProviderListClient
|
2026-02-08 16:25:25 +09:00
|
|
|
}): (input: unknown, output: unknown) => Promise<void> {
|
|
|
|
|
return async (input, output): Promise<void> => {
|
|
|
|
|
const normalizedInput = buildChatParamsInput(input)
|
|
|
|
|
if (!normalizedInput) return
|
|
|
|
|
if (!isChatParamsOutput(output)) return
|
|
|
|
|
|
2026-03-18 14:21:27 +01:00
|
|
|
const storedPromptParams = getSessionPromptParams(normalizedInput.sessionID)
|
|
|
|
|
if (storedPromptParams) {
|
|
|
|
|
if (storedPromptParams.temperature !== undefined) {
|
|
|
|
|
output.temperature = storedPromptParams.temperature
|
|
|
|
|
}
|
|
|
|
|
if (storedPromptParams.topP !== undefined) {
|
|
|
|
|
output.topP = storedPromptParams.topP
|
|
|
|
|
}
|
|
|
|
|
if (storedPromptParams.options) {
|
|
|
|
|
output.options = {
|
|
|
|
|
...output.options,
|
|
|
|
|
...storedPromptParams.options,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-18 20:37:42 +01:00
|
|
|
const variantCapabilities = await getVariantCapabilities(args.client, normalizedInput.model)
|
|
|
|
|
|
|
|
|
|
const compatibility = resolveCompatibleModelSettings({
|
|
|
|
|
providerID: normalizedInput.model.providerID,
|
|
|
|
|
modelID: normalizedInput.model.modelID,
|
|
|
|
|
desired: {
|
2026-03-25 09:28:59 +01:00
|
|
|
variant: typeof normalizedInput.message.variant === "string"
|
|
|
|
|
? normalizedInput.message.variant
|
|
|
|
|
: undefined,
|
2026-03-18 20:37:42 +01:00
|
|
|
reasoningEffort: typeof output.options.reasoningEffort === "string"
|
|
|
|
|
? output.options.reasoningEffort
|
|
|
|
|
: undefined,
|
|
|
|
|
},
|
|
|
|
|
capabilities: {
|
|
|
|
|
variants: variantCapabilities,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (normalizedInput.rawMessage) {
|
|
|
|
|
if (compatibility.variant !== undefined) {
|
|
|
|
|
normalizedInput.rawMessage.variant = compatibility.variant
|
|
|
|
|
} else {
|
|
|
|
|
delete normalizedInput.rawMessage.variant
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
normalizedInput.message = normalizedInput.rawMessage as { variant?: string }
|
|
|
|
|
|
|
|
|
|
if (compatibility.reasoningEffort !== undefined) {
|
|
|
|
|
output.options.reasoningEffort = compatibility.reasoningEffort
|
|
|
|
|
} else if ("reasoningEffort" in output.options) {
|
|
|
|
|
delete output.options.reasoningEffort
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-08 16:25:25 +09:00
|
|
|
await args.anthropicEffort?.["chat.params"]?.(normalizedInput, output)
|
|
|
|
|
}
|
|
|
|
|
}
|