Skip to content

fix(provider): add self-hosted compat mode (auto continue for self-hosted LLMs) - #1730

Closed
iHardRock wants to merge 2 commits into
Twigpine:mainfrom
iHardRock:fix_continue_for_local
Closed

iHardRock wants to merge 2 commits into
Twigpine:mainfrom
iHardRock:fix_continue_for_local

Conversation

@iHardRock

@iHardRock iHardRock commented Jun 19, 2026 •

Copy link
Copy Markdown
Contributor

Self-hosted OpenAI-compatible backends (llama-server, Ollama, vLLM) often stop after tool results: the agent exits without follow-up tool calls, and users must run /continue. Local models may also echo "[Tool results received]" or emit tool calls as JSON text instead of structured API tool_calls.

Add an explicit per-profile "Self-hosted compat" option (parseTextToolCalls). When enabled, behaviour is gated only by this option — not by host, port, or RFC1918/private-network detection. This works for any base URL (172.16.x.x, 192.168.x.x, hostname, public IP).

Self-hosted compat enables:

  • parsing JSON tool calls from assistant text (streaming and non-streaming)
  • post-tool continuation prompts in the API request
  • automatic nudge when the model stalls after tool results (empty response, "[Tool results received]", or no tool calls)
  • withholding and stripping of semantic-placeholder assistant messages from history
  • fast path (skip cloud-oriented stableStringify, strict tool schemas, and tool-history compression)
  • toolless self-heal retry on tool_call_incompatible errors

Mistral/Devstral-only: inject the synthetic "[Tool results received]" assistant boundary only for Mistral-class backends. Other providers no longer receive it.

Provider UI: add Self-hosted compat step to openai-compatible profiles; Ollama preset defaults to enabled. Wire OPENAI_PARSE_TEXT_TOOL_CALLS through profile env.

Tests: LAN/172.16 endpoints, post-tool stall helpers, continuation prompt after tool→user, placeholder stripping, fast-path option gating, toolless retry.

Testing

  • [ *] bun run build
  • [ *] bun run smoke
  • [ *] bun run check

Notes

  • Configure option in provider settings

Summary by CodeRabbit

  • New Features

    • Added a “Self-hosted compat” option in provider settings for OpenAI-compatible endpoints to enable text-based tool call parsing.
  • Bug Fixes

    • Improved handling of stalled post-tool assistant responses to prevent incorrect loop completion behavior.
    • Added a local tool-result continuation recovery path to keep agent flows moving after tool execution.
    • Improved UI/report and environment handling in tests to reduce CI failures caused by ANSI output and persisted parse settings.

…ol stalls

Self-hosted OpenAI-compatible backends (llama-server, Ollama, vLLM) often stop
after tool results: the agent exits without follow-up tool calls, and users must
run /continue. Local models may also echo "[Tool results received]" or emit tool
calls as JSON text instead of structured API tool_calls.

Add an explicit per-profile "Self-hosted compat" option (parseTextToolCalls).
When enabled, behaviour is gated only by this option — not by host, port, or
RFC1918/private-network detection. This works for any base URL (172.16.x.x,
192.168.x.x, hostname, public IP).

Self-hosted compat enables:
- parsing JSON tool calls from assistant text (streaming and non-streaming)
- post-tool continuation prompts in the API request
- automatic nudge when the model stalls after tool results (empty response,
  "[Tool results received]", or no tool calls)
- withholding and stripping of semantic-placeholder assistant messages from history
- fast path (skip cloud-oriented stableStringify, strict tool schemas, and
  tool-history compression)
- toolless self-heal retry on tool_call_incompatible errors

Mistral/Devstral-only: inject the synthetic "[Tool results received]" assistant
boundary only for Mistral-class backends. Other providers no longer receive it.

Provider UI: add Self-hosted compat step to openai-compatible profiles; Ollama
preset defaults to enabled. Wire OPENAI_PARSE_TEXT_TOOL_CALLS through profile env.

Tests: LAN/172.16 endpoints, post-tool stall helpers, continuation prompt after
tool→user, placeholder stripping, fast-path option gating, toolless retry.
@coderabbitai

coderabbitai Bot commented Jun 19, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 81676811-85aa-4842-822f-1cbf9f19264e

📥 Commits

Reviewing files that changed from the base of the PR and between e2abaf5 and c5c3a8d.

📒 Files selected for processing (1)
  • src/services/api/providerConfig.localFastPath.test.ts
📜 Recent review details
🧰 Additional context used
📓 Path-based instructions (14)
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript with strict mode and ESM imports

Files:

  • src/services/api/providerConfig.localFastPath.test.ts
{src/commands/**/*.ts,src/services/**/*.ts,src/entrypoints/**/*.ts}

📄 CodeRabbit inference engine (AGENTS.md)

Use chalk for terminal color in CLI code

Files:

  • src/services/api/providerConfig.localFastPath.test.ts
{src/services/**/*.ts,src/utils/**/*.ts}

📄 CodeRabbit inference engine (AGENTS.md)

Use execa for child processes

Files:

  • src/services/api/providerConfig.localFastPath.test.ts
{src/integrations/**/*.ts,src/services/**/*.ts}

📄 CodeRabbit inference engine (AGENTS.md)

Test the exact provider/model path you changed when possible for provider modifications

