Skip to content
Closed
4 changes: 4 additions & 0 deletions lua/claude_handler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ function _M.handle()

-- Translate Claude request to OpenAI format
local oai_request = claude_fmt.request_to_openai(claude_body)

-- Thinking mode handling (operates on OpenAI request format)
oai_request, model = e2ee.handle_thinking(oai_request)

local oai_body = cjson.encode(oai_request)

if not is_streaming then
Expand Down
13 changes: 10 additions & 3 deletions lua/e2ee_discovery.lua
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,18 @@ function _M.resolve_chute_id(model, api_key)
return model
end

-- Strip the :THINKING suffix before map lookup. v1/models returns
-- base model IDs only; :THINKING is a proxy-side flag and must not
-- leak into the lookup key. Other suffixes (e.g. LoRA names) are
-- left intact so they surface as proper errors upstream rather than
-- silently resolving to the base chute.
local base_model = model:match("^(.-):THINKING$") or model

local now = ngx.now()

-- Check cache
if model_map and now < model_map_expires then
local entry = model_map[model]
local entry = model_map[base_model]
if entry then
return check_confidential(model, entry)
end
Expand All @@ -114,7 +121,7 @@ function _M.resolve_chute_id(model, api_key)
if not map then
-- If we have a stale cache, try it
if model_map then
local entry = model_map[model]
local entry = model_map[base_model]
if entry then
return check_confidential(model, entry)
end
Expand All @@ -125,7 +132,7 @@ function _M.resolve_chute_id(model, api_key)
model_map = map
model_map_expires = now + MODEL_MAP_TTL

local entry = map[model]
local entry = map[base_model]
return check_confidential(model, entry)
end

Expand Down
93 changes: 93 additions & 0 deletions lua/e2ee_handler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,31 @@ local API_BASE = "https://api.chutes.ai"

local _M = {}

-- Per-model thinking defaults. Mirrors chutes-api's invocation router
-- (api/invocation/router.py). The /v1/models endpoint exposes a
-- "reasoning" capability flag in supported_features but not the
-- per-model default value, so these stay duplicated until the API
-- grows a dedicated attribute.
local THINKING_DEFAULT_ON_PREFIXES = {
"deepseek-ai/DeepSeek-V3.2-Speciale",
"zai-org/GLM-4.7",
"moonshotai/Kimi-K2.5",
}
local THINKING_DEFAULT_ON_EXACT = {
["deepseek-ai/DeepSeek-V3.2-Speciale"] = true,
["deepseek-ai/DeepSeek-V3.2-Speciale-TEE"] = true,
["zai-org/GLM-4.7"] = true,
["zai-org/GLM-4.7-TEE"] = true,
["moonshotai/Kimi-K2.5"] = true,
["moonshotai/Kimi-K2.5-TEE"] = true,
}
local MIMO_PREFIX = "XiaomiMiMo/MiMo-V2-Flash"
local MIMO_EXACT_TEE = "XiaomiMiMo/MiMo-V2-Flash-TEE"

local function starts_with(s, prefix)
return s:sub(1, #prefix) == prefix
end

--- Extract API key from Authorization header or x-api-key
function _M.get_api_key()
local headers = ngx.req.get_headers()
Expand All @@ -40,6 +65,68 @@ function _M.get_api_key()
return key
end

--- Handle thinking mode for OpenAI format requests
-- Takes an OpenAI request table, applies thinking logic, and returns the modified table
-- Also strips :THINKING suffix from model name and returns the model name
function _M.handle_thinking(oai_request)
if not oai_request or not oai_request.model then
return oai_request, nil
end

local model = oai_request.model
local enable_thinking = false
local think_header = ngx.req.get_headers()["X-Enable-Thinking"]
if think_header and think_header:lower() == "true" then
enable_thinking = true
end
if model:match(":THINKING$") then
model = model:sub(1, -#":THINKING" - 1)
oai_request.model = model
enable_thinking = true
end
if enable_thinking then
if not oai_request.chat_template_kwargs then
oai_request.chat_template_kwargs = {}
end
oai_request.chat_template_kwargs.thinking = true
oai_request.chat_template_kwargs.enable_thinking = true
end

local kwargs = oai_request.chat_template_kwargs
if kwargs then
-- Normalize the two spellings so downstream sees both.
if kwargs.thinking ~= nil and kwargs.enable_thinking == nil then
kwargs.enable_thinking = kwargs.thinking
end
if kwargs.enable_thinking ~= nil and kwargs.thinking == nil then
kwargs.thinking = kwargs.enable_thinking
end

if kwargs.thinking == nil then
for _, prefix in ipairs(THINKING_DEFAULT_ON_PREFIXES) do
if starts_with(model, prefix) then
kwargs.thinking = true
kwargs.enable_thinking = true
break
end
end
end

if kwargs.thinking == nil and starts_with(model, MIMO_PREFIX) then
kwargs.thinking = false
kwargs.enable_thinking = false
end
else
if THINKING_DEFAULT_ON_EXACT[model] then
oai_request.chat_template_kwargs = { thinking = true, enable_thinking = true }
elseif model == MIMO_EXACT_TEE then
oai_request.chat_template_kwargs = { thinking = false, enable_thinking = false }
end
end

return oai_request, model
end

--- Send error response
function _M.send_error(status, message)
ngx.status = status
Expand Down Expand Up @@ -345,6 +432,12 @@ function _M.handle()
return _M.send_error(400, "missing 'model' field")
end

-- Thinking mode handling
payload, model = _M.handle_thinking(payload)

-- Re-encode body with any chat_template_kwargs modifications
body = cjson.encode(payload)

local is_streaming = (payload.stream == true)
local original_path = ngx.var.uri

Expand Down
4 changes: 4 additions & 0 deletions lua/responses_handler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ function _M.handle()

-- Translate Responses request to OpenAI format
local oai_request = resp_fmt.request_to_openai(resp_body)

-- Thinking mode handling (operates on OpenAI request format)
oai_request, model = e2ee.handle_thinking(oai_request)

local oai_body = cjson.encode(oai_request)

if not is_streaming then
Expand Down
Loading