bridgeContextMiddleware injects the agent UUID (store.WithAgentID) but never the
agent key, so session tools (sessions_list/history/send/status) that resolve the
caller via tools.ToolAgentKeyFromCtx always get "" and fail with "agent context
required" when invoked over /mcp/bridge (e.g. by a claude-cli provider agent).
Session keys are namespaced by agent key (agent:<key>:...), not UUID, and the
bridge has no RunContext fallback.
Inject the key in the same block that already fetches the agent for shell-deny
overrides — symmetric with store.WithShellDenyGroups, no extra DB call, no new
imports.
#1094 makes the same fix at this site but bundles it into a large, stale
(CONFLICTING) ACP/i18n refactor; this is the minimal extraction of that slice.
It omits #1094's companion store.WithAgentKey write, which nothing on dev reads
(every session tool reads tools.ToolAgentKeyFromCtx).
Add internal/gateway/bridge_context_test.go: a regression guard asserting a
signed X-Agent-ID lands the agent key in tools.ToolAgentKeyFromCtx, plus a
no-store negative control.
Signed-off-by: Jaegeon Oh <zezaeoh@gmail.com>
Upstream returns HTTP 400 `thinking is enabled but reasoning_content
is missing in assistant tool call message at index N` when an
assistant message with tool_calls is replayed in history without a
reasoning_content field. Kimi has server-side thinking enabled by
default for kimi-k2-turbo-preview, so the field is required even when
goclaw doesn't have captured reasoning content to send (e.g. the model
emitted a tool_call without any thinking, or the stream chunk that
carried it was lost).
The existing branch already gates on
openAIWireAssistantReasoningContent(model) (kimi/deepseek/o-series)
and emits the field only when Thinking != "". Extend so kimi_coding
also emits an empty string when Thinking is unset — satisfies Kimi's
"must be present" check without inventing reasoning content. Other
providers in the allowlist keep today's behavior: omit when empty.
Three new tests:
- kimi_coding always carries reasoning_content on assistant
- kimi_coding preserves real Thinking content when set
- non-kimi providers (deepseek) do NOT inject empty reasoning_content
Reference: NousResearch/hermes-agent plugins/model-providers/kimi-coding
documents the same upstream behavior (thinking enabled by default,
reasoning_content roundtrip required).
Moonshot's Kimi Coding endpoint is OpenAI-compatible on the wire but
has two non-standard rules:
1. Every request must carry `User-Agent: claude-code/0.1.0` — without
it the upstream rejects the call outright.
2. `temperature` is locked to the server default; passing any other
value returns HTTP 400 `invalid temperature: only 1 is allowed for
this model`.
Rather than special-case either, this commit generalises both:
- WithExtraHeaders on OpenAIProvider — static headers attached to
every outgoing request. Reusable by any future provider that needs
pinned identity headers; mirrored in adapter_openai.ToRequest so
callers using the adapter path see the same shape.
- The existing skipTemp branch in openai_request.go gets a
provider_type check — kimi_coding joins o1/o3/o4/gpt-5-mini in
omitting `temperature` from the request body.
Provider wiring:
- store.ProviderKimiCoding constant + ValidProviderTypes entry +
KimiCoding{DefaultAPIBase,DefaultModel,RequiredUserAgent}.
- case store.ProviderKimiCoding in both registration switches
(cmd/gateway_providers.go and internal/http/providers.go).
- UI dropdown entry with the API base pre-filled.
5 unit tests cover: real outgoing header injection, adapter-path
header mirroring, empty-map WithExtraHeaders no-op, kimi_coding
strips temperature, and the negative control (other providers still
forward temperature).
Admin flow: Providers → Add → "Kimi Coding (Moonshot)" → paste API
key → save.
- Iterate over all DNS responses instead of just the first IP to prevent
multi-IP SSRF bypass where a blocked IP appears after a public one
- Make resolver injectable via package-level lookupHost variable
- Replace real-DNS test (google.com) with stubbed resolver covering:
single public/private IP, DNS failure, empty results, invalid IP,
multi-IP public→private, private→public, all public, metadata IP,
IPv6 loopback
- Add fmt import to test file for stubbed error messages
- Add validateSelfHostedDomain() with SSRF protection (blocked CIDRs,
hostname resolution, localhost/.local/.localhost rejection)
- Validate port range 1-65535 for self-hosted domains with ports
- Mirror SSRF + port validation in frontend form
- Add comprehensive tests: SSRF blocked IPs, invalid ports, valid public
domains
Add CLI argument support to pkg-helper and fallback execution via $PATH in dep_installer when the Unix socket is unavailable.
💘 Generated with Crush
Assisted-by: Crush:gemini-3.1-pro-preview-customtools
Split bitrixDomainRegex into bitrixCloudDomainRegex (cloud SaaS domains)
and selfHostedDomainRegex (custom FQDNs). Users can now register portals
with self-hosted Bitrix24 instances like bx.mycompany.com instead of
being restricted to *.bitrix24.{com,eu,...} domains.
Quick Ack and Intermediate Replies now require generated delivery text in generated modes, while fixed-template mode remains explicit. Includes provider/model resolution coverage and UI copy updates.
* fix(pipeline): preserve pending messages during compaction
When PruneStage or ThinkStage triggers mid-loop compaction,
ReplaceHistory() clears the pending buffer. If the current
iteration's assistant(tool_calls) message is in pending, it gets
discarded. The next iteration then sends tool-result messages
without a preceding tool_calls message, causing DeepSeek/OpenAI
to reject the request with HTTP 400:
Messages with role 'tool' must be a response to a preceding
message with 'tool_calls'
Fix: save pending before ReplaceHistory and restore it after.
Reproduction:
- Agent with 64K context window
- 4-5 consecutive tool calls (e.g. read_file) in one session
- Token count exceeds soft budget during ToolStage
- PruneStage compaction clears pending including assistant(tool_calls)
- Next iteration fails with HTTP 400
* test(pipeline): regression guards for pending preservation during compaction
Add focused unit tests covering both ReplaceHistory call sites that previously
dropped the pending buffer:
- PruneStage mid-loop compaction must preserve the in-flight
assistant(tool_calls) message so tool_calls -> tool_result pairing survives,
preventing the OpenAI/DeepSeek 400 on the next provider call.
- ThinkStage emergency overflow compaction must preserve already-staged pending
messages (e.g. iteration-budget nudges) across the retry.
Both tests fail without the preservation fix and pass with it.
Co-authored-by: ollielin <2137816+ollielin@users.noreply.github.com>
---------
Co-authored-by: Duy /zuey/ <duy@wearetopgroup.com>
Co-authored-by: ollielin <ollielin@tencent.com>
Co-authored-by: Thieu Nguyen <thieunguyen.tech@gmail.com>
Co-authored-by: ollielin <2137816+ollielin@users.noreply.github.com>