Files:

  • src/services/api/providerConfig.localFastPath.test.ts
**/*.{ts,tsx,js,jsx,py,json,md}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Follow the existing code style in the touched files

Files:

  • src/services/api/providerConfig.localFastPath.test.ts
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Add or update tests when the change affects behavior

Files:

  • src/services/api/providerConfig.localFastPath.test.ts
src/**/*provider*.{ts,tsx,js}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Provider implementations must follow documented patterns in docs/integrations/

Files:

  • src/services/api/providerConfig.localFastPath.test.ts
**/*.test.{ts,tsx,js}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Test the exact provider/model path you changed when possible

Files:

  • src/services/api/providerConfig.localFastPath.test.ts
**/*.{ts,tsx,js,jsx,py}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Keep comments useful and concise

Files:

  • src/services/api/providerConfig.localFastPath.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Follow TypeScript strict mode and type safety practices by running typecheck before submitting

Files:

  • src/services/api/providerConfig.localFastPath.test.ts
**/*

⚙️ CodeRabbit configuration file

**/*: Apply the OpenClaude maintainer review rubric from AGENTS.md. Review the current diff, not stale discussion context. Separate real blockers from suggestions. Do not request changes for vague style churn. Treat approval as merge-ready from CodeRabbit's side, pending required human review and GitHub Checks. If checks are failing or unavailable, say so clearly instead of implying the PR is fully ready.

Files:

  • src/services/api/providerConfig.localFastPath.test.ts
{src/services/api/**,src/integrations/**,src/utils/model/**,src/utils/provider*.ts,src/commands/provider/**}

⚙️ CodeRabbit configuration file

{src/services/api/**,src/integrations/**,src/utils/model/**,src/utils/provider*.ts,src/commands/provider/**}: Review provider routing, model selection, env precedence, auth/token handling, OpenAI-compatible shims, retries, proxy behavior, and outbound HTTP behavior with high scrutiny. Block on silent default changes, hidden fallback expansion, credential reuse mistakes, hardcoded provider assumptions, or new network reach that is not intentional and documented.

Files:

  • src/services/api/providerConfig.localFastPath.test.ts
{src/**/*.test.ts,src/**/*.test.tsx,tests/**,scripts/**/*.test.ts,vscode-extension/**/*.test.js}

⚙️ CodeRabbit configuration file

{src/**/*.test.ts,src/**/*.test.tsx,tests/**,scripts/**/*.test.ts,vscode-extension/**/*.test.js}: Review tests for meaningful coverage of the changed behavior, isolation of global/env/config state, async cleanup, fake timers, provider profile leaks, and Windows-compatible assumptions. Block when risky runtime changes lack focused regression coverage or tests assert implementation details while missing the user-visible behavior.

Files:

  • src/services/api/providerConfig.localFastPath.test.ts
**

⚙️ CodeRabbit configuration file

**: # AGENTS.md - AI Agent Coding Guide

This guide is for AI coding agents working in the OpenClaude repository. Read it before changing code, and also follow CONTRIBUTING.md for contributor policy, PR expectations, review follow-up, and project scope.

Project Snapshot

OpenClaude is a coding-agent CLI for cloud and local model providers. It supports OpenAI-compatible APIs, Anthropic, Gemini, DeepSeek, Ollama, MCP, local backends, slash commands, tools, agents, and a React/Ink terminal UI.

The installed CLI runs on Node.js >=22.0.0. Bun is used for source builds, scripts, dependency management, and tests.

Work Style

  • Keep changes focused on one problem.
  • Prefer existing patterns in the file or nearby module.
  • Avoid unrelated formatting, renames, dependency changes, or broad rewrites.
  • Add or update tests when behavior changes.
  • Update docs when setup, commands, provider behavior, or user-facing behavior changes.
  • For new features, larger refactors, dependencies, or runtime changes, follow the issue-first guidance in CONTRIBUTING.md.

Stack And Conventions

  • TypeScript with strict mode and ESM imports.
  • React + Ink for terminal UI.
  • Bun lockfile and Bun scripts for development workflows.
  • Node runtime for the built CLI.
  • Python exists for legacy/local-provider helper code. Do not add new Python code or expand Python-based features unless a maintainer explicitly approves that direction.

Common libraries and patterns:

  • chalk for terminal color.
  • commander for CLI argument parsing.
  • execa for child processes.
  • Existing service, provider, settings, permission, and UI patterns over new abstractions.

