Skip to content

Commit c68df9f

Browse files
Sync public snapshot from freebuff-private
Source: CodebuffAI/freebuff-private@5b61ef9971492a9e02df1d1168b60f593612ff79
1 parent 61c718e commit c68df9f

6 files changed

Lines changed: 118 additions & 54 deletions

File tree

‎bun.lock‎

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

‎freebuff/cli/release/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "freebuff",
3-
"version": "0.2.27",
3+
"version": "0.2.28",
44
"description": "The world's strongest free coding agent",
55
"license": "MIT",
66
"bin": {

‎sdk/src/__tests__/client.test.ts‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,28 @@ describe('CodebuffClient', () => {
3131
expect(mockFetch).toHaveBeenCalledTimes(1)
3232
})
3333

34+
test('a BYOK Anthropic connection is probed at /models with the version header its native API requires', async () => {
35+
const seen: Array<{ url: string; version: string | null; auth: string | null }> = []
36+
const mockFetch = mock((input: URL, init?: RequestInit) => {
37+
const headers = new Headers(init?.headers)
38+
seen.push({ url: String(input), version: headers.get('anthropic-version'), auth: headers.get('authorization') })
39+
return Promise.resolve({ ok: true, body: null } as Response)
40+
})
41+
setFetchMock(mockFetch)
42+
const client = new CodebuffClient({
43+
byok: {
44+
id: 'c', revision: 1, name: 'Anthropic', provider: 'openai-compatible',
45+
baseUrl: 'https://api.anthropic.com/v1', model: 'claude-sonnet-4-5',
46+
credentialRef: 'env:ANTHROPIC_API_KEY', createdAt: '2026-10-12T00:00:00.000Z',
47+
updatedAt: '2026-10-12T00:00:00.000Z', apiKey: 'sk-ant-test',
48+
},
49+
})
50+
expect(await client.checkConnection()).toBe(true)
51+
expect(seen).toEqual([
52+
{ url: 'https://api.anthropic.com/v1/models', version: '2023-06-01', auth: 'Bearer sk-ant-test' },
53+
])
54+
})
55+
3456
test('returns false when response is not ok', async () => {
3557
const mockFetch = mock(() =>
3658
Promise.resolve({

‎sdk/src/byok.test.ts‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,25 @@ describe('BYOK connection lifecycle', () => {
316316
expect(JSON.stringify(result)).not.toContain(input.apiKey)
317317
})
318318

319+
test('Anthropic is asked with the version header its native /models requires', async () => {
320+
const seen: Array<Record<string, string | null>> = []
321+
const { store } = fixture((async (_url: unknown, init?: RequestInit) => {
322+
const headers = new Headers(init?.headers)
323+
seen.push({
324+
auth: headers.get('authorization'),
325+
version: headers.get('anthropic-version'),
326+
})
327+
return Response.json({ data: [{ id: 'claude-sonnet-4-5' }] })
328+
}) as unknown as typeof fetch)
329+
const added = await store.create({
330+
...proxy,
331+
baseUrl: 'https://api.anthropic.com/v1',
332+
model: 'claude-sonnet-4-5',
333+
})
334+
expect(await store.validate(added)).toMatchObject({ ok: true, modelListed: true })
335+
expect(seen).toEqual([{ auth: `Bearer ${input.apiKey}`, version: '2023-06-01' }])
336+
})
337+
319338
test('a listing without ids makes no claim about the model', async () => {
320339
const { store } = fixture(
321340
(async () => new Response('ok')) as unknown as typeof fetch,

‎sdk/src/byok.ts‎

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,24 @@ export function normalizeByokBaseUrl(
232232
}
233233
return url.toString().replace(/\/+$/, '')
234234
}
235+
/**
236+
* Headers for a provider's `/models` listing. OpenRouter lists without a key.
237+
* Anthropic's OpenAI-compatible endpoint answers `/chat/completions` on the
238+
* bearer alone but its `/models` is the native API, which refuses any request
239+
* without `anthropic-version` (HTTP 400 "anthropic-version: header is required").
240+
*/
241+
export function byokListingHeaders(
242+
connection: Pick<ResolvedByokConnection, 'provider' | 'apiKey'>,
243+
base: string,
244+
): Record<string, string> {
245+
if (connection.provider === 'openrouter') return {}
246+
const headers: Record<string, string> = {
247+
Authorization: `Bearer ${connection.apiKey}`,
248+
}
249+
if (new URL(base).hostname === 'api.anthropic.com')
250+
headers['anthropic-version'] = '2023-06-01'
251+
return headers
252+
}
235253
export function byokCompletionUrl(
236254
connection: Pick<ByokConnection, 'provider' | 'baseUrl'>,
237255
): string {
@@ -433,10 +451,7 @@ export async function discoverByokContextWindow(
433451
const window = (async () => {
434452
try {
435453
const response = await fetchImpl(`${base}/models`, {
436-
headers:
437-
connection.provider === 'openrouter'
438-
? {}
439-
: { Authorization: `Bearer ${connection.apiKey}` },
454+
headers: byokListingHeaders(connection, base),
440455
redirect: 'error',
441456
signal: AbortSignal.timeout(DISCOVERY_TIMEOUT_MS),
442457
})
@@ -1053,7 +1068,10 @@ export function createByokConnectionStore(params: {
10531068
const response = await fetchImpl(
10541069
base + (resolved.provider === 'openrouter' ? '/key' : '/models'),
10551070
{
1056-
headers: { Authorization: `Bearer ${resolved.apiKey}` },
1071+
headers: {
1072+
Authorization: `Bearer ${resolved.apiKey}`,
1073+
...byokListingHeaders(resolved, base),
1074+
},
10571075
redirect: 'error',
10581076
signal: AbortSignal.timeout(10_000),
10591077
},

‎sdk/src/client.ts‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { API_KEY_ENV_VAR } from '@codebuff/common/constants/paths'
33
import { getWebsiteUrl } from './constants'
44
import { getCodebuffApiKeyFromEnv } from './env'
55
import { run } from './run'
6-
import { normalizeByokBaseUrl } from './byok'
6+
import { byokListingHeaders, normalizeByokBaseUrl } from './byok'
77

88
import type { RunOptions, CodebuffClientOptions } from './run'
99
import type { RunState } from './run-state'
@@ -87,7 +87,10 @@ export class CodebuffClient {
8787
this.options.byok.baseUrl,
8888
)
8989
const response = await fetch(new URL(this.options.byok.provider === 'openrouter' ? 'key' : 'models', baseUrl.replace(/\/?$/, '/')), {
90-
headers: { Authorization: `Bearer ${this.options.byok.apiKey}` },
90+
headers: {
91+
Authorization: `Bearer ${this.options.byok.apiKey}`,
92+
...byokListingHeaders(this.options.byok, baseUrl),
93+
},
9194
signal: AbortSignal.timeout(5000),
9295
redirect: 'error',
9396
})

0 commit comments

Comments
 (0)