Repository navigation
feat: use the OpenAI Responses API so tools keep reasoning - #9
Merged
Merged
Conversation
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.
|
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.
Reviewed cde46b2. AI can be wrong, a maintainer still decides. Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Official OpenAI providers now use the Responses API (
POST /v1/responses) instead of Chat Completions. Reasoning models such asgpt-6-lunaandgpt-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
providerType === "openai"and the base URL is empty or anapi.openai.comhost (regional hosts such aseu.api.openai.comincluded). These keep using Chat Completions exactly as before:openai_compatibleproviders: Ollama, OpenRouter, vLLM, LM Studio, Groq and othersopenaiproviders with a custom base URL (proxies, gateways)store: false. In stateless mode OpenAI returns reasoning items withencrypted_contentby default.reasoning_statechunk. The engine already keeps that chunk in memory for the current run only (the same mechanism Anthropic thinking blocks use).function_callitems, followed by thefunction_call_outputitems, as the reasoning guide recommends.id/statusare stripped because stored ids cannot be looked up withstore: false. Reasoning items withoutencrypted_contentare not replayed.previous_response_idand no server-side storage.system→instructions{ type: "function", name, description, parameters, strict: false }. Responses tries strict mode whenstrictis omitted, and our schemas are not written for it.maxTokens→max_output_tokensreasoning.effortis not sent, so the model default applies.reasoning: { summary: "auto" }so the Thinking block keeps working.reasoning. This is remembered per base URL and model, so later steps don't pay for the retry.response.output_text.deltaandresponse.refusal.delta→ textresponse.reasoning_summary_text.deltaandresponse.reasoning_text.delta→ reasoning, separated between items and summary partsresponse.output_item.done→ function calls and reasoning itemsresponse.completed/response.incomplete→ usage (input_tokens/output_tokens) and stop reasonresponse.failedanderror→AiProviderErrorChanges
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 thestream_optionsandreasoning_effort: "none"retries.tests/backend/providers/openai-responses.test.ts(new, 33 tests). It covers:response.failed/error/truncated streamsopenaiprovider without a base URL now setbaseUrl: "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-lunashowed "128,000 (default for unknown models)" in the provider form.gpt-5.4,gpt-5.5,gpt-5.6-luna/sol/terra, the-provariants) 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/nanoandgpt-5.6-cyber: 400,000.gpt-5*-chat-latest: 128,000.GET /v1/modelsreports no context window, so for OpenAI this table is the source. Tests are incompaction.test.ts.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/responsesrequest with reasoning left on (noreasoning_effort: "none"). Compatible providers' requests are unchanged. The repo's checks pass.Commands (Node 24 per
.nvmrc):tsc -p .) ✅--check .✅dev-1.3.0: 789 passed, 1 skipped). One run hit an unrelated vitest teardown error intests/frontend/activate.test.tsx("Closing rpc while onUserConsoleLog was pending"). Two reruns were clean.Reproduction:
gpt-6-lunawith one tool against a fake API that refuses tools with reasoning on Chat Completions, using the real 400 text:Each mutation was applied on its own and reverted:
idstrict: falsereasoningopenai_compatibleto ResponsesAfter reverting, all 33 tests pass.
Manual test
ghcr.io/termix-ssh/termix:latestin a throwaway Podman container. The plugin build was mounted as the bundledaiplugin 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.gpt-6-luna.gpt-6-luna(before the context-window commit it showed "128,000 (default for unknown models)").Screenshots
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-lunawith 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:gpt-5.6-*gpt-4.1through ResponsesAssistant
phasevalues (commentary/final_answer) are not sent. The docs recommend them for long tool loops but don't require them.