Skip to content

fix(runtime): keep providerExecuted on non-streamed tool results - #3446

Merged
kwakayama merged 2 commits into
mainfrom
fix/provider-executed-direct-generate
Aug 7, 2026
Merged

fix(runtime): keep providerExecuted on non-streamed tool results#3446
kwakayama merged 2 commits into
mainfrom
fix/provider-executed-direct-generate

Conversation

@kojiwakayama

@kojiwakayama kojiwakayama commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

A provider-executed tool result loses its providerExecuted marker on the non-streamed generate path, and is then persisted into conversation history as an ordinary client-side tool result.

The bug

src/runtime/runtime-bridge.ts has two result builders that should agree:

  • Streamed (buildGenerateResultFromStream) propagates providerExecuted in both its tool-result and tool-error cases.
  • Non-streamed (buildDirectGenerateResult) never set it.

The type guard isDirectToolResultPart narrowed to a shape that omitted providerExecuted entirely, so the field was invisible at the type level and the omission never surfaced as a compile error.

It is on the default path, not an edge case

Providers really do send it. extensions/ext-llm-anthropic/src/anthropic-provider.ts:275-282 sets providerExecuted: true unconditionally on every web_search / web_fetch / MCP tool-result block, typed as the literal true. extensions/ext-llm-openai/src/openai-provider.ts:837-844 does the same for web_search_call.

And the non-streamed path is the common one: shouldGenerateViaStream is just model._generateViaStream === true, and that flag is set in exactly one place — src/provider/veryfront-cloud/provider.ts:17. So veryfront-cloud models divert to the stream builder (which was correct), while direct-key Anthropic and OpenAI models take doGenerate and hit the buggy builder.

Consequences

  1. Wrong persisted data — the real impact. persistGeneratedToolResult passes generatedToolResult.providerExecuted === true into createToolResultMessage, whose parameter defaults to false and drives a conditional spread (tool-result-continuation.ts:17,28). A genuinely provider-executed result was therefore written into conversation history with the marker absent entirely. Downstream code keys on that marker — e.g. src/chat/conversation.ts:376, and the completeness rule exercised by finalized-message.test.ts:62 ("fails local web_fetch input-available tools without providerExecuted").
  2. Observability gap. traceProviderExecutedTool fires only under providerExecuted === true, so provider-executed tools were never traced in generate mode.

Link 1 is traced through the file:line hops above rather than proven end-to-end; link 2 is covered by the test below.

The fix

Two lines in runtime-bridge.ts: widen isDirectToolResultPart's narrowed type with providerExecuted?: boolean, and add the same conditional spread the streamed builder uses, so the field is omitted rather than set to false.

Proof, in commit order

The two commits are deliberately ordered test-then-fix so the red/green is visible in history rather than asserted here.

Against the unfixed code, the new test fails on exactly the missing field:

[Diff] Actual / Expected
[
  {
+   providerExecuted: true,
    result: [ { title: "Veryfront", url: "https://veryfront.com" } ],
    toolCallId: "tool-web-3",
    toolName: "web_search",
  },
  {
    isError: true,
+   providerExecuted: true,
    result: { message: "fetch blocked" },
    ...

All 26 pre-existing steps in that file passed at the test-only commit, so the failure is attributable to the bug and nothing else. After the fix: 27 passed, 0 failed.

Why this survived until now

The neighbouring uses the direct generate path for provider-native tools test feeds model content carrying no providerExecuted field at all — an unrealistic fixture, since the real Anthropic provider always sets it on server-tool blocks. Nothing could observe the field being dropped because nothing ever supplied it. That test still passes unchanged (the conditional spread omits the field when absent) and was not modified.

Evidence

  • deno task test:unit: 3801 passed / 27916 steps / 0 failed / 1 ignored, exit 0. No pre-existing test broke; none needed updating.
  • deno task verify:quick exit 0; deno check src/index.ts clean; fmt and lint clean.
  • docs/api-reference/veryfront/embedding.md is a one-line change: it pins a source line into runtime-bridge.ts, and the fix shifted #L973#L975. This was not assumed pre-existing — the base versions were checked out and docs:api-reference:check reported "current (43 files)", confirming the staleness came from this change. Regenerated with deno task docs on Deno 2.7.7 (the CI-pinned version), never hand-edited.

Known adjacent issue, deliberately not fixed here

tool-call parts drop providerExecuted on the same path (runtime-bridge.ts:568-573), while both providers set it on the matching tool-call part and the streamed path carries it. Same asymmetry, adjacent lines. Whether RuntimeGenerateTextResult["toolCalls"] should carry it depends on whether any consumer reads it, which hasn't been investigated — so it is flagged rather than swept in.

Summary by CodeRabbit

  • Bug Fixes

    • Preserved provider execution status for tool results during direct generation.
    • Tool results now retain this status consistently for both successful and errored outcomes, including associated metadata and error indicators.
    • Added validation support for the optional execution-status field.
  • Documentation

    • Corrected the source-code link for the similarity function in the API reference.

…sults

Anthropic's buildAnthropicGenerateResult and OpenAI's Responses
normalizer both return doGenerate content parts carrying
providerExecuted: true for server-side tools. The streamed result
builder propagates that flag; buildDirectGenerateResult does not.

This test drives the non-streamed path with that provider shape and
fails, proving the flag is lost before it reaches the agent loop.
buildDirectGenerateResult dropped providerExecuted while the streamed
builder propagated it, so the agent loop saw undefined for every
provider-executed tool result on the doGenerate path.

That cost more than telemetry: persistGeneratedToolResult passes the
flag to createToolResultMessage, so a genuinely provider-executed
result was persisted into conversation history as not provider-executed.

Propagate the flag with the same conditional spread the streamed
builder uses, so it stays omitted rather than set to false.
@kojiwakayama
kojiwakayama requested a review from kwakayama as a code owner August 7, 2026 03:24
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 67bc6695-ea97-48c9-94c9-3d54f209c1c4

📥 Commits

Reviewing files that changed from the base of the PR and between 1e894d9 and 63c772b.

📒 Files selected for processing (3)
  • docs/api-reference/veryfront/embedding.md
  • src/runtime/runtime-bridge.test.ts
  • src/runtime/runtime-bridge.ts

📝 Walkthrough

Walkthrough

The runtime bridge now accepts and preserves providerExecuted on direct tool results. Tests cover successful and errored provider-tool results. The embedding API documentation updates the similarity source reference.

Changes

Provider execution metadata

Layer / File(s) Summary
Validate and preserve provider execution metadata
src/runtime/runtime-bridge.ts, src/runtime/runtime-bridge.test.ts
Direct tool-result validation accepts the optional providerExecuted flag. Direct generation preserves the flag for successful and errored results.
Update API source reference
docs/api-reference/veryfront/embedding.md
The similarity source link points to runtime line 975.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers: kwakayama

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the runtime fix that preserves providerExecuted on non-streamed tool results.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/provider-executed-direct-generate

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

@kwakayama
kwakayama enabled auto-merge August 7, 2026 03:32
@kwakayama
kwakayama added this pull request to the merge queue Aug 7, 2026
Merged via the queue into main with commit e0ec3a2 Aug 7, 2026
31 checks passed
@kwakayama
kwakayama deleted the fix/provider-executed-direct-generate branch August 7, 2026 03:52
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