Skip to content

fix(opengateway): 도구 결과 및 빈 완료 응답 처리 보완 - #7281

Closed
HeiTuz wants to merge 3 commits into
lidge-jun:devfrom
HeiTuz:fix/opengateway-tool-result-arrays
Closed

HeiTuz wants to merge 3 commits into
lidge-jun:devfrom
HeiTuz:fix/opengateway-tool-result-arrays

Conversation

@HeiTuz

@HeiTuz HeiTuz commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Codex의 OpenGateway 대화가 도구 결과 전송 시 400으로 중단되거나, 공급자가 텍스트 없이 completed를 보내 조용히 끝나는 문제를 처리합니다. 요청의 결과 배열을 호환 형식으로 변환하고, 응답에서는 출력 전의 확실한 빈 완료만 검사합니다.

  • 적용 범위는 https://apis.opengateway.ai/v1/responses입니다. 텍스트 결과 배열을 문자열로 변환하고 call_id와 텍스트 순서를 유지합니다.
  • 이미지가 섞인 도구 결과는 이미지 바이트와 mixed-content 순서를 별도 user 메시지에 보존합니다. 이미지 메시지는 연속된 도구 결과 묶음 뒤에 배치하므로 역순 병렬 결과 B → A도 분리하지 않습니다.
  • GLM upstream이 거부하는 이미지 detail: original을 지원되는 high로 변환합니다. 이미지 바이트는 바꾸지 않습니다.
  • 다른 목적지와 원본 요청 객체는 바꾸지 않습니다. 알 수 없는/암호화된 결과 배열은 조용히 삭제하지 않고 기존 검증에 남깁니다.
  • 기존 custom-tool lowering은 호출 종류만 function으로 바꾸고 output 배열을 그대로 남겼습니다. 이 PR은 최종 outbound 직렬화 직전에 빠진 변환을 수행합니다.
  • native SSE/JSON의 정상 형식 빈 완료가 출력 예산을 소진했으면 incomplete / max_output_tokens로 알립니다. 예산을 임의로 늘리지 않습니다. 예산이 남았고 명시적 store:false·완전한 입력·client-only 도구인 경우에만 기존 전송 한도 안에서 한 번 복구합니다. 재시도 실패의 원래 HTTP 상태·본문·헤더를 유지하고, 두 번째 빈 200은 명시적 실패로 끝냅니다.
  • 첫 텍스트·거절(빈 refusal 포함)·도구 동작·알 수 없는 이벤트부터 원본 바이트를 그대로 넘깁니다. 1 MiB / 1,024 이벤트·청크 / 30초는 검사 보관 한도이며, 닿으면 원본 relay로 전환합니다. upstream을 30초에 중단하지 않고 기존 stall/abort 정책을 유지합니다. 명시적 재시도 끄기 설정도 존중합니다.

최소 재현

2026-10-18 KST, 정식 v2.88.0(6936d988)과 실제 OpenGateway 키로 확인했습니다. 아래 합성 요청에는 개인 대화나 자격 증명이 없습니다. deepseek/deepseek-v4.1-flash-ultrafast와 z-ai/glm-5.3-flash-ultrafast 모두 같은 결과입니다.

payload.json:

{
  "model": "deepseek/deepseek-v4.1-flash-ultrafast",
  "input": [
    {"role": "user", "content": "Reply exactly OK."},
    {"type": "function_call", "call_id": "call_probe", "name": "echo", "arguments": "{\"value\":\"OK\"}"},
    {"type": "function_call_output", "call_id": "call_probe", "output": [{"type": "input_text", "text": "OK"}]}
  ],
  "tools": [{"type": "function", "name": "echo", "description": "Return a value.", "parameters": {"type": "object", "properties": {"value": {"type": "string"}}, "required": ["value"], "additionalProperties": false}}],
  "reasoning": {"effort": "low"},
  "max_output_tokens": 128,
  "stream": false
}
curl --silent --show-error --include \
  https://apis.opengateway.ai/v1/responses \
  -H "Authorization: Bearer ${OPENGATEWAY_API_KEY}" \
  --json @payload.json

수정 전에는 다음 오류를 반환합니다.

{"error":{"message":"The request was malformed or contained invalid parameters.","type":"invalid_request_error"}}