Repository Map

  • src/commands/ - slash and CLI command implementations.
  • src/components/ - React/Ink UI components.
  • src/services/ - API, MCP, OAuth, wiki, voice, and other service integrations.
  • src/tools/ - tool implementations.
  • src/utils/ - shared utilities.
  • `src/integration...

Files:

  • src/services/api/providerConfig.localFastPath.test.ts
🔇 Additional comments (1)
src/services/api/providerConfig.localFastPath.test.ts (1)

105-112: LGTM!

Also applies to: 115-115, 119-126


📝 Walkthrough

Walkthrough

Adds a parseTextToolCalls boolean field to ProviderProfile that enables a self-hosted OpenAI-compatible mode. When active: the OpenAI shim parses JSON-embedded tool calls from streamed or buffered text, manages a semantic placeholder boundary around tool results, and appends a local continuation prompt. The agent query loop gains post-tool stall detection (via new continuation.ts helpers) and a recovery nudge path. A new ProviderManager wizard step exposes the setting in the UI.

Changes

Self-hosted compat: text tool-call parsing and post-tool stall recovery

Layer / File(s) Summary
Config field and compat-mode gate exports
src/utils/config.ts, src/services/api/providerConfig.ts
Adds parseTextToolCalls?: boolean to ProviderProfile. Exports OPENAI_PARSE_TEXT_TOOL_CALLS_ENV, TOOL_RESULT_SEMANTIC_PLACEHOLDER, LOCAL_TOOL_CONTINUATION_PROMPT, shouldParseTextToolCalls, shouldInjectToolResultSemanticBoundary. Rewrites getLocalFastPathConfig and shouldAttemptLocalToollessRetry to gate on shouldParseTextToolCalls instead of local-URL detection.
Profile env plumbing and preset defaults
src/utils/providerProfile.ts, src/utils/providerProfiles.ts, src/utils/providerProfiles.test.ts
Threads parseTextToolCalls through ProfileEnv, PROFILE_ENV_KEYS, buildOllamaProfileEnv, buildOpenAIProfileEnv, new appendParseTextToolCallsEnv, sanitizeProfile, toProfile, new providerProfileSupportsTextToolCallParsing, isProcessEnvAlignedWithProfile, and all startup env construction paths. Ollama preset now defaults parseTextToolCalls: true.
Post-tool stall detection utilities
src/utils/continuation.ts, src/__tests__/bugfixes.test.ts
Adds isToolResultSemanticPlaceholderText, isStalledPostToolResponse, conversationHasPendingToolFollowUp, stripSemanticPlaceholderAssistants, and shouldNudgePostToolTurn. Tested for placeholder/stall classification, nudge decisions, and placeholder stripping.
OpenAI shim: message conversion helpers and text tool-call fallback parser
src/services/api/openaiShim.ts
Adds appendLocalToolContinuationPrompt, assistant-text-to-plain helper, appendAssistantTextContentBlocks. Extends convertMessages with injectToolResultSemanticBoundary to conditionally skip placeholder text or inject the semantic placeholder at tool→user transitions.
OpenAI shim: streaming/non-streaming fallback wiring and retry logic
src/services/api/openaiShim.ts
Replaces Ollama-specific stream gate with enableTextToolCallFallback param; updates text-delta handling, flush, and terminal-finish. Threads shouldParseTextToolCalls into both streaming and non-streaming call sites. Updates request construction for semantic boundary, continuation prompt, include_usage, shouldStripResponsesStore, and resolveOpenAIShimRuntimeContext. Reworks retry budget and gating to use hasLocalRetryCandidates.
Query loop: stall withholding and post-tool recovery nudge
src/query.ts
Adds withheldStallResponse logic to suppress stalled assistant messages. Adds post-tool stall recovery block that strips placeholder assistants, appends LOCAL_TOOL_CONTINUATION_PROMPT, transitions to continuation_nudge state, and increments continuationNudgeCount.
ProviderManager UI: parseTextToolCalls wizard step
src/components/ProviderManager.tsx, src/components/ProviderManager.test.tsx
Adds parseTextToolCalls to DraftField and FORM_STEPS, gates step on providerProfileSupportsTextToolCallParsing, updates toDraft/presetToDraft/profileSummary/persistDraft/renderForm. Tests add submitSelfHostedCompatStep helper and update step counts.
Test isolation and new integration tests
src/services/api/openaiShim*.test.ts, src/services/api/providerConfig.local*.test.ts, src/commands/ctx_viz/ctx_viz.test.ts
Adds OPENAI_PARSE_TEXT_TOOL_CALLS env snapshot/restore to all affected suites. New shim tests cover: no semantic placeholder on LAN backends, local continuation prompt appended after tool results, stale placeholder dropped. New llama-server streaming integration test. Updated providerConfig tests for compat-gated toolless retry, fast-path, and semantic boundary. ANSI stripping fix for ctx_viz test.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • Gitlawb/openclaude#1076: Implements the text-based tool-call extraction pipeline in openaiShim.ts (parsing JSON-embedded tool calls from streamed text and synthesizing tool_use events) that this PR extends and gates on OPENAI_PARSE_TEXT_TOOL_CALLS.
  • Gitlawb/openclaude#1600: Modifies the OpenAI shim's post-tool coalescing to use the "[Tool results received]" semantic boundary placeholder, which this PR consumes in stall detection and continuation recovery logic.

Suggested labels

enhancement

Suggested reviewers

  • jatmn
  • kevincodex1
🚥 Pre-merge checks | ✅ 6 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (6 passed)
Check name Status Explanation
Title check ✅ Passed Title accurately describes the main change: adding a self-hosted compatibility mode with automatic continuation for self-hosted LLMs. It directly matches the changeset scope.
Description check ✅ Passed Description covers what changed, why, testing checkboxes, and notes. It thoroughly explains the feature, benefits, and testing scope beyond template requirements.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Risk Surface Disclosed ✅ Passed PR discloses risk surface for provider routing (explicit flag gating instead of hostname/port detection), outbound network behavior (tool parsing, continuations), and startup config (OPENAI_PARSE_T...
No Hidden Policy Change ✅ Passed All new behaviors are explicitly gated by opt-in profile option or env var. Semantic boundary injection change is documented. No hidden policy changes to defaults, routing, telemetry, or permissions.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/__tests__/bugfixes.test.ts`:
- Around line 181-197: The test fixture toolRound uses the double-cast pattern
`as unknown as` which bypasses TypeScript type checking and can hide issues with
malformed fixtures. Replace this cast pattern with a `satisfies Message[]` type
assertion on the toolRound array literal to ensure strict type safety at compile
time. Apply the same change to the other instances mentioned at lines 210-211
that use the same problematic casting pattern.

