Skip to content

feat(agents,models): surface all AI SDK data through funkai#61

Merged
zrosenbauer merged 18 commits intomainfrom
feat/surface-ai-sdk-data
Mar 25, 2026
Merged

feat(agents,models): surface all AI SDK data through funkai#61
zrosenbauer merged 18 commits intomainfrom
feat/surface-ai-sdk-data

Conversation

@zrosenbauer
Copy link
Member

Summary

Full parity with Vercel AI SDK — funkai no longer eats data.

  • GenerateResult/StreamResult now extend AI SDK's GenerateTextResult/StreamTextResult directly, exposing toolCalls, toolResults, reasoning, sources, files, steps, warnings, providerMetadata, and all other fields for free
  • TokenUsage removed — replaced with AI SDK's LanguageModelUsage (nested inputTokenDetails/outputTokenDetails), mirroring their design for provider-specific breakdowns
  • messages field removed — consumers use result.response.messages (AI SDK default)
  • Message type alias removed — use ModelMessage from ai directly
  • BaseGenerateResult/BaseStreamResult created as shared contracts for flow agents (which can't provide full AI SDK fields)
  • onStepStart wired — was accepted on config but never fired for agent tool-loop steps
  • All AI SDK input params added: toolChoice, providerOptions, activeTools, prepareStep, repairToolCall, headers, experimental_include, experimental_context, experimental_download, onToolCallStart, onToolCallFinish
  • formatGenerateResult helper centralizes result construction with oxlint-disable for required casts

Breaking Changes

Before After
result.messages result.response.messages
import { Message } import { ModelMessage } from 'ai'
import { TokenUsage } import { LanguageModelUsage } from 'ai'
usage.cacheReadTokens usage.inputTokenDetails?.cacheReadTokens
usage.reasoningTokens usage.outputTokenDetails?.reasoningTokens
onStepStart (never fired) onStepStart fires for agent tool-loop steps

New fields on GenerateResult (from AI SDK)

toolCalls, toolResults, staticToolCalls, dynamicToolCalls, staticToolResults, dynamicToolResults, reasoning, reasoningText, sources, files, steps, content, warnings, request, response, providerMetadata, rawFinishReason

Test plan

  • pnpm typecheck passes
  • pnpm build passes
  • pnpm test --filter=@funkai/models passes
  • pnpm test --filter=@funkai/agents passes
  • pnpm lint passes
  • Verify result.toolCalls, result.steps, result.reasoning present on .generate() return
  • Verify onStepStart fires for agent tool-loop steps
  • Verify flow agent results work with BaseGenerateResult

BREAKING CHANGE: Full parity with Vercel AI SDK — funkai no longer eats data.

- GenerateResult/StreamResult now extend AI SDK's GenerateTextResult/StreamTextResult
  directly, exposing toolCalls, toolResults, reasoning, sources, files, steps,
  warnings, providerMetadata, and all other fields
- Replace custom TokenUsage with AI SDK's LanguageModelUsage (nested
  inputTokenDetails/outputTokenDetails)
- Remove promoted `messages` field — use `result.response.messages` instead
- Remove `Message` type alias — use `ModelMessage` from ai SDK
- Add BaseGenerateResult/BaseStreamResult shared contracts for flow agents
- Wire onStepStart hook for agent tool-loop steps
- Add all AI SDK input params: toolChoice, providerOptions, activeTools,
  prepareStep, repairToolCall, headers, experimental_include,
  experimental_context, experimental_download, onToolCallStart, onToolCallFinish
- Add formatGenerateResult helper to centralize result construction

Co-Authored-By: Claude <noreply@anthropic.com>
@changeset-bot
Copy link

changeset-bot bot commented Mar 25, 2026

🦋 Changeset detected

Latest commit: 4fc0627

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@funkai/agents Minor
@funkai/models Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link

vercel bot commented Mar 25, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
funkai Ignored Ignored Preview Mar 25, 2026 10:12pm

Request Review

@coderabbitai
Copy link

coderabbitai bot commented Mar 25, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Unifies agent/model types to AI SDK types (ModelMessage, LanguageModelUsage), introduces BaseGenerateResult/BaseStreamResult (removes messages), adds per-step onStepStart and aiSdkParams passthrough, refactors token usage to nested input/output detail objects, and removes example debug message-count logs.

Changes

