From 64615a9b191c2cf34b4e92921332ed127e660a25 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 30 Jul 2026 18:32:17 +0800 Subject: [PATCH] fix(all lua adapters): type-safe field access in transform_response All 5 adapters (deepseek, github, groq, mistral, openai) now: - Guard against json.decode returning nil (null body) - Use type() == "table" instead of truthy checks for table access - Prevents 'attempt to index a non-table object(nil)' crashes --- internal/lua/adapters/deepseek.lua | 10 +++++----- internal/lua/adapters/github.lua | 10 +++++----- internal/lua/adapters/groq.lua | 10 +++++----- internal/lua/adapters/mistral.lua | 10 +++++----- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/internal/lua/adapters/deepseek.lua b/internal/lua/adapters/deepseek.lua index abae3dc..bcc6e41 100644 --- a/internal/lua/adapters/deepseek.lua +++ b/internal/lua/adapters/deepseek.lua @@ -21,7 +21,7 @@ end function adapter.transform_response(raw_body) local ok, resp = pcall(json.decode, raw_body) - if not ok then return raw_body end + if not ok or resp == nil then return raw_body end local unified = { content = "", @@ -29,20 +29,20 @@ function adapter.transform_response(raw_body) token_usage = { prompt = 0, completion = 0, total = 0 } } - if resp.usage then + if type(resp.usage) == "table" then unified.token_usage.prompt = resp.usage.prompt_tokens or 0 unified.token_usage.completion = resp.usage.completion_tokens or 0 unified.token_usage.total = resp.usage.total_tokens or 0 end - if resp.choices and #resp.choices > 0 then + if type(resp.choices) == "table" and #resp.choices > 0 then local ch = resp.choices[1] - if ch.message then + if type(ch.message) == "table" then unified.content = ch.message.content or "" if ch.message.reasoning_content then unified.reasoning_content = ch.message.reasoning_content end - if ch.message.tool_calls then + if type(ch.message.tool_calls) == "table" then local tcs = {} for _, tc in ipairs(ch.message.tool_calls) do local args_ok, args = pcall(json.decode, tc["function"].arguments) diff --git a/internal/lua/adapters/github.lua b/internal/lua/adapters/github.lua index 75d121f..c27103b 100644 --- a/internal/lua/adapters/github.lua +++ b/internal/lua/adapters/github.lua @@ -21,7 +21,7 @@ end function adapter.transform_response(raw_body) local ok, resp = pcall(json.decode, raw_body) - if not ok then return raw_body end + if not ok or resp == nil then return raw_body end local unified = { content = "", @@ -29,17 +29,17 @@ function adapter.transform_response(raw_body) token_usage = { prompt = 0, completion = 0, total = 0 } } - if resp.usage then + if type(resp.usage) == "table" then unified.token_usage.prompt = resp.usage.prompt_tokens or 0 unified.token_usage.completion = resp.usage.completion_tokens or 0 unified.token_usage.total = resp.usage.total_tokens or 0 end - if resp.choices and #resp.choices > 0 then + if type(resp.choices) == "table" and #resp.choices > 0 then local ch = resp.choices[1] - if ch.message then + if type(ch.message) == "table" then unified.content = ch.message.content or "" - if ch.message.tool_calls then + if type(ch.message.tool_calls) == "table" then local tcs = {} for _, tc in ipairs(ch.message.tool_calls) do local args_ok, args = pcall(json.decode, tc["function"].arguments) diff --git a/internal/lua/adapters/groq.lua b/internal/lua/adapters/groq.lua index 1b2780f..3d66033 100644 --- a/internal/lua/adapters/groq.lua +++ b/internal/lua/adapters/groq.lua @@ -20,7 +20,7 @@ end function adapter.transform_response(raw_body) local ok, resp = pcall(json.decode, raw_body) - if not ok then return raw_body end + if not ok or resp == nil then return raw_body end local unified = { content = "", @@ -28,17 +28,17 @@ function adapter.transform_response(raw_body) token_usage = { prompt = 0, completion = 0, total = 0 } } - if resp.usage then + if type(resp.usage) == "table" then unified.token_usage.prompt = resp.usage.prompt_tokens or 0 unified.token_usage.completion = resp.usage.completion_tokens or 0 unified.token_usage.total = resp.usage.total_tokens or 0 end - if resp.choices and #resp.choices > 0 then + if type(resp.choices) == "table" and #resp.choices > 0 then local ch = resp.choices[1] - if ch.message then + if type(ch.message) == "table" then unified.content = ch.message.content or "" - if ch.message.tool_calls then + if type(ch.message.tool_calls) == "table" then local tcs = {} for _, tc in ipairs(ch.message.tool_calls) do local args_ok, args = pcall(json.decode, tc["function"].arguments) diff --git a/internal/lua/adapters/mistral.lua b/internal/lua/adapters/mistral.lua index b98c2c4..763da2a 100644 --- a/internal/lua/adapters/mistral.lua +++ b/internal/lua/adapters/mistral.lua @@ -20,7 +20,7 @@ end function adapter.transform_response(raw_body) local ok, resp = pcall(json.decode, raw_body) - if not ok then return raw_body end + if not ok or resp == nil then return raw_body end local unified = { content = "", @@ -28,17 +28,17 @@ function adapter.transform_response(raw_body) token_usage = { prompt = 0, completion = 0, total = 0 } } - if resp.usage then + if type(resp.usage) == "table" then unified.token_usage.prompt = resp.usage.prompt_tokens or 0 unified.token_usage.completion = resp.usage.completion_tokens or 0 unified.token_usage.total = resp.usage.total_tokens or 0 end - if resp.choices and #resp.choices > 0 then + if type(resp.choices) == "table" and #resp.choices > 0 then local ch = resp.choices[1] - if ch.message then + if type(ch.message) == "table" then unified.content = ch.message.content or "" - if ch.message.tool_calls then + if type(ch.message.tool_calls) == "table" then local tcs = {} for _, tc in ipairs(ch.message.tool_calls) do local args_ok, args = pcall(json.decode, tc["function"].arguments)