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
7 changes: 5 additions & 2 deletions containers/api-proxy/ai-credits-pricing.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ module.exports = Object.freeze({
'gemini-3.1-flash': { input: 0.75, cachedInput: 0.075, cacheWrite: null, output: 4.50 },
'gemini-3.1-pro': { input: 2.00, cachedInput: 0.20, cacheWrite: null, output: 12.00 },
'gemini-3.5-flash': { input: 1.50, cachedInput: 0.15, cacheWrite: null, output: 9.00 },
'mai-code-1-flash': { input: 0.75, cachedInput: 0.075, cacheWrite: null, output: 4.50 },
'raptor-mini': { input: 0.25, cachedInput: 0.025, cacheWrite: null, output: 2.00 },
'mai-code-1-flash': { input: 0.75, cachedInput: 0.075, cacheWrite: null, output: 4.50 },
'raptor-mini': { input: 0.25, cachedInput: 0.025, cacheWrite: null, output: 2.00 },
// Embedding models (output tokens are not produced; output cost is 0)
'text-embedding-3-small': { input: 0.02, cachedInput: 0, cacheWrite: null, output: 0.00 },
'text-embedding-ada-002': { input: 0.10, cachedInput: 0, cacheWrite: null, output: 0.00 },
});
34 changes: 34 additions & 0 deletions containers/api-proxy/guards/ai-credits-guard.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,40 @@ describe('ai-credits-guard', () => {
}));
});

it('resolves text-embedding-3-small with input-only pricing', () => {
const usage = applyAiCreditsUsage({ input_tokens: 1_000_000, output_tokens: 500 }, 'text-embedding-3-small');
// input: 1M × $0.02/M / 10_000 denominator = 2.0 AIC
// output: 500 tokens × $0.00/M = 0 AIC (embedding models produce no output cost)
expect(usage).not.toBeNull();
expect(usage.aiCreditsThisResponse).toBeCloseTo(2.0, 5);
expect(usage.outputCreditsThisResponse).toBe(0);
});

it('resolves text-embedding-3-small-inference via prefix match', () => {
const usage = applyAiCreditsUsage({ input_tokens: 1_000_000, output_tokens: 0 }, 'text-embedding-3-small-inference');
// Same pricing as text-embedding-3-small via prefix match
expect(usage).not.toBeNull();
expect(usage.aiCreditsThisResponse).toBeCloseTo(2.0, 5);
});

it('resolves text-embedding-ada-002 with input-only pricing', () => {
const usage = applyAiCreditsUsage({ input_tokens: 1_000_000, output_tokens: 1000 }, 'text-embedding-ada-002');
// input: 1M × $0.10/M / 10_000 denominator = 10.0 AIC
// output: 1000 tokens × $0.00/M = 0 AIC (embedding models produce no output cost)
expect(usage).not.toBeNull();
expect(usage.aiCreditsThisResponse).toBeCloseTo(10.0, 5);
expect(usage.outputCreditsThisResponse).toBe(0);
});

it('does not reject embedding models when maxAiCredits is active', () => {
process.env.AWF_MAX_AI_CREDITS = '10';
resetAiCreditsGuardForTests();

expect(checkUnknownModelRejection('text-embedding-3-small')).toBeNull();
expect(checkUnknownModelRejection('text-embedding-3-small-inference')).toBeNull();
expect(checkUnknownModelRejection('text-embedding-ada-002')).toBeNull();
});

it('uses bundled models.dev pricing for known catalog models', () => {
const usage = applyAiCreditsUsage({
input_tokens: 100,
Expand Down
Loading