In `@src/services/api/openaiShim.ts`:
- Around line 3510-3513: The condition `!choice?.message?.tool_calls` in the
enableTextToolCallFallback variable only checks if tool_calls is falsy, but an
empty array is truthy, so fallback parsing is skipped when tool_calls exists as
an empty array. Modify the condition to enable text fallback when tool_calls is
either not present or is an empty array by checking both the existence and
length of the tool_calls array.
- Around line 595-606: The code currently collapses the content array (which may
contain text, images, and other content types) into plain text using
joinTextContentParts and then appends promptSuffix as a string, which drops
non-text blocks like images. Instead of converting the entire content array to a
string, preserve the array structure by appending the promptSuffix as a new text
content block to the array when last.content is an array, maintaining all
existing content blocks including non-text elements, rather than replacing the
entire content with a merged string.

In `@src/services/api/providerConfig.localFastPath.test.ts`:
- Around line 104-121: The tests in the "auto/empty string fall through to
profile option" and "garbage values fall through to profile option" test cases
are setting process.env[ENV_VAR] to specific values ('auto', '', 'maybe') but
then calling getLocalFastPathConfig with selfHostedEnv as the second parameter,
which omits OPENCLAUDE_LOCAL_FAST_PATH and prevents the set environment variable
values from being used. To properly exercise the override values being tested,
remove the selfHostedEnv parameter from the getLocalFastPathConfig calls in
these tests, or pass an environment object that includes the ENV_VAR so the
function actually evaluates the values being set.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0a8835fe-8cfb-45bb-af58-e63e96c4e730

📥 Commits

Reviewing files that changed from the base of the PR and between b581bd9 and e2abaf5.

📒 Files selected for processing (18)
  • src/__tests__/bugfixes.test.ts
  • src/commands/ctx_viz/ctx_viz.test.ts
  • src/components/ProviderManager.test.tsx
  • src/components/ProviderManager.tsx
  • src/query.ts
  • src/services/api/openaiShim.compression.test.ts
  • src/services/api/openaiShim.diagnostics.test.ts
  • src/services/api/openaiShim.ollamaTextToolCalls.test.ts
  • src/services/api/openaiShim.test.ts
  • src/services/api/openaiShim.ts
  • src/services/api/providerConfig.local.test.ts
  • src/services/api/providerConfig.localFastPath.test.ts
  • src/services/api/providerConfig.ts
  • src/utils/config.ts
  • src/utils/continuation.ts
  • src/utils/providerProfile.ts
  • src/utils/providerProfiles.test.ts
  • src/utils/providerProfiles.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (16)
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript with strict mode and ESM imports

Files:

  • src/utils/config.ts
  • src/services/api/openaiShim.compression.test.ts
  • src/commands/ctx_viz/ctx_viz.test.ts
  • src/utils/providerProfiles.test.ts
  • src/services/api/openaiShim.diagnostics.test.ts
  • src/services/api/openaiShim.ollamaTextToolCalls.test.ts
  • src/utils/continuation.ts
  • src/services/api/providerConfig.local.test.ts
  • src/services/api/openaiShim.test.ts
  • src/components/ProviderManager.test.tsx
  • src/__tests__/bugfixes.test.ts
  • src/query.ts
  • src/components/ProviderManager.tsx
  • src/services/api/providerConfig.localFastPath.test.ts
  • src/services/api/providerConfig.ts
  • src/utils/providerProfile.ts
  • src/utils/providerProfiles.ts
  • src/services/api/openaiShim.ts
{src/services/**/*.ts,src/utils/**/*.ts}

📄 CodeRabbit inference engine (AGENTS.md)

Use execa for child processes

Files:

  • src/utils/config.ts
  • src/services/api/openaiShim.compression.test.ts
  • src/utils/providerProfiles.test.ts
  • src/services/api/openaiShim.diagnostics.test.ts
  • src/services/api/openaiShim.ollamaTextToolCalls.test.ts
  • src/utils/continuation.ts
  • src/services/api/providerConfig.local.test.ts
  • src/services/api/openaiShim.test.ts
  • src/services/api/providerConfig.localFastPath.test.ts
  • src/services/api/providerConfig.ts
  • src/utils/providerProfile.ts
  • src/utils/providerProfiles.ts
  • src/services/api/openaiShim.ts
**/*.{ts,tsx,js,jsx,py,json,md}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Follow the existing code style in the touched files

Files:

  • src/utils/config.ts
  • src/services/api/openaiShim.compression.test.ts
  • src/commands/ctx_viz/ctx_viz.test.ts
  • src/utils/providerProfiles.test.ts
  • src/services/api/openaiShim.diagnostics.test.ts
  • src/services/api/openaiShim.ollamaTextToolCalls.test.ts
  • src/utils/continuation.ts
  • src/services/api/providerConfig.local.test.ts
  • src/services/api/openaiShim.test.ts
  • src/components/ProviderManager.test.tsx
  • src/__tests__/bugfixes.test.ts
  • src/query.ts
  • src/components/ProviderManager.tsx
  • src/services/api/providerConfig.localFastPath.test.ts
  • src/services/api/providerConfig.ts
  • src/utils/providerProfile.ts
  • src/utils/providerProfiles.ts
  • src/services/api/openaiShim.ts
**/*.{ts,tsx,js,jsx,py}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Keep comments useful and concise

