Skip to content

feat: use the OpenAI Responses API so tools keep reasoning - #9

Merged
LukeGus merged 3 commits into
Termix-SSH:dev-1.3.0from
Rhiz3K:feat/openai-responses-api
Oct 22, 2026
Merged

LukeGus merged 3 commits into
Termix-SSH:dev-1.3.0from
Rhiz3K:feat/openai-responses-api

Conversation

@Rhiz3K

@Rhiz3K Rhiz3K commented Oct 22, 2026

Copy link
Copy Markdown

Summary

Official OpenAI providers now use the Responses API (POST /v1/responses) instead of Chat Completions. Reasoning models such as gpt-6-luna and gpt-5.6-* can then call the assistant's function tools with their reasoning.

#8 was fixed in 4672df0. That fix retries Chat Completions with reasoning_effort: "none", so it works, but tool-using turns lose reasoning. It also costs one rejected request per model call. This PR keeps 4672df0's retry for every provider that still uses Chat Completions. Official OpenAI no longer needs it.

Relates to #8 (follow-up to 4672df0). Base: dev-1.3.0.

Design

  • Which endpoint: Responses is used only when providerType === "openai" and the base URL is empty or an api.openai.com host (regional hosts such as eu.api.openai.com included). These keep using Chat Completions exactly as before:
    • openai_compatible providers: Ollama, OpenRouter, vLLM, LM Studio, Groq and others
    • openai providers with a custom base URL (proxies, gateways)
  • Stateless: requests send store: false. In stateless mode OpenAI returns reasoning items with encrypted_content by default.
    • These items are emitted as the existing reasoning_state chunk. The engine already keeps that chunk in memory for the current run only (the same mechanism Anthropic thinking blocks use).
    • On the next step of the run they are replayed before the function_call items, followed by the function_call_output items, as the reasoning guide recommends.
    • Item id/status are stripped because stored ids cannot be looked up with store: false. Reasoning items without encrypted_content are not replayed.
    • Nothing new is persisted. No previous_response_id and no server-side storage.
  • Request mapping:
    • system → instructions
    • tools → flat { type: "function", name, description, parameters, strict: false }. Responses tries strict mode when strict is omitted, and our schemas are not written for it.
    • maxTokens → max_output_tokens
    • reasoning.effort is not sent, so the model default applies.
  • Reasoning summaries: the request includes reasoning: { summary: "auto" } so the Thinking block keeps working.
    • Some organizations must be verified before they get summaries, and non-reasoning models (e.g. gpt-4.1) reject the option.
    • In those cases a 400 that mentions reasoning, summaries or encrypted content is retried once without reasoning. This is remembered per base URL and model, so later steps don't pay for the retry.
  • Streaming events handled:
    • response.output_text.delta and response.refusal.delta → text
    • response.reasoning_summary_text.delta and response.reasoning_text.delta → reasoning, separated between items and summary parts
    • response.output_item.done → function calls and reasoning items
    • response.completed / response.incomplete → usage (input_tokens/output_tokens) and stop reason
    • response.failed and error → AiProviderError
    • A stream that closes without a terminal event is treated as an error, so a partial reply is not saved as the answer.
  • No new setting or migration. A provider-form option to choose the endpoint or reasoning effort could come later. For now, a custom base URL keeps a provider on Chat Completions.

Changes

  • src/backend/providers/openai-responses.ts (new): usesResponsesApi, request and input mapping, retry and stream parsing.
  • src/backend/providers/openai.ts: hands over to Responses for official OpenAI. The Chat Completions path is unchanged, including the stream_options and reasoning_effort: "none" retries.
  • tests/backend/providers/openai-responses.test.ts (new, 33 tests). It covers:
    • endpoint selection
    • request mapping
    • streaming events
    • a multi-step tool round trip with encrypted reasoning replay
    • the summary/reasoning retry and its memory
    • no retry on other 400s, and no fallback on 404/405/422/500
    • response.failed/error/truncated streams
    • compatible providers at an OpenAI URL staying on Chat Completions
  • Existing tests whose mock streams Chat Completions while configuring an openai provider without a base URL now set baseUrl: "https://llm.example/v1", so they keep exercising the path they mock. No assertion was changed. Files: providers.test.ts, reasoning-usage.test.ts, routes.test.ts, ops/routes.test.ts, ops/assistant-routes.test.ts, ops/runs.test.ts, assets/annotate.test.ts, tasks/fixtures.ts.
  • CHANGELOG.md: entry under 1.3.0.
  • src/backend/context-window.ts: windows of current OpenAI models, taken from OpenAI's model pages. Before this, gpt-6-luna showed "128,000 (default for unknown models)" in the provider form.
    • GPT-5.4 and later (gpt-5.4, gpt-5.5, gpt-5.6-luna/sol/terra, the -pro variants) and every GPT-6 model (gpt-6-luna, gpt-6-astra, gpt-6-sol, gpt-6.1-sol): 1,050,000.
    • gpt-5.4-mini/nano and gpt-5.6-cyber: 400,000.
    • gpt-5*-chat-latest: 128,000.
    • Older GPT-5: still 400,000.
    • OpenAI's GET /v1/models reports no context window, so for OpenAI this table is the source. Tests are in compaction.test.ts.
  • Provider configs carry the plugin log (ProviderConfig.log). When OpenAI refuses reasoning summaries, the retry now writes one warning per model with OpenAI's reason. A missing Thinking block can then be told apart from a model that simply wrote no summary.

