feat(models): bridge Chat Completions and Responses through Claude runtime
This commit is contained in:
1 parent
8fcfb69408
commit
d773961168
21 files changed
+1346
-68
No files matched your search
@@ -0,0 +1,246 @@
|
||||
/** Offline integration only. NEVER imports/runs the installed Claude SDK or opens a network port. */
|
||||
import assert from 'node:assert/strict'
|
||||
import { mkdtemp, rm } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join, resolve } from 'node:path'
|
||||
import { pathToFileURL } from 'node:url'
|
||||
import { build } from 'esbuild'
|
||||
|
||||
const temp = await mkdtemp(join(tmpdir(), 'ycode-claude-bridge-offline-'))
|
||||
const queries = [], leases = [], kernels = []
|
||||
let queryFailure = false, runtimeFailure = false, grantFailure = false, acquireGate
|
||||
const deferred = () => { let resolve; const promise = new Promise(r => { resolve = r }); return { promise, resolve } }
|
||||
const tick = () => new Promise(r => setImmediate(r))
|
||||
const mocks = {
|
||||
electron: { app: { getPath: () => temp, getAppPath: () => temp } },
|
||||
'@anthropic-ai/claude-agent-sdk': {
|
||||
createSdkMcpServer: config => config,
|
||||
tool: (name, description, schema, handler) => ({ name, description, schema, handler }),
|
||||
query: request => {
|
||||
if (queryFailure) throw new Error('mock SDK startup failure')
|
||||
const ended = deferred()
|
||||
const instance = {
|
||||
closeCount: 0, interrupt: async () => {},
|
||||
close() { this.closeCount++; ended.resolve() },
|
||||
async *[Symbol.asyncIterator]() { await ended.promise },
|
||||
applyFlagSettings: async () => { if (grantFailure) throw new Error('mock grant replay failure') }, setPermissionMode: async () => {},
|
||||
getContextUsage: async () => ({ categories: [] })
|
||||
}
|
||||
queries.push({ ...request, instance })
|
||||
return instance
|
||||
}
|
||||
},
|
||||
'private-runtimes': { privateRuntimeEnvironment: async env => { if (runtimeFailure) throw new Error('mock runtime failure'); return env } },
|
||||
'agent/tools/browser': { browserTool: { description: 'inert browser', run: () => { throw new Error('Unexpected browser execution') } } },
|
||||
'capabilities/claude-capabilities': { createClaudeCapabilityRuntime: () => ({ server: {}, setRequestSelection() {} }) },
|
||||
'model-bridge': { acquireModelBridge: async model => {
|
||||
const id = leases.length + 1
|
||||
const lease = { model: structuredClone(model), baseUrl: `http://127.0.0.1:${24000 + id}/lease-${id}`, apiKey: `mock-local-token-${id}`, closeCount: 0, async close() { this.closeCount++ } }
|
||||
leases.push(lease)
|
||||
if (acquireGate) await acquireGate.promise
|
||||
return lease
|
||||
} }
|
||||
}
|
||||
const portKeys = new Map(Object.keys(mocks).map(key => [key === 'electron' || key.startsWith('@') ? key : resolve('src/main', key), key]))
|
||||
globalThis.__claudeBridgeOfflineMocks = mocks
|
||||
let checks = 0
|
||||
const check = async (name, body) => { await body(); checks++; console.log(`PASS ${name}`) }
|
||||
function model(api = 'openai-completions', suffix = '') {
|
||||
return { profile: { id: `offline${suffix}`, name: 'Offline', providerId: 'offline', model: `model${suffix}`, baseUrl: 'https://invalid.example/v1', authMode: 'api-key', apiPreference: api, protocol: api === 'anthropic-messages' ? 'anthropic' : 'openai', contextWindowTokens: 100000 }, config: { api, model: `model${suffix}`, baseUrl: 'https://invalid.example/v1', apiKey: `FAKE-NOT-A-CREDENTIAL${suffix}` } }
|
||||
}
|
||||
const options = (id, value = model()) => ({ sessionId: id, workspaceRoot: temp, model: value, permissionMode: 'default-sandbox', agentMode: 'craft', onSessionId() {} })
|
||||
try {
|
||||
const bundle = join(temp, 'kernel.mjs')
|
||||
await build({ stdin: { contents: "export * from './src/main/agent/claude/claude-kernel'; export { rememberSessionGrant } from './src/main/agent/claude/session-grants'", resolveDir: process.cwd(), loader: 'ts' }, outfile: bundle, bundle: true, platform: 'node', format: 'esm', logLevel: 'warning', metafile: true, plugins: [{ name: 'strict-offline-ports', setup(b) {
|
||||
b.onResolve({ filter: /.*/ }, args => {
|
||||
const path = args.path.startsWith('.') ? resolve(args.resolveDir, args.path).replace(/\.(ts|js)$/, '').replace(/\/index$/, '') : args.path
|
||||
const key = portKeys.get(path)
|
||||
if (key) return { path: key, namespace: 'offline' }
|
||||
if (args.path.includes('claude-agent-sdk')) throw new Error('Actual SDK resolution forbidden')
|
||||
})
|
||||
b.onLoad({ filter: /.*/, namespace: 'offline' }, args => ({ contents: Object.keys(mocks[args.path]).map(name => `export const ${name} = globalThis.__claudeBridgeOfflineMocks[${JSON.stringify(args.path)}][${JSON.stringify(name)}]`).join('\n'), loader: 'js' }))
|
||||
} }] }).then(result => {
|
||||
assert(!Object.keys(result.metafile.inputs).some(path => path.includes('node_modules/@anthropic-ai/')), 'real SDK must never enter bundle')
|
||||
assert(!Object.keys(result.metafile.inputs).some(path => /src\/main\/model-bridge\//.test(path) && !path.endsWith('/model-bridge/paths.ts')), 'real network bridge must never enter bundle')
|
||||
})
|
||||
const { ClaudeKernel, getClaudeKernel, disposeClaudeKernels, rememberSessionGrant } = await import(pathToFileURL(bundle).href)
|
||||
const make = opts => { const kernel = new ClaudeKernel(opts); kernels.push(kernel); return kernel }
|
||||
let baseline
|
||||
await check('both OpenAI APIs use isolated bridge env, not upstream credentials', async () => {
|
||||
for (const api of ['openai-completions', 'openai-responses']) {
|
||||
const value = model(api), kernel = make(options(api, value)), before = leases.length
|
||||
await kernel.ensureStarted()
|
||||
const lease = leases.at(-1), request = queries.at(-1)
|
||||
assert.equal(leases.length, before + 1)
|
||||
assert.deepEqual(lease.model, { ...value, config: { ...value.config, thinkingLevel: 'off' } })
|
||||
assert.equal(request.options.env.ANTHROPIC_BASE_URL, lease.baseUrl)
|
||||
assert.equal(request.options.env.ANTHROPIC_API_KEY, lease.apiKey)
|
||||
assert(!JSON.stringify(request.options.env).includes(value.config.apiKey))
|
||||
assert.equal(request.options.model, value.config.model)
|
||||
baseline ??= request.options
|
||||
await kernel.close(); assert.equal(lease.closeCount, 1)
|
||||
await kernel.close(); assert.equal(lease.closeCount, 1)
|
||||
}
|
||||
})
|
||||
await check('bridge lease receives effective low/off thinking without mutating resolved config', async () => {
|
||||
for (const api of ['openai-completions', 'openai-responses']) {
|
||||
for (const [requested, reasoning, expected] of [['low', true, 'low'], ['off', true, 'off'], [undefined, false, 'off']]) {
|
||||
const value = model(api)
|
||||
value.profile.supportsReasoning = reasoning
|
||||
value.profile.supportedEfforts = ['low', 'high']
|
||||
value.config.thinkingLevel = 'high'
|
||||
const kernel = make({ ...options(`thinking-${api}-${requested}`, value), thinkingLevel: requested })
|
||||
await kernel.ensureStarted()
|
||||
assert.equal(leases.at(-1).model.config.thinkingLevel, expected)
|
||||
assert.equal(value.config.thinkingLevel, 'high', 'bridge-specific effective level must not mutate resolved model')
|
||||
await kernel.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
await check('Anthropic remains direct and permission/tool hooks remain identical', async () => {
|
||||
const before = leases.length, value = model('anthropic-messages')
|
||||
const kernel = make(options('anthropic', value)); await kernel.ensureStarted()
|
||||
const opts = queries.at(-1).options
|
||||
assert.equal(leases.length, before)
|
||||
assert.equal(opts.env.ANTHROPIC_BASE_URL, 'https://invalid.example')
|
||||
assert.equal(opts.env.ANTHROPIC_API_KEY, value.config.apiKey)
|
||||
for (const key of ['permissionMode', 'permissionPrompts', 'settingSources', 'strictMcpConfig', 'tools', 'allowedTools', 'disallowedTools', 'plugins', 'agents', 'skills']) assert.deepEqual(opts[key], baseline[key], key)
|
||||
assert.equal(opts.permissionMode, 'default'); assert.equal(opts.permissionPrompts, 'host')
|
||||
assert.deepEqual(Object.keys(opts.hooks), ['PreToolUse'])
|
||||
assert.equal(typeof opts.canUseTool, 'function')
|
||||
for (const policy of [opts, baseline]) {
|
||||
const result = await policy.hooks.PreToolUse[0].hooks[0]({ hook_event_name: 'PreToolUse', tool_name: 'Bash', tool_input: {}, tool_use_id: 'inactive' })
|
||||
assert.equal(result.hookSpecificOutput.permissionDecision, 'deny')
|
||||
}
|
||||
await kernel.close()
|
||||
})
|
||||
await check('two simultaneous sessions have separate leases and independent cleanup', async () => {
|
||||
const a = make(options('dual-a', model('openai-completions', '-a'))), b = make(options('dual-b', model('openai-responses', '-b')))
|
||||
const before = leases.length
|
||||
await Promise.all([a.ensureStarted(), b.ensureStarted()])
|
||||
const [la, lb] = leases.slice(before)
|
||||
assert.notEqual(la.apiKey, lb.apiKey); assert.notEqual(la.baseUrl, lb.baseUrl)
|
||||
await a.close(); assert.equal(la.closeCount, 1); assert.equal(lb.closeCount, 0)
|
||||
await b.close(); assert.equal(lb.closeCount, 1)
|
||||
})
|
||||
await check('concurrent ensureStarted acquires exactly one lease and SDK query', async () => {
|
||||
const kernel = make(options('concurrent')), before = leases.length, q = queries.length
|
||||
acquireGate = deferred()
|
||||
const starts = Array.from({ length: 6 }, () => kernel.ensureStarted())
|
||||
await tick(); acquireGate.resolve(); acquireGate = undefined
|
||||
await Promise.all(starts)
|
||||
assert.equal(leases.length, before + 1); assert.equal(queries.length, q + 1)
|
||||
await kernel.close(); assert.equal(leases.at(-1).closeCount, 1)
|
||||
})
|
||||
await check('API/model/base URL/key configuration changes retire old leases', async () => {
|
||||
let opts = options('fingerprint'), kernel = await getClaudeKernel(opts)
|
||||
await kernel.ensureStarted(); assert.equal(await getClaudeKernel(opts), kernel)
|
||||
for (const patch of [{ api: 'openai-responses' }, { model: 'changed-model' }, { baseUrl: 'https://other.invalid/v1' }, { apiKey: 'FAKE-CHANGED-KEY' }]) {
|
||||
const oldLease = leases.at(-1)
|
||||
opts = { ...opts, model: { ...opts.model, config: { ...opts.model.config, ...patch } } }
|
||||
const next = await getClaudeKernel(opts); assert.notEqual(next, kernel)
|
||||
assert.equal(oldLease.closeCount, 1); await next.ensureStarted(); kernel = next
|
||||
}
|
||||
await kernel.close()
|
||||
})
|
||||
await check('failed SDK startup releases lease and permits clean retry', async () => {
|
||||
const kernel = make(options('failed'))
|
||||
queryFailure = true
|
||||
await assert.rejects(kernel.ensureStarted(), /mock SDK startup failure/)
|
||||
queryFailure = false
|
||||
assert.equal(leases.at(-1).closeCount, 1)
|
||||
await kernel.ensureStarted(); assert.equal(leases.at(-1).closeCount, 0)
|
||||
await kernel.close(); assert.equal(leases.at(-1).closeCount, 1)
|
||||
})
|
||||
await check('failed session grant replay releases producer and bridge before retry', async () => {
|
||||
const kernel = make(options('grant-replay'))
|
||||
rememberSessionGrant('grant-replay', { rules: ['Read(**)'], updates: [] }, temp)
|
||||
grantFailure = true
|
||||
await assert.rejects(kernel.ensureStarted(), /mock grant replay failure/)
|
||||
grantFailure = false; await tick()
|
||||
assert.equal(leases.at(-1).closeCount, 1)
|
||||
await kernel.ensureStarted(); await kernel.close()
|
||||
assert.equal(leases.at(-1).closeCount, 1)
|
||||
})
|
||||
await check('runtime environment failure cannot strand a bridge lease', async () => {
|
||||
const kernel = make(options('runtime-failure')), before = leases.length
|
||||
runtimeFailure = true
|
||||
await assert.rejects(kernel.ensureStarted(), /mock runtime failure/)
|
||||
runtimeFailure = false
|
||||
for (const lease of leases.slice(before)) assert.equal(lease.closeCount, 1)
|
||||
await kernel.close()
|
||||
})
|
||||
await check('close during asynchronous acquisition releases lease without SDK startup', async () => {
|
||||
const kernel = make(options('close-during-start')), q = queries.length
|
||||
acquireGate = deferred()
|
||||
const start = kernel.ensureStarted().catch(error => error)
|
||||
await tick()
|
||||
const closing = kernel.close()
|
||||
acquireGate.resolve(); acquireGate = undefined
|
||||
await Promise.all([start, closing])
|
||||
assert.equal(queries.length, q); assert.equal(leases.at(-1).closeCount, 1)
|
||||
})
|
||||
await check('unsupported Gemini fails before bridge or SDK creation', async () => {
|
||||
const value = model('google-generative-ai'); value.profile.protocol = 'gemini'
|
||||
const kernel = make(options('unsupported', value)), before = leases.length, q = queries.length
|
||||
await assert.rejects(kernel.ensureStarted(), /协议/)
|
||||
assert.equal(leases.length, before); assert.equal(queries.length, q)
|
||||
await kernel.close()
|
||||
})
|
||||
await check('resolved model snapshot cannot be mutated after kernel creation', async () => {
|
||||
const value = model(), kernel = make(options('snapshot', value))
|
||||
value.config.apiKey = 'FAKE-MUTATED'; value.config.model = 'mutated-model'
|
||||
await kernel.ensureStarted()
|
||||
assert.equal(leases.at(-1).model.config.apiKey, 'FAKE-NOT-A-CREDENTIAL')
|
||||
assert.equal(queries.at(-1).options.model, 'model')
|
||||
await kernel.close()
|
||||
})
|
||||
await check('bridge preserves full-access tool preauthorization and question approval boundary', async () => {
|
||||
const kernel = make({ ...options('hook-policy'), permissionMode: 'full-access' }), controller = new AbortController()
|
||||
const run = kernel.runTurn({ runId: 'hook-run', userMessageId: 'user', prompt: 'offline', emit() {}, interactions: {}, signal: controller.signal })
|
||||
const rejected = assert.rejects(run, error => error.name === 'AbortError')
|
||||
await tick()
|
||||
const opts = queries.at(-1).options, hook = opts.hooks.PreToolUse[0].hooks[0]
|
||||
assert.equal(opts.permissionMode, 'default')
|
||||
for (const [name, decision] of [['Bash', 'allow'], ['AskUserQuestion', 'ask']]) {
|
||||
const result = await hook({ hook_event_name: 'PreToolUse', tool_name: name, tool_input: {}, tool_use_id: name })
|
||||
assert.equal(result.hookSpecificOutput.permissionDecision, decision)
|
||||
}
|
||||
controller.abort(); await rejected; await kernel.close()
|
||||
})
|
||||
await check('unexpected producer termination releases bridge lease', async () => {
|
||||
const kernel = make(options('producer-end'))
|
||||
await kernel.ensureStarted(); const lease = leases.at(-1)
|
||||
queries.at(-1).instance.close(); await tick()
|
||||
assert.equal(lease.closeCount, 1)
|
||||
await kernel.close(); assert.equal(lease.closeCount, 1)
|
||||
})
|
||||
await check('cancellation during bridge acquisition releases newly started producer', async () => {
|
||||
const kernel = make(options('cancel-start')), controller = new AbortController()
|
||||
acquireGate = deferred()
|
||||
const run = kernel.runTurn({ runId: 'cancel-start-run', userMessageId: 'user', prompt: 'offline', emit() {}, interactions: {}, signal: controller.signal })
|
||||
const rejected = assert.rejects(run, error => error.name === 'AbortError')
|
||||
await tick(); controller.abort(); acquireGate.resolve(); acquireGate = undefined
|
||||
await rejected; await tick()
|
||||
assert.equal(leases.at(-1).closeCount, 1)
|
||||
await kernel.close()
|
||||
})
|
||||
await check('turn cancellation retires producer and releases its bridge lease', async () => {
|
||||
const kernel = make(options('cancel')), controller = new AbortController()
|
||||
const run = kernel.runTurn({ runId: 'cancel-run', userMessageId: 'user', prompt: 'offline', emit() {}, interactions: {}, signal: controller.signal })
|
||||
const rejected = assert.rejects(run, error => error.name === 'AbortError')
|
||||
await tick(); assert.equal(kernel.busy, true)
|
||||
const lease = leases.at(-1)
|
||||
controller.abort(); await rejected; await tick()
|
||||
assert.equal(lease.closeCount, 1)
|
||||
await kernel.close(); assert.equal(lease.closeCount, 1)
|
||||
})
|
||||
await disposeClaudeKernels()
|
||||
assert(leases.every(lease => lease.closeCount === 1), 'all leases released exactly once')
|
||||
console.log(`PASS ${checks} offline Claude/bridge host groups; no actual SDK startup, network or credentials`)
|
||||
} finally {
|
||||
acquireGate?.resolve()
|
||||
await Promise.all(kernels.map(kernel => kernel.close().catch(() => {})))
|
||||
delete globalThis.__claudeBridgeOfflineMocks
|
||||
await rm(temp, { recursive: true, force: true })
|
||||
}
|
||||
Reference in new issue
Block a user