Files:

  • src/utils/config.ts
  • src/services/api/openaiShim.compression.test.ts
  • src/commands/ctx_viz/ctx_viz.test.ts
  • src/utils/providerProfiles.test.ts
  • src/services/api/openaiShim.diagnostics.test.ts
  • src/services/api/openaiShim.ollamaTextToolCalls.test.ts
  • src/utils/continuation.ts
  • src/services/api/providerConfig.local.test.ts
  • src/services/api/openaiShim.test.ts
  • src/components/ProviderManager.test.tsx
  • src/__tests__/bugfixes.test.ts
  • src/query.ts
  • src/components/ProviderManager.tsx
  • src/services/api/providerConfig.localFastPath.test.ts
  • src/services/api/providerConfig.ts
  • src/utils/providerProfile.ts
  • src/utils/providerProfiles.ts
  • src/services/api/openaiShim.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Follow TypeScript strict mode and type safety practices by running typecheck before submitting

Files:

  • src/utils/config.ts
  • src/services/api/openaiShim.compression.test.ts
  • src/commands/ctx_viz/ctx_viz.test.ts
  • src/utils/providerProfiles.test.ts
  • src/services/api/openaiShim.diagnostics.test.ts
  • src/services/api/openaiShim.ollamaTextToolCalls.test.ts
  • src/utils/continuation.ts
  • src/services/api/providerConfig.local.test.ts
  • src/services/api/openaiShim.test.ts
  • src/components/ProviderManager.test.tsx
  • src/__tests__/bugfixes.test.ts
  • src/query.ts
  • src/components/ProviderManager.tsx
  • src/services/api/providerConfig.localFastPath.test.ts
  • src/services/api/providerConfig.ts
  • src/utils/providerProfile.ts
  • src/utils/providerProfiles.ts
  • src/services/api/openaiShim.ts
**/*

⚙️ CodeRabbit configuration file

**/*: Apply the OpenClaude maintainer review rubric from AGENTS.md. Review the current diff, not stale discussion context. Separate real blockers from suggestions. Do not request changes for vague style churn. Treat approval as merge-ready from CodeRabbit's side, pending required human review and GitHub Checks. If checks are failing or unavailable, say so clearly instead of implying the PR is fully ready.

Files:

  • src/utils/config.ts
  • src/services/api/openaiShim.compression.test.ts
  • src/commands/ctx_viz/ctx_viz.test.ts
  • src/utils/providerProfiles.test.ts
  • src/services/api/openaiShim.diagnostics.test.ts
  • src/services/api/openaiShim.ollamaTextToolCalls.test.ts
  • src/utils/continuation.ts
  • src/services/api/providerConfig.local.test.ts
  • src/services/api/openaiShim.test.ts
  • src/components/ProviderManager.test.tsx
  • src/__tests__/bugfixes.test.ts
  • src/query.ts
  • src/components/ProviderManager.tsx
  • src/services/api/providerConfig.localFastPath.test.ts
  • src/services/api/providerConfig.ts
  • src/utils/providerProfile.ts
  • src/utils/providerProfiles.ts
  • src/services/api/openaiShim.ts
**

⚙️ CodeRabbit configuration file

**: # AGENTS.md - AI Agent Coding Guide

This guide is for AI coding agents working in the OpenClaude repository. Read it before changing code, and also follow CONTRIBUTING.md for contributor policy, PR expectations, review follow-up, and project scope.

Project Snapshot

OpenClaude is a coding-agent CLI for cloud and local model providers. It supports OpenAI-compatible APIs, Anthropic, Gemini, DeepSeek, Ollama, MCP, local backends, slash commands, tools, agents, and a React/Ink terminal UI.

The installed CLI runs on Node.js >=22.0.0. Bun is used for source builds, scripts, dependency management, and tests.

Work Style

  • Keep changes focused on one problem.
  • Prefer existing patterns in the file or nearby module.
  • Avoid unrelated formatting, renames, dependency changes, or broad rewrites.
  • Add or update tests when behavior changes.
  • Update docs when setup, commands, provider behavior, or user-facing behavior changes.
  • For new features, larger refactors, dependencies, or runtime changes, follow the issue-first guidance in CONTRIBUTING.md.

Stack And Conventions

  • TypeScript with strict mode and ESM imports.
  • React + Ink for terminal UI.
  • Bun lockfile and Bun scripts for development workflows.
  • Node runtime for the built CLI.
  • Python exists for legacy/local-provider helper code. Do not add new Python code or expand Python-based features unless a maintainer explicitly approves that direction.

Common libraries and patterns:

  • chalk for terminal color.
  • commander for CLI argument parsing.
  • execa for child processes.
  • Existing service, provider, settings, permission, and UI patterns over new abstractions.