Verification

Finish condition: an official OpenAI provider with tools sends one /v1/responses request with reasoning left on (no reasoning_effort: "none"). Compatible providers' requests are unchanged. The repo's checks pass.

Commands (Node 24 per .nvmrc):

  • typecheck (tsc -p .) ✅
  • prettier --check . ✅
  • vitest ✅: 87 files, 823 passed, 1 skipped (base dev-1.3.0: 789 passed, 1 skipped). One run hit an unrelated vitest teardown error in tests/frontend/activate.test.tsx ("Closing rpc while onUserConsoleLog was pending"). Two reruns were clean.

Reproduction: gpt-6-luna with one tool against a fake API that refuses tools with reasoning on Chat Completions, using the real 400 text:

base dev-1.3.0 (2a92bc7): ["/v1/chat/completions reasoning_effort=-", "/v1/chat/completions reasoning_effort=none"]
this branch:              ["/v1/responses reasoning_effort=- reasoning={\"summary\":\"auto\"}"]

Each mutation was applied on its own and reverted:

Mutation Result
Replay reasoning items with their stored id 1 test failed
Omit strict: false 3 failed
Remove the retry without reasoning 5 failed
Accept a stream that ends without a terminal event 2 failed
Route openai_compatible to Responses 2 failed

After reverting, all 33 tests pass.

Manual test

  • Termix ghcr.io/termix-ssh/termix:latest in a throwaway Podman container. The plugin build was mounted as the bundled ai plugin at /app/dist/plugins/ai, so it loads like the shipped copy: Loaded plugin ai@1.3.0, Activated plugin ai, and all migrations applied.
  • OpenAI provider with no base URL, so requests go to the Responses API. Model gpt-6-luna.
  • Assistant request with tools: the model streamed a Thinking block (reasoning summary) and then made six tool calls in one run (6x "Running Termix CLI") before giving the correct answer. There was no HTTP 400, and reasoning stayed on.
  • Context window: the provider form shows "Context window: 1,050,000" for gpt-6-luna (before the context-window commit it showed "128,000 (default for unknown models)").
  • The screenshots were taken on the stacked follow-up build (Advanced provider settings), so the provider form also shows its expanded Advanced section. That section is not part of this PR.

Screenshots

Assistant with Thinking and tool calls (gpt-6-luna) Provider form: context window 1,050,000
Assistant with Thinking block and six Termix CLI tool calls Provider form showing context window 1,050,000 for gpt-6-luna

The provider form screenshot comes from the stacked Advanced-settings build; the Advanced section in it belongs to the follow-up PR.

Verified against the live OpenAI API for gpt-6-luna with tools and reasoning summaries (see Manual test above; the automated tests use no API key). Every request and event shape comes from the current OpenAI docs (Responses streaming events reference, function calling, reasoning and migration guides), exercised with mocked fetch. Worth a manual check:

  • a multi-step tool run on gpt-5.6-*
  • an unverified org (summary retry)
  • a non-reasoning model such as gpt-4.1 through Responses

Assistant phase values (commentary/final_answer) are not sent. The docs recommend them for long tool loops but don't require them.

Official OpenAI providers (no base URL or an api.openai.com host) now
stream from /v1/responses statelessly (store: false). Encrypted reasoning
items are replayed with function_call and function_call_output items
within a run, so models such as gpt-6-luna and gpt-5.6 can call tools
without turning reasoning off. Reasoning summaries are requested and
dropped (and remembered per model) when the org or model refuses them.

OpenAI compatible providers and custom base URLs keep Chat Completions,
including the reasoning_effort "none" retry from 4672df0.

Refs Termix-SSH#8
GPT-5.4 and later and every GPT-6 model hold 1,050,000 tokens, the
GPT-5 chat snapshots 128,000 and GPT-5.4 mini/nano and GPT-5.6 cyber
400,000, per OpenAI's model pages. gpt-6-luna used to fall back to the
128k default.
The retry without reasoning summaries was silent, so a missing Thinking
block could not be told apart from a model that wrote no summary.
Provider configs now carry the plugin log and the retry warns once per
model with OpenAI's reason.
@termix-triage-bot

Copy link
Copy Markdown

AI review | Looks safe to merge

Routes official OpenAI providers to the Responses API to preserve reasoning during tool calls, while keeping compatible providers and custom URLs on Chat Completions. Adds context window mappings for newer models and handles reasoning summary fallbacks with in-memory caching. Tests are updated to isolate the new path. No rule violations or security issues found.

  • minor Plugin PRs should target main, not dev-1.3.0.

Reviewed cde46b2. AI can be wrong, a maintainer still decides. Comment /termix review to run it again.

@LukeGus
LukeGus merged commit 59d8b9a into Termix-SSH:dev-1.3.0 Oct 22, 2026
2 checks passed
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