Cohort / File(s) Summary
Linting config
/.oxlintrc.json
Disabled unicorn/prefer-ternary and oxc/no-optional-chaining globally; removed per-test override.
Examples
examples/basic-agent/src/index.ts, examples/streaming/src/index.ts
Removed debug logs that printed message counts and an unused Message import.
Public exports
packages/agents/src/index.ts, packages/models/src/index.ts, packages/models/src/provider/index.ts
Reworked exported types: removed Message/TokenUsage; added ModelMessage, BaseGenerateResult, BaseStreamResult, and LanguageModelUsage.
Core agent types & API
packages/agents/src/core/agents/types.ts, packages/agents/src/core/provider/types.ts
Added BaseGenerateResult/BaseStreamResult; agent defaults now use ModelMessage[]; replaced local TokenUsage with SDK LanguageModelUsage; added ai-sdk passthrough fields and tool lifecycle callbacks.
Base agent impl & tests
packages/agents/src/core/agents/base/agent.ts, packages/agents/src/core/agents/base/agent.test.ts
PreparedGeneration computes aiSdkParams; added onStepStart; generate/stream delegate to SDK-shaped results via format helpers; removed aggregated messages handling; tests updated for SDK-shaped usage and removed messages assertions; added gated promises and suppression helpers.
Prompt utils & tests
packages/agents/src/core/agents/base/utils.ts, packages/agents/src/core/agents/base/utils.test.ts
buildPrompt and prompt-return typing now use ModelMessage[]; removed toTokenUsage and its tests.
Flow agent, steps & tests
packages/agents/src/core/agents/flow/..., packages/agents/src/core/agents/flow/steps/*
Flow internals now use ModelMessage/LanguageModelUsage; removed messages from generated/stream results; stream returns BaseStreamResult; token aggregation rewired to nested input/output details; extensive test adjustments.
Flow messages helpers
packages/agents/src/core/agents/flow/messages.ts
Message constructors and collectors now produce/consume ModelMessage.
Step factory/result types
packages/agents/src/core/agents/flow/steps/factory.ts, packages/agents/src/core/agents/flow/steps/result.ts
Step result typing changed to use BaseGenerateResult/LanguageModelUsage; messages removed from step propagation.
Token usage aggregation & cost
packages/agents/src/core/provider/usage.ts, packages/agents/src/core/provider/usage.test.ts, packages/models/src/cost/calculate.ts, packages/models/src/cost/calculate.test.ts
Replaced flat token counters with nested inputTokenDetails/outputTokenDetails; aggregation and cost functions now consume/produce LanguageModelUsage; tests updated.
Tracing & context
packages/agents/src/lib/context.ts, packages/agents/src/lib/trace.ts, packages/agents/src/lib/trace.test.ts
Context and trace usage typing switched to ModelMessage[] / LanguageModelUsage; collectors and tests adjusted.
Integration & lifecycle tests
packages/agents/src/integration/lifecycle.test.ts
Integration tests adjusted to assert forwarded onStepStart events and updated PromiseLike rejection-suppression patterns.
Prompts registry
packages/prompts/src/registry.ts
deepFreeze() now treats only plain objects as namespace nodes to avoid deep-freezing non-plain objects.
Utility helpers
packages/agents/src/utils/promise.ts
Added gatePromise and suppressRejection helpers to gate promise resolution and safely suppress PromiseLike rejections.
Minor/formatting/tests
many *.test.ts
Numerous tests tightened to strict boolean assertions (`toBe(true
Changesets & metadata
.changeset/*.md
Added changesets documenting surfaced AI SDK data and type/export changes.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client
  participant Agent as Agent
  participant Flow as FlowAgent
  participant SDK as AI SDK
  participant Tool as Tool

  Client->>Agent: generate(params + aiSdkParams)
  Agent->>Flow: prepareExecution(params, aiSdkParams)
  Flow->>Agent: emit onStepStart(stepId, stepOperation)
  Agent->>SDK: generateText / streamText(ModelMessage[], aiSdkParams)
  SDK->>Agent: step updates / final (output, usage, finishReason, steps)
  alt step invokes tool
    SDK->>Tool: tool call
    Tool-->>SDK: tool result
    SDK->>Agent: step update (toolCall/toolResult)
  end
  Agent->>Client: return BaseGenerateResult / BaseStreamResult (output, usage, finishReason)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 65.79% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main objective: surfacing all AI SDK data through funkai by removing internal abstractions and extending SDK result types.
Description check ✅ Passed The description comprehensively documents the changes, breaking changes, new fields, and test requirements, directly relating to the changeset.

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


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

- Update test assertions for LanguageModelUsage nested shape (inputTokenDetails/outputTokenDetails)
- Remove messages field from test mocks and assertions (field removed in parent commit)
- Add onStepStart events to lifecycle test expectations (now fires for AI SDK tool-loop steps)
- Fix PromiseLike .then(undefined, fn) to Promise.resolve().catch() for lint compliance
- Align aggregateTokens with sumTokenUsages: return undefined for zero detail fields
- Fix duplicate 'ai' import and remove unused BaseGenerateResult import in flow-agent.ts

Co-Authored-By: Claude <noreply@anthropic.com>
All three `as any` / `as unknown as` casts were unnecessary — the types
are fully compatible without casting:

- formatGenerateResult: spread + output override satisfies GenerateResult
- streamResult construction: spread + overrides satisfies StreamResult
- synthetic GenerateTextResult for onFinish: add explicit experimental_output
  field instead of casting from StepResult

Co-Authored-By: Claude <noreply@anthropic.com>
…kBy, allow optional chaining

- Improve AIOutput type alias comment: explain the AI SDK namespace+interface
  merge that prevents `import type { Output }` from working as a type param
- Replace custom omitNil helper with es-toolkit's pickBy(obj, isNotNil)
- Allow optional chaining in lint config (oxc/no-optional-chaining: off)

Co-Authored-By: Claude <noreply@anthropic.com>
@zrosenbauer zrosenbauer marked this pull request as ready for review March 25, 2026 20:53
Replace ugly inline casts (`Parameters<NonNullable<...>>` and multi-line
config.onFinish casts) with proper type aliases:

- `OnFinishHook<TInput, TOutput>` unifies the discriminated union's
  two `onFinish` callback signatures
- `FlowGenerateParams` carries `TOutput` through `GenerateParams` so
  `params.onFinish` sees `BaseGenerateResult<TOutput>` not `<string>`
- `BaseGenerateResult` now uses `Pick` from AI SDK types (DRY)
- `GenerateResult`/`StreamResult` directly override `output` field

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/agents/src/core/agents/flow/steps/factory.ts (1)

236-243: ⚠️ Potential issue | 🟠 Major

Preserve the outer $.agent() duration in onStepFinish.

lastAIStep.current is already a StepFinishEvent, so ...aiFields still includes framework-owned fields. At minimum, Line 242 can overwrite the outer duration with the duration of the last nested AI step, which makes flow onStepFinish timings wrong whenever the delegated agent runs multiple internal steps. Drop duration from the forwarded extras before merging them into the outer event.

Suggested fix
       buildFinishEventExtras: () => {
         // Spread AI SDK fields from the last agent tool-loop step
         if (isNotNil(lastAIStep.current)) {
-          const { stepId: _sid, stepOperation: _sop, ...aiFields } = lastAIStep.current;
+          const {
+            stepId: _sid,
+            stepOperation: _sop,
+            duration: _duration,
+            ...aiFields
+          } = lastAIStep.current;
           return aiFields;
         }
         return {};
       },
Based on learnings: `packages/agents/docs/**/*.md`: execution order for `$.agent()` is step hooks + flow agent step hooks (base `step.onStart` → flow `onStepStart` → execute → step `onFinish` → flow `onStepFinish`).

Also applies to: 412-416

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/agents/src/core/agents/flow/steps/factory.ts` around lines 236 -
243, The onStepFinish handler is merging nested agent finish data
(lastAIStep.current) into the outer StepFinishEvent and inadvertently overwrites
the outer duration; before spreading extras (aiFields/lastAIStep.current) into
finishEvent (the object created in onStepFinish), remove the duration field from
the forwarded extras so the outer $.agent() duration remains intact—i.e., take
extras (or lastAIStep.current), delete or omit its duration property, then
spread the sanitized extras into finishEvent (apply the same change where
similar merging occurs around the other referenced block at the 412-416
pattern).
packages/agents/src/core/agents/flow/types.ts (1)

342-355: ⚠️ Potential issue | 🟡 Minor

Fix the exported stream() docs.

Line 350 still documents StreamResult, but Line 354 now returns BaseStreamResult<TOutput>. The JSDoc is still advertising the old stream surface after the breaking type change.

Proposed fix
-   * `@returns` A `Result` wrapping the `StreamResult`.
+   * `@returns` A `Result` wrapping the `BaseStreamResult`.

As per coding guidelines, "Breaking changes without proper documentation" should be flagged.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/agents/src/core/agents/flow/types.ts` around lines 342 - 355, The
JSDoc for the exported method stream() is outdated: it still mentions
StreamResult while the signature returns BaseStreamResult<TOutput>; update the
doc comment above the stream( params: GenerateParams<...> ):
Promise<Result<BaseStreamResult<TOutput>>> declaration to reference
BaseStreamResult<TOutput> (and adjust any descriptive text about the stream
surface/events to match the new BaseStreamResult shape), ensuring consistency
between the documented return type and the actual signature for the stream()
method.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/agents/src/core/agents/base/agent.ts`:
- Around line 536-547: The code currently uses a non-null assertion
steps.at(-1)! when building generateResult which will throw if steps is empty;
update the block that awaits aiResult.steps (symbol: aiResult.steps) to handle
an empty array defensively by deriving a safe lastStep (e.g., const last =
steps.length ? steps.at(-1) : { /* minimal fallback shape matching step type */
}), then pass last (not a non-null asserted value) into formatGenerateResult
along with totalUsage/ output; ensure the fallback provides the required fields
expected by formatGenerateResult so aborted or zero-step cases do not throw.
- Around line 286-296: Replace the ternary used to assign onStepStart with an
explicit if/else block: check isNotNil(mergedOnStepStart) and if true assign
onStepStart to the async function that builds the StepStartEvent (using stepId =
`${config.name}:${stepStartCounter.value++}`, stepOperation "agent", and
agentChain currentChain) and awaits mergedOnStepStart(event); otherwise assign
onStepStart = undefined; keep the same referenced symbols (onStepStart,
mergedOnStepStart, isNotNil, StepStartEvent, stepStartCounter, config.name,
currentChain) so behavior is unchanged but the ternary lint rule is satisfied.

In `@packages/agents/src/core/agents/flow/steps/agent.test.ts`:
- Around line 18-21: MOCK_USAGE used in mockAgent doesn't match the AI SDK's
LanguageModelUsage shape — tests currently use flat fields like
cacheReadTokens/cacheWriteTokens/reasoningTokens and then cast to GenerateResult
in mockAgent; update MOCK_USAGE to use the nested inputTokenDetails and
outputTokenDetails objects with the correct properties (e.g., tokens,
promptTokens, completionTokens or whatever the SDK defines) so that the
Result<GenerateResult> created in mockAgent has a properly shaped usage without
relying on a type cast; adjust the MOCK_USAGE constant and ensure mockAgent (and
any tests referencing MOCK_USAGE) construct usage as LanguageModelUsage-like
nested objects rather than flat token fields.

In `@packages/agents/src/core/agents/types.ts`:
- Around line 16-17: The file contains unused imports UIMessage and
UIMessageStreamOptions in packages/agents/src/core/agents/types.ts; remove those
named imports from the import list so only used symbols remain (e.g., delete
UIMessage and UIMessageStreamOptions entries) and run the type checker to ensure
no other references to UIMessage or UIMessageStreamOptions exist in that module.

In `@packages/agents/src/core/provider/usage.ts`:
- Around line 53-55: The usage() function currently takes TokenUsageRecord[] but
must accept the broader LanguageModelUsage[] produced by collectUsages(); change
the usage(records: TokenUsageRecord[]) signature to usage(records:
LanguageModelUsage[]) and update any internal typings to treat each item as
LanguageModelUsage (only reading token counts), while leaving usageByAgent() and
usageByModel() signatures narrow (TokenUsageRecord[]) unchanged; also apply the
same widening to the other occurrence referenced around lines 128-135 so callers
like collectUsages(result.trace) type-check without changes.

In `@packages/models/src/cost/calculate.ts`:
- Around line 42-49: The calculateCost function double-counts cached input and
reasoning output tokens by charging both the aggregate inputTokens/outputTokens
and the individual cacheRead/cacheWrite/reasoning line items; fix it in
calculateCost by using usage.inputTokenDetails.noCacheTokens (when present)
instead of usage.inputTokens for the base input cost, and using
usage.outputTokenDetails.textTokens (when present) instead of usage.outputTokens
for the base output cost, then multiply those base-only counts by
pricing.input/pricing.output and add cacheRead/cacheWrite/reasoning charges only
once (use pricing.cacheRead, pricing.cacheWrite, pricing.reasoning as before) so
total = baseInput + baseOutput + cacheRead + cacheWrite + reasoning; update
references to input/inputTokens, output/outputTokens,
inputTokenDetails.noCacheTokens, and outputTokenDetails.textTokens in the
calculateCost function.

---

Outside diff comments:
In `@packages/agents/src/core/agents/flow/steps/factory.ts`:
- Around line 236-243: The onStepFinish handler is merging nested agent finish
data (lastAIStep.current) into the outer StepFinishEvent and inadvertently
overwrites the outer duration; before spreading extras
(aiFields/lastAIStep.current) into finishEvent (the object created in
onStepFinish), remove the duration field from the forwarded extras so the outer
$.agent() duration remains intact—i.e., take extras (or lastAIStep.current),
delete or omit its duration property, then spread the sanitized extras into
finishEvent (apply the same change where similar merging occurs around the other
referenced block at the 412-416 pattern).

In `@packages/agents/src/core/agents/flow/types.ts`:
- Around line 342-355: The JSDoc for the exported method stream() is outdated:
it still mentions StreamResult while the signature returns
BaseStreamResult<TOutput>; update the doc comment above the stream( params:
GenerateParams<...> ): Promise<Result<BaseStreamResult<TOutput>>> declaration to
reference BaseStreamResult<TOutput> (and adjust any descriptive text about the
stream surface/events to match the new BaseStreamResult shape), ensuring
consistency between the documented return type and the actual signature for the
stream() method.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 050cb857-b94a-4fc0-bda6-2e37ea49a3fd

📥 Commits

Reviewing files that changed from the base of the PR and between b6ae40c and f1ec3f6.

📒 Files selected for processing (30)
  • .oxlintrc.json
  • examples/basic-agent/src/index.ts
  • examples/streaming/src/index.ts
  • packages/agents/src/core/agents/base/agent.test.ts
  • packages/agents/src/core/agents/base/agent.ts
  • packages/agents/src/core/agents/base/utils.test.ts
  • packages/agents/src/core/agents/base/utils.ts
  • packages/agents/src/core/agents/flow/flow-agent.test.ts
  • packages/agents/src/core/agents/flow/flow-agent.ts
  • packages/agents/src/core/agents/flow/messages.ts
  • packages/agents/src/core/agents/flow/steps/agent.test.ts
  • packages/agents/src/core/agents/flow/steps/agent.ts
  • packages/agents/src/core/agents/flow/steps/factory.test.ts
  • packages/agents/src/core/agents/flow/steps/factory.ts
  • packages/agents/src/core/agents/flow/steps/result.ts
  • packages/agents/src/core/agents/flow/types.ts
  • packages/agents/src/core/agents/types.ts
  • packages/agents/src/core/provider/types.ts
  • packages/agents/src/core/provider/usage.test.ts
  • packages/agents/src/core/provider/usage.ts
  • packages/agents/src/index.ts
  • packages/agents/src/integration/lifecycle.test.ts
  • packages/agents/src/lib/context.ts
  • packages/agents/src/lib/trace.test.ts
  • packages/agents/src/lib/trace.ts
  • packages/models/src/cost/calculate.test.ts
  • packages/models/src/cost/calculate.ts
  • packages/models/src/index.ts
  • packages/models/src/provider/index.ts
  • packages/models/src/provider/types.ts
💤 Files with no reviewable changes (3)
  • examples/streaming/src/index.ts
  • examples/basic-agent/src/index.ts
  • packages/models/src/provider/types.ts

- Replace hand-written BaseGenerateResult/BaseStreamResult interfaces
  with Pick-based types derived from AI SDK's GenerateTextResult and
  StreamTextResult — no more re-declaring usage/finishReason/fullStream
- GenerateResult and StreamResult extend AI SDK types directly, only
  overriding output
- Remove unused imports (UIMessage, UIMessageStreamOptions,
  AsyncIterableStream, FinishReason, LanguageModelUsage)
- Fix lint: capitalize comments, expand single-line JSDoc to multiline,
  replace ternary with if/else, use array destructuring in tests,
  replace typeof checks with es-toolkit isPlainObject in prompts

Co-Authored-By: Claude <noreply@anthropic.com>
zrosenbauer and others added 2 commits March 25, 2026 17:16
Fixed oxfmt formatting in 13 files across agents, models, and prompts packages.

Co-Authored-By: Claude <noreply@anthropic.com>
- Replace non-null assertion on steps.at(-1) with defensive null check
- Update MOCK_USAGE to use nested inputTokenDetails/outputTokenDetails
- Widen usage() and aggregateTokens() to accept LanguageModelUsage[]
- Fix double-counting in calculateCost by using noCacheTokens/textTokens

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/prompts/src/registry.ts (1)

1-71: ⚠️ Potential issue | 🟡 Minor

CI is blocked by unresolved OXFmt formatting issues.

The pipeline reports oxfmt: Formatting issue detected; please run OXFmt and commit the formatter output before merge.

As per coding guidelines, "Use OXFmt for code formatting across the project."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/prompts/src/registry.ts` around lines 1 - 71, CI failed due to OXFmt
formatting issues in this file; run the OXFmt formatter on
packages/prompts/src/registry.ts (or project root if configured), apply the
formatter changes, verify createPromptRegistry / deepFreeze / isPromptModule
formatting is updated, and commit the resulting changes so the pipeline can
pass.
packages/agents/src/core/agents/flow/steps/factory.ts (1)

387-391: ⚠️ Potential issue | 🟠 Major

Use totalUsage for delegated agents to capture multi-step tool-loop costs.

AI SDK GenerateTextResult includes both usage (final step only) and totalUsage (sum across all tool-loop steps). For nested agents that run multiple steps, returning only usage undercounts in extractStepUsage(), poisoning the flow trace's collectUsages() aggregation. Access totalUsage from the delegated agent result and pass it through, or update extractStepUsage() to prefer totalUsage when available.

As per coding guidelines, "Ensure token usage tracking is not bypassed."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/agents/src/core/agents/flow/steps/factory.ts` around lines 387 -
391, The delegated-agent branch is returning only full.usage which undercounts
multi-step tool-loop costs; update the return to prefer full.totalUsage when
present (e.g., set usage: await full.totalUsage ?? await full.usage or include a
totalUsage field) so extractStepUsage() / collectUsages() receive the aggregated
token usage for nested agents; locate the return that references full.output,
full.usage, and full.finishReason and replace the usage value (or add
totalUsage) to propagate GenerateTextResult.totalUsage for delegated agents.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.changeset/surface-ai-sdk-data-models.md:
- Line 2: Update the changeset entry for "@funkai/models": replace the current
release type "patch" with "minor" to reflect breaking public API/type changes
(e.g., removed TokenUsage export and shape migration); locate the changeset line
containing "@funkai/models": patch and change it to "@funkai/models": minor so
the pre-1.0 policy uses a minor bump for breaking changes.

In `@packages/agents/src/core/agents/base/agent.ts`:
- Around line 589-603: The current streamResult spreads aiResult, which exposes
promise fields (e.g., text, finishReason, steps, totalUsage) that will attempt
to consume the original AI SDK stream already processed by processStream();
instead, construct streamResult without spreading aiResult and rebind those
promise fields to resolve from done (e.g., text: done.then(r => r.text),
finishReason: done.then(r => r.finishReason), steps: done.then(r => r.steps),
totalUsage: done.then(r => r.totalUsage), output: done.then(r => r.output)),
while keeping fullStream and the two response helpers
(toTextStreamResponse/toUIMessageStreamResponse) delegated to aiResult as
currently implemented; retain the existing catch on streamResult.output to
suppress unhandled rejections.

---

Outside diff comments:
In `@packages/agents/src/core/agents/flow/steps/factory.ts`:
- Around line 387-391: The delegated-agent branch is returning only full.usage
which undercounts multi-step tool-loop costs; update the return to prefer
full.totalUsage when present (e.g., set usage: await full.totalUsage ?? await
full.usage or include a totalUsage field) so extractStepUsage() /
collectUsages() receive the aggregated token usage for nested agents; locate the
return that references full.output, full.usage, and full.finishReason and
replace the usage value (or add totalUsage) to propagate
GenerateTextResult.totalUsage for delegated agents.

In `@packages/prompts/src/registry.ts`:
- Around line 1-71: CI failed due to OXFmt formatting issues in this file; run
the OXFmt formatter on packages/prompts/src/registry.ts (or project root if
configured), apply the formatter changes, verify createPromptRegistry /
deepFreeze / isPromptModule formatting is updated, and commit the resulting
changes so the pipeline can pass.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d1167e1a-a843-4559-b6da-15c950d88bbe

📥 Commits

Reviewing files that changed from the base of the PR and between f1ec3f6 and 57001eb.

📒 Files selected for processing (9)
  • .changeset/surface-ai-sdk-data-agents.md
  • .changeset/surface-ai-sdk-data-models.md
  • packages/agents/src/core/agents/base/agent.test.ts
  • packages/agents/src/core/agents/base/agent.ts
  • packages/agents/src/core/agents/flow/flow-agent.ts
  • packages/agents/src/core/agents/flow/steps/factory.test-d.ts
  • packages/agents/src/core/agents/flow/steps/factory.ts
  • packages/agents/src/core/agents/types.ts
  • packages/prompts/src/registry.ts

zrosenbauer and others added 2 commits March 25, 2026 17:32
- Replace .then(undefined, noop) with .catch(noop) for prefer-catch
- Fix capitalized comment and non-null assertion in agent.ts
- Disable unicorn/prefer-ternary (conflicts with no-ternary rule)

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
packages/models/src/cost/calculate.ts (1)

