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
10 changes: 10 additions & 0 deletions .github/agentic-price-check-providers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,13 @@ providers:
Compare per-million input, cached-input, and output token prices with input_mtok, cache_read_mtok, and output_mtok. Ignore
the account-wide free Neuron allocation. This YAML deliberately covers only token-priced models. Do not add image, tile,
step, character, or audio-minute prices during this check.

- name: Cursor
file: prices/providers/cursor.yml
scope: Cursor Models pool variants with public on-demand token pricing.
sources:
- https://cursor.com/docs/models-and-pricing
notes: >-
The Cursor Models table is the complete in-scope catalog. Compare Input, Cache Read, and Output with input_mtok,
cache_read_mtok, and output_mtok. Cache Write is not priced. Treat each Fast row as a separate variant. Exclude third-party
models, the Cursor Token Rate, usage pools, plan allowances, regional uplifts, Legacy Enterprise Auto, and legacy Max Mode.
10 changes: 5 additions & 5 deletions .github/workflows/AGENTIC_PRICE_CHECK.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ pricing page** and file a GitHub issue when they diverge. This complements the
existing aggregator-based checks (`make check-for-price-discrepancies`, which
uses LiteLLM / OpenRouter / etc.) by reading the authoritative source directly.

| Workflow | Providers | Issue |
| ----------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ---------------------------------- |
| `agentic-price-check-openai-anthropic.md` | OpenAI, Anthropic | `[price-check/openai-anthropic] …` |
| `agentic-price-check-google-mistral.md` | Google (Gemini), Mistral | `[price-check/google-mistral] …` |
| `agentic-price-check-direct-providers.md` | DeepSeek, xAI, Groq, Cerebras, MiniMax, MoonshotAI, Avian, Perplexity, Cohere, Voyage AI, Cloudflare Workers AI | `[price-check/direct-providers] …` |
| Workflow | Providers | Issue |
| ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ---------------------------------- |
| `agentic-price-check-openai-anthropic.md` | OpenAI, Anthropic | `[price-check/openai-anthropic] …` |
| `agentic-price-check-google-mistral.md` | Google (Gemini), Mistral | `[price-check/google-mistral] …` |
| `agentic-price-check-direct-providers.md` | DeepSeek, xAI, Groq, Cerebras, MiniMax, MoonshotAI, Avian, Perplexity, Cohere, Voyage AI, Cloudflare Workers AI, Cursor | `[price-check/direct-providers] …` |

Each agent is told the **exact pricing URL** to fetch per provider, reads the recorded
`prices:` from the YAML, and compares. The direct-provider workflow reads its provider
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/agentic-price-check-direct-providers.lock.yml

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions .github/workflows/agentic-price-check-direct-providers.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
emoji: '🏷️'
name: 'Price Check: Direct Providers'
description: 'Check eleven direct provider catalogs for changed prices, new models, removed models, and unreadable sources.'
description: 'Check twelve direct provider catalogs for changed prices, new models, removed models, and unreadable sources.'
on:
workflow_dispatch:
schedule: weekly on monday
Expand Down Expand Up @@ -76,6 +76,7 @@ network:
- cohere.com
- docs.voyageai.com
- developers.cloudflare.com
- cursor.com
---

# Price Check: Direct Providers
Expand Down Expand Up @@ -144,5 +145,5 @@ If any price change, new model, potential removal, unchecked field, or unreadabl
Use tables and one row per finding. End with `Checked YYYY-MM-DD.` using the run date.

Call `safeoutputs noop` only when all manifest providers and sources were read successfully, every active price field and tier was
checked, every recorded value matched, and catalog comparison found no new or potentially removed models. State that all eleven
checked, every recorded value matched, and catalog comparison found no new or potentially removed models. State that all twelve
direct providers match.
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ huggingface-get: ## get huggingface prices
cloudflare-get: ## get cloudflare workers ai prices
uv run -m prices get_cloudflare_prices

.PHONY: cursor-get
cursor-get: ## get cursor model prices
uv run -m prices get_cursor_prices

.PHONY: ovhcloud-get
ovhcloud-get: ## get ovhcloud ai endpoints prices
uv run -m prices get_ovhcloud_prices
Expand All @@ -76,7 +80,7 @@ quicksilverpro-get: ## get quicksilver pro prices
uv run -m prices get_quicksilverpro_prices

.PHONY: get-all-prices
get-all-prices: helicone-get openrouter-get litellm-get simonw-prices-get huggingface-get cloudflare-get ovhcloud-get quicksilverpro-get ## get all prices
get-all-prices: helicone-get openrouter-get litellm-get simonw-prices-get huggingface-get cloudflare-get cursor-get ovhcloud-get quicksilverpro-get ## get all prices

