Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# Port plan — Zoo PR #555 → `feature/zoo-555-add-fable-5-support-across-anthropic-providers`

> **For the executor (read first).** Do the steps in order. Do not improvise or
> refactor beyond what is written (YAGNI). Every code block is already adapted to
> this repo. This repo is **Tumble Code**: never introduce the strings "Roo" or
> "Zoo" in user-facing text. The model id `claude-fable-5` is an **internal model
> id**, not branding — keep it verbatim.

---

## 0. Context

- **Upstream:** Zoo PR #555 — "Add Fable 5 support across Anthropic providers" (commit `cc2654521`).
- **What it does:** Registers the `claude-fable-5` model across every Anthropic-family
provider path — direct Anthropic, Bedrock, Vertex, OpenRouter, Requesty, and the
Vercel AI Gateway — including model metadata, the adaptive-thinking guard, and the
`supportsTemperature: false` plumbing so temperature is omitted for this model.
- **Model facts (mirror upstream, reviewed in PR #555):** context window `1_000_000`;
direct Anthropic `maxTokens` `128_000` (overridden to 8k when reasoning effort is
off); Bedrock/Vertex `maxTokens` `8192`; prices in=$10 / out=$50 / cacheWrite=$12.5 /
cacheRead=$1 per M; `supportsImages`, `supportsPromptCache`, `supportsReasoningBudget`,
`supportsReasoningBinary` all true; `supportsTemperature: false`. Uses the same
adaptive-thinking contract as Opus 4.7/4.8.
- **Why we want it:** new top-tier Claude model; our providers have no `claude-fable-5`
entry yet (verified: `grep claude-fable-5` empty across `packages/types/src/providers/*`
and `src/api/providers/*`). Low risk, high value — matches our model-support cadence.
- **Adaptations vs. the raw upstream diff (IMPORTANT):**
1. **Branding:** upstream edits a bedrock comment to say "Zoo Code UI"; our fork already
reads `// display: "summarized" surfaces thinking content in the UI.`
([bedrock.ts:439](../src/api/providers/bedrock.ts#L439)). Keep "the UI" — do NOT
introduce "Zoo Code". The Fable-5 mentions in the doc comment are optional polish.
2. **model-params.ts:** upstream adds the `supportsTemperature === false` guard to the
generic **`else`** branch. Our `format === "anthropic"` branch already has that guard
([model-params.ts:151](../src/api/transform/model-params.ts#L151)); the `else` branch
(our lines 179-187) does NOT. Apply the guard to the `else` branch only.
3. Dependencies `getAnthropicProviderReasoning` / `AnthropicProviderReasoningParams`
already exist in [reasoning.ts:61](../src/api/transform/reasoning.ts#L61) — the
requesty refactor applies cleanly.
- **Original authors — credit them:**

```text
Co-authored-by: T <taltas@users.noreply.github.com>
Co-authored-by: Elliott de Launay <edelauna@gmail.com>
```

## 1. Preconditions

- [ ] Branch `feature/zoo-555-...` created off `main`.
- [ ] No `claude-fable-5` entry exists yet in any provider type/handler.
- [ ] `getAnthropicProviderReasoning` exported from `src/api/transform/reasoning.ts`.

## 2. Source edits

### Edit A — `packages/types/src/providers/anthropic.ts`
Insert a `"claude-fable-5"` entry into `anthropicModels` (before `claude-opus-4-5-20251101`):
maxTokens 128_000, contextWindow 1_000_000, images/cache true, in 10 / out 50 /
cacheWrite 12.5 / cacheRead 1, supportsReasoningBudget/Binary true, supportsTemperature
false, with the upstream description.

### Edit B — `packages/types/src/providers/bedrock.ts`
- Add `"anthropic.claude-fable-5"` to `bedrockModels` (maxTokens 8192, contextWindow 1M,
cache true + `minTokensPerCachePoint`/`maxCachePoints`/`cachableFields`, same prices,
reasoning flags, supportsTemperature false, description).
- Append `"anthropic.claude-fable-5"` to `BEDROCK_GLOBAL_INFERENCE_MODEL_IDS` with the
cross-region comment line.

### Edit C — `packages/types/src/providers/vertex.ts`
Add `"claude-fable-5"` to `vertexModels` (maxTokens 8192, contextWindow 1M, etc.).

### Edit D — `packages/types/src/providers/openrouter.ts`
Add `"anthropic/claude-fable-5"` to both `OPEN_ROUTER_PROMPT_CACHING_MODELS` and
`OPEN_ROUTER_REASONING_BUDGET_MODELS`.

### Edit E — `packages/types/src/providers/vercel-ai-gateway.ts`
Add `"anthropic/claude-fable-5"` to both `VERCEL_AI_GATEWAY_PROMPT_CACHING_MODELS` and
`VERCEL_AI_GATEWAY_VISION_AND_TOOLS_MODELS`.

### Edit F — `src/api/providers/anthropic.ts`
Add `case "claude-fable-5":` to the two switch statements (after `claude-opus-4-8`).

### Edit G — `src/api/providers/bedrock.ts`
Add `baseModelId.includes("fable-5") ||` to the `isAdaptiveThinkingModel` guard. Optional:
mention Fable 5 in the adjacent doc comments — but keep "the UI" wording (no "Zoo Code").

### Edit H — `src/api/providers/requesty.ts`
Swap import to `{ AnthropicProviderReasoningParams, getAnthropicProviderReasoning }`,
change the two `thinking?: AnthropicReasoningParams` to `AnthropicProviderReasoningParams`,
and in `getModel()` compute `reasoning = getAnthropicProviderReasoning({ model: info,
reasoningBudget: params.reasoningBudget, settings: this.options })` and return
`{ id, info, ...params, reasoning }`.

### Edit I — `src/api/providers/vercel-ai-gateway.ts`
Gate temperature on `info.supportsTemperature !== false && this.supportsTemperature(modelId)`
in both `createMessage` and `completePrompt`.

### Edit J — `src/api/providers/fetchers/openrouter.ts`
Add the `anthropic/claude-fable-5` block setting maxTokens + reasoningBinary + temperature.

### Edit K — `src/api/providers/fetchers/requesty.ts`
Add the `anthropic/claude-fable-5` override block (reasoning flags + supportsTemperature false).

### Edit L — `src/api/providers/fetchers/vercel-ai-gateway.ts`
Add the `anthropic/claude-fable-5` → `supportsTemperature = false` block.

### Edit M — `src/api/transform/model-params.ts`
In the **`else`** branch only, prepend `if (model.supportsTemperature === false) {
params.temperature = undefined }` and drop the stale 2-line OpenRouter TODO.

### Edit N — `src/api/providers/anthropic-vertex.ts` (fork-specific; NOT in Zoo's diff)
Zoo's PR did not touch this file because Zoo's Vertex handler already routed
adaptive-binary models through `getAnthropicProviderReasoning` (from an earlier PR).
**Our fork never got that change** — our `getModel()` returned `params.reasoning`
from `getModelParams` (`getAnthropicReasoning`), which always emits
`{ type: "enabled", budget_tokens }`. So Fable 5 (and Opus 4.7/4.8) on Vertex sent
the wrong thinking config. Root cause proven via the new vertex adaptive test failing
with `{ type: "enabled", budget_tokens: 8192 }` instead of `{ type: "adaptive" }`.
Fix: import `getAnthropicProviderReasoning`; in `getModel()` compute
`thinking = getAnthropicProviderReasoning({ model: info, reasoningBudget:
params.reasoningBudget, reasoningEffort: params.reasoningEffort, settings: this.options })`
and return `{ ...params, reasoning: thinking }`. Because `{ type: "adaptive" }` is not in
the SDK's `ThinkingConfigParam` union, change the `createMessage`/`completePrompt` `params`
declarations from a `: Anthropic.Messages.MessageCreateParams…` annotation to an
`as Anthropic.Messages.MessageCreateParams…` cast (mirrors `AnthropicHandler`).

## 3. Tests (port from upstream, adapt anchors)

Add the Fable-5 cases to: `anthropic.spec.ts`, `anthropic-vertex.spec.ts`, `bedrock.spec.ts`,
`requesty.spec.ts`, `vercel-ai-gateway.spec.ts`, `fetchers/__tests__/openrouter.spec.ts`,
`fetchers/__tests__/vercel-ai-gateway.spec.ts`, `transform/__tests__/model-params.spec.ts`,
`shared/__tests__/api.spec.ts`, and create new `fetchers/__tests__/requesty.spec.ts`.

## 4. Out of scope
- No "Zoo Code" branding. No TTS/router/cloud. Internal id `claude-fable-5` stays.

## 5. Verify
- `pnpm --filter @roo-code/types check-types` clean.
- `cd src && npx vitest run api/providers/__tests__/anthropic.spec.ts api/providers/__tests__/bedrock.spec.ts api/providers/__tests__/anthropic-vertex.spec.ts api/providers/__tests__/requesty.spec.ts api/providers/__tests__/vercel-ai-gateway.spec.ts api/providers/fetchers/__tests__/openrouter.spec.ts api/providers/fetchers/__tests__/requesty.spec.ts api/providers/fetchers/__tests__/vercel-ai-gateway.spec.ts api/transform/__tests__/model-params.spec.ts shared/__tests__/api.spec.ts` all green.

## 6. Acceptance
- [ ] All new Fable-5 tests pass; touched suites green.
- [ ] No "Roo"/"Zoo" user-facing strings introduced.

## 7. Record
```bash
node .claude/skills/zoo-port/scripts/zoo-prs.mjs record --pr 555 --status ported \
--branch feature/zoo-555-add-fable-5-support-across-anthropic-providers \
--plan ai_plans/2026-06-17_zoo-555-add-fable-5-support-across-anthropic-providers.md
```
156 changes: 156 additions & 0 deletions ai_plans/2026-06-17_zoo-588-extract-reasoning-from-delta-helper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# Port plan — Zoo PR #588 → `feature/zoo-588-extract-reasoning-from-delta-helper`

> **For the executor (read first).** Do the steps in order. Do not improvise or
> refactor beyond what is written (YAGNI). Every code block is already adapted to
> this repo. This repo is **Tumble Code**: never introduce the strings "Roo" or
> "Zoo" in user-facing text or test names.

---

## 0. Context

- **Upstream:** Zoo PR #588 — "use extractReasoningFromDelta helper for reasoning
streams" (commit `6daa153ac`, merged 2026-06-15).
- **What it does:** Replaces the duplicated inline `reasoning_content` extraction
block in each OpenAI-compatible streaming provider with the shared
`extractReasoningFromDelta(delta)` helper. The helper prefers
`delta.reasoning_content` (DeepSeek-R1 / QwQ style) and **falls back to
`delta.reasoning`** (OpenRouter style) — so each provider gains the
router-style fallback for free. Adds streaming coverage for the new paths.
- **Why we want it:** removes copy-pasted logic, gives every provider the
`reasoning` fallback, and centralizes one well-tested helper. Low risk —
behavior-preserving for the existing `reasoning_content` path.

- **Adaptations vs. the raw upstream diff (IMPORTANT):**

1. **Helper already exists here.** `src/api/providers/utils/extract-reasoning.ts`
is already present in our fork (and already used by
`base-openai-compatible-provider.ts` and `lite-llm.ts`). Its semantics match
Zoo's (`reasoning_content` first, then `reasoning`). **Do NOT recreate it.**
The port is purely: import it into each surviving provider and swap the
inline block.
2. **Two of Zoo's seven providers do not exist in our fork.** `git ls-files`
shows no `src/api/providers/mimo.ts` and no `src/api/providers/opencode-go.ts`.
**Skip both** — only port the 5 that exist: deepseek, openai, qwen-code,
requesty, unbound.
3. **`requesty.ts` already imports from `../transform/reasoning`** (touched by
the #555 port). Add the `extract-reasoning` import as a separate line next to
the other `./utils/` imports; do not disturb the reasoning import.
4. **No `unbound.spec.ts` in our fork.** Zoo adds 3 streaming tests to it, but
our fork has no unbound test file and no OpenAI mock harness for one.
**Scope cut (YAGNI):** apply the `unbound.ts` production refactor, but do NOT
create a brand-new test file just for these 3 tests. The helper itself is
already covered by `utils/__tests__/extract-reasoning.spec.ts`, and the
refactor is behavior-preserving. Document the cut here so it isn't mistaken
for an omission.
5. **Test name branding.** Zoo's unbound spec has an `it("identifies itself as
Zoo Code …")` anchor — irrelevant here since we skip that file. Do not
introduce "Zoo Code" anywhere.

- **Original authors — credit them:**

```text
Co-authored-by: dw <41457565+daewoongoh@users.noreply.github.com>
Co-authored-by: Oh Daewoong <dw.oh@samsung.com>
```

## 1. Preconditions

- [ ] Branch `feature/zoo-588-extract-reasoning-from-delta-helper` created off the
`feature/zoo-555-…` branch (stacked — files do not overlap with #555 except
`requesty.ts`, which #555 already touched, so stacking avoids a conflict).
- [ ] `src/api/providers/utils/extract-reasoning.ts` exports
`extractReasoningFromDelta` (verify; it already exists).
- [ ] `mimo.ts` / `opencode-go.ts` confirmed absent (`git ls-files`).

## 2. Source edits

Each surviving provider gets (a) one import line and (b) the inline block swapped
for the helper call. The replacement block is identical everywhere:

```ts
const reasoningText = extractReasoningFromDelta(delta)
if (reasoningText) {
yield { type: "reasoning", text: reasoningText }
}
```

### Edit A — `src/api/providers/deepseek.ts`

- Import after `import { OpenAiHandler } from "./openai"`:
`import { extractReasoningFromDelta } from "./utils/extract-reasoning"`
- Replace the `if ("reasoning_content" in delta …)` block (keep the two
`// Handle reasoning_content …` comment lines above it).

### Edit B — `src/api/providers/openai.ts`

- Import after `import { handleOpenAIError } from "./utils/openai-error-handler"`:
`import { extractReasoningFromDelta } from "./utils/extract-reasoning"`
- Replace the inline block immediately before
`yield* this.processToolCalls(delta, finishReason, activeToolCallIds)`.

### Edit C — `src/api/providers/qwen-code.ts`

- Import after `import { BaseProvider } from "./base-provider"`:
`import { extractReasoningFromDelta } from "./utils/extract-reasoning"`
- Replace the inline block before the
`// Handle tool calls in stream …` comment.

### Edit D — `src/api/providers/requesty.ts`

- Import after `import { applyRouterToolPreferences } from "./utils/router-tool-preferences"`:
`import { extractReasoningFromDelta } from "./utils/extract-reasoning"`
- Replace the `if (delta && "reasoning_content" in delta …)` block before the
`// Handle native tool calls` comment.

### Edit E — `src/api/providers/unbound.ts`

- Import after `import { applyRouterToolPreferences } from "./utils/router-tool-preferences"`:
`import { extractReasoningFromDelta } from "./utils/extract-reasoning"`
- Replace the `if (delta && "reasoning_content" in delta …)` block before the
`// Handle native tool calls` comment.

## 3. Tests (port from upstream, adapt anchors)

Add the 3 streaming tests (`reasoning_content`, `reasoning` fallback, preference
when both present) to the providers that **have** a test file, inside the
`createMessage` describe block:

- `src/api/providers/__tests__/deepseek.spec.ts` — after the cache-tokens test,
before `describe("processUsageMetrics", …)`.
- `src/api/providers/__tests__/openai.spec.ts` — after the "Test response" text
test, before `it("should handle tool calls in streaming responses", …)`.
- `src/api/providers/__tests__/qwen-code-native-tools.spec.ts` — after the
`call_qwen_test` test, before
`it("should preserve thinking block handling alongside tool calls", …)`.
- `src/api/providers/__tests__/requesty.spec.ts` — after the streaming "API Error"
test, before `describe("native tool support", …)`. Each test constructs
`new RequestyHandler(mockOptions)` and uses the file-level `mockCreate`.

**Skipped:** `unbound.spec.ts` (does not exist here — see §0 adaptation 4).

## 4. Out of scope

- No new `unbound.spec.ts`. No `mimo` / `opencode-go` (absent). No change to the
helper itself. No "Zoo"/"Roo" strings.

## 5. Verify

- `pnpm --filter tumble-code check-types` clean.
- `cd src && npx vitest run api/providers/__tests__/deepseek.spec.ts api/providers/__tests__/openai.spec.ts api/providers/__tests__/qwen-code-native-tools.spec.ts api/providers/__tests__/requesty.spec.ts` all green.
- `cd src && npx eslint` on the 5 providers + 4 specs clean.

## 6. Acceptance

- [ ] All 5 providers call `extractReasoningFromDelta`; no inline
`reasoning_content` block remains in them.
- [ ] New reasoning tests pass in the 4 spec files; touched suites green.
- [ ] No "Roo"/"Zoo" user-facing strings introduced.

## 7. Record

```bash
node .claude/skills/zoo-port/scripts/zoo-prs.mjs record --pr 588 --status ported \
--branch feature/zoo-588-extract-reasoning-from-delta-helper \
--plan ai_plans/2026-06-17_zoo-588-extract-reasoning-from-delta-helper.md
```
Loading
Loading