Skip to content

Claude Desktop App third-party inference support + refresh model catalog - #248

Open
muhmd-sinan wants to merge 19 commits into
jwadow:mainfrom
muhmd-sinan:feat/claude-desktop-third-party-inference
Open

Claude Desktop App third-party inference support + refresh model catalog#248
muhmd-sinan wants to merge 19 commits into
jwadow:mainfrom
muhmd-sinan:feat/claude-desktop-third-party-inference

Conversation

@muhmd-sinan

Copy link
Copy Markdown

This PR adds support for Anthropic's Claude Desktop App
"Configure third-party inference" feature and refreshes the model catalog
against the current Kiro CLI output (2.11.1, snapshot 2026-07-16).

- Change DEFAULT_SERVER_HOST from 0.0.0.0 to 127.0.0.1 so a personal-use install doesn't quietly expose the gateway to the LAN. Docker/LAN users can still set SERVER_HOST=0.0.0.0 explicitly.
- Add missing fallback models: claude-opus-4.8, claude-sonnet-4.5-1m, claude-sonnet-4.6-1m, claude-opus-4.6-1m. The runtime.kiro.dev endpoint doesn't expose /ListAvailableModels, so this list is what clients see out of the box.
- Extend GET /v1/models with Anthropic-shape fields (type, display_name, created_at, has_more, first_id, last_id) alongside the existing OpenAI-shape (object, created, owned_by). Anthropic and OpenAI SDKs each ignore fields they don't recognize, so a single endpoint serves both client families - specifically, this makes Claude Desktop's third-party inference model dropdown populate.
- Accept x-api-key OR Authorization: Bearer on /v1/models (Claude Desktop sends x-api-key).
- Add GET /v1/models/{model_id} on the Anthropic router (Anthropic Models retrieve API).
- Extract _humanize_model_id helper to derive display names from internal ids.
- Use hmac.compare_digest for API key checks on both routers to eliminate timing side-channel on PROXY_API_KEY.
Long-running Anthropic streams (extended thinking, big tool loops) can go 30+ seconds between upstream events. Some clients (Claude Desktop in particular) and intermediaries kill idle SSE sockets in that window.
- Add _iter_with_ping generator that injects 'event: ping' every 15s of upstream silence. Ping events are part of Anthropic's public streaming spec so any compliant client ignores them safely.
- Include input_tokens (in addition to output_tokens) in message_delta.usage to match Anthropic's current streaming spec.
Preserves existing cache_read_input_tokens / cache_creation_input_tokens forwarding when the upstream reports them.
The v2.3 banner uses ghost/speech-balloon/arrow emoji which crash Python on Windows when stdout has been redirected to a file, when running under cp1252 (default on many pre-Windows-10 systems), or when launched detached via Start-Process with -RedirectStandardOutput. UnicodeEncodeError killed the process before uvicorn could bind.
- Reconfigure sys.stdout to UTF-8 with errors='replace' at banner time (best-effort - some stream types don't support reconfigure).
- Replace ghost/arrow glyphs with plain ASCII so the banner still renders even when reconfigure fails.
- Wrap each print in a try/except UnicodeEncodeError as a final safety net.
No behavior change in a normal UTF-8 terminal.
Optional PowerShell + .cmd shims for running the gateway on Windows and integrating with Claude Desktop's third-party inference feature. All under a windows/ subfolder to make the OS specificity obvious; nothing here is required to run the gateway.
- gw-start / gw-stop / gw-status / gw-ping: manage the gateway as a background process, log to %TEMP%, expose port + /health + real Kiro round-trip checks.
- gw-launch-claude + 'Claude with Kiro.cmd': single-click launcher that starts the gateway if needed and opens Claude Desktop via its AppUserModelID.
- gw-make-shortcut: registers a .lnk on the Desktop and/or Start Menu using Claude Desktop's own icon.
- gw-install-autostart: registers a Task Scheduler entry to start the gateway hidden at every logon (requires an elevated shell).
- windows/README.md documents usage and known caveats.
- .gitignore: exclude runtime pid/log files generated by the helper scripts.
- Add a 'Claude Desktop (Third-Party Inference)' section explaining what to put in each field of the Configure third-party inference panel and why Test connection lights up green.
- Add a 'Fork notes' section summarizing the changes this fork carries on top of upstream v2.3 (streaming ping, hybrid /v1/models, loopback bind, refreshed model list, etc.).
- Link out to windows/README.md.
…lumbing

Kiro's actual model catalog moved on. Snapshot from 'kiro-cli chat --list-models --format json-pretty' on 2026-07-16 (kiro-cli 2.11.1):
- Add: claude-sonnet-5, claude-opus-4.8, gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna
- Drop: -1m suffixed variants. Kiro doesn't expose them - the 1M-context models advertise their window directly on the base id (claude-sonnet-4.6, claude-opus-4.6/4.7/4.8, claude-sonnet-5, auto).
Also carry the real contextWindowTokens per model in FALLBACK_MODELS. cache.get_max_input_tokens() now reads either the legacy tokenLimits.maxInputTokens (from /ListAvailableModels) or the modern contextWindowTokens (from FALLBACK_MODELS / kiro-cli), so clients see the correct context size for every model.
Anthropic's public spec only defines role user/assistant in the messages
array, but Claude Desktop's third-party inference feature sends role=system
inside messages (in addition to the top-level system field). Every request
from that client currently gets a 422:

  {'type':'literal_error','loc':['body','messages',1,'role'],
   'msg':"Input should be 'user' or 'assistant'", 'input':'system'}

- Widen AnthropicMessage.role to Literal['user','assistant','system'].
- In the /v1/messages handler, sweep any role=='system' messages out of the
  array BEFORE conversion/tokenization/streaming, extract their text (str or
  list-of-blocks), and merge into request_data.system (string, list, or None
  supported).

Downstream code (converters, tokenizer, streaming) still sees canonical
user/assistant only, so nothing else has to change.
8000 is aggressively common (dev servers, jupyter, django, minio, http
proxies, corporate captive portals). Move the Windows helper scripts and
their docs to 8787, which is much less contested and clearer as an
Anthropic-shaped gateway port. .env has been kept out of the repo (it's
gitignored) but the runtime default users see now matches.

No behavior change beyond the default port; every script still accepts
-Port to override.
@cla-bot

cla-bot Bot commented Jul 16, 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.

… picker

Claude Desktop's in-chat model picker whitelists Anthropic-canonical dash
form ids (claude-opus-4-8, claude-sonnet-4-6, claude-haiku-4-5). Kiro's
native ids use dot form in the version segment (claude-opus-4.8,
claude-sonnet-4.6). Models advertised only under the dot form get flagged
"Unavailable" in Claude Desktop's picker, even when they work fine over
the wire.

Fix: /v1/models now emits both forms for every Claude-family model whose
version uses a dot. Non-Claude families and single-digit-version Claude
ids (claude-sonnet-4, claude-sonnet-5) are unaffected - they either
match the whitelist already or aren't gated on it.

- New helper: _anthropic_public_alias() in routes_anthropic.py maps
  claude-opus-4.8 -> claude-opus-4-8. Only touches haiku/sonnet/opus
  ids that contain a dot; returns None otherwise.
- routes_openai.get_models() emits a second entry per aliased id
  with a shared display_name so the picker doesn't show duplicate
  labels.
- The reverse direction (incoming request with dash form) is already
  handled by normalize_model_name() in kiro/model_resolver.py, so
  both ids route to the same Kiro model.

Verified: /v1/models now returns 25 entries (18 canonical + 7 dash aliases);
POST /v1/messages with model="claude-opus-4-8" round-trips to Kiro and
returns a valid reply.
@cla-bot

cla-bot Bot commented Jul 16, 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.

…k notes

- Add "Enabling 1M-context on individual models" subsection under the Claude
  Desktop section. Claude Desktop treats the 1M window as an opt-in per model
  (default in its UI is 200k) even though Kiro serves 1M natively. Walk users
  through: Configure third-party inference -> Models -> +Add model ->
  entering the dash-form id (claude-opus-4-8 / claude-sonnet-4-6 / etc.) and
  toggling 1M context on.
- Add "Getting all models to appear in the picker" subsection explaining the
  dash-form alias behavior.
- Bump the base URL example from :8000 to :8787 to match the current default
  in windows/ scripts and .env.example.
- Refresh Fork notes: Sonnet 5 + Opus 4.8 + GPT-5.6 preview trio, dropped
  imaginary -1m variants, mention dash aliases and role=system folding,
  correct context window plumbing, port default.
@cla-bot

cla-bot Bot commented Jul 16, 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.

Native Anthropic web_search (Path A) has been broken on the runtime.kiro.dev
endpoint for a while. Two independent bugs:

1. The /mcp POST body was missing profileArn. Kiro's runtime endpoint now
   requires it on every tool call (same as /generateAssistantResponse).
   Without it we got:
     HTTP 400 {"message":"profileArn is required for this request."}
   The whole web_search flow then stalled - call_kiro_mcp_api() returned
   (None, None), handle_native_web_search() emitted an empty SSE stream,
   and clients (Claude Desktop in particular) spun on "Searching the
   web..." forever because they never got a terminal event.

2. Even after fixing (1), MCP web_search returns 403 on many account
   tiers (Builder ID, kiro-cli OIDC, and some Enterprise IDC setups):
     HTTP 403 {"message":"User is not authorized to make this call."}
   That's a tenant-side entitlement issue nothing on the gateway can
   fix. But previously we responded with HTTP 500 + a fake error blob,
   which again caused Claude Desktop to hang.

Changes:

- mcp_tools.call_kiro_mcp_api(): pull profileArn from auth_manager (or
  fall back to config.PROFILE_ARN) and attach it at the top of the
  JSON-RPC envelope. Also include the response body in the error log so
  the next endpoint-shape change is diagnosable.

- mcp_tools.handle_native_web_search(): when call_kiro_mcp_api() fails,
  fabricate a synthetic results dict marked isError=True with a
  human-readable errorMessage. Downstream SSE/JSON emitters now render
  it as a valid Anthropic web_search_tool_result_error content block
  (streaming) or a bracketed "[web_search error] ..." note inside the
  <web_search> XML wrapper (non-streaming). The model reads that and
  gracefully falls back to its own knowledge instead of stalling.

- generate_anthropic_web_search_sse(): switch to Anthropic's documented
  web_search_tool_result_error content shape (error_code +
  error_message) when isError is set.

- generate_search_summary(): short-circuit into an [error]... note when
  isError is set, so non-streaming callers see the same information.

Verified end-to-end: POST /v1/messages with a native web_search tool now
returns stop_reason=end_turn with a legitimate error block, and Claude
Desktop no longer hangs.
@cla-bot

cla-bot Bot commented Jul 16, 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.

…ment

Native Anthropic web_search (Path A) was still returning "unavailable" even
after adding profileArn, because we were hitting the wrong host.

Kiro exposes two runtime endpoints with different entitlement rules:

- runtime.{region}.kiro.dev
    - /generateAssistantResponse: works for all tiers.
    - /mcp: returns 403 "User is not authorized to make this call" for
      Builder ID, kiro-cli OIDC, and most Enterprise IDC accounts.

- q.{region}.amazonaws.com (the legacy Q Developer host)
    - /mcp: works for the same accounts and returns real search results.

Verified empirically by hitting both hosts with the same token, profileArn,
and payload: runtime.kiro.dev returned 403, q.amazonaws.com returned 200
with a full result set. kiro-cli's own web_search calls in its DNS cache
also confirm it uses the q.{region}.amazonaws.com host.

Change:
- mcp_tools.call_kiro_mcp_api() extracts the region from
  auth_manager.q_host (which today is set to runtime.{region}.kiro.dev)
  and builds https://q.{region}.amazonaws.com/mcp explicitly. Regex-based
  region extraction stays multi-region friendly.
- Keeps the profileArn-at-top-level payload shape from the previous fix,
  because q.amazonaws.com/mcp still requires it.
- The is_error fallback path from the previous commit stays as a safety
  net for future breakage on this endpoint.

Verified end-to-end with a real Anthropic-style request (tools=[{type:
web_search_20250305}]): gateway now streams a proper web_search_tool_result
with 10 results back to the client. Confirmed with a `use web_search
'kiro gateway github'` prompt against claude-haiku-4.5.
@cla-bot

cla-bot Bot commented Jul 16, 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.

…t text

The web_search flow was appending a debug-style summary wrapped in
<web_search>...</web_search> XML tags as a visible text content block on
the assistant turn. Claude Desktop rendered those tags verbatim in the
chat UI (the model wasn't emitting them - the gateway was). Result: every
web_search answer began with:

    <web_search>
    Search results for "...":
    1. Title: **...**
       Published: ...
       URL: ...
    ...
    </web_search>

That's not how Anthropic's native web_search flow works. In the real
Anthropic API, web_search emits a server_tool_use block plus a structured
web_search_tool_result block, then ends the turn with stop_reason=tool_use.
The client re-sends the conversation (now including that tool_result as
context) and the model composes the prose reply on the next turn. The
tool_result is structured context for the model, not user-facing prose.

Fix all four code paths that used to emit the XML summary as visible text:

1. mcp_tools.generate_anthropic_web_search_sse (Path A / streaming):
   emit tool_use + tool_result blocks only, then message_delta with
   stop_reason=tool_use. No text block.

2. mcp_tools.handle_native_web_search / non-streaming Anthropic path:
   return a message with just server_tool_use + web_search_tool_result
   in content and stop_reason=tool_use. No trailing text block.

3. mcp_tools.handle_native_web_search / non-streaming OpenAI path:
   emit a real tool_calls response (id/type/function/arguments) with
   finish_reason=tool_calls and content=null. Same shape OpenAI's own
   function-calling responses use, so any OpenAI SDK loops correctly.

4. streaming_openai.stream_kiro_to_openai (Path B interception):
   emit a tool_calls delta with the same shape and let the client loop
   back. Drop the summary chunk streaming entirely.

Verified end-to-end. Non-streaming test: `stop_reason=tool_use`, content
blocks are `server_tool_use, web_search_tool_result`, no <web_search>
substring anywhere. Streaming test: no <web_search> substring in the SSE
payload.
@cla-bot

cla-bot Bot commented Jul 16, 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.

…tool_use

Claude Desktop's third-party inference client validates the stop_reason
against the last content block. When the last block is a
web_search_tool_result, stop_reason MUST be tool_use. Any other value
triggers a client-side validation error:

  [ede_diagnostic] result_type=assistant
  last_content_type=web_search_tool_result stop_reason=end_turn

That's exactly what we were sending. Path B (streaming interception of
web_search inside stream_kiro_to_anthropic) emits server_tool_use +
web_search_tool_result content blocks directly, but the outer stop_reason
switch computes based on `tool_blocks` (populated by CLIENT-side tools)
and `content_was_truncated`. server_tool_use never touches tool_blocks,
so we fell through to end_turn.

Add an `emitted_server_tool` flag that Path B sets after intercepting a
web_search tool_use event. The stop-reason switch treats it the same
as tool_blocks:

    if content_was_truncated:
        stop_reason = "max_tokens"
    elif tool_blocks or emitted_server_tool:
        stop_reason = "tool_use"
    else:
        stop_reason = "end_turn"

Path A (native web_search via type: web_search_20250305) already emitted
tool_use correctly - the fix there landed in the previous commit. This
covers the parallel Path B path.

Verified end-to-end with an Anthropic streaming request that triggers
web_search: blocks_seen = ['thinking', 'server_tool_use',
'web_search_tool_result'], stop_reason = 'tool_use', no <web_search>
substring in the SSE payload.
@cla-bot

cla-bot Bot commented Jul 16, 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.

…tant turns

Claude Desktop round-trips server_tool_use + web_search_tool_result blocks
attached to the ASSISTANT turn on every follow-up request (native Anthropic
web_search wire format). Two problems fell out of that:

1. Pydantic rejected the request with 422 because ContentBlock only knew
   about tool_use / tool_result. Added ServerToolUseContentBlock and
   WebSearchToolResultContentBlock to the ContentBlock union so assistant
   messages can carry them without validation errors.

2. Even after the 422 disappeared, Kiro payload builder only reads
   tool_results off userInputMessage entries. Anthropic pairs the result
   with the assistant turn, so the search snippets never reached the
   model and the follow-up reply was a generic "how can I help".

Fix: in convert_anthropic_messages(), when an assistant content list
contains web_search_tool_result blocks, split the turn into
  (a) an assistant UnifiedMessage carrying the tool_calls (server_tool_use
      is already accepted as a tool_use alias)
  (b) a synthetic user UnifiedMessage carrying the flattened tool_results

Kiro then sees the exact same shape it already accepts for client tools:
assistantResponseMessage.toolUses  ->  userInputMessage.toolResults.
The model reads the flattened snippets (title + URL + encrypted_content)
and the follow-up prose references the search normally.

Verified via manual repro against a live gateway on 127.0.0.1:8787:
  request has assistant turn with server_tool_use + web_search_tool_result,
  follow-up user turn "summarize the search",
  response.stop_reason=end_turn,
  response text references every detail from the fake search snippet
  (Cardiff, Root's 99, 4 wickets, 1-1 series).
@cla-bot

cla-bot Bot commented Jul 16, 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.

Shortcut broke after Claude Desktop's Store package updated from
1.21459.3.0 to 1.22209.0.0 because gw-make-shortcut.ps1 baked the
version-scoped Appx path straight into the .lnk's IconLocation.
gw-launch-claude.ps1 similarly hardcoded the AUMID as its primary path.

- Extract Claude.exe's first icon into windows/claude.ico so the .lnk
  points at a stable local file that survives Claude updates. Fall
  back to the live Appx exe if extraction fails, then to the default
  PowerShell icon.
- gw-launch-claude.ps1 now discovers Claude's AUMID via Get-StartApps
  and cascades through explorer.exe -> direct Claude.exe if
  Start-Process AppsFolder fails. The -ClaudeAppId override stays for
  emergencies.
- Delete stale .lnk before rewriting so Explorer picks up the new
  icon, and nudge the shell with SHChangeNotify.

Also adds windows/update-credentials.cmd (double-clickable) plus
gw-update-creds.ps1: runs kiro-cli login interactively, reads the
new expires_at back out of the SQLite auth_kv to confirm the token
rotated, then restarts the gateway synchronously via gw-start.ps1.
Supports -SkipLogin and -NoRestart for partial flows.

Docs updated in windows/README.md.
@cla-bot

cla-bot Bot commented Jul 17, 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.

…esktop

Claude Desktop's third-party inference model picker hard-filters ids to
Anthropic's Claude family and additionally marks any id with a dot in
the version segment as Unavailable. That makes Kiro's gpt-5.6-sol,
gpt-5.6-terra, and gpt-5.6-luna unusable from the picker even though
they work over the wire.

Advertise each raw id under two Claude-shaped aliases so the picker
whitelists them:

  claude-sol-5.6   / claude-sol-5-6   -> gpt-5.6-sol
  claude-terra-5.6 / claude-terra-5-6 -> gpt-5.6-terra
  claude-luna-5.6  / claude-luna-5-6  -> gpt-5.6-luna

Dot form is the intended user-facing id; dash form is a safety net
against picker builds that reject dots.

Hide the raw gpt-5.6-* ids from /v1/models via HIDDEN_FROM_LIST so the
dropdown isn't cluttered with duplicate Unavailable entries. Raw ids
still resolve when a client requests them directly - this is a
display filter, not an access filter.

Zero resolver changes needed: normalize_model_name returns each alias
untouched, alias resolution (layer 0) rewrites to the real id, and the
real id is already in the fallback catalog.

Tests:
- 6 parametrized alias resolution tests (dot + dash forms).
- 6 parametrized normalize-identity tests (guards the regex against
  future patterns eating alias keys).
- Aliases-visible / raw-hidden test on get_available_models().
- Raw-id-still-resolves test proving HIDDEN_FROM_LIST is display-only.
- Default-config presence test so a merge can't silently drop the block.
- /v1/models envelope test confirming the six aliases surface and no
  raw gpt-5.6-* id leaks.

Docs: README.md gains a Using GPT-5.6 in Claude Desktop section with
the alias table and the 272k context caveat (do not turn on the 1M
toggle for these models - Kiro will reject the overflow). Windows
README picks up a shorter matching note.

Verified via live /v1/models against 127.0.0.1:8787: 28 models, six
GPT aliases present, no raw gpt-5.6-* visible, auto-kiro still present
with auto hidden.
@cla-bot

cla-bot Bot commented Jul 17, 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.

…reach Kiro

The GPT-5.6 disguise aliases (claude-sol/terra/luna-5.6 and their dash
companions) surfaced correctly in /v1/models but Kiro API rejected any
message request against them with "Invalid model ID or insufficient
subscription level". Round-trip proved the alias table was ignored on
the request path.

Root cause: the two converters (converters_openai.py, converters_anthropic.py)
build their Kiro payload via the get_model_id_for_kiro helper, not the
full ModelResolver. That helper only performed normalization + hidden
model rewrite (layers 1 and 3) - layer 0 (alias resolution) was
handled exclusively inside ModelResolver.resolve. So the picker-facing
disguise ids escaped alias rewriting and landed on the wire verbatim.

Fix get_model_id_for_kiro to mirror the layer 0 -> 1 -> 3 pipeline:

- Add optional aliases parameter. Defaults to None which pulls
  kiro.config.MODEL_ALIASES at call time (late import to avoid cycles
  and to let tests monkey-patch the config).
- Rewrite aliases BEFORE normalization so alias keys like
  "claude-luna-5.6" (which contain a dot the normalize regex would
  otherwise leave untouched) are converted to their real Kiro ids
  before any regex runs.
- Pass aliases={} explicitly in the "unknown model pass-through" test
  so it isn't accidentally rewritten by the production alias map.

Tests added to TestGetModelIdForKiro:
- 7 parametrized cases covering all six GPT aliases plus auto-kiro,
  verifying the helper produces the real Kiro id from every alias
  form.
- test_explicit_alias_map_wins_over_config proves callers can pass a
  custom map to shadow config.
- test_empty_alias_map_disables_alias_layer confirms {} skips the
  layer entirely.

Live verification against 127.0.0.1:8787:
- POST /v1/messages model=claude-luna-5.6 returned pong (dot form).
- POST /v1/messages model=claude-terra-5-6 returned pong (dash form).
- POST /v1/messages model=claude-haiku-4.5 still works (no regression).

Also patches windows/gw-update-creds.ps1: kiro-cli login refuses to run
while a stale session is present ("Already logged in, please logout
with kiro-cli logout first"). The script now runs kiro-cli logout up
front so retries after a token expiry always land on a clean state.
@cla-bot

cla-bot Bot commented Jul 17, 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.

@muhmd-sinan

muhmd-sinan commented Jul 17, 2026 via email

Copy link
Copy Markdown
Author

@ankitcharolia

Copy link
Copy Markdown

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

It supports all necessary endpoints for OpenAI and Anthropic.

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.

2 participants