.PHONE: update-price-discrepancies
update-price-discrepancies: ## update price discrepancies
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ The following providers are currently supported:
- [Cerebras](prices/providers/cerebras.yml) - 7 models
- [Cloudflare Workers AI](prices/providers/cloudflare.yml) - 47 models
- [Cohere](prices/providers/cohere.yml) - 9 models
- [Cursor](prices/providers/cursor.yml) - 6 models
- [Deepseek](prices/providers/deepseek.yml) - 7 models
- [Doubleword](prices/providers/doubleword.yml) - 20 models
- [Fireworks](prices/providers/fireworks.yml) - 32 models
Expand Down
29 changes: 29 additions & 0 deletions packages/js/src/__tests__/extractUsage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { data } from '../data'
import { calcPrice, extractUsage } from '../index'

const anthropicProvider: Provider = data.find((provider) => provider.id === 'anthropic')!
const cursorProvider: Provider = data.find((provider) => provider.id === 'cursor')!
const fractionalProvider: Provider = {
api_pattern: 'fractional',
extractors: [
Expand All @@ -31,6 +32,34 @@ afterEach(() => {

describe('extractUsage', () => {
describe('successful extraction', () => {
it('should extract Cursor usage events', () => {
const responseData = {
model: 'grok-4.6-fast',
tokenUsage: {
cacheReadTokens: 30,
cacheWriteTokens: 20,
inputTokens: 100,
outputTokens: 40,
totalCents: 0.099,
},
}

const { model, usage } = extractUsage(cursorProvider, responseData, 'usage-event')

expect(model).toBe('grok-4.6-fast')
expect(usage).toEqual({
cache_read_tokens: 30,
cache_write_tokens: 20,
input_tokens: 150,
output_tokens: 40,
})

const price = calcPrice(usage, model!, { providerId: 'cursor' })
expect(price?.input_price).toBeCloseTo(0.00051, 8)
expect(price?.output_price).toBeCloseTo(0.00048, 8)
expect(price?.total_price).toBeCloseTo(0.00099, 8)
})

it('should extract usage with cache tokens', () => {
const responseData = {
id: 'msg_0152tnC3YpjyASTB9qxqDJXu',
Expand Down
15 changes: 15 additions & 0 deletions packages/js/src/__tests__/generatedData.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,21 @@ describe('generated data split', () => {
expect(registry.getUnitForPriceKey('requests_kcount')?.usageKey).toBe('requests')
})

it.each([
{ expectedTotalPrice: 3.2, model: 'composer-2.5' },
{ expectedTotalPrice: 18.5, model: 'composer-2.5-fast' },
{ expectedTotalPrice: 8.5, model: 'grok-4.5' },
{ expectedTotalPrice: 23, model: 'grok-4.5-fast' },
{ expectedTotalPrice: 8.5, model: 'grok-4.6' },
{ expectedTotalPrice: 17, model: 'grok-4.6-fast' },
])('prices Cursor $model', ({ expectedTotalPrice, model }) => {
const result = calcPrice({ cache_read_tokens: 1_000_000, input_tokens: 2_000_000, output_tokens: 1_000_000 }, model, {
providerId: 'cursor',
})

expect(result?.total_price).toBe(expectedTotalPrice)
})

it.each([
{ expectedInputPrice: (200_000 * 3) / 1_000_000, inputTokens: 200_000 },
{ expectedInputPrice: (200_001 * 6) / 1_000_000, inputTokens: 200_001 },
Expand Down
7 changes: 7 additions & 0 deletions packages/js/src/__tests__/matcher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,19 @@ describe('Provider Matching', () => {
expect(matchProvider(actualProviders, { modelId: 'open-mixtral-8x7b' })?.id).toBe('mistral')
expect(matchProvider(actualProviders, { modelId: 'mistralai/voxtral-small-24b-2507' })).toBeUndefined()
})

it('infers Cursor only for its Composer namespace', () => {
expect(matchProvider(actualProviders, { modelId: 'composer-2.5' })?.id).toBe('cursor')
expect(matchProvider(actualProviders, { modelId: 'grok-4.6' })?.id).not.toBe('cursor')
})
})

describe('matchProvider with providerId', () => {
it('should find providers by exact ID match', () => {
expect(matchProvider(actualProviders, { providerId: 'google' })?.id).toBe('google')
expect(matchProvider(actualProviders, { providerId: 'anthropic' })?.id).toBe('anthropic')
expect(matchProvider(actualProviders, { providerId: 'openai' })?.id).toBe('openai')
expect(matchProvider(actualProviders, { providerId: 'cursor' })?.id).toBe('cursor')
})

it('should find providers by provider_match logic', () => {
Expand Down Expand Up @@ -93,6 +99,7 @@ describe('Provider Matching', () => {

it('should match a provider at the start of the URL', () => {
expect(matchProvider(actualProviders, { providerApiUrl: 'https://api.openai.com/v1/chat/completions' })?.id).toBe('openai')
expect(matchProvider(actualProviders, { providerApiUrl: 'https://api.cursor.com/v1/agents' })?.id).toBe('cursor')
})

it('should not match a provider embedded later in the URL', () => {
Expand Down
188 changes: 188 additions & 0 deletions packages/js/src/data.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading