Skip to content

refactor(responses): lower agent_message in a request middleware - #576

Merged
Menci merged 1 commit into
Menci:mainfrom
pStrikeZ:feat/agent-message-lowering-middleware
Oct 3, 2026
Merged

Menci merged 1 commit into
Menci:mainfrom
pStrikeZ:feat/agent-message-lowering-middleware

Conversation

@pStrikeZ

@pStrikeZ pStrikeZ commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

Follows the design suggested in discussion: move the agent_message lowering out of the translators and into a Responses middleware, gated by a flag.

Behaviour

Codex delivers sub-agent tasks as agent_message input items. Today the two Responses translators (to Anthropic Messages and to OpenAI Chat Completions) lower them to a plain user message framed as coming from another agent rather than the user. A native Responses upstream receives the item untouched.

This PR adds the interceptor withOpenAIResponsesAgentMessageShim, which rewrites every agent_message into the same framed user message when either:

  • the target protocol is not OpenAI Responses, or
  • the new flag openai-responses-agent-message-shim is enabled.

The flag is off by default for every provider kind, so existing behaviour is unchanged. The new option lets an operator enable the lowering for a Responses-compatible upstream that drops or rejects the item. The two translators no longer handle agent_message; an item that reached them unlowered would fail with the existing invalid-input-item error.

The framing text is unchanged. agentMessageContent (and its tests) moved into the gateway next to the interceptor, since it now has a single user. This is the one placement choice you may want to weigh in on; moving it back into translate is trivial if you prefer that.

The interceptor sits early in the chain, before the terminal dispatch translates the payload, so no other interceptor and no translator sees the raw item.

Tests

  • Interceptor unit tests: native target with the flag off keeps the same payload object, the flag on lowers it, anthropicMessages and openaiChatCompletions targets lower even without the flag, payloads without agent_message are untouched, un-lowerable content rejects with TranslatorInputError.
  • End-to-end tests in attempt_test.ts: the Anthropic and Chat Completions targets receive the same framed user text as before; native target forwards the item untouched with the flag off and lowers it with the flag on.
  • Translator tests now assert rejection of an unlowered agent_message.

pnpm run verify passes except for codex-reset-cards_test.tsx ("keeps the redemption key after an ambiguous failure and reopening confirmation"), which is flaky and unrelated: it is untouched by this change, passes on its own, and failed in only some runs of the surrounding directory.

Codex sends sub-agent tasks as `agent_message` input items. The two
Responses translators lowered them to framed user messages themselves, so a
native Responses upstream that does not understand the item could only
receive it untouched.

Add the `withOpenAIResponsesAgentMessageShim` interceptor, which rewrites
every `agent_message` into the same framed user message when the target
protocol is not OpenAI Responses, or when the new
`openai-responses-agent-message-shim` flag is enabled. The flag defaults to
off for every provider, so native targets keep receiving the item as before.
The interceptor runs ahead of the rest of the chain and thus before the
terminal dispatch translates the payload.

Remove the `agent_message` handling from both translators; an item that
reaches them unlowered now fails with the existing invalid-input-item error.
Move `agentMessageContent` and its tests into the gateway next to the
interceptor, and move the translated-target expectations to the interceptor
and attempt tests.
@Menci
Menci merged commit c7e4d78 into Menci:main Oct 3, 2026
8 checks passed
yyyr-p added a commit to yyyr-p/Floway that referenced this pull request Oct 4, 2026
Integrates upstream commits e286553..c7e4d78:
- refactor(responses): lower agent_message in a request middleware (Menci#576)
- fix(claude-code): update subscription request defaults (Menci#571)
- fix(compaction): use the context compaction shim by default (Menci#572)
- fix(codex): derive quota expiry from exhausted windows (Menci#570)
- fix(tls): support IP-literal certificate identities (Menci#569)
- fix(web-search): resolve the OpenAI search passthrough lazily (Menci#568)
- fix(provider-codex): update stable client identity for newer models (Menci#567)
- fix(provider-codex): preserve Responses Lite wire semantics (Menci#543)
- feat(web): adopt the blue Floway logo (Menci#563)
- feat(codex): show and redeem ChatGPT reset cards (Menci#528)
- fix(responses): answer Codex WebSocket prewarms locally (Menci#552)
- fix(responses): handle positional tool declarations in middleware (Menci#558)
- fix(gateway): preserve first-token timing across tool continuations (Menci#561)
- fix(responses): consume hosted-tool streams before billing metadata (Menci#560)
- fix(codex): restore output omitted from terminal snapshots (Menci#551)
- fix(codex): preserve model catalog context windows (Menci#559)
- fix(web): disable inactive upstream disclosure (Menci#534)
- fix(translate): isolate translated request payloads (Menci#557)
- feat(models): support Claude Sonnet 5.5 (Menci#555)
- feat(models): support GPT-6.1 Sol (Menci#553)
- fix(web): allow imported credentials on upstream creation (Menci#556)
- fix(gateway): preserve hosted tool selection across aliases and continuations (Menci#549)
- fix(translate): restore Responses callable identities (Menci#547)
- perf(translate): bound namespace tool name allocation work (Menci#548)
- fix(translate): preserve namespace descriptions on child tools (Menci#545)
- feat(requests): show each request's time to first token (Menci#550)
- fix(translate): reject ambiguous flat callable kinds (Menci#542)
- fix(gateway): preserve client tool identity in hosted dispatch (Menci#538)
- fix(web): mask credential headers in request record exports (Menci#539)

Conflict resolutions:
- upstreams/access-control.tsx: keep the fork's parameterized title,
  description, and error props (title/description ?? defaults, plus
  defaultOpen/revealOn) alongside upstream Menci#534's disclosureDisabled={!override}.
- upstreams/access-control_test.tsx: union the Control helper to accept both
  error and initialOverride, and keep both Menci#534's disabled-disclosure case and
  the fork's validation-error cases.
- control-plane/routes.ts: union the schemas.ts named imports -- the fork's
  OAuth2 admin bodies plus upstream Menci#528's codexRateLimitResetCreditsBody and
  codexRateLimitResetConsumeBody; both sides' route blocks merged cleanly.
- requests/ttft_test.tsx (Menci#550): pass the fork's required exchangeStreams={[]}
  to the RequestDetailPanel cases that construct it with no collected streams.

Verified with pnpm run verify (typegen, lint, typecheck, 611 test files /
6644 tests, test:installers, check:agents-md, check:generated-assets,
check:verify-parity, build:web).
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