Skip to content

Commit c3d72c6

Browse files
Sync public snapshot from freebuff-private
Source: CodebuffAI/freebuff-private@815720e09517a90bbcc1679d8fb0d7eca4f0ba47
1 parent 851e290 commit c3d72c6

5 files changed

Lines changed: 65 additions & 19 deletions

File tree

‎bun.lock‎

Lines changed: 1 addition & 3 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.14",
3+
"version": "0.2.15",
44
"description": "The world's strongest free coding agent",
55
"license": "MIT",
66
"bin": {

‎sdk/src/__tests__/byok-run.test.ts‎

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import path from 'node:path'
55
import { afterEach, describe, expect, test } from 'bun:test'
66

77
import { CodebuffClient } from '../client'
8-
import { createByokConnectionStore, type ByokConnection } from '../byok'
8+
import { ByokConnectionStateError, createByokConnectionStore, type ByokConnection } from '../byok'
99
import { getWebsiteUrl } from '../constants'
1010
import type { RunState } from '../run-state'
1111

@@ -319,21 +319,58 @@ describe('direct BYOK SDK runs', () => {
319319
expect(requests.every((url) => url === 'http://127.0.0.1:9876/v1/chat/completions')).toBe(true)
320320
}, 30_000)
321321

322-
test('stops before provider dispatch when a saved connection was replaced or removed', async () => {
322+
test.each([
323+
['changed', 'BYOK connection changed; select its current revision to continue'],
324+
['removed', 'BYOK connection was removed; select another provider or a Freebuff model to continue'],
325+
['corrupt', 'BYOK connection metadata is corrupt; restore it before continuing'],
326+
['locked', 'BYOK settings are locked. Close other Freebuff processes and retry.'],
327+
['unknown', 'Could not verify the saved BYOK connection. Reselect the provider and retry.'],
328+
])('reports a %s local connection failure without provider dispatch', async (failure, expected) => {
323329
let requests = 0
324330
globalThis.fetch = (async () => {
325331
requests += 1
326332
return sse({ id: 'unexpected', object: 'chat.completion.chunk', created: 1, model: 'm', choices: [] })
327333
}) as unknown as typeof fetch
334+
let records: ByokConnection[] = []
335+
let failCheck = false
336+
const store = createByokConnectionStore({
337+
metadataStore: {
338+
async get() { return records },
339+
async set(next) { records = next },
340+
async withLock(operation) {
341+
if (failCheck && failure === 'locked')
342+
throw new ByokConnectionStateError(expected)
343+
if (failCheck && failure === 'unknown')
344+
throw new Error('provider-key-canary must not leak')
345+
return operation()
346+
},
347+
},
348+
secretStore: {
349+
async get() { return 'provider-key-canary' },
350+
async set() {},
351+
async delete() {},
352+
},
353+
})
354+
const saved = await store.create({
355+
name: 'Local failure test', provider: 'openrouter', model: 'scripted/model',
356+
credentialRef: 'env:PROVIDER_KEY',
357+
})
358+
const resolved = await store.resolve(saved)
359+
if (failure === 'changed') await store.update({ ...saved, patch: { model: 'new/model' } })
360+
if (failure === 'removed') await store.remove(saved)
361+
if (failure === 'corrupt') records = [{ ...saved, model: '' }]
362+
failCheck = true
328363
const client = new CodebuffClient({
329364
agentDefinitions: [agent],
330-
byok: connection({ assertCurrent: async () => { throw new Error('connection revision no longer current') } }),
365+
byok: resolved,
331366
})
332367

333-
const result = await client.run({ agent: agent.id, prompt: 'Do not use the replaced connection.' })
368+
const result = await client.run({ agent: agent.id, prompt: 'Do not use the unavailable connection.' })
334369
expect(result.output.type).toBe('error')
335370
if (result.output.type !== 'error') throw new Error('expected BYOK connection error')
336-
expect(result.output.message).toContain('Could not connect to the BYOK provider')
371+
expect(result.output.message).toContain(expected)
372+
expect(result.output.message).not.toContain('provider-key-canary')
373+
expect(result.output.message).not.toContain('network connection')
337374
expect(requests).toBe(0)
338375
}, 30_000)
339376

‎sdk/src/byok.ts‎

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,11 @@ export class ByokCredentialError extends Error {
125125
override name = 'ByokCredentialError'
126126
}
127127

128+
/** A local connection-state failure with a safe, SDK-authored message. */
129+
export class ByokConnectionStateError extends Error {
130+
override name = 'ByokConnectionStateError'
131+
}
132+
128133
/** The environment variable a connection reads its key from, if any. */
129134
export function byokCredentialVariable(
130135
connection: Pick<ByokConnection, 'credentialRef'>,
@@ -196,11 +201,11 @@ export function byokCompletionUrl(
196201
function parseConnections(value: unknown): ByokConnection[] {
197202
const parsed = z.array(connectionSchema).safeParse(value)
198203
if (!parsed.success)
199-
throw new Error(
204+
throw new ByokConnectionStateError(
200205
'BYOK connection metadata is corrupt; restore it before continuing',
201206
)
202207
if (new Set(parsed.data.map((item) => item.id)).size !== parsed.data.length) {
203-
throw new Error('BYOK connection metadata contains duplicate connections')
208+
throw new ByokConnectionStateError('BYOK connection metadata contains duplicate connections')
204209
}
205210
for (const item of parsed.data) {
206211
byokModelLimits(item)
@@ -209,7 +214,7 @@ function parseConnections(value: unknown): ByokConnection[] {
209214
!envReference.test(item.credentialRef) &&
210215
!item.credentialRef.startsWith(`connection:${item.id}:`)
211216
) {
212-
throw new Error(
217+
throw new ByokConnectionStateError(
213218
'BYOK credential reference belongs to a different connection',
214219
)
215220
}
@@ -660,11 +665,11 @@ export function createByokConnectionStore(params: {
660665
const connections = await read()
661666
const connection = connections.find((item) => item.id === id)
662667
if (!connection)
663-
throw new Error(
668+
throw new ByokConnectionStateError(
664669
'BYOK connection was removed; select another provider or a Freebuff model to continue',
665670
)
666671
if (connection.revision !== revision)
667-
throw new Error(
672+
throw new ByokConnectionStateError(
668673
'BYOK connection changed; select its current revision to continue',
669674
)
670675
return { connection, connections }
@@ -979,7 +984,7 @@ export function createBunByokMetadataStore(
979984
return parseConnections(JSON.parse(await fs.readFile(file, 'utf8')))
980985
} catch (error) {
981986
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return []
982-
throw new Error(
987+
throw new ByokConnectionStateError(
983988
'Cannot read BYOK connection metadata; restore the configuration before continuing',
984989
)
985990
}
@@ -1024,7 +1029,7 @@ export function createBunByokMetadataStore(
10241029
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error
10251030
await recoverByokLock(lock)
10261031
if (Date.now() - started >= 5000)
1027-
throw new Error(
1032+
throw new ByokConnectionStateError(
10281033
'BYOK settings are locked. Close other Freebuff processes and retry. If the problem persists, close all Freebuff processes before removing connections.lock from the BYOK configuration directory.',
10291034
)
10301035
await new Promise((resolve) => setTimeout(resolve, 25))

‎sdk/src/impl/model-provider.ts‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { APICallError } from 'ai'
2121
import { byokReasoningRetryBody, byokRequestTransform } from './byok-request'
2222
import { getWebsiteUrl } from '../constants'
2323
import { getByokOpenrouterApiKeyFromEnv } from '../env'
24-
import { byokCompletionUrl, byokCredentialVariable } from '../byok'
24+
import { ByokConnectionStateError, byokCompletionUrl, byokCredentialVariable } from '../byok'
2525
import type { ResolvedByokConnection } from '../byok'
2626

2727
import type { LanguageModel } from 'ai'
@@ -401,8 +401,14 @@ export function getModelForRequest({
401401
// Revocation is a durable policy decision, never a transport retry.
402402
try {
403403
await byok.assertCurrent?.()
404-
} catch {
405-
throw new Error(BYOK_CONNECTION_FAILURE_MESSAGE)
404+
} catch (error) {
405+
// Only our typed state errors carry display-safe messages. Custom
406+
// resolvers and filesystem errors may contain credentials or paths.
407+
throw new Error(
408+
error instanceof ByokConnectionStateError
409+
? error.message
410+
: 'Could not verify the saved BYOK connection. Reselect the provider and retry. If the problem persists, fully quit and restart Freebuff.',
411+
)
406412
}
407413
try {
408414
const send = (init: RequestInit | undefined) =>

0 commit comments

Comments
 (0)