Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
ad41bb9
fix(types): test project, split check-types, UI package, gateways
odilitime Mar 27, 2026
1093dd0
fix(tests): stabilize Milady billing cron and MILADY_PRICING mocks
odilitime Mar 27, 2026
e306880
feat(providers): Anthropic thinking options and route wiring
odilitime Mar 27, 2026
e1f36be
docs: Milady test mocks, Anthropic COT, changelog, and WHYs
odilitime Mar 27, 2026
42ab187
feat(agents): per-agent Anthropic thinking budget (MCP/A2A) + docs
odilitime Mar 27, 2026
99924fb
Fix spread order to ensure deploy-wide thinking config cannot be over…
cursoragent Mar 27, 2026
31a963e
Fix spread order to ensure deploy-wide thinking config cannot be over…
cursor[bot] Mar 27, 2026
9b2bed4
packages: improve error handling (env-validator.ts)
odilitime Mar 27, 2026
d1ba7cb
packages: add tests for docker-ssh-cloud-deploy.test
odilitime Mar 27, 2026
fe10e6b
packages: add tests for rate-limit (rate-limit.ts)
odilitime Mar 27, 2026
aed9df8
misc: improve code quality
odilitime Mar 27, 2026
2f7ad95
app: fix issues in app (route.ts)
odilitime Mar 27, 2026
1fe79e7
docs: add review dismissal comments
odilitime Mar 27, 2026
4d58eff
packages: add tests for setup-server (setup-server.ts)
odilitime Mar 27, 2026
29ad666
packages: add tests for webhooks-e2e.test (webhooks-e2e.test.ts)
odilitime Mar 27, 2026
9940a31
packages: add tests for field-encryption.test (field-encryption.test.ts)
odilitime Mar 27, 2026
557b66e
packages: add tests for provisioning-jobs.test
odilitime Mar 27, 2026
35f6784
packages: add tests for z-milady-billing-route.test
odilitime Mar 27, 2026
2817993
packages: Temperature silently dropped when thinking is enabled (seo.ts)
odilitime Mar 27, 2026
cb1f343
app: Potential issue | 🟠 Major (route.ts)
odilitime Mar 27, 2026
5470955
packages: add tests for oauth-cache-invalidation.test
odilitime Mar 27, 2026
3ff2fc3
packages: add tests for facilitator-service.test
odilitime Mar 27, 2026
1db5ad6
app: add tests for route (route.ts)
odilitime Mar 27, 2026
705d40e
packages: 3. `env-validator` and parser disagree on whether `"0"` is ...
odilitime Mar 27, 2026
ecd95c3
packages: 1. Rate limit change breaks local dev workflows
odilitime Mar 27, 2026
bc0fe7e
packages: add tests for x402-topup.test (x402-topup.test.ts)
odilitime Mar 27, 2026
97a014f
misc: improve code quality
odilitime Mar 27, 2026
e1a426b
packages: 2. Temperature silently dropped when COT enabled
odilitime Mar 27, 2026
2ca3e28
packages: add type safety (runtime-factory.ts)
odilitime Mar 27, 2026
e973f7e
packages: add tests for anthropic-thinking.test
odilitime Mar 27, 2026
5d56e57
app: add tests for route (route.ts)
odilitime Mar 27, 2026
ba7ff77
app: fix issues in app (route.ts)
odilitime Mar 27, 2026
2f2aeeb
packages: Temperature silently dropped when thinking is enabled (seo.ts)
odilitime Mar 27, 2026
3f00c01
packages: fix issues in packages (env-validator.ts)
odilitime Mar 27, 2026
08c27ff
app: Extended thinking applied to image generation endpoints (route.ts)
odilitime Mar 27, 2026
b9c92c2
packages: 3. `env-validator` and parser disagree on whether `"0"` is ...
odilitime Mar 27, 2026
7caa325
misc: improve code quality
odilitime Mar 27, 2026
8c3e23b
packages: add type safety (app-promotion.ts)
odilitime Mar 27, 2026
07813b4
packages: add tests for app-automation (app-automation.ts)
odilitime Mar 27, 2026
0a81562
packages: fix lint or formatting (seo.ts)
odilitime Mar 27, 2026
d435a8c
packages: improve error handling (env-validator.ts)
odilitime Mar 27, 2026
dc61ad5
packages: Minor Issues (rate-limit.ts)
odilitime Mar 27, 2026
0f7314c
packages: 2. Temperature silently dropped when COT enabled
odilitime Mar 27, 2026
82e680e
packages: add tests for anthropic-thinking.test
odilitime Mar 27, 2026
a7bdea3
app: fix security issue (route.ts)
odilitime Mar 27, 2026
90eaec3
packages: add type safety (runtime-factory.ts)
odilitime Mar 27, 2026
96f3615
packages: fix issues in packages (anthropic-thinking.ts)
odilitime Mar 27, 2026
9cc7cf5
packages: fix before merge (rate-limit.ts)
odilitime Mar 27, 2026
bcbc223
packages: add or fix tests (anthropic-thinking.ts)
odilitime Mar 27, 2026
b7e33a9
packages: fix like (anthropic-thinking.ts)
odilitime Mar 27, 2026
18bfb11
packages: consolidate duplicate logic
odilitime Mar 27, 2026
9b558af
packages: add type safety (runtime-factory.ts)
odilitime Mar 27, 2026
df1e49c
packages: add tests for anthropic-thinking.test
odilitime Mar 27, 2026
a73b0b1
packages: add tests for x402-topup.test (x402-topup.test.ts)
odilitime Mar 27, 2026
edf761f
packages: add tests for app-automation (app-automation.ts)
odilitime Mar 27, 2026
602a0f1
app: A2A route omits thinking tokens from credit reservation (route.ts)
odilitime Mar 27, 2026
022d426
packages: add tests for anthropic-thinking.test
odilitime Mar 27, 2026
4d4037c
app: 6. MCP generation tools inherit `ANTHROPICCOTBUDGET`
odilitime Mar 27, 2026
6455e14
misc: improve code quality
odilitime Mar 27, 2026
7d3e6e7
packages: add tests for anthropic-thinking.test
odilitime Mar 27, 2026
a2b03cb
app: A2A route omits thinking tokens from credit reservation (route.ts)
odilitime Mar 27, 2026
914ee8b
packages: 7. `check-types-split.ts` now checks `packages/lib` as a si...
odilitime Mar 27, 2026
c991a33
app: 💡 Minor / Suggestions (route.ts)
odilitime Mar 27, 2026
63d0930
misc: improve code quality
odilitime Mar 27, 2026
1e12524
app: improve a guard (route.ts)
odilitime Mar 27, 2026
99b53bc
packages: 5. `app-builder-ai-sdk.ts` inherits CoT without a comment
odilitime Mar 27, 2026
b737e41
app: 6. MCP generation tools inherit `ANTHROPICCOTBUDGET`
odilitime Mar 27, 2026
9516525
packages: add tests for anthropic-thinking.test
odilitime Mar 27, 2026
8a872e8
packages: 6. `RATELIMITMULTIPLIER` is not validated in `env-validator...
odilitime Mar 27, 2026
66e9b98
packages: 9. Import ordering in `runtime-factory.ts`
odilitime Mar 27, 2026
8b14b89
app: consolidate duplicate logic (route.ts)
odilitime Mar 27, 2026
421fda1
app: improve a guard (route.ts)
odilitime Mar 27, 2026
718e962
packages: 5. `app-builder-ai-sdk.ts` inherits CoT without a comment
odilitime Mar 27, 2026
ddd4e20
app: 6. MCP generation tools inherit `ANTHROPICCOTBUDGET`
odilitime Mar 27, 2026
3d6bd66
misc: add tests for anthropic-thinking (anthropic-thinking.ts)
odilitime Mar 27, 2026
d292214
packages: 7. `a2a/skills.ts` — no character context for CoT (skills.ts)
odilitime Mar 27, 2026
8174ca9
packages: 9. Import ordering in `runtime-factory.ts`
odilitime Mar 27, 2026
4807344
app: fix issues in app (route.ts)
odilitime Mar 27, 2026
19f22c5
packages: 3. App-builder does not explicitly disable CoT
odilitime Mar 27, 2026
3465972
app: improve a guard (route.ts)
odilitime Mar 27, 2026
e6bb1a8
packages: 5. `app-builder-ai-sdk.ts` inherits CoT without a comment
odilitime Mar 27, 2026
e093709
app: 6. MCP generation tools inherit `ANTHROPICCOTBUDGET`
odilitime Mar 27, 2026
8d71533
packages: Cursor search/replace artifact left in production code
odilitime Mar 27, 2026
67138cc
misc: improve code quality
odilitime Mar 27, 2026
3b0b079
docs: add review dismissal comments
odilitime Mar 27, 2026
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
8 changes: 8 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ PRIVY_WEBHOOK_SECRET=replace_with_strong_random_secret
# Anthropic API Key (for Claude - used by AI App Builder)
# Get from: https://console.anthropic.com/settings/keys
ANTHROPIC_API_KEY=sk-ant-your_anthropic_key_here
# Default Anthropic extended-thinking budget (tokens) when a cloud agent character does not set
# user_characters.settings.anthropicThinkingBudgetTokens. Per-agent: set that JSON key (integer ≥ 0; 0 = off).
# Optional ANTHROPIC_COT_BUDGET_MAX caps any effective budget (character or default).
# Why not from API bodies: untrusted clients must not raise thinking cost; agents own policy via stored settings.
# Unset, empty, or 0 = no default budget (agent can still set a positive per-character budget unless max is 0).
# ANTHROPIC_COT_BUDGET=1024
# ANTHROPIC_COT_BUDGET_MAX=8192
# See docs/anthropic-cot-budget.md
# ============================================================================

# OpenAI API Key (for direct OpenAI access and ElizaOS)
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ jobs:
run: bun run lint
- name: Run typecheck
run: bun run check-types
- name: Run test project typecheck
run: bun run check-types:tests

unit-tests:
runs-on: ubuntu-latest
Expand Down
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Changelog

All notable engineering changes to this repository are recorded here. For **product-facing** release notes on the docs site, see `packages/content/changelog.mdx`.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Unreleased]

### Added

- **Per-agent Anthropic extended thinking** — `user_characters.settings.anthropicThinkingBudgetTokens` (integer ≥ 0) controls thinking for **MCP** and **A2A** agent chat when the model is Anthropic. **`ANTHROPIC_COT_BUDGET_MAX`** optionally caps any effective budget (character or env default). **Why:** Agent owners set policy in stored character data; request bodies must not carry budgets (untrusted MCP/A2A callers). Env still supplies defaults where no character field exists and caps worst-case cost.
- **`ANTHROPIC_COT_BUDGET`** (existing) — Clarified role as **default** when the character omits `anthropicThinkingBudgetTokens` (or value is invalid), plus baseline for routes without a resolved character. **Why:** One deploy-level knob for generic chat; per-agent overrides stay in JSON.
- **`parseThinkingBudgetFromCharacterSettings`**, **`resolveAnthropicThinkingBudgetTokens`**, **`parseAnthropicCotBudgetMaxFromEnv`**, **`ANTHROPIC_THINKING_BUDGET_CHARACTER_SETTINGS_KEY`** — See `packages/lib/providers/anthropic-thinking.ts`. **Why:** Single resolution path and a stable settings key for dashboards/APIs.
- **`packages/lib/providers/cloud-provider-options.ts`** — Shared type for merged `providerOptions`. **Why:** Type-safe merges without `any`.
- **`mockMiladyPricingMinimumDepositForRouteTests`** — Test helper in `packages/tests/helpers/mock-milady-pricing-for-route-tests.ts`. **Why:** Partial `MILADY_PRICING` mocks broke Milady billing cron under full `bun run test:unit`.

### Changed

- **`POST /api/agents/{id}/mcp`** (`chat` tool) and **`POST /api/agents/{id}/a2a`** (`chat`) pass character `settings` into `mergeAnthropicCotProviderOptions`. **Why:** Those routes always resolve a `user_characters` row; other v1 routes remain env-only until a character is available on the request path.
- **Milady billing cron unit tests** — `z-milady-billing-route.test.ts`, queue-backed DB mocks, `package.json` script paths. **Why:** `mock.module` ordering and partial pricing objects caused flaky full-suite failures.

### Documentation

- **`docs/anthropic-cot-budget.md`** — Per-agent settings, env default/max, operator checklist, MCP/A2A scope.
- **`docs/unit-testing-milady-mocks.md`** — Milady `mock.module` pitfalls.
- **`docs/ROADMAP.md`** — Done / near-term items.
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,9 @@ cloud/
├── .env.example # Environment template
├── docs/ # Detailed documentation
│ ├── API_REFERENCE.md # Complete API reference
│ ├── anthropic-cot-budget.md # ANTHROPIC_COT_BUDGET + provider merge WHYs
│ ├── unit-testing-milady-mocks.md # Bun mock.module + Milady pricing test WHYs
│ ├── ROADMAP.md # Product direction and done items
│ ├── DEPLOYMENT.md # Deployment guide
│ ├── DEPLOYMENT_TROUBLESHOOTING.md # Troubleshooting
│ ├── STRIPE_SETUP.md # Stripe integration
Expand Down Expand Up @@ -553,10 +556,17 @@ Tests are split by kind; use the right script for what you want to run:
| `bun run test:unit` | `tests/unit/` | Unit tests (mocked deps, fast) | Env preload only; some skip without `DATABASE_URL` |
| `bun run test:integration` | `tests/integration/` | API/DB/E2E integration tests | `DATABASE_URL` (+ migrations); some need a running server |
| `bun run test:runtime` | `tests/runtime/` | Runtime/factory and perf tests | `DATABASE_URL` (+ migrations), heavier |
| `bun run test` | all of the above | Full suite in one run | Same as integration + runtime for those layers |
| `bun run test` | `test:repo-unit:bulk` + `special` | Two staged **unit** batches (see `package.json` for included/excluded files) | Env preload only (same family as `test:unit`) |
| `bun run test:playwright` | `tests/playwright/` | Playwright E2E (optional) | `@playwright/test` installed |

Env is loaded from `.env`, `.env.local`, and `.env.test` via preload. See `docs/test-failure-assessment.md` for skip behavior and remaining failure categories.
Env is loaded from `.env`, `.env.local`, and `.env.test` via preload.

### Engineering docs (WHYs)

- **[docs/unit-testing-milady-mocks.md](docs/unit-testing-milady-mocks.md)** — Why partial `MILADY_PRICING` mocks break other Milady modules under Bun, and how the billing cron tests isolate `mock.module("@/db/client")` contention.
- **[docs/anthropic-cot-budget.md](docs/anthropic-cot-budget.md)** — Per-agent `settings.anthropicThinkingBudgetTokens` (MCP/A2A), env default (`ANTHROPIC_COT_BUDGET`) and cap (`ANTHROPIC_COT_BUDGET_MAX`), and **why** thinking budgets are not request parameters.
- **[CHANGELOG.md](CHANGELOG.md)** — Engineering changelog (Keep a Changelog style).
- **[docs/ROADMAP.md](docs/ROADMAP.md)** — Product direction and rationale; “Done” links to the above where relevant.

### Development Workflow

Expand Down Expand Up @@ -711,6 +721,8 @@ const { messages, input, handleSubmit, isLoading } = useChat({

**Anthropic Messages API (Claude Code):** For tools that expect the [Anthropic Messages API](https://docs.anthropic.com/en/api/messages) (e.g. Claude Code), use **POST /api/v1/messages** with the same request/response shape. Set `ANTHROPIC_BASE_URL=https://cloud.milady.ai/api/v1` and `ANTHROPIC_API_KEY` to your Cloud API key so usage goes through Cloud credits instead of a direct Anthropic key. See [API docs → Anthropic Messages](/docs/api/messages). *Why: single API key and billing for both OpenAI-style and Anthropic-style clients.*

