Skip to content

Commit cddf895

Browse files
mikelambertclaudewardpeetMastra Code (anthropic/claude-opus-4-6)DanielSLew
authored
Add User-Agent header for Anthropic API calls (#13087)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: wardpeet <ward@coding-tech.com> Co-authored-by: Mastra Code (anthropic/claude-opus-4-6) <noreply@mastra.ai> Co-authored-by: Daniel Lew <51924260+DanielSLew@users.noreply.github.com>
1 parent b9a77b9 commit cddf895

8 files changed

Lines changed: 81 additions & 37 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@mastra/core': patch
3+
---
4+
5+
Added a `mastra/<version>` User-Agent header to all provider API requests (OpenAI, Anthropic, Google, Mistral, Groq, xAI, DeepSeek, and others) across models.dev, Netlify, and Azure gateways for better traffic attribution.

packages/core/src/llm/model/gateways/azure.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { InMemoryServerCache } from '../../../cache/inmemory.js';
44
import { MastraError } from '../../../error/index.js';
55
import { MastraModelGateway } from './base.js';
66
import type { ProviderConfig } from './base.js';
7+
import { MASTRA_USER_AGENT } from './constants.js';
78

89
interface AzureTokenResponse {
910
token_type: 'Bearer';
@@ -299,10 +300,12 @@ export class AzureOpenAIGateway extends MastraModelGateway {
299300
async resolveLanguageModel({
300301
modelId,
301302
apiKey,
303+
headers,
302304
}: {
303305
modelId: string;
304306
providerId: string;
305307
apiKey: string;
308+
headers?: Record<string, string>;
306309
}): Promise<LanguageModelV2> {
307310
const apiVersion = this.config.apiVersion || '2024-04-01-preview';
308311

@@ -311,6 +314,7 @@ export class AzureOpenAIGateway extends MastraModelGateway {
311314
apiKey,
312315
apiVersion,
313316
useDeploymentBasedUrls: true,
317+
headers: { 'User-Agent': MASTRA_USER_AGENT, ...headers },
314318
})(modelId);
315319
}
316320
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { MASTRA_USER_AGENT } from './constants.js';
3+
4+
describe('MASTRA_USER_AGENT', () => {
5+
it('should be defined', () => {
6+
expect(MASTRA_USER_AGENT).toBeDefined();
7+
});
8+
9+
it('should contain "mastra"', () => {
10+
expect(MASTRA_USER_AGENT).toContain('mastra');
11+
});
12+
13+
it('should match the expected format', () => {
14+
expect(MASTRA_USER_AGENT).toMatch(/^mastra\/\d+\.\d+/);
15+
});
16+
});

packages/core/src/llm/model/gateways/constants.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
declare const __MASTRA_VERSION__: string;
2+
3+
export const MASTRA_USER_AGENT = typeof __MASTRA_VERSION__ !== 'undefined' ? `mastra/${__MASTRA_VERSION__}` : 'mastra';
4+
15
// anything in this list will use the corresponding ai sdk package instead of using openai-compat endpoints
26
export const PROVIDERS_WITH_INSTALLED_PACKAGES = [
37
'anthropic',

packages/core/src/llm/model/gateways/models-dev.ts

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { createOpenRouter } from '@openrouter/ai-sdk-provider-v5';
1515
import { parseModelRouterId } from '../gateway-resolver.js';
1616
import { MastraModelGateway } from './base.js';
1717
import type { GatewayLanguageModel, ProviderConfig } from './base.js';
18-
import { EXCLUDED_PROVIDERS, PROVIDERS_WITH_INSTALLED_PACKAGES } from './constants.js';
18+
import { EXCLUDED_PROVIDERS, MASTRA_USER_AGENT, PROVIDERS_WITH_INSTALLED_PACKAGES } from './constants.js';
1919

2020
interface ModelsDevProviderInfo {
2121
id: string;
@@ -198,47 +198,41 @@ export class ModelsDevGateway extends MastraModelGateway {
198198
}): Promise<GatewayLanguageModel> {
199199
const baseURL = this.buildUrl(`${providerId}/${modelId}`);
200200

201+
const mastraHeaders = { 'User-Agent': MASTRA_USER_AGENT, ...headers };
202+
201203
switch (providerId) {
202204
case 'openai':
203-
return createOpenAI({ apiKey }).responses(modelId);
205+
return createOpenAI({ apiKey, headers: mastraHeaders }).responses(modelId);
204206
case 'gemini':
205207
case 'google':
206-
return createGoogleGenerativeAI({
207-
apiKey,
208-
}).chat(modelId);
208+
return createGoogleGenerativeAI({ apiKey, headers: mastraHeaders }).chat(modelId);
209209
case 'anthropic':
210-
return createAnthropic({ apiKey })(modelId);
210+
return createAnthropic({ apiKey, headers: mastraHeaders })(modelId);
211211
case 'mistral':
212-
return createMistral({ apiKey })(modelId);
212+
return createMistral({ apiKey, headers: mastraHeaders })(modelId);
213213
case 'groq':
214-
return createGroq({ apiKey })(modelId);
214+
return createGroq({ apiKey, headers: mastraHeaders })(modelId);
215215
case 'openrouter':
216-
// Cast needed: @openrouter/ai-sdk-provider@1.2.3 resolves against a newer @ai-sdk/provider
217-
// version than @ai-sdk/provider-v5, causing structural type incompatibility
218-
return createOpenRouter({ apiKey, headers })(modelId) as unknown as GatewayLanguageModel;
216+
return createOpenRouter({ apiKey, headers: mastraHeaders })(modelId);
219217
case 'xai':
220-
return createXai({
221-
apiKey,
222-
})(modelId);
218+
return createXai({ apiKey, headers: mastraHeaders })(modelId);
223219
case 'deepseek':
224-
return createDeepSeek({
225-
apiKey,
226-
})(modelId);
220+
return createDeepSeek({ apiKey, headers: mastraHeaders })(modelId);
227221
case 'perplexity':
228-
return createPerplexity({ apiKey })(modelId);
222+
return createPerplexity({ apiKey, headers: mastraHeaders })(modelId);
229223
case 'cerebras':
230-
return createCerebras({ apiKey })(modelId);
224+
return createCerebras({ apiKey, headers: mastraHeaders })(modelId);
231225
case 'togetherai':
232-
return createTogetherAI({ apiKey })(modelId);
226+
return createTogetherAI({ apiKey, headers: mastraHeaders })(modelId);
233227
case 'deepinfra':
234-
return createDeepInfra({ apiKey })(modelId);
228+
return createDeepInfra({ apiKey, headers: mastraHeaders })(modelId);
235229
case 'vercel':
236-
return createGateway({ apiKey, headers })(modelId);
230+
return createGateway({ apiKey, headers: mastraHeaders })(modelId);
237231
case 'moonshotai':
238232
case 'moonshotai-cn': {
239233
// moonshotai uses Anthropic-compatible API endpoint
240234
if (!baseURL) throw new Error(`No API URL found for ${providerId}/${modelId}`);
241-
return createAnthropic({ apiKey, baseURL })(modelId);
235+
return createAnthropic({ apiKey, baseURL, headers: mastraHeaders })(modelId);
242236
}
243237
default: {
244238
// Check if this provider uses a specific SDK package (e.g., kimi-for-coding uses @ai-sdk/anthropic)
@@ -247,28 +241,32 @@ export class ModelsDevGateway extends MastraModelGateway {
247241

248242
if (npm === '@ai-sdk/anthropic') {
249243
if (!baseURL) throw new Error(`No API URL found for ${providerId}/${modelId}`);
250-
return createAnthropic({ apiKey, baseURL })(modelId);
244+
return createAnthropic({ apiKey, baseURL, headers: mastraHeaders })(modelId);
251245
}
252246

253247
if (npm === '@ai-sdk/openai') {
254248
if (!baseURL) throw new Error(`No API URL found for ${providerId}/${modelId}`);
255-
return createOpenAI({ apiKey, baseURL }).chat(modelId);
249+
return createOpenAI({ apiKey, baseURL, headers: mastraHeaders }).chat(modelId);
256250
}
257251

258252
if (npm === '@ai-sdk/google') {
259253
if (!baseURL) throw new Error(`No API URL found for ${providerId}/${modelId}`);
260-
return createGoogleGenerativeAI({ apiKey, baseURL }).chat(modelId);
254+
return createGoogleGenerativeAI({ apiKey, baseURL, headers: mastraHeaders }).chat(modelId);
261255
}
262256

263257
if (npm === '@ai-sdk/mistral') {
264258
if (!baseURL) throw new Error(`No API URL found for ${providerId}/${modelId}`);
265-
return createMistral({ apiKey, baseURL })(modelId);
259+
return createMistral({ apiKey, baseURL, headers: mastraHeaders })(modelId);
266260
}
267261

268262
if (!baseURL) throw new Error(`No API URL found for ${providerId}/${modelId}`);
269-
return createOpenAICompatible({ name: providerId, apiKey, baseURL, supportsStructuredOutputs: true }).chatModel(
270-
modelId,
271-
);
263+
return createOpenAICompatible({
264+
name: providerId,
265+
apiKey,
266+
baseURL,
267+
headers: mastraHeaders,
268+
supportsStructuredOutputs: true,
269+
}).chatModel(modelId);
272270
}
273271
}
274272
}

packages/core/src/llm/model/gateways/netlify.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { InMemoryServerCache } from '../../../cache/inmemory.js';
77
import { MastraError } from '../../../error/index.js';
88
import { MastraModelGateway } from './base.js';
99
import type { ProviderConfig } from './base.js';
10+
import { MASTRA_USER_AGENT } from './constants.js';
1011

1112
interface NetlifyProviderResponse {
1213
token_env_var: string;
@@ -187,16 +188,18 @@ export class NetlifyGateway extends MastraModelGateway {
187188
}): Promise<LanguageModelV2> {
188189
const baseURL = await this.buildUrl(`${providerId}/${modelId}`);
189190

191+
const mastraHeaders = { 'User-Agent': MASTRA_USER_AGENT, ...headers };
192+
190193
switch (providerId) {
191194
case 'openai':
192-
return createOpenAI({ apiKey, baseURL, headers }).responses(modelId);
195+
return createOpenAI({ apiKey, baseURL, headers: mastraHeaders }).responses(modelId);
193196
case 'gemini':
194197
return createGoogleGenerativeAI({
195198
baseURL: `${baseURL}/v1beta/`,
196199
apiKey,
197200
headers: {
198201
'user-agent': 'google-genai-sdk/',
199-
...(headers ? headers : {}),
202+
...mastraHeaders,
200203
},
201204
}).chat(modelId);
202205
case 'anthropic':
@@ -205,14 +208,17 @@ export class NetlifyGateway extends MastraModelGateway {
205208
baseURL: `${baseURL}/v1/`,
206209
headers: {
207210
'anthropic-version': '2023-06-01',
208-
'user-agent': 'anthropic/',
209-
...(headers ? headers : {}),
211+
...mastraHeaders,
210212
},
211213
})(modelId);
212214
default:
213-
return createOpenAICompatible({ name: providerId, apiKey, baseURL, supportsStructuredOutputs: true }).chatModel(
214-
modelId,
215-
);
215+
return createOpenAICompatible({
216+
name: providerId,
217+
apiKey,
218+
baseURL,
219+
headers: mastraHeaders,
220+
supportsStructuredOutputs: true,
221+
}).chatModel(modelId);
216222
}
217223
}
218224
}

packages/core/tsup.config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { generateTypes } from '@internal/types-builder';
55
import { defineConfig } from 'tsup';
66
import type { Options } from 'tsup';
77

8+
const pkg = JSON.parse(fs.readFileSync(path.join(import.meta.dirname, 'package.json'), 'utf-8'));
9+
810
import treeshakeDecoratorsBabelPlugin from './tools/treeshake-decorators';
911

1012
type Plugin = NonNullable<Options['plugins']>[number];
@@ -69,6 +71,9 @@ export default defineConfig({
6971
preset: 'smallest',
7072
},
7173
plugins: [treeshakeDecorators],
74+
define: {
75+
__MASTRA_VERSION__: JSON.stringify(pkg.version),
76+
},
7277
sourcemap: true,
7378
onSuccess: async () => {
7479
await new Promise(resolve => setTimeout(resolve, 1000));

packages/core/vitest.config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1+
import fs from 'node:fs';
12
import path from 'node:path';
23
import { defineConfig } from 'vitest/config';
34

5+
const pkg = JSON.parse(fs.readFileSync(path.resolve(__dirname, 'package.json'), 'utf-8'));
6+
47
export default defineConfig({
8+
define: {
9+
__MASTRA_VERSION__: JSON.stringify(pkg.version),
10+
},
511
resolve: {
612
alias: {
713
'@internal/workflow-test-utils': path.resolve(__dirname, '../../workflows/_test-utils/src'),

0 commit comments

Comments
 (0)