1-1: ⚠️ Potential issue | 🟡 Minor

Fix oxfmt formatting issue.

Pipeline reports formatting check failure. Run oxfmt to auto-fix.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/models/src/cost/calculate.ts` at line 1, Run the oxfmt formatter to
fix the formatting failure in the calculate.ts module: reformat the file that
contains the import of LanguageModelUsage (the line `import type {
LanguageModelUsage } from "ai";`) so it passes the pipeline check (or run oxfmt
--write on the file/changes) and commit the resulting formatting changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/agents/src/core/agents/base/agent.ts`:
- Around line 591-604: The StreamResult currently spreads ...aiResult which
leaks promise-based fields (e.g., text, finishReason, steps, usage) that will
attempt to consume aiResult.fullStream and can error if awaited after
processStream() has already consumed the stream; update the return so you do NOT
include those inherited promise fields: explicitly construct the StreamResult
with only the resolved output (output), fullStream, and the passthrough methods
(toTextStreamResponse, toUIMessageStreamResponse) from aiResult, or
alternatively extract and resolve any needed promise values inside
processStream() and attach their concrete values to the result before returning;
modify the code around StreamResult, aiResult, processStream, output and
fullStream to remove or replace the inherited promise properties to avoid
exposing consumptive promises to callers.

In `@packages/agents/src/core/agents/flow/flow-agent.test.ts`:
- Around line 758-759: The test intentionally uses .then(undefined, () => {}) on
PromiseLike values (result.usage and result.finishReason) which trips the
prefer-catch ESLint rule; add an ESLint suppression before each intentional
occurrence (e.g., an eslint-disable-next-line comment for prefer-catch)
immediately above the lines where result.usage.then(undefined, () => {}),
result.finishReason.then(undefined, () => {}), and the similar occurrences
around the other blocks (the ones near the second and third pairs) so the linter
is silenced for these deliberate PromiseLike handling cases.

---

Duplicate comments:
In `@packages/models/src/cost/calculate.ts`:
- Line 1: Run the oxfmt formatter to fix the formatting failure in the
calculate.ts module: reformat the file that contains the import of
LanguageModelUsage (the line `import type { LanguageModelUsage } from "ai";`) so
it passes the pipeline check (or run oxfmt --write on the file/changes) and
commit the resulting formatting changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a144d65a-5422-4927-bdd5-b4b8ba666e3f

📥 Commits

Reviewing files that changed from the base of the PR and between 57001eb and 165ba3b.

📒 Files selected for processing (15)
  • packages/agents/src/core/agents/base/agent.ts
  • packages/agents/src/core/agents/base/utils.test.ts
  • packages/agents/src/core/agents/base/utils.ts
  • packages/agents/src/core/agents/flow/flow-agent.test.ts
  • packages/agents/src/core/agents/flow/flow-agent.ts
  • packages/agents/src/core/agents/flow/messages.ts
  • packages/agents/src/core/agents/flow/steps/agent.test.ts
  • packages/agents/src/core/agents/flow/steps/factory.ts
  • packages/agents/src/core/agents/types.ts
  • packages/agents/src/core/provider/types.ts
  • packages/agents/src/core/provider/usage.ts
  • packages/agents/src/lib/context.ts
  • packages/models/src/cost/calculate.test.ts
  • packages/models/src/cost/calculate.ts
  • packages/prompts/src/registry.ts

- Bump @funkai/models changeset from patch to minor (breaking changes)
- Gate AI SDK stream promise fields through done signal to prevent
  race conditions with stream consumption
- Add gatePromise/suppressRejection utils for PromiseLike handling
- Replace inline .then(undefined, noop) patterns with suppressRejection()

Co-Authored-By: Claude <noreply@anthropic.com>
- Replace toBeTruthy()/toBeFalsy() with toBe(true)/toBe(false) in tests
- Capitalize continuation comments to satisfy capitalized-comments rule
- Remove unused StreamResult import from flow steps factory
- Add oxlint disable for prefer-catch where PromiseLike lacks .catch()
- Remove TODO trigger word in output.ts comment

Co-Authored-By: Claude <noreply@anthropic.com>
Use toBeInstanceOf(ReadableStream) instead of toBe(true) for response
body check.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/agents/src/core/agents/flow/flow-agent.test.ts (1)

544-546: ⚠️ Potential issue | 🔴 Critical

The error-path suppressors still use .catch() on PromiseLike fields.

These assertions were updated for the new BaseStreamResult shape, but the later cleanup blocks still call .catch() on finishReason, and Line 947 does the same for output. That no longer type-checks against the public PromiseLike contract, so the file stays red with TS2339.

🛠️ Representative fix
-    result.usage.catch(() => {});
-    result.finishReason.catch(() => {});
+    result.usage.catch(() => {});
+    // eslint-disable-next-line promise/prefer-catch -- public stream fields are PromiseLike
+    result.finishReason.then(undefined, () => {});
-    await result.output.catch(() => {});
+    // eslint-disable-next-line promise/prefer-catch -- public stream fields are PromiseLike
+    await result.output.then(undefined, () => undefined);

Also applies to: 758-759, 786-787, 829-830, 934-935, 947-947

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/agents/src/core/agents/flow/flow-agent.test.ts` around lines 544 -
546, The tests call .catch() directly on PromiseLike fields (e.g.,
result.finishReason and result.output), which doesn't type-check; update each
suppression to wrap the PromiseLike in Promise.resolve(...) before calling
.catch (for example replace result.finishReason.catch(...) and
result.output.catch(...) with Promise.resolve(result.finishReason).catch(...)
and Promise.resolve(result.output).catch(...)), or alternatively await the
PromiseLike inside an async cleanup; apply this change to all occurrences
(including the instances referenced around result.finishReason and result.output
in the test).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/agents/src/core/agents/base/agent.ts`:
- Around line 537-552: The streaming path builds the GenerateResult from
lastStep (steps.at(-1)), which causes fields like text and sources to come from
the last step instead of the accumulated aiResult, producing inconsistent
onFinish payloads; fix by using the full aiResult fields when calling
formatGenerateResult (await aiResult.output, aiResult.text, aiResult.sources,
aiResult.totalUsage, etc.) and then merge/override only the step-specific fields
you need (e.g., steps) so formatGenerateResult receives the complete result
object (reference aiResult, steps, lastStep, formatGenerateResult,
GenerateResult, onFinish).

---

Duplicate comments:
In `@packages/agents/src/core/agents/flow/flow-agent.test.ts`:
- Around line 544-546: The tests call .catch() directly on PromiseLike fields
(e.g., result.finishReason and result.output), which doesn't type-check; update
each suppression to wrap the PromiseLike in Promise.resolve(...) before calling
.catch (for example replace result.finishReason.catch(...) and
result.output.catch(...) with Promise.resolve(result.finishReason).catch(...)
and Promise.resolve(result.output).catch(...)), or alternatively await the
PromiseLike inside an async cleanup; apply this change to all occurrences
(including the instances referenced around result.finishReason and result.output
in the test).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: eb594473-c32c-433a-91c8-03b0b494fac5

📥 Commits

Reviewing files that changed from the base of the PR and between 165ba3b and bcc369b.

📒 Files selected for processing (6)
  • .oxlintrc.json
  • packages/agents/src/core/agents/base/agent.test.ts
  • packages/agents/src/core/agents/base/agent.ts
  • packages/agents/src/core/agents/flow/flow-agent.test.ts
  • packages/agents/src/core/agents/flow/flow-agent.ts
  • packages/models/src/cost/calculate.ts

The jsdoc-js/multiline-blocks oxlint rule flags single-line JSDoc
blocks on Linux CI but not macOS locally. Expand all single-line
blocks in changed files to multiline format.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/agents/src/core/agents/base/output.ts (1)

59-74: ⚠️ Potential issue | 🟡 Minor

Use Zod's public .unwrap() API instead of accessing private _zod.def.element.

The private API structure _zod.def.element is correctly understood for Zod v4.3.6, and the defensive guards with fallback error are solid. However, Zod v4 exposes a public API for accessing the element schema: arraySchema.unwrap(). The comment claiming "Zod v4 does not expose a public API" is outdated. Replace the private API access with the public method to avoid relying on internals.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/agents/src/core/agents/base/output.ts` around lines 59 - 74, Replace
the private `_zod.def.element` access with Zod's public unwrap API: call
schema.unwrap() (guarding that schema has an unwrap method) to retrieve the
element schema and pass it to Output.array({ element: ... }) instead of
def.def.element; keep the existing fallback error if unwrap is unavailable or
does not return an element schema. Ensure you reference the local variable
`schema`, the `Output.array` call, and preserve the defensive behavior when the
element schema cannot be obtained.
packages/agents/src/core/agents/flow/steps/factory.test.ts (1)

282-289: ⚠️ Potential issue | 🟡 Minor

MOCK_USAGE uses flat token fields instead of nested AI SDK structure.

This mock uses cacheReadTokens, cacheWriteTokens, reasoningTokens at the top level, but LanguageModelUsage from the AI SDK expects nested inputTokenDetails and outputTokenDetails objects. This inconsistency with agent.test.ts (which correctly uses the nested structure) may mask type mismatches.

Fix to match AI SDK LanguageModelUsage structure
 const MOCK_USAGE = {
   inputTokens: 100,
   outputTokens: 50,
   totalTokens: 150,
-  cacheReadTokens: 0,
-  cacheWriteTokens: 0,
-  reasoningTokens: 0,
+  inputTokenDetails: {
+    noCacheTokens: 100,
+    cacheReadTokens: 0,
+    cacheWriteTokens: 0,
+  },
+  outputTokenDetails: {
+    textTokens: 50,
+    reasoningTokens: 0,
+  },
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/agents/src/core/agents/flow/steps/factory.test.ts` around lines 282
- 289, MOCK_USAGE is using flat token fields but should match the AI SDK's
LanguageModelUsage nested shape; update the MOCK_USAGE constant in
factory.test.ts to use inputTokenDetails and outputTokenDetails objects
(containing token counts and fields like cacheReadTokens, cacheWriteTokens,
reasoningTokens) and totalTokens where appropriate to mirror the structure used
in agent.test.ts and the LanguageModelUsage type so tests align with the SDK
types.
♻️ Duplicate comments (1)
packages/agents/src/core/agents/base/agent.ts (1)

544-553: ⚠️ Potential issue | 🟠 Major

Streaming onFinish receives step-local text and sources instead of accumulated values.

In multi-step tool loops, the streaming path spreads lastStep (the final step only) into formatGenerateResult, so onFinish callbacks receive only that step's text and sources. The non-streaming path passes the full aiResult directly, providing accumulated values across all steps. This inconsistency breaks the contract that onFinish should receive the same result structure regardless of execution mode.

🛠️ Proposed fix
         const generateResult = formatGenerateResult<TOutput>(
           {
             ...lastStep,
+            text: await aiResult.text,
+            sources: await aiResult.sources,
             totalUsage: await aiResult.totalUsage,
             steps,
             output: await aiResult.output,
             experimental_output: await aiResult.output,
           },
           output,
         );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/agents/src/core/agents/base/agent.ts` around lines 544 - 553, The
streaming path builds generateResult by spreading lastStep which passes only the
final step's text/sources to formatGenerateResult and thus to onFinish; change
the streaming branch in the function that constructs generateResult (the code
invoking formatGenerateResult with lastStep, totalUsage, steps, output,
experimental_output) to use the accumulated aiResult fields for text/sources (or
explicitly aggregate text and sources from steps/aiResult) instead of lastStep’s
step-local values so that onFinish receives the same accumulated result
structure as the non-streaming path; update references to generateResult,
formatGenerateResult, lastStep, aiResult, steps, output, experimental_output,
and totalUsage accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/agents/src/core/agents/base/output.ts`:
- Around line 59-74: Replace the private `_zod.def.element` access with Zod's
public unwrap API: call schema.unwrap() (guarding that schema has an unwrap
method) to retrieve the element schema and pass it to Output.array({ element:
... }) instead of def.def.element; keep the existing fallback error if unwrap is
unavailable or does not return an element schema. Ensure you reference the local
variable `schema`, the `Output.array` call, and preserve the defensive behavior
when the element schema cannot be obtained.

In `@packages/agents/src/core/agents/flow/steps/factory.test.ts`:
- Around line 282-289: MOCK_USAGE is using flat token fields but should match
the AI SDK's LanguageModelUsage nested shape; update the MOCK_USAGE constant in
factory.test.ts to use inputTokenDetails and outputTokenDetails objects
(containing token counts and fields like cacheReadTokens, cacheWriteTokens,
reasoningTokens) and totalTokens where appropriate to mirror the structure used
in agent.test.ts and the LanguageModelUsage type so tests align with the SDK
types.

---

Duplicate comments:
In `@packages/agents/src/core/agents/base/agent.ts`:
- Around line 544-553: The streaming path builds generateResult by spreading
lastStep which passes only the final step's text/sources to formatGenerateResult
and thus to onFinish; change the streaming branch in the function that
constructs generateResult (the code invoking formatGenerateResult with lastStep,
totalUsage, steps, output, experimental_output) to use the accumulated aiResult
fields for text/sources (or explicitly aggregate text and sources from
steps/aiResult) instead of lastStep’s step-local values so that onFinish
receives the same accumulated result structure as the non-streaming path; update
references to generateResult, formatGenerateResult, lastStep, aiResult, steps,
output, experimental_output, and totalUsage accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e089cea1-8cd3-4c81-8225-3c99cbfc2f69

📥 Commits

Reviewing files that changed from the base of the PR and between bcc369b and 4c29c04.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (35)
  • .changeset/surface-ai-sdk-data-models.md
  • package.json
  • packages/agents/package.json
  • packages/agents/src/core/agents/base/agent.test.ts
  • packages/agents/src/core/agents/base/agent.ts
  • packages/agents/src/core/agents/base/output.ts
  • packages/agents/src/core/agents/evolve.test.ts
  • packages/agents/src/core/agents/flow/engine.test.ts
  • packages/agents/src/core/agents/flow/flow-agent.test.ts
  • packages/agents/src/core/agents/flow/flow-agent.ts
  • packages/agents/src/core/agents/flow/messages.test.ts
  • packages/agents/src/core/agents/flow/steps/agent.test.ts
  • packages/agents/src/core/agents/flow/steps/all.test.ts
  • packages/agents/src/core/agents/flow/steps/each.test.ts
  • packages/agents/src/core/agents/flow/steps/factory.test.ts
  • packages/agents/src/core/agents/flow/steps/factory.ts
  • packages/agents/src/core/agents/flow/steps/map.test.ts
  • packages/agents/src/core/agents/flow/steps/race.test.ts
  • packages/agents/src/core/agents/flow/steps/reduce.test.ts
  • packages/agents/src/core/agents/flow/steps/while.test.ts
  • packages/agents/src/core/agents/flow/stream-response.test.ts
  • packages/agents/src/lib/context.test.ts
  • packages/agents/src/lib/runnable.test.ts
  • packages/agents/src/lib/trace.test.ts
  • packages/agents/src/utils/promise.ts
  • packages/agents/src/utils/result.test.ts
  • packages/agents/src/utils/zod.test.ts
  • packages/cli/package.json
  • packages/cli/src/lib/prompts/__tests__/lint.test.ts
  • packages/models/package.json
  • packages/models/src/catalog/index.test.ts
  • packages/prompts/package.json
  • packages/prompts/src/partials-dir.test.ts
  • packages/prompts/src/registry.test.ts
  • pnpm-workspace.yaml

zrosenbauer and others added 2 commits March 25, 2026 18:10
Co-Authored-By: Claude Code <noreply@anthropic.com>
The rule triggers on CI (Linux) but not locally (macOS) due to
platform differences in oxlint's JS plugin runner. Downgrade to
warning to unblock CI.

Co-Authored-By: Claude Code <noreply@anthropic.com>
@zrosenbauer zrosenbauer merged commit 6528121 into main Mar 25, 2026
4 checks passed
@zrosenbauer zrosenbauer deleted the feat/surface-ai-sdk-data branch March 25, 2026 22:13
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.

1 participant