**Public cloud agents (MCP / A2A) — Anthropic extended thinking:** For **`POST /api/agents/{id}/mcp`** (`chat` tool) and **`POST /api/agents/{id}/a2a`** (`chat`), extended thinking uses the character’s **`settings.anthropicThinkingBudgetTokens`** when the model is Anthropic (`0` = off; omitted = fall back to `ANTHROPIC_COT_BUDGET`). Optional **`ANTHROPIC_COT_BUDGET_MAX`** clamps any effective budget. *Why: the agent owner controls cost/quality per agent; MCP/A2A clients cannot pass a thinking budget in the request (untrusted input).* See [docs/anthropic-cot-budget.md](docs/anthropic-cot-budget.md).

### 2. AI Image Generation

**Location**: `/dashboard/image` and `/app/api/v1/generate-image/route.ts`
Expand Down
130 changes: 130 additions & 0 deletions anthropic-thinking.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { describe, expect, it } from "bun:test";
import {
validateBudgetTokens,
getThinkingConfig,
buildThinkingParam,
supportsExtendedThinking,
type ThinkingConfig,
type CharacterThinkingSettings,
// Note: imports are structured to unify testing across different modules consistently
} from "./anthropic-thinking";
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Root test imports from nonexistent module path

Medium Severity

The root-level anthropic-thinking.test.ts imports validateBudgetTokens, getThinkingConfig, buildThinkingParam, and CharacterThinkingSettings from ./anthropic-thinking, but no such file exists at the repository root. The actual implementation lives at packages/lib/providers/anthropic-thinking.ts and exports completely different functions. This test will fail at import time.