같은 요청의 마지막 output만 "OK" 문자열로 바꾸면 두 모델 모두 HTTP 200과 OK를 반환합니다. OCX 경유 재현은 URL을 http://127.0.0.1:10100/v1/responses로, 모델을 opengateway/deepseek-deepseek-v4.1-flash-ultrafast 또는 opengateway/z-ai-glm-5.3-flash-ultrafast로 바꾸면 됩니다. 로컬 인증이 설정된 환경은 해당 로컬 자격 증명을 사용합니다.

추가 대조: GLM에 같은 PNG를 input_image로 보내되 detail: original을 지정하면 Input should be 'auto', 'low' or 'high'로 400을 반환했습니다. high로 바꾼 수정본은 HTTP 200과 실제 이미지 내용 설명을 반환했습니다.

빈 완료의 공급자·OCX 비교 재현

다음 요청을 위 /v1/responses curl로 보내면 두 모델에서 텍스트가 없는 response.completed가 재현됐습니다.

{
  "model": "z-ai/glm-5.3-flash-ultrafast",
  "input": [{"role": "user", "content": "83476319 곱하기 34839727을 정확하게 계산하세요. 최종 숫자만 답하세요."}],
  "reasoning": {"effort": "high"},
  "max_output_tokens": 16,
  "stream": true
}
같은 질문·출력 예산 16 GLM DeepSeek
공급자 Chat API (messages, reasoning_effort: high, max_tokens: 16) 빈 content, finish_reason: length 빈 content, finish_reason: length
공급자 Responses API delta/done/snapshot 모두 빈 텍스트, completed, output=16/reasoning=16 동일
수정 전 OCX 빈 completed 그대로 전달 동일
수정 후 OCX incomplete / max_output_tokens, 추가 호출 없음 동일

동일 물리 요청의 upstream Response.clone()과 production handleResponses 출력을 함께 수집해 비교했습니다. 이 재현에서는 OCX가 텍스트를 잃은 것이 아니라 공급자 원본부터 비었고 종료 상태가 잘못됐으며, native passthrough가 이를 그대로 성공 처리했습니다. 기존 translated empty-completion guard는 이 경로에서 실행되지 않았습니다. 공식 Responses 문서는 incomplete 상태와 store/previous_response_id를 지원하지만, store 생략을 false라고 보장하지 않으므로 생략된 경우 자동 재생하지 않습니다.

Verification

실제 호출 결과

시나리오 GLM 5.3 Flash ultrafast DeepSeek V4.1 Flash ultrafast
upstream 직접 호출: 결과 배열 400 400
같은 입력, 결과만 문자열 200 / OK 200 / OK
수정 OCX: 실제 Codex 셸 도구 실행 → 후속 응답 exit 0 / OCX_REPRO_OK exit 0 / OCX_REPRO_OK
수정 OCX: SSE tool call → 로컬 echo 실행 → 결과 배열 continuation 두 요청 200, 실제 결과 문자열 일치 두 요청 200, 실제 결과 문자열 일치
수정 OCX: max 결과 배열 재생 200 / OK 200 / OK

실제 Desktop 번들 Codex CLI 0.163.0-alpha.10.2의 exec --ephemeral로 printf OCX_REPRO_OK를 실행했습니다. 수정 전 DeepSeek에서는 셸 실행이 exit 0으로 끝난 뒤 모델 후속 요청이 400 / turn.failed / CLI exit 1이었습니다. 수정 후 두 모델 모두 도구 결과를 읽고 OCX_REPRO_OK를 반환했으며 turn.completed와 0이 아닌 사용량을 확인했습니다. SSE 검증은 모델이 선택한 echo 인자를 로컬에서 실행한 뒤 _EXECUTED를 붙인 결과를 배열로 반환하고, 최종 응답이 실제 실행 결과와 일치하는지 검사했습니다.

DeepSeek max 첫 진단은 30초 클라이언트 제한으로 취소(499)됐습니다. 시간 제한을 120초, 출력 예산을 1024로 조정한 별도 검사에서 HTTP 200 / OK를 확인했습니다. 취소된 실행을 성공으로 집계하지 않았습니다.

추가 실제 텍스트·복구 검증

  • 정상 한국어 답변: 동일 물리 요청에서 upstream와 downstream 텍스트가 정확히 일치하고, 두 모델 모두 1회 호출만 발생했습니다.
  • 위 수학 질문의 예산을 명시적으로 4096으로 준 별도 검사에서 두 모델 모두 2908292164924913을 텍스트로 반환했습니다. GLM의 예산 1024 검사는 추론에 전부 사용돼 미완료였으며 성공으로 집계하지 않았습니다.
  • 실제 캡처한 빈 upstream SSE를 첫 시도 fixture로 재생하고, store:false·client-only 요청에서 두 번째 시도를 실제 API로 보냈습니다. 두 모델 모두 정확히 1회 재시도 후 한국어 텍스트를 반환했습니다. 이것을 해당 순간 자연발생한 빈 응답의 회복으로 표현하지 않습니다. 두 시도의 usage 합산과 sendCount=2도 확인했습니다.
  • 최종 수정본을 로컬 서비스에 반영한 뒤 Desktop 번들 CLI에서 printf OCX_TEXT_READY 실행 → 결과 확인 → 한국어 설명을 요청했습니다. GLM·DeepSeek 모두 exit 0 / turn.completed / 실제 한국어 최종 답변을 확인했습니다. 단순 HTTP 200을 성공 기준으로 삼지 않았습니다.
  • 실제 Codex 요청은 store:false이지만 web_search 같은 hosted tool이 outbound에 남을 수 있습니다. 해당 요청은 빈 완료 시 명시적 실패만 하고 자동 재생하지 않습니다. 서버 실행을 중복시킬 수 있는 조건을 완화하지 않았습니다.
  • 정상 31초 SSE를 출력 전·후 각각 검증했고, 취소·재시도 없이 원본 바이트 및 SHA-256 일치를 확인했습니다. 다른 목적지, 거절, unknown 이벤트, 부분 terminal, caller abort, 보관 한도, 재시도 401/429/503 및 요청 전송 한도도 회귀 검사에 포함했습니다.

자동 검증과 기준 커밋

  • 회귀 테스트를 먼저 추가해 배열 변환·이미지 detail·이미지 분리가 실패하는 것을 확인한 뒤 수정했습니다. 독립 검토에서 찾은 역순 병렬 결과도 별도 red → green 테스트로 고정했습니다.
  • 입력 배열 수정 당시 v2.88.0 작업 트리에서 Bun 1.4.3으로 bun scripts/test.ts --changed=HEAD 실행: 19,352 pass / 34 skip / 0 fail, 813개 파일, 207.34초. 기준 HEAD는 6936d988입니다.
  • 이 PR은 dev의 2a10a2bc 위에 올렸습니다. 위 검증 기준과 이 dev의 차이는 package/desktop 버전 번호 4개뿐이고, 구현·테스트·의존성 lockfile은 동일합니다. 리베이스로 수정 내용이 바뀌지 않았습니다.
  • 입력 배열 커밋 bb41ac3에서 bun test tests/providers/opengateway-provider.test.ts: 17 pass / 0 fail / 53 assertions.
  • 빈 완료 수정의 최종 작업 트리(기준 bb41ac3)에서 Bun 1.4.3 bun scripts/test.ts --changed=HEAD: 15,648 pass / 34 skip / 0 fail, 625개 파일, 165.59초. 저장한 검사 시작 파일 해시와 최종 8개 변경 파일이 모두 일치합니다. 두 실행의 수치는 겹치는 테스트를 포함하므로 합산하지 않습니다. 전체 2,468개 파일 실행은 아니며 기존 opt-in/플랫폼 skip을 변경하지 않았습니다.
  • 빈 완료 focused: 121 pass / 0 fail, 4개 파일. 최종 bun run typecheck, bun run structure:check, bun scripts/file-size-ratchet.ts, bun run privacy:scan, diff whitespace 검사 통과.
  • 초기 연관 실행에서 수정하지 않은 listener 종료·포트 재바인딩 테스트가 1회 실패했습니다. 테스트 변경 없이 해당 파일 단독 38/38, 후속 연관 검사 및 최종 검사 실패 0을 확인했습니다.
  • 최종 문서 변경 검증: cd docs-site && bun run build 성공, 577페이지 / 81,423개 내부 링크 검사.
  • 독립 코드 검토에서 찾은 역순 결과, 강제 30초 종료, 부분 snapshot 복원, malformed/refusal 재생 문제를 수정했습니다. 최종 재검토 APPROVE / blockers 없음이며 type/size/structure gate는 별도 실제 실행으로 통과했습니다. GitHub Codex/CodeRabbit 검토 및 CI와는 별도의 로컬 검토입니다.

검증 한계

  • 원래 약 45MB 긴 대화의 통째 재생은 하지 않았습니다. 빈 완료 한 건 직전의 27개 이력을 최소 도구 선언으로 재구성해 원래 공급자에 비교 요청했지만, 원본 request를 완전히 재생한 것으로 취급하지 않습니다. 공개 가능한 증거는 합성 최소 요청과 새 실제 Codex 세션으로 만들었으며 사용자 원문은 PR에 넣지 않았습니다.
  • 최초 GLM 빈 답변 한 건(22 output tokens)은 원본 스트림이 남아 있지 않아 내부 원인을 확정하지 못했습니다. 후속 재현으로 확인한 length→completed 오표시와 동일 원인이라고 단정하지 않습니다. 불확실한 stream이나 보관 한도 이후에는 원본을 유지하며, 이 PR은 모든 공급자 답변 품질이나 임의의 malformed snapshot을 복원하지 않습니다.
  • 이미지 바이트 보존과 GLM 이미지 인식은 확인했습니다. DeepSeek는 직접 upstream Responses 이미지 입력에서도 이미지를 인식하지 못하는 현상이 있어, 이 PR이 DeepSeek의 이미지 인식까지 고쳤다고 주장하지 않습니다.
  • 실제 키·사용자 원문·이미지 base64·로컬 계정 설정은 PR에 포함하지 않았습니다. 로컬 테스트 결과를 upstream CI 성공으로 취급하지 않습니다.

Checklist

  • Scope stays focused and avoids unrelated cleanup. OpenGateway Responses 입력 호환성과 출력 전 빈 완료 처리에 한정했습니다.
  • Docs or release notes were updated when needed. provider 문서와 전송 계약을 갱신했습니다.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults. 인증/키 설정 변경 없이 목적지 범위를 제한했고 privacy scan을 통과했습니다.

기여자 정책에 따라 Draft로 제출합니다. GitHub 검토 및 maintainer CI는 아직 대기 중입니다.

Review readiness checklist

This PR stays in draft until every box below is ticked. Tick all four boxes once the requirements are met:

  • Required local validation passed; commands, results, and any full-suite exception are documented.

  • I pushed my PR to a recent dev commit (at most 10 behind; a maintainer may still ask for the exact tip before merge).

  • I resolved all correct Codex and CodeRabbit findings.

  • My PR is ready for review.

@coderabbitai

coderabbitai Bot commented Oct 17, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

✅ Deterministic PR hygiene checks passed.

@github-actions github-actions Bot added the bug Something isn't working label Oct 17, 2026
@github-actions

github-actions Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

⏳ DRAFT

  • review readiness checklist open (0/4 boxes ticked).

What to do

  • Tick all four boxes in the PR description once you're done (currently 0/4).

Review readiness checklist

  • ⬜ Required local validation passed; commands, results, and any full-suite exception are documented.
  • ⬜ I pushed my PR to a recent dev commit (at most 10 behind; a maintainer may still ask for the exact tip before merge).
  • ⬜ I resolved all correct Codex and CodeRabbit findings.
  • ⬜ My PR is ready for review.

0/4 boxes ticked.

This PR stays in draft until every box above is ticked.

@HeiTuz HeiTuz changed the title fix(opengateway): 도구 결과 배열과 이미지 입력의 400 오류 수정 fix(opengateway): 도구 결과 및 빈 완료 응답 처리 보완 Oct 17, 2026
@Ingwannu

Copy link
Copy Markdown
Owner

Scoped review at d337e6ecfb: keep input normalization and empty-completion recovery separately testable. Tool-result text/image projection must preserve call pairing and refuse unsupported parts rather than fabricate success. Empty-response inspection must release original bytes on hold/size expiry, cancel owned upstream reads on true terminal/cancel paths, preserve tool-ownership metadata and consume at most the one authorized physical retry with correct per-attempt usage. Please keep SSE/JSON, reason-only/empty, max-output incomplete, stalled/cancelled reads and replay-unsafe controls in exact-head receipts. Current rollup has quality/bot checks but no functional CI execution. Draft remains appropriate; no OpenGateway call or broad approval was performed.

입력 정규화는 호출 쌍을 유지하고 미지원 part를 성공 문자열로 바꾸지 않는다.
빈 완료 검사는 원본 바이트를 넘긴 뒤 참조를 놓고, terminal/cancel에서만 upstream read를 취소한다.
재작성 응답은 도구 소유권과 시도별 usage를 유지하며 물리 재시도는 한 번이다.
@HeiTuz

HeiTuz commented Oct 18, 2026

Copy link
Copy Markdown
Contributor Author

Scoped follow-up at 16263b78.

Input normalization and empty-completion recovery are now separately testable: tests/adapters/openai/opengateway-input.test.ts and tests/responses/opengateway-completion-receipts.test.ts.

Tool-result projection aligns call/output pairs before placing image messages, so those messages stay after the tool-result group instead of splitting a pair. Image-only results keep a call_id marker rather than an empty success string. Refusal and other unsupported parts stay on the original array.

Empty-response inspection releases held byte references after forwarding the original chunk on hold/size expiry and does not cancel a healthy upstream there. True terminal and consumer-cancel paths cancel the owned upstream read without awaiting a hanging cancel. Rewritten responses carry tool-ownership metadata. Each consumed attempt records its own usage once, and the guard makes at most one physical retry.

Exact-head receipts cover SSE/JSON failure heads, reason-only versus empty output, max-output incomplete, stalled and cancelled reads, and replay-unsafe pass-through. Local focused tests, typecheck, structure check, and the docs build passed. This remains a draft: no OpenGateway call was made, and this rollup still has no functional CI execution.

@Ingwannu

Copy link
Copy Markdown
Owner

Intake/scoped review at 16263b787fb7ddf8e42db368a192d039ec9cc62b.

The new receipt separates tool-output normalization from empty-completion recovery, including image placement after complete tool pairs, unsupported media refusal and a non-replayable cancellation after body ownership commits. Keep the off-provider, retry-limit and cancellation controls visible. The new author-reported tests are acknowledged; I did not run this branch locally and its Draft/CI requirements remain.

Current-head functional workflows are awaiting fork approval; that is pending execution, not a passing test result. No local suite was run for this branch in this intake. No workflow approval, new scan, live configuration change or merge was performed. Required review/readiness gates remain in force.

lidge-jun added a commit that referenced this pull request Oct 19, 2026
Carry the input-normalization slice of draft #7281 (16263b7).
Preserve call/result identities and image content while projecting text arrays;
reject unsupported or malformed parts explicitly instead of forwarding them.
Keep the normalizer independently covered by mocked, immutable-input tests.

Co-authored-by: HeiTuz <79418013+HeiTuz@users.noreply.github.com>
lidge-jun added a commit that referenced this pull request Oct 19, 2026
Carry the independent completion guard and replay-safety tests from draft
#7281 (16263b7). Inspect SSE/JSON only before meaningful output, release the
held prefix on time/size expiry, and permit at most one eligible retry callback.
Release the owned reader lock on cancellation and remove a redundant counter.

Co-authored-by: HeiTuz <79418013+HeiTuz@users.noreply.github.com>
lidge-jun added a commit that referenced this pull request Oct 19, 2026
Port the remaining adapter/dispatch integration from draft #7281 (16263b7)
onto current dev. Map input incompatibility to HTTP 400 before any send;
cap empty-completion recovery at one physical send, including HTTP 503 and
connection-reset failures, while preserving existing admission and accounting.

Cover the actual handler with mocked fetches and keep English/Korean guidance
consistent. Split the OpenGateway structure contract to stay within document
budgets and distinguish native recovery from the translated opt-in guard.

Verification: typecheck, 118 focused tests, 27 layout tests, 9 size-ratchet tests,
structure and privacy checks, and the docs build passed. Changed-mode rerun
passed 19780 tests with 34 skips (30s timeout, two workers); the first run had
one unrelated package-tree subprocess timeout, which also passed standalone.

