Skip to content

Commit 3aed04c

Browse files
aayush-kapoorgr2m
andauthored
feat(provider/openai-compatible): set user-agent header for createOpenAICompatible (#8616)
## Background As added for other providers/provider-utils/`ai`, we're setting user-agent property for `openai-compatible` ## Summary - added a `version.ts` file to extract that package's version - inject that version number to the user-agent string when we build and test this package - modified the `getHeaders()` function so that we append this package's string to existing user-agent header (if any) ## Manual Verification updated test file `openai-compatible-provider.test.ts` to ensure the package version is being attached to the header ## Tasks - [x] Tests have been added / updated (for bug fixes / features) - [ ] Documentation has been added / updated (for bug fixes / features) - [x] A _patch_ changeset for relevant packages has been added (for bug fixes / features - run `pnpm changeset` in the project root) - [x] Formatting issues have been fixed (run `pnpm prettier-fix` in the project root) ## Related Issues Fixes #8612 --------- Co-authored-by: Gregor Martynus <[email protected]>
1 parent 0294b58 commit 3aed04c

File tree

9 files changed

+77
-19
lines changed

9 files changed

+77
-19
lines changed

.changeset/soft-moons-tie.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@ai-sdk/openai-compatible': patch
3+
---
4+
5+
feat(provider/openai-compatible): set `user-agent` header for `createOpenAICompatible`

packages/openai-compatible/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@ export type {
2424
OpenAICompatibleProvider,
2525
OpenAICompatibleProviderSettings,
2626
} from './openai-compatible-provider';
27+
export { VERSION } from './version';

packages/openai-compatible/src/openai-compatible-provider.test.ts

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ import { OpenAICompatibleCompletionLanguageModel } from './completion/openai-com
44
import { OpenAICompatibleEmbeddingModel } from './embedding/openai-compatible-embedding-model';
55
import { vi, describe, beforeEach, it, expect } from 'vitest';
66

7+
// Mock version
8+
vi.mock('./version', () => ({
9+
VERSION: '0.0.0-test',
10+
}));
11+
712
const OpenAICompatibleChatLanguageModelMock = vi.mocked(
813
OpenAICompatibleChatLanguageModel,
914
);
@@ -37,7 +42,7 @@ describe('OpenAICompatibleProvider', () => {
3742
baseURL: 'https://api.example.com',
3843
name: 'test-provider',
3944
apiKey: 'test-api-key',
40-
headers: { 'Custom-Header': 'value' },
45+
headers: { 'custom-header': 'value' },
4146
queryParams: { 'Custom-Param': 'value' },
4247
};
4348

@@ -50,20 +55,21 @@ describe('OpenAICompatibleProvider', () => {
5055
const headers = config.headers();
5156

5257
expect(headers).toEqual({
53-
Authorization: 'Bearer test-api-key',
54-
'Custom-Header': 'value',
58+
authorization: 'Bearer test-api-key',
59+
'custom-header': 'value',
60+
'user-agent': 'ai-sdk/openai-compatible/0.0.0-test',
5561
});
5662
expect(config.provider).toBe('test-provider.chat');
5763
expect(config.url({ modelId: 'model-id', path: '/v1/chat' })).toBe(
5864
'https://api.example.com/v1/chat?Custom-Param=value',
5965
);
6066
});
6167

62-
it('should create headers without Authorization when no apiKey provided', () => {
68+
it('should create headers without authorization when no apiKey provided', () => {
6369
const options = {
6470
baseURL: 'https://api.example.com',
6571
name: 'test-provider',
66-
headers: { 'Custom-Header': 'value' },
72+
headers: { 'custom-header': 'value' },
6773
};
6874

6975
const provider = createOpenAICompatible(options);
@@ -75,7 +81,8 @@ describe('OpenAICompatibleProvider', () => {
7581
const headers = config.headers();
7682

7783
expect(headers).toEqual({
78-
'Custom-Header': 'value',
84+
'custom-header': 'value',
85+
'user-agent': 'ai-sdk/openai-compatible/0.0.0-test',
7986
});
8087
});
8188
});
@@ -85,7 +92,7 @@ describe('OpenAICompatibleProvider', () => {
8592
baseURL: 'https://api.example.com',
8693
name: 'test-provider',
8794
apiKey: 'test-api-key',
88-
headers: { 'Custom-Header': 'value' },
95+
headers: { 'custom-header': 'value' },
8996
queryParams: { 'Custom-Param': 'value' },
9097
};
9198

@@ -100,8 +107,9 @@ describe('OpenAICompatibleProvider', () => {
100107
const headers = config.headers();
101108

102109
expect(headers).toEqual({
103-
Authorization: 'Bearer test-api-key',
104-
'Custom-Header': 'value',
110+
authorization: 'Bearer test-api-key',
111+
'custom-header': 'value',
112+
'user-agent': 'ai-sdk/openai-compatible/0.0.0-test',
105113
});
106114
expect(config.provider).toBe('test-provider.chat');
107115
expect(config.url({ modelId: 'model-id', path: '/v1/chat' })).toBe(
@@ -120,8 +128,9 @@ describe('OpenAICompatibleProvider', () => {
120128
const headers = config.headers();
121129

122130
expect(headers).toEqual({
123-
Authorization: 'Bearer test-api-key',
124-
'Custom-Header': 'value',
131+
authorization: 'Bearer test-api-key',
132+
'custom-header': 'value',
133+
'user-agent': 'ai-sdk/openai-compatible/0.0.0-test',
125134
});
126135
expect(config.provider).toBe('test-provider.completion');
127136
expect(
@@ -139,8 +148,9 @@ describe('OpenAICompatibleProvider', () => {
139148
const headers = config.headers();
140149

141150
expect(headers).toEqual({
142-
Authorization: 'Bearer test-api-key',
143-
'Custom-Header': 'value',
151+
authorization: 'Bearer test-api-key',
152+
'custom-header': 'value',
153+
'user-agent': 'ai-sdk/openai-compatible/0.0.0-test',
144154
});
145155
expect(config.provider).toBe('test-provider.embedding');
146156
expect(

packages/openai-compatible/src/openai-compatible-provider.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,17 @@ import {
44
LanguageModelV2,
55
ProviderV2,
66
} from '@ai-sdk/provider';
7-
import { FetchFunction, withoutTrailingSlash } from '@ai-sdk/provider-utils';
7+
import {
8+
FetchFunction,
9+
withoutTrailingSlash,
10+
withUserAgentSuffix,
11+
getRuntimeEnvironmentUserAgent,
12+
} from '@ai-sdk/provider-utils';
813
import { OpenAICompatibleChatLanguageModel } from './chat/openai-compatible-chat-language-model';
914
import { OpenAICompatibleCompletionLanguageModel } from './completion/openai-compatible-completion-language-model';
1015
import { OpenAICompatibleEmbeddingModel } from './embedding/openai-compatible-embedding-model';
1116
import { OpenAICompatibleImageModel } from './image/openai-compatible-image-model';
17+
import { VERSION } from './version';
1218

1319
export interface OpenAICompatibleProvider<
1420
CHAT_MODEL_IDS extends string = string,
@@ -96,10 +102,13 @@ export function createOpenAICompatible<
96102
fetch?: FetchFunction;
97103
}
98104

99-
const getHeaders = () => ({
105+
const headers = {
100106
...(options.apiKey && { Authorization: `Bearer ${options.apiKey}` }),
101107
...options.headers,
102-
});
108+
};
109+
110+
const getHeaders = () =>
111+
withUserAgentSuffix(headers, `ai-sdk/openai-compatible/${VERSION}`);
103112

104113
const getCommonModelConfig = (modelType: string): CommonModelConfig => ({
105114
provider: `${providerName}.${modelType}`,
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
declare const __PACKAGE_VERSION__: string | undefined;
2+
export const VERSION: string =
3+
typeof __PACKAGE_VERSION__ !== 'undefined'
4+
? __PACKAGE_VERSION__
5+
: '0.0.0-test';

packages/openai-compatible/tsup.config.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,24 @@ export default defineConfig([
66
format: ['cjs', 'esm'],
77
dts: true,
88
sourcemap: true,
9+
define: {
10+
__PACKAGE_VERSION__: JSON.stringify(
11+
(await import('./package.json', { with: { type: 'json' } })).default
12+
.version,
13+
),
14+
},
915
},
1016
{
1117
entry: ['src/internal/index.ts'],
1218
outDir: 'dist/internal',
1319
format: ['cjs', 'esm'],
1420
dts: true,
1521
sourcemap: true,
22+
define: {
23+
__PACKAGE_VERSION__: JSON.stringify(
24+
(await import('./package.json', { with: { type: 'json' } })).default
25+
.version,
26+
),
27+
},
1628
},
1729
]);

packages/openai-compatible/vitest.edge.config.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
import { defineConfig } from 'vite';
2+
import { readFileSync } from 'node:fs';
3+
4+
const version = JSON.parse(
5+
readFileSync(new URL('./package.json', import.meta.url), 'utf-8'),
6+
).version;
27

38
// https://vitejs.dev/config/
49
export default defineConfig({
10+
define: {
11+
__PACKAGE_VERSION__: JSON.stringify(version),
12+
},
513
test: {
614
environment: 'edge-runtime',
715
include: ['**/*.test.ts', '**/*.test.tsx'],

packages/openai-compatible/vitest.node.config.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
import { defineConfig } from 'vite';
2+
import { readFileSync } from 'node:fs';
3+
4+
const version = JSON.parse(
5+
readFileSync(new URL('./package.json', import.meta.url), 'utf-8'),
6+
).version;
27

38
// https://vitejs.dev/config/
49
export default defineConfig({
10+
define: {
11+
__PACKAGE_VERSION__: JSON.stringify(version),
12+
},
513
test: {
614
environment: 'node',
715
include: ['**/*.test.ts', '**/*.test.tsx'],

pnpm-lock.yaml

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)