Fix in Cursor Fix in Web

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Dismissed: Comment targets line 9 but identifier validateBudgetTokens not found in file — code may have been removed or renamed


describe("anthropic-thinking", () => {
describe("validateBudgetTokens", () => {
it("returns default budget when undefined", () => {
expect(validateBudgetTokens(undefined)).toBe(10000);
});

it("clamps to minimum budget", () => {
expect(validateBudgetTokens(500)).toBe(1000);
expect(validateBudgetTokens(0)).toBe(1000);
expect(validateBudgetTokens(-100)).toBe(1000);
});

it("clamps to maximum budget", () => {
expect(validateBudgetTokens(150000)).toBe(100000);
expect(validateBudgetTokens(100001)).toBe(100000);
});

it("returns valid values within range", () => {
expect(validateBudgetTokens(1000)).toBe(1000);
expect(validateBudgetTokens(50000)).toBe(50000);
expect(validateBudgetTokens(100000)).toBe(100000);
});
});

describe("getThinkingConfig", () => {
it("returns disabled config when settings undefined", () => {
expect(getThinkingConfig(undefined)).toEqual({ enabled: false });
});

it("returns disabled config when anthropicThinking undefined", () => {
expect(getThinkingConfig({})).toEqual({ enabled: false });
});

it("returns disabled config when enabled is false", () => {
const settings: CharacterThinkingSettings = {
anthropicThinking: { enabled: false },
};
expect(getThinkingConfig(settings)).toEqual({ enabled: false });
});

it("returns enabled config with default budget", () => {
const settings: CharacterThinkingSettings = {
anthropicThinking: { enabled: true },
};
expect(getThinkingConfig(settings)).toEqual({
enabled: true,
budgetTokens: 10000,
});
});

it("returns enabled config with custom budget", () => {
const settings: CharacterThinkingSettings = {
anthropicThinking: { enabled: true, budgetTokens: 25000 },
};
expect(getThinkingConfig(settings)).toEqual({
enabled: true,
budgetTokens: 25000,
});
});

it("validates and clamps budget tokens", () => {
const settings: CharacterThinkingSettings = {
anthropicThinking: { enabled: true, budgetTokens: 500 },
};
expect(getThinkingConfig(settings)).toEqual({
enabled: true,
budgetTokens: 1000,
});
});
});

describe("buildThinkingParam", () => {
it("returns undefined when disabled", () => {
const config: ThinkingConfig = { enabled: false };
expect(buildThinkingParam(config)).toBeUndefined();
});

it("returns thinking param when enabled with budget", () => {
const config: ThinkingConfig = { enabled: true, budgetTokens: 15000 };
expect(buildThinkingParam(config)).toEqual({
type: "enabled",
budget_tokens: 15000,
});
});

it("uses default budget when budgetTokens undefined", () => {
const config: ThinkingConfig = { enabled: true };
expect(buildThinkingParam(config)).toEqual({
type: "enabled",
budget_tokens: 10000,
});
});
});

describe("supportsExtendedThinking", () => {
it("returns true for claude-3-5-sonnet models", () => {
expect(supportsExtendedThinking("claude-3-5-sonnet-20241022")).toBe(true);
expect(supportsExtendedThinking("claude-3-5-sonnet")).toBe(true);
expect(supportsExtendedThinking("Claude-3-5-Sonnet")).toBe(true);
});

it("returns true for claude-3.5-sonnet models", () => {
expect(supportsExtendedThinking("claude-3.5-sonnet")).toBe(true);
});

it("returns true for claude-3-opus models", () => {
expect(supportsExtendedThinking("claude-3-opus-20240229")).toBe(true);
expect(supportsExtendedThinking("claude-3-opus")).toBe(true);
expect(supportsExtendedThinking("Claude-3-Opus")).toBe(true);
});

it("returns false for unsupported models", () => {
expect(supportsExtendedThinking("claude-3-haiku")).toBe(false);
expect(supportsExtendedThinking("claude-2")).toBe(false);
expect(supportsExtendedThinking("gpt-4")).toBe(false);
expect(supportsExtendedThinking("gemini-pro")).toBe(false);
});
});
});
29 changes: 27 additions & 2 deletions app/api/agents/[id]/a2a/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
* - API key authentication (uses org credits)
*
* When monetization is enabled, the agent creator earns their markup percentage.
*
* **Anthropic extended thinking:** JSON-RPC `chat` merges thinking from
* `user_characters.settings.anthropicThinkingBudgetTokens`. **Why:** Budget lives on the character
* record, not in caller-supplied params (A2A peers are not trusted to set token limits).
*/