Co-authored-by: HeiTuz <79418013+HeiTuz@users.noreply.github.com>
lidge-jun added a commit that referenced this pull request Oct 19, 2026
…l-result handling for 2.90 (#7395)

* fix(opengateway): normalize paired tool result inputs

Carry the input-normalization slice of draft #7281 (16263b7).
Preserve call/result identities and image content while projecting text arrays;
reject unsupported or malformed parts explicitly instead of forwarding them.
Keep the normalizer independently covered by mocked, immutable-input tests.

Co-authored-by: HeiTuz <79418013+HeiTuz@users.noreply.github.com>

* fix(opengateway): inspect empty completions with bounded replay

Carry the independent completion guard and replay-safety tests from draft
#7281 (16263b7). Inspect SSE/JSON only before meaningful output, release the
held prefix on time/size expiry, and permit at most one eligible retry callback.
Release the owned reader lock on cancellation and remove a redundant counter.

Co-authored-by: HeiTuz <79418013+HeiTuz@users.noreply.github.com>

* fix(opengateway): integrate completion recovery with the send budget

Port the remaining adapter/dispatch integration from draft #7281 (16263b7)
onto current dev. Map input incompatibility to HTTP 400 before any send;
cap empty-completion recovery at one physical send, including HTTP 503 and
connection-reset failures, while preserving existing admission and accounting.

Cover the actual handler with mocked fetches and keep English/Korean guidance
consistent. Split the OpenGateway structure contract to stay within document
budgets and distinguish native recovery from the translated opt-in guard.

Verification: typecheck, 118 focused tests, 27 layout tests, 9 size-ratchet tests,
structure and privacy checks, and the docs build passed. Changed-mode rerun
passed 19780 tests with 34 skips (30s timeout, two workers); the first run had
one unrelated package-tree subprocess timeout, which also passed standalone.

Co-authored-by: HeiTuz <79418013+HeiTuz@users.noreply.github.com>

* fix(mistral): preserve cache keys and recover from rate limits

Canonical key-auth Mistral Chat now sends prompt-cache keys, paces each
credential/model pair from measured refusal headers with bounded,
abortable waits outside the upstream header deadline, and retries 429s
with a slower default budget. A server delay beyond the retry budget is
kept non-replayable and its Retry-After reaches the caller. Relays,
other adapters, OAuth credentials and xAI are unchanged.

Co-authored-by: adaurban <31841049+adaurban@users.noreply.github.com>

* fix(mistral): pace native Chat sends through the Mistral transport

A single-key native Chat route never re-resolved its transport, so the
new Mistral retry default replayed an over-budget Retry-After after 60
seconds instead of returning the refusal.

Co-authored-by: adaurban <31841049+adaurban@users.noreply.github.com>

* fix(opengateway): share the exact-URL boundary between normalization and recovery

Co-authored-by: HeiTuz <79418013+HeiTuz@users.noreply.github.com>

* test(responses): inventory the OpenGateway completion owners

Co-authored-by: HeiTuz <79418013+HeiTuz@users.noreply.github.com>

* fix(mistral): keep evicted pacing deadlines in force

At 128 live identities the pacer dropped the oldest deadline, so that
credential/model pair could send before its Retry-After. Remember the
latest evicted deadline and make identities without an entry wait it
out; memory stays bounded and the only cost is conservative pacing
under extreme fan-out.

Co-authored-by: adaurban <31841049+adaurban@users.noreply.github.com>

* test(layout): keep B4b registrations within the layout file budget

Merged with #7397, the three new rows pushed layout.json to 2001 lines.
Fold them onto neighboring rows; the file stays at dev's 1998.

Co-authored-by: adaurban <31841049+adaurban@users.noreply.github.com>
Co-authored-by: HeiTuz <79418013+HeiTuz@users.noreply.github.com>

* fix(mistral): hold only same-key replays on an over-budget Retry-After

Marking the refusal non-replayable also blocked key-pool rotation and
sanitized the upstream body. Use a same-target retry hold that only the
same-key 429 loops check (native Chat, adapter dispatch and
continuation, image and web-search loops), carried across response
rewraps, so another pooled key can still serve and the original status,
body and Retry-After reach the caller. Also keep an evicted identity's
live deadline in force when it re-enters with a shorter delay.

Co-authored-by: adaurban <31841049+adaurban@users.noreply.github.com>

---------

Co-authored-by: HeiTuz <79418013+HeiTuz@users.noreply.github.com>
Co-authored-by: adaurban <31841049+adaurban@users.noreply.github.com>
@lidge-jun

Copy link
Copy Markdown
Owner

Carried into dev via #7395 (squash 1cf98c0): unsupported result parts now refused with 400 before send, reader lock released on cancel, empty-completion recovery capped at one physical send, exact-URL predicate shared by normalization and recovery. Thanks @HeiTuz!

@lidge-jun lidge-jun closed this Oct 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants