Skip to content

Fix Claude Code / Opus 4.8 compatibility and web_search reliability - #235

Open
coderhisham wants to merge 6 commits into
jwadow:mainfrom
coderhisham:feat/claude-code-compat-and-reliability
Open

Fix Claude Code / Opus 4.8 compatibility and web_search reliability#235
coderhisham wants to merge 6 commits into
jwadow:mainfrom
coderhisham:feat/claude-code-compat-and-reliability

Conversation

@coderhisham

Copy link
Copy Markdown

This PR bundles several fixes that make the gateway work reliably with the latest Claude Code (Opus 4.8), plus quality-of-life improvements for running the server. All changes keep the gateway's pass-through/transparent philosophy, are consistent across both the OpenAI and Anthropic surfaces (and streaming + non-streaming), and ship with tests. Full suite passes (only 2 environment-specific .env default-host/port tests fail locally, unrelated to these changes).

What's included

1. feat(models): Claude Opus 4.8 (1M context) + configurable context windows — closes #208

  • Adds claude-opus-4.8 to FALLBACK_MODELS with a 1M maxInputTokens, so it's recognized on the runtime.kiro.dev endpoint (which has no /ListAvailableModels).
  • Makes context accounting configurable: DEFAULT_MAX_INPUT_TOKENS is now an env var, and a new MODEL_CONTEXT_WINDOWS JSON env lets users declare per-model windows that take precedence over discovered/default limits. Affects token-usage estimation only; requests stay pass-through.

2. fix(anthropic): accept inline system role + server-side web_search blocks — closes #190, #219 (also #211, #226)

  • Claude Code injects inline role: "system" messages, which the strict Literal["user","assistant"] rejected with 422. AnthropicMessage.role is now a free-form string (mirroring the OpenAI model); inline system messages are hoisted into the top-level system prompt, matching the OpenAI adapter and avoiding spurious user turns.
  • After a web_search, follow-up turns echo back server_tool_use / web_search_tool_result blocks. These are now part of the ContentBlock union, and their content is folded into text so search grounding survives conversion.

3. fix(mcp): reliable web_search, never leak the tool call — closes #231, #173

  • The /mcp call now sends the full Kiro client-identity headers (it previously sent only Authorization, causing 403) and includes top-level profileArn for Enterprise/Builder accounts.
  • On any MCP failure, the gateway no longer leaks a raw web_search tool_use/tool_call to the client (which surfaced as "No such tool available: web_search"). It now degrades gracefully — Anthropic emits a native web_search_tool_result error; OpenAI emits an in-band "unavailable" note — across streaming and non-streaming.

4. fix(auth): actionable 401 on invalid/expired refresh token

  • A rejected refresh token (e.g. AWS SSO OIDC invalid_grant) previously surfaced as a generic 500. It now raises a typed error mapped to a clear 401 with re-auth guidance in both routes. Transient/server errors still propagate unchanged.

5. feat(cli): --stop and --force to free the server port

  • --stop stops any process listening on the resolved port and exits; --force stops the occupant and starts anyway. Adds a preflight check so an occupied port gives an actionable message instead of a raw "address already in use" traceback. Cross-platform (lsof/netstat), only targets processes listening on the exact port, never the current process.

6. fix(server): bounded graceful shutdown so Ctrl+C frees the port promptly

  • With long-lived streaming, uvicorn's graceful shutdown could hang on open SSE connections and keep the port bound. Adds SHUTDOWN_TIMEOUT (env, default 10s) passed as timeout_graceful_shutdown. A second Ctrl+C still forces immediate exit.

Testing

  • New/updated unit tests for every change: models validation, converters (system-role hoisting + echoed web_search blocks), MCP headers/profileArn, web_search anti-leak across both APIs and both modes, auth 401 detection, port utilities, CLI flags, and config.
  • Network-isolated per the project's test philosophy.

Notes

  • New env vars documented in .env.example: DEFAULT_MAX_INPUT_TOKENS, MODEL_CONTEXT_WINDOWS, SHUTDOWN_TIMEOUT.
  • Happy to split this into per-concern PRs if you'd prefer to review/merge them independently.

…xt windows (jwadow#208)

Add claude-opus-4.8 to the fallback model list with a 1M maxInputTokens so it
is recognized on the runtime.kiro.dev endpoint (which has no /ListAvailableModels).

Make context-window accounting configurable: DEFAULT_MAX_INPUT_TOKENS is now an
env var, and MODEL_CONTEXT_WINDOWS lets users declare per-model windows (e.g. 1M)
that take precedence over discovered/default limits. This only affects token-usage
estimation; requests remain pass-through.

Adds tests for the override resolution chain, the env parser, and the opus-4.8
fallback entry.
When the auth provider rejects the refresh token (e.g. AWS SSO OIDC 400
invalid_grant), the gateway previously surfaced it as a generic HTTP 500
'Internal error'. Add InvalidRefreshTokenError, raise it from get_access_token
when the provider response indicates an invalid/expired token, and map it to a
clear 401 (authentication_error) with re-auth guidance in both API routes.

Transient/server errors (e.g. 500) still propagate unchanged. Adds tests for the
detector and for get_access_token raising the typed error vs propagating others.
…ow#231, jwadow#173)

Two root causes made web_search fail and surface as 'No such tool available' in
clients:

1. The /mcp call sent only a bare Authorization header. It now sends the full
   Kiro client-identity headers (User-Agent/x-amz-user-agent/x-amzn-kiro-agent-mode)
   adjusted for JSON-RPC, and includes top-level profileArn for Enterprise
   accounts. Error logging now includes the response body.

2. On MCP failure the gateway fell through and emitted the raw web_search
   tool_use/tool_call to the client. All paths now degrade gracefully:
   - Anthropic streaming + non-streaming emit a native web_search_tool_result
     (error) instead of leaking a tool_use.
   - OpenAI streaming (and non-streaming via reuse) emit an in-band unavailable
     note instead of a tool_call.

Adds shared outcome helpers and tests across both APIs and both modes.
…blocks (jwadow#190, jwadow#219)

Claude Code sends two shapes the strict models rejected with 422:

1. Inline messages with role 'system' in the messages array. AnthropicMessage.role
   is now a free-form string (mirroring OpenAI ChatMessage); inline system
   messages are hoisted into the top-level system prompt (separate_inline_system_messages),
   matching the OpenAI adapter and avoiding spurious user turns.

2. Follow-up turns echoing back the server_tool_use / web_search_tool_result
   blocks the gateway emitted. These block types are now part of the ContentBlock
   union, and convert_anthropic_content_to_text folds an echoed
   web_search_tool_result into text so search grounding survives conversion.

Adds model-validation, converter, and route tests for both shapes.
Stopping a stuck/orphaned gateway previously meant manually running lsof + kill.

Add cross-platform port utilities (kiro/port_utils.py) and two CLI options:
- --stop: stop any process listening on the resolved port, then exit.
- --force: if the port is in use at startup, stop the occupant and start anyway.

Also adds a preflight check so an occupied port produces a clear, actionable
message (with the exact --stop/--force commands) instead of a raw
'address already in use' traceback. Only processes LISTENING on the exact port
are targeted, and the current process is always excluded.

Adds test_port_utils.py covering detection, lsof/netstat parsing, self-exclusion,
graceful/forceful termination, and free_port orchestration.
On Ctrl+C/SIGTERM, uvicorn waits for in-flight connections to drain. With
long-lived streaming (STREAMING_READ_TIMEOUT=300), an open SSE connection kept
the process alive and the port bound for minutes - which is why a stopped
gateway sometimes lingered on the port.

Add SHUTDOWN_TIMEOUT (env, default 10s) and pass it to uvicorn as
timeout_graceful_shutdown, so shutdown is bounded and the port is freed promptly.
A second Ctrl+C still forces an immediate exit.

Adds CLI-parsing tests for --stop/--force and config tests for SHUTDOWN_TIMEOUT.
@cla-bot

cla-bot Bot commented Jun 26, 2026

Copy link
Copy Markdown

Thanks for the PR! 🎉

Before merge, we need a one-time CLA confirmation.
It confirms that you have the right to contribute this code and allow the project to use it.

Full CLA text:
https://github.com/jwadow/kiro-gateway/blob/main/CLA.md

Please reply once with:

I have read the CLA and I accept its terms

You need to write once, all further messages from me can be ignored.

@ankitcharolia

Copy link
Copy Markdown

@coderhisham could you give a try to this gateway: https://github.com/ankitcharolia/kiro-gateway

It works quite well with All AI harness and actively being developed. The most important thing is that it is ACP compliant

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BUG: web search issue BUG: models not include claude-opus-4.8 BUG: 422 error when newer Claude Code client inlines role: "system" in messages array

2 participants