import { gateway } from "@ai-sdk/gateway";
Expand All @@ -20,6 +24,11 @@ import { z } from "zod";
import type { UserCharacter } from "@/db/schemas/user-characters";
import { requireAuthOrApiKeyWithOrg } from "@/lib/auth";
import { calculateCost, estimateRequestCost, getProviderFromModel } from "@/lib/pricing";
import {
mergeAnthropicCotProviderOptions,
parseThinkingBudgetFromCharacterSettings,
resolveAnthropicThinkingBudgetTokens,
} from "@/lib/providers/anthropic-thinking";
import { agentMonetizationService } from "@/lib/services/agent-monetization";
import { charactersService } from "@/lib/services/characters/characters";
import type { CreditReservation } from "@/lib/services/credits";
Expand Down Expand Up @@ -254,6 +263,7 @@ async function handleChat(
inference_markup_percentage: string | null;
system: string | null;
bio: string | string[];
settings: Record<string, unknown>;
},
params: Record<string, unknown>,
rpcId: string | number,
Expand Down Expand Up @@ -284,9 +294,19 @@ async function handleChat(
})),
];

// Calculate estimated costs
// Calculate estimated costs, including potential thinking budget
// Use resolveAnthropicThinkingBudgetTokens to get effective budget (same as MCP route)
// Add thinking budget on top of base output tokens for accurate credit reservation
const provider = getProviderFromModel(model);
const baseCost = await estimateRequestCost(model, fullMessages);
const agentThinkingBudget = parseThinkingBudgetFromCharacterSettings(character.settings);
const effectiveThinkingBudget = resolveAnthropicThinkingBudgetTokens(
model,
process.env,
agentThinkingBudget ?? undefined,
);
// Add thinking budget to base output estimate (500 tokens) to match MCP route behavior
const maxOutputTokens = effectiveThinkingBudget != null ? 500 + effectiveThinkingBudget : undefined;
const baseCost = await estimateRequestCost(model, fullMessages, maxOutputTokens);

// Apply markup if monetization is enabled
const markupPct = Number(character.inference_markup_percentage || 0);
Expand Down Expand Up @@ -321,6 +341,11 @@ async function handleChat(
const result = await streamText({
model: gateway.languageModel(model),
messages: fullMessages,
...mergeAnthropicCotProviderOptions(
model,
process.env,
agentThinkingBudget,
),
});

let fullText = "";
Expand Down
27 changes: 26 additions & 1 deletion app/api/agents/[id]/mcp/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
* - API key authentication (uses org credits)
*
* When monetization is enabled, the agent creator earns their markup percentage.
*
* **Anthropic extended thinking:** The `chat` tool merges `providerOptions` using
* `user_characters.settings.anthropicThinkingBudgetTokens` (see `parseThinkingBudgetFromCharacterSettings`).
* **Why:** Thinking budget is owner-defined on the character, not passed by MCP clients (untrusted).
*/

import { gateway } from "@ai-sdk/gateway";
Expand All @@ -19,6 +23,11 @@ import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";
import { requireAuthOrApiKeyWithOrg } from "@/lib/auth";
import { calculateCost, estimateTokens, getProviderFromModel } from "@/lib/pricing";
import {
mergeAnthropicCotProviderOptions,
parseThinkingBudgetFromCharacterSettings,
resolveAnthropicThinkingBudgetTokens,
} from "@/lib/providers/anthropic-thinking";
import { agentMonetizationService } from "@/lib/services/agent-monetization";
import { charactersService } from "@/lib/services/characters/characters";
import type { CreditReservation } from "@/lib/services/credits";
Expand Down Expand Up @@ -263,6 +272,7 @@ async function handleToolCall(
inference_markup_percentage: string | null;
system: string | null;
bio: string | string[];
settings: Record<string, unknown>;
},
params: Record<string, unknown>,
rpcId: string | number,
Expand Down Expand Up @@ -320,6 +330,16 @@ async function handleToolCall(
const provider = getProviderFromModel(model);
const markupPct = Number(character.inference_markup_percentage || 0);

// Resolve effective thinking budget before reservation (applies ANTHROPIC_COT_BUDGET_MAX cap)
const agentThinkingBudget = parseThinkingBudgetFromCharacterSettings(character.settings);
const effectiveThinkingBudget =
resolveAnthropicThinkingBudgetTokens(model, process.env, agentThinkingBudget) ?? 0;
// Include thinking budget in output token estimate for Anthropic models
const baseOutputTokens = 500;
const estimatedOutputTokens = model.includes("claude") && effectiveThinkingBudget > 0
? baseOutputTokens + effectiveThinkingBudget
: baseOutputTokens;

// Reserve credits BEFORE LLM call to prevent TOCTOU race condition
let reservation: CreditReservation;
try {
Expand All @@ -328,7 +348,7 @@ async function handleToolCall(
model,
provider,
estimatedInputTokens: estimateTokens(systemPrompt + message),
estimatedOutputTokens: 500,
estimatedOutputTokens,
userId: authResult.user.id,
description: `Agent MCP: ${character.name}`,
});
Expand All @@ -350,6 +370,11 @@ async function handleToolCall(
const result = await streamText({
model: gateway.languageModel(model),
messages,
...mergeAnthropicCotProviderOptions(
model,
process.env,
agentThinkingBudget,
),
});

let fullText = "";
Expand Down
16 changes: 12 additions & 4 deletions app/api/mcp/tools/generation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
import { gateway } from "@ai-sdk/gateway";
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { streamText } from "ai";
import {
mergeAnthropicCotProviderOptions,
mergeGoogleImageModalitiesWithAnthropicCot,
} from "@/lib/providers/anthropic-thinking";
import { z } from "zod/v3";
import { uploadBase64Image } from "@/lib/blob";
import { calculateCost, getProviderFromModel, IMAGE_GENERATION_COST } from "@/lib/pricing";
Expand Down Expand Up @@ -134,9 +138,14 @@ export function registerGenerationTools(server: McpServer): void {
generationId = generation.id;

// Generate text (non-streaming for MCP)
// MCP text generation intentionally inherits ANTHROPIC_COT_BUDGET if set in env.
// Unlike SEO/promotion routes (which pass 0 to disable for temperature compat),
// interactive text-gen benefits from extended thinking. No explicit temperature
// is set here, so CoT's temperature override is acceptable.
const result = await streamText({
model: gateway.languageModel(model),
prompt,
...mergeAnthropicCotProviderOptions(model),
});

let fullText = "";
Expand Down Expand Up @@ -294,11 +303,10 @@ export function registerGenerationTools(server: McpServer): void {

const enhancedPrompt = `${prompt}, ${aspectRatioDescriptions[aspectRatio]}`;

const geminiImageModel = "google/gemini-2.5-flash-image";
const result = streamText({
model: "google/gemini-2.5-flash-image",
providerOptions: {
google: { responseModalities: ["TEXT", "IMAGE"] },
},
model: geminiImageModel,
...mergeGoogleImageModalitiesWithAnthropicCot(geminiImageModel),
prompt: `Generate an image: ${enhancedPrompt}`,
});

Expand Down
Loading
Loading