Repository Map

  • src/commands/ - slash and CLI command implementations.
  • src/components/ - React/Ink UI components.
  • src/services/ - API, MCP, OAuth, wiki, voice, and other service integrations.
  • src/tools/ - tool implementations.
  • src/utils/ - shared utilities.
  • `src/integration...

Files:

  • src/utils/config.ts
  • src/services/api/openaiShim.compression.test.ts
  • src/commands/ctx_viz/ctx_viz.test.ts
  • src/utils/providerProfiles.test.ts
  • src/services/api/openaiShim.diagnostics.test.ts
  • src/services/api/openaiShim.ollamaTextToolCalls.test.ts
  • src/utils/continuation.ts
  • src/services/api/providerConfig.local.test.ts
  • src/services/api/openaiShim.test.ts
  • src/components/ProviderManager.test.tsx
  • src/__tests__/bugfixes.test.ts
  • src/query.ts
  • src/components/ProviderManager.tsx
  • src/services/api/providerConfig.localFastPath.test.ts
  • src/services/api/providerConfig.ts
  • src/utils/providerProfile.ts
  • src/utils/providerProfiles.ts
  • src/services/api/openaiShim.ts
{src/commands/**/*.ts,src/services/**/*.ts,src/entrypoints/**/*.ts}

📄 CodeRabbit inference engine (AGENTS.md)

Use chalk for terminal color in CLI code

Files:

  • src/services/api/openaiShim.compression.test.ts
  • src/commands/ctx_viz/ctx_viz.test.ts
  • src/services/api/openaiShim.diagnostics.test.ts
  • src/services/api/openaiShim.ollamaTextToolCalls.test.ts
  • src/services/api/providerConfig.local.test.ts
  • src/services/api/openaiShim.test.ts
  • src/services/api/providerConfig.localFastPath.test.ts
  • src/services/api/providerConfig.ts
  • src/services/api/openaiShim.ts
{src/integrations/**/*.ts,src/services/**/*.ts}

📄 CodeRabbit inference engine (AGENTS.md)

Test the exact provider/model path you changed when possible for provider modifications

Files:

  • src/services/api/openaiShim.compression.test.ts
  • src/services/api/openaiShim.diagnostics.test.ts
  • src/services/api/openaiShim.ollamaTextToolCalls.test.ts
  • src/services/api/providerConfig.local.test.ts
  • src/services/api/openaiShim.test.ts
  • src/services/api/providerConfig.localFastPath.test.ts
  • src/services/api/providerConfig.ts
  • src/services/api/openaiShim.ts
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Add or update tests when the change affects behavior

Files:

  • src/services/api/openaiShim.compression.test.ts
  • src/commands/ctx_viz/ctx_viz.test.ts
  • src/utils/providerProfiles.test.ts
  • src/services/api/openaiShim.diagnostics.test.ts
  • src/services/api/openaiShim.ollamaTextToolCalls.test.ts
  • src/services/api/providerConfig.local.test.ts
  • src/services/api/openaiShim.test.ts
  • src/components/ProviderManager.test.tsx
  • src/__tests__/bugfixes.test.ts
  • src/services/api/providerConfig.localFastPath.test.ts
**/*.test.{ts,tsx,js}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Test the exact provider/model path you changed when possible

Files:

  • src/services/api/openaiShim.compression.test.ts
  • src/commands/ctx_viz/ctx_viz.test.ts
  • src/utils/providerProfiles.test.ts
  • src/services/api/openaiShim.diagnostics.test.ts
  • src/services/api/openaiShim.ollamaTextToolCalls.test.ts
  • src/services/api/providerConfig.local.test.ts
  • src/services/api/openaiShim.test.ts
  • src/components/ProviderManager.test.tsx
  • src/__tests__/bugfixes.test.ts
  • src/services/api/providerConfig.localFastPath.test.ts
{src/services/api/**,src/integrations/**,src/utils/model/**,src/utils/provider*.ts,src/commands/provider/**}

⚙️ CodeRabbit configuration file

{src/services/api/**,src/integrations/**,src/utils/model/**,src/utils/provider*.ts,src/commands/provider/**}: Review provider routing, model selection, env precedence, auth/token handling, OpenAI-compatible shims, retries, proxy behavior, and outbound HTTP behavior with high scrutiny. Block on silent default changes, hidden fallback expansion, credential reuse mistakes, hardcoded provider assumptions, or new network reach that is not intentional and documented.

Files:

  • src/services/api/openaiShim.compression.test.ts
  • src/utils/providerProfiles.test.ts
  • src/services/api/openaiShim.diagnostics.test.ts
  • src/services/api/openaiShim.ollamaTextToolCalls.test.ts
  • src/services/api/providerConfig.local.test.ts
  • src/services/api/openaiShim.test.ts
  • src/services/api/providerConfig.localFastPath.test.ts
  • src/services/api/providerConfig.ts
  • src/utils/providerProfile.ts
  • src/utils/providerProfiles.ts
  • src/services/api/openaiShim.ts
{src/**/*.test.ts,src/**/*.test.tsx,tests/**,scripts/**/*.test.ts,vscode-extension/**/*.test.js}

⚙️ CodeRabbit configuration file

{src/**/*.test.ts,src/**/*.test.tsx,tests/**,scripts/**/*.test.ts,vscode-extension/**/*.test.js}: Review tests for meaningful coverage of the changed behavior, isolation of global/env/config state, async cleanup, fake timers, provider profile leaks, and Windows-compatible assumptions. Block when risky runtime changes lack focused regression coverage or tests assert implementation details while missing the user-visible behavior.

Files:

  • src/services/api/openaiShim.compression.test.ts
  • src/commands/ctx_viz/ctx_viz.test.ts
  • src/utils/providerProfiles.test.ts
  • src/services/api/openaiShim.diagnostics.test.ts
  • src/services/api/openaiShim.ollamaTextToolCalls.test.ts
  • src/services/api/providerConfig.local.test.ts
  • src/services/api/openaiShim.test.ts
  • src/components/ProviderManager.test.tsx
  • src/__tests__/bugfixes.test.ts
  • src/services/api/providerConfig.localFastPath.test.ts
{src/commands/**/*.ts,src/entrypoints/**/*.ts}

📄 CodeRabbit inference engine (AGENTS.md)

Use commander for CLI argument parsing

Files:

  • src/commands/ctx_viz/ctx_viz.test.ts
src/**/*provider*.{ts,tsx,js}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Provider implementations must follow documented patterns in docs/integrations/

Files:

  • src/utils/providerProfiles.test.ts
  • src/services/api/providerConfig.local.test.ts
  • src/services/api/providerConfig.localFastPath.test.ts
  • src/services/api/providerConfig.ts
  • src/utils/providerProfile.ts
  • src/utils/providerProfiles.ts
src/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use React + Ink for terminal UI implementations

Files:

  • src/components/ProviderManager.test.tsx
  • src/components/ProviderManager.tsx
🔇 Additional comments (17)
src/query.ts (3)

60-70: LGTM!


1074-1115: LGTM!


1779-1845: LGTM!

src/utils/config.ts (1)

232-239: LGTM!

src/services/api/providerConfig.ts (1)

411-414: LGTM!

Also applies to: 430-433, 471-477, 517-575, 621-635

src/utils/providerProfile.ts (1)

78-78: LGTM!

Also applies to: 186-187, 353-371, 683-684, 742-743

src/utils/providerProfiles.ts (1)

24-24: LGTM!

Also applies to: 69-69, 260-262, 303-311, 385-385, 631-634, 823-823, 1057-1058, 1092-1092

src/utils/providerProfiles.test.ts (1)

69-69: LGTM!

Also applies to: 1004-1028, 1594-1594

src/components/ProviderManager.tsx (1)

57-57: LGTM!

Also applies to: 131-131, 180-187, 244-245, 280-281, 311-321, 808-810, 1433-1434, 1529-1533, 1966-1987

src/components/ProviderManager.test.tsx (1)

104-111: LGTM!

Also applies to: 652-652, 1093-1099, 1169-1188, 2021-2069

src/utils/continuation.ts (1)

1-1: LGTM!

Also applies to: 33-135

src/services/api/openaiShim.compression.test.ts (1)

18-18: LGTM!

Also applies to: 137-137

src/commands/ctx_viz/ctx_viz.test.ts (1)

23-23: LGTM!

Also applies to: 212-255

src/services/api/openaiShim.diagnostics.test.ts (1)

19-19: LGTM!

Also applies to: 41-41, 217-219

src/services/api/openaiShim.ollamaTextToolCalls.test.ts (1)

216-223: LGTM!

Also applies to: 272-279, 381-388, 440-447, 490-550, 576-583

src/services/api/openaiShim.test.ts (1)

48-48: LGTM!

Also applies to: 187-187, 228-228, 5119-5119, 5416-5595, 5665-5665

src/services/api/providerConfig.local.test.ts (1)

6-12: LGTM!

Also applies to: 25-25, 51-51, 225-317

Comment on lines +181 to +197
const toolRound = [
{
type: 'assistant',
isApiErrorMessage: false,
message: {
role: 'assistant',
content: [{ type: 'tool_use', id: 'call_1', name: 'Read', input: {} }],
},
},
{
type: 'user',
message: {
role: 'user',
content: [{ type: 'tool_result', tool_use_id: 'call_1', content: 'ok' }],
},
},
] as unknown as import('../types/message.js').Message[]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Avoid as unknown as in strict-mode test fixtures.

These double-casts bypass message-shape checks and can hide malformed fixtures. Prefer typed literals (e.g., satisfies Message[]) so the regression test fails at compile time when the contract drifts.

As per coding guidelines, "Follow TypeScript strict mode and type safety practices by running typecheck before submitting."

Also applies to: 210-211

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/__tests__/bugfixes.test.ts` around lines 181 - 197, The test fixture
toolRound uses the double-cast pattern `as unknown as` which bypasses TypeScript
type checking and can hide issues with malformed fixtures. Replace this cast
pattern with a `satisfies Message[]` type assertion on the toolRound array
literal to ensure strict type safety at compile time. Apply the same change to
the other instances mentioned at lines 210-211 that use the same problematic
casting pattern.

Source: Coding guidelines

Comment on lines +595 to +606
const mergedText = joinTextContentParts(
typeof last.content === 'string' || !last.content
? []
: last.content,
)
if (mergedText.includes(LOCAL_TOOL_CONTINUATION_PROMPT)) {
return openaiMessages
}
return [
...openaiMessages.slice(0, -1),
{ ...last, content: mergedText + promptSuffix },
]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve structured user content when appending continuation prompts.

When last.content is an array, this branch collapses it to text and rewrites it as a string. That drops non-text blocks (for example image parts) and changes the outbound request payload semantics.

Suggested fix
-    const mergedText = joinTextContentParts(
-      typeof last.content === 'string' || !last.content
-        ? []
-        : last.content,
-    )
+    const existingParts = Array.isArray(last.content) ? last.content : []
+    const mergedText = joinTextContentParts(existingParts)
     if (mergedText.includes(LOCAL_TOOL_CONTINUATION_PROMPT)) {
       return openaiMessages
     }
     return [
       ...openaiMessages.slice(0, -1),
-      { ...last, content: mergedText + promptSuffix },
+      {
+        ...last,
+        content: [
+          ...existingParts,
+          { type: 'text', text: LOCAL_TOOL_CONTINUATION_PROMPT },
+        ],
+      },
     ]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/api/openaiShim.ts` around lines 595 - 606, The code currently
collapses the content array (which may contain text, images, and other content
types) into plain text using joinTextContentParts and then appends promptSuffix
as a string, which drops non-text blocks like images. Instead of converting the
entire content array to a string, preserve the array structure by appending the
promptSuffix as a new text content block to the array when last.content is an
array, maintaining all existing content blocks including non-text elements,
rather than replacing the entire content with a merged string.

Comment on lines +3510 to 3513
const enableTextToolCallFallback =
options?.enableTextToolCallFallback === true &&
!choice?.message?.tool_calls

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Enable text fallback when tool_calls is present but empty.

!choice?.message?.tool_calls treats tool_calls: [] as “present”, so fallback parsing is skipped even though no structured tool calls exist.

Suggested fix
-    const enableTextToolCallFallback =
-      options?.enableTextToolCallFallback === true &&
-      !choice?.message?.tool_calls
+    const hasStructuredToolCalls =
+      Array.isArray(choice?.message?.tool_calls) &&
+      choice.message.tool_calls.length > 0
+    const enableTextToolCallFallback =
+      options?.enableTextToolCallFallback === true &&
+      !hasStructuredToolCalls
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const enableTextToolCallFallback =
options?.enableTextToolCallFallback === true &&
!choice?.message?.tool_calls
const hasStructuredToolCalls =
Array.isArray(choice?.message?.tool_calls) &&
choice.message.tool_calls.length > 0
const enableTextToolCallFallback =
options?.enableTextToolCallFallback === true &&
!hasStructuredToolCalls
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/api/openaiShim.ts` around lines 3510 - 3513, The condition
`!choice?.message?.tool_calls` in the enableTextToolCallFallback variable only
checks if tool_calls is falsy, but an empty array is truthy, so fallback parsing
is skipped when tool_calls exists as an empty array. Modify the condition to
enable text fallback when tool_calls is either not present or is an empty array
by checking both the existence and length of the tool_calls array.

Comment thread src/services/api/providerConfig.localFastPath.test.ts Outdated
…process.env

The auto/empty/garbage override cases passed selfHostedEnv without ENV_VAR,
so they never read the values set on process.env. Use process.env for LAN
cases and an isolated env snapshot for the public-host negative assertion.

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found one issue that needs to be addressed before this is ready.

Findings

  • [P2] Enable self-hosted compat in the preferred Ollama setup path
    src/commands/provider/provider.tsx:968
    The PR makes self-hosted compat the sole gate for text tool-call parsing, post-tool continuation, and the local fast path, and the PR description says the Ollama preset defaults it on. However, the preferred /provider Ollama flows still call buildOllamaProfileEnv(...) without parseTextToolCalls: true, so saving the recommended Ollama profile or choosing an Ollama model through the preferred /provider path persists only OPENAI_BASE_URL and OPENAI_MODEL. Users who configure Ollama through that preferred path will not get OPENAI_PARSE_TEXT_TOOL_CALLS=1, so this fix will not apply to the main built-in self-hosted provider setup. Please thread the new option through both Ollama /provider save paths and add coverage for the saved env. More generally, this should be a visible provider-profile setting wherever users configure providers: if /config exposes it, it should edit the active provider profile rather than setting a single global toggle, because enabling these local-model workarounds globally would also affect cloud/OpenAI-compatible providers after a provider switch.

@jatmn jatmn linked an issue Jul 7, 2026 that may be closed by this pull request
@jatmn

jatmn commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the contribution and for writing up the self-hosted provider failure mode.

I’m going to close this PR rather than keep it open in its current form. The branch now has merge conflicts with current main, and the earlier review feedback was not addressed. Since then, several overlapping fixes have landed on main, including text tool-call recovery and stalled OpenAI-compatible stream recovery, so carrying this branch forward would require a substantial rebase and rescope rather than a normal review iteration.

The remaining useful idea here is narrower: if self-hosted providers still need an explicit profile-level compatibility toggle or additional post-tool continuation handling after the current main behavior, that should come back as a fresh, focused PR against current main, with the preferred /provider setup path covered as well.

Closing this as stale/superseded by later work and no longer mergeable as-is.

@jatmn jatmn closed this Jul 7, 2026
@iHardRock
iHardRock deleted the fix_continue_for_local branch July 14, 2026 14:56
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.

Bug: self-hosted LLMs stops after each tool call

2 participants