Files
goodbuddy/src/agent-daemon/model-bridge.test.ts
T
mesalogo 3b560c9a5e feat: add external knowledge retrieval and cross-project activity
Connect Dify, FastGPT and RAGFlow through read-only bindings, encrypted local credentials, shared retrieval and persistent citations. Configuration and removal affect GoodBuddy only.

Show active conversations across projects, retain older live work, and improve project dialog scrolling. Queue concurrent questions and route managed SSH OpenCode questions through the Agent. Preserve recoverable read/search errors and partial search results.

Document the Electron UI automation standard, real model and external knowledge validation, and remaining acceptance limits.

Validation: earlier full regression passed 4081 tests with 66 skipped; subsequent focused remote-question checks and real Linux evidence are recorded in feature progress. Precommit typecheck and lint passed; latest project dialog and shared UI suites passed 58 tests. The full suite was not rerun for the final dialog changes.

Release note: Use external knowledge in conversations, find active work across projects, and answer concurrent local or managed SSH OpenCode questions. Managed SSH questions require the updated Agent adapter.
2026-09-13 13:10:01 +08:00

904 lines
27 KiB
TypeScript

import { EventEmitter } from 'node:events'
import {
chmodSync,
existsSync,
lstatSync,
mkdtempSync,
rmSync,
writeFileSync
} from 'node:fs'
import {
request as httpRequest,
type RequestOptions
} from 'node:http'
import { tmpdir } from 'node:os'
import net from 'node:net'
import { Duplex } from 'node:stream'
import { join, resolve } from 'node:path'
import { afterEach, describe, expect, it, vi } from 'vitest'
import {
REMOTE_MODEL_GATEWAY_LIMITS,
type RemoteModelGatewayRequest,
type RemoteModelGatewayResponse
} from '../shared/remote-model-gateway-contracts'
import {
createOpenCodeModelBridgeProviderConfig,
MODEL_BRIDGE_SDK_AUTH_SENTINEL,
ModelBridgeLoopbackProxy,
runOpenCodeModelBridgeHelper,
type ModelBridgeHelperSpawn
} from './model-bridge-helper'
import { createOpenCodeLaunchProfile } from './opencode-runtime-profile'
import { createRuntimeBundleTestFixture } from './runtime-bundle-test-fixture'
import {
createUnixModelBridgeExchange,
MODEL_BRIDGE_BROKER_SOCKET_NAME,
ModelBridgeBrokerServer,
type ModelBridgeExchange
} from './model-bridge-broker'
const temporaryPaths: string[] = []
const modelBridgeRouteToken = 'A'.repeat(43)
const validResponse: RemoteModelGatewayResponse = {
status: 201,
headers: {
'content-type': 'application/json',
'x-request-id': 'provider-request'
},
bodyBase64: Buffer.from('{"ok":true}', 'utf8').toString(
'base64'
)
}
afterEach(() => {
vi.restoreAllMocks()
for (const path of temporaryPaths.splice(0)) {
rmSync(path, { recursive: true, force: true })
}
})
describe('model bridge loopback helper', () => {
it.each(['ask', 'execute'] as const)(
'preserves the %s launch profile offline resources in the final OpenCode spawn',
async (workMode) => {
const fixture = await createRuntimeBundleTestFixture()
temporaryPaths.push(fixture.root)
const socketPath = join(fixture.root, 'bridge', 'model.sock')
const profile = createOpenCodeLaunchProfile({
...fixture,
workspaceDirectory: fixture.root,
workMode,
modelBridge: {
agentExecutablePath: join(fixture.root, 'agent', 'goodbuddy-agent'),
bridgeDirectory: join(fixture.root, 'bridge'),
socketPath,
policy: {
protocol: 'anthropic-messages',
model: 'private-model',
modelProfileDigest: `sha256:${'9'.repeat(64)}`,
supportsImageInput: false
}
}
})
const spawn = vi.fn<ModelBridgeHelperSpawn>(() => {
const child = new EventEmitter()
queueMicrotask(() => child.emit('close', 0, null))
return child
})
const resources = {
HOME: fixture.root,
PATH: join(fixture.root, 'tools'),
TMPDIR: join(fixture.root, 'tmp'),
XDG_CACHE_HOME: join(fixture.root, 'cache'),
XDG_CONFIG_HOME: join(fixture.root, 'config'),
XDG_DATA_HOME: join(fixture.root, 'data'),
XDG_STATE_HOME: join(fixture.root, 'state')
}
await expect(runOpenCodeModelBridgeHelper({
socketPath,
protocol: 'anthropic-messages',
model: 'private-model',
supportsImageInput: false,
workMode,
opencodeEntrypoint: join(fixture.bundleDirectory, 'bin', 'opencode'),
environment: {
...profile.env,
...resources,
OPENCODE_CONFIG_CONTENT: '{"model":"must-not-be-used"}',
OPENCODE_CONFIG: '/unrelated/config.json',
ANTHROPIC_API_KEY: 'must-not-be-forwarded',
NODE_OPTIONS: '--inspect'
},
spawn
})).resolves.toBe(0)
expect(spawn).toHaveBeenCalledOnce()
const [executable, args, options] = spawn.mock.calls[0]!
expect(executable).toBe(join(fixture.bundleDirectory, 'bin', 'opencode'))
expect(args).toEqual(['acp'])
expect(options).toMatchObject({ shell: false, stdio: 'inherit' })
expect(options.env).toMatchObject({
OPENCODE_ENABLE_QUESTION_TOOL: 'true',
...resources,
LANG: profile.env.LANG,
LC_ALL: profile.env.LC_ALL,
OPENCODE_CONFIG_DIR: join(fixture.bundleDirectory, 'config', 'opencode'),
OPENCODE_DISABLE_AUTOUPDATE: '1',
OPENCODE_DISABLE_CLAUDE_CODE_SKILLS: '1',
OPENCODE_DISABLE_EMBEDDED_WEB_UI: '1',
OPENCODE_DISABLE_EXTERNAL_SKILLS: '1',
OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER: '1',
OPENCODE_DISABLE_LSP_DOWNLOAD: '1',
OPENCODE_DISABLE_MODELS_FETCH: '1',
OPENCODE_DISABLE_PROJECT_CONFIG: '1',
OPENCODE_DISABLE_SHARE: '1'
})
expect(options.env.OPENCODE_CONFIG).toBeUndefined()
expect(options.env.ANTHROPIC_API_KEY).toBeUndefined()
expect(options.env.NODE_OPTIONS).toBeUndefined()
const config = JSON.parse(options.env.OPENCODE_CONFIG_CONTENT!)
expect(config.model).toBe('goodbuddy-anthropic/private-model')
expect(config.snapshot).toBe(false)
expect(config.permission).toBe(workMode === 'ask' ? 'ask' : 'allow')
expect(config.plugin).toHaveLength(1)
}
)
it('forwards only the bounded request contract and returns the response', async () => {
const exchange = vi.fn<ModelBridgeExchange>(
async () => validResponse
)
const proxy = new ModelBridgeLoopbackProxy({ exchange })
const origin = await proxy.listen()
try {
const response = await sendHttp(origin, {
path: '/v1/messages',
headers: {
accept: 'application/json',
'content-type': 'application/json',
'x-api-key': MODEL_BRIDGE_SDK_AUTH_SENTINEL
},
body: '{"model":"private-model","messages":[]}'
})
expect(response).toEqual({
status: 201,
headers: expect.objectContaining({
'content-type': 'application/json',
'x-request-id': 'provider-request'
}),
body: '{"ok":true}'
})
expect(exchange).toHaveBeenCalledOnce()
const [request, context] = exchange.mock.calls[0]!
expect(request).toEqual({
method: 'POST',
path: '/v1/messages',
headers: {
accept: 'application/json',
'content-type': 'application/json'
},
bodyBase64: Buffer.from(
'{"model":"private-model","messages":[]}',
'utf8'
).toString('base64')
})
expect(context.requestId).toMatch(/^model-/u)
expect(context.signal.aborted).toBe(false)
} finally {
await proxy.close()
}
})
it('acknowledges delivery only after the HTTP response flushes', async () => {
const acknowledgeDelivery = vi.fn(async () => undefined)
const failDelivery = vi.fn(async () => undefined)
const proxy = new ModelBridgeLoopbackProxy({
exchange: async () => ({
response: validResponse,
acknowledgeDelivery,
failDelivery
})
})
const origin = await proxy.listen()
try {
const response = await sendHttp(origin, {
path: '/v1/messages',
body: '{}'
})
expect(response.body).toBe('{"ok":true}')
expect(acknowledgeDelivery).toHaveBeenCalledOnce()
expect(failDelivery).not.toHaveBeenCalled()
} finally {
await proxy.close()
}
})
it('flushes and acknowledges a multi-frame-sized HTTP response', async () => {
const body = Buffer.alloc(600 * 1024, 0x61)
const acknowledgeDelivery = vi.fn(async () => undefined)
const failDelivery = vi.fn(async () => undefined)
const proxy = new ModelBridgeLoopbackProxy({
exchange: async () => ({
response: {
status: 200,
headers: { 'content-type': 'application/octet-stream' },
bodyBase64: body.toString('base64')
},
acknowledgeDelivery,
failDelivery
})
})
const origin = await proxy.listen()
try {
const response = await sendHttp(origin, {
path: '/v1/messages',
body: '{}'
})
expect(Buffer.byteLength(response.body)).toBe(body.byteLength)
expect(acknowledgeDelivery).toHaveBeenCalledOnce()
expect(failDelivery).not.toHaveBeenCalled()
} finally {
await proxy.close()
}
})
it.each([
{
name: 'non-POST method',
request: { method: 'GET', path: '/v1/messages' },
status: 405
},
{
name: 'unknown path',
request: { path: '/images/generations' },
status: 404
},
{
name: 'path query',
request: { path: '/v1/messages?token=secret' },
status: 404
},
{
name: 'credential header',
request: {
path: '/v1/messages',
headers: { authorization: 'Bearer remote-secret' }
},
status: 400
}
])('rejects $name without dispatch', async ({ request, status }) => {
const exchange = vi.fn(async () => validResponse)
const proxy = new ModelBridgeLoopbackProxy({ exchange })
const origin = await proxy.listen()
try {
const response = await sendHttp(origin, request)
expect(response.status).toBe(status)
expect(response.body).not.toContain('remote-secret')
expect(exchange).not.toHaveBeenCalled()
} finally {
await proxy.close()
}
})
it('rejects a declared oversized body before dispatch', async () => {
const exchange = vi.fn(async () => validResponse)
const proxy = new ModelBridgeLoopbackProxy({ exchange })
const origin = await proxy.listen()
try {
const response = await sendHttp(origin, {
path: '/v1/messages',
headers: {
'content-length': String(
REMOTE_MODEL_GATEWAY_LIMITS.maximumRequestBodyBytes + 1
)
},
body: ''
})
expect(response.status).toBe(413)
expect(exchange).not.toHaveBeenCalled()
} finally {
await proxy.close()
}
})
it('rejects loopback requests without the per-helper route capability', async () => {
const exchange = vi.fn(async () => validResponse)
const proxy = new ModelBridgeLoopbackProxy({ exchange })
const origin = await proxy.listen()
try {
const response = await sendHttp(origin, {
authenticated: false,
path: '/v1/messages',
headers: {
'x-api-key': MODEL_BRIDGE_SDK_AUTH_SENTINEL
},
body: '{}'
})
expect(response.status).toBe(404)
expect(exchange).not.toHaveBeenCalled()
} finally {
await proxy.close()
}
})
it('drops harmless SDK metadata headers but rejects chunked framing', async () => {
const exchange = vi.fn<ModelBridgeExchange>(
async () => validResponse
)
const proxy = new ModelBridgeLoopbackProxy({ exchange })
const origin = await proxy.listen()
try {
const accepted = await sendHttp(origin, {
path: '/v1/messages',
headers: {
'content-type': 'application/json',
'x-stainless-runtime': 'node'
},
body: '{}'
})
expect(accepted.status).toBe(201)
expect(exchange).toHaveBeenCalledOnce()
expect(exchange.mock.calls[0]![0].headers).toEqual({
'content-type': 'application/json'
})
const rejected = await sendHttp(origin, {
path: '/v1/messages',
headers: {
'content-type': 'application/json',
'transfer-encoding': 'chunked'
},
body: '{}'
})
expect(rejected.status).toBe(400)
expect(exchange).toHaveBeenCalledOnce()
} finally {
await proxy.close()
}
})
it('accepts only the fixed SDK compatibility marker and strips it', async () => {
const exchange = vi.fn<ModelBridgeExchange>(
async () => validResponse
)
const proxy = new ModelBridgeLoopbackProxy({ exchange })
const origin = await proxy.listen()
try {
const accepted = await sendHttp(origin, {
path: '/v1/responses',
headers: {
authorization: `Bearer ${MODEL_BRIDGE_SDK_AUTH_SENTINEL}`
},
body: '{}'
})
expect(accepted.status).toBe(201)
expect(exchange).toHaveBeenCalledOnce()
expect(exchange.mock.calls[0]![0].headers).toEqual({})
const acceptedChat = await sendHttp(origin, {
path: '/v1/chat/completions',
headers: {
authorization: `Bearer ${MODEL_BRIDGE_SDK_AUTH_SENTINEL}`
},
body: '{}'
})
expect(acceptedChat.status).toBe(201)
expect(exchange).toHaveBeenCalledTimes(2)
expect(exchange.mock.calls[1]![0].headers).toEqual({})
for (const authorization of [
'Bearer remote-secret',
`Basic ${MODEL_BRIDGE_SDK_AUTH_SENTINEL}`
]) {
const rejected = await sendHttp(origin, {
path: '/v1/responses',
headers: { authorization },
body: '{}'
})
expect(rejected.status).toBe(400)
}
expect(exchange).toHaveBeenCalledTimes(2)
} finally {
await proxy.close()
}
})
it('times out once, aborts dispatch, and never returns raw errors', async () => {
let dispatchSignal: AbortSignal | undefined
const exchange = vi.fn(
async (
_request: RemoteModelGatewayRequest,
context: { signal: AbortSignal }
) => {
dispatchSignal = context.signal
await new Promise(() => undefined)
return validResponse
}
)
const proxy = new ModelBridgeLoopbackProxy({
exchange,
requestTimeoutMs: 20
})
const origin = await proxy.listen()
try {
const response = await sendHttp(origin, {
path: '/v1/messages',
body: '{}'
})
expect(response.status).toBe(504)
expect(response.body).toBe(
'{"error":{"code":"bridge-timeout"}}'
)
expect(dispatchSignal?.aborted).toBe(true)
expect(exchange).toHaveBeenCalledOnce()
} finally {
await proxy.close()
}
})
it('maps dispatch errors without leaking credentials', async () => {
const privateValue = 'provider-key-do-not-leak'
const proxy = new ModelBridgeLoopbackProxy({
exchange: async () => {
throw new Error(`Upstream used ${privateValue}`)
}
})
const origin = await proxy.listen()
try {
const response = await sendHttp(origin, {
path: '/v1/messages',
body: '{}'
})
expect(response.status).toBe(502)
expect(response.body).toBe(
'{"error":{"code":"bridge-failed"}}'
)
expect(response.body).not.toContain(privateValue)
} finally {
await proxy.close()
}
})
it('queues concurrent OpenCode requests instead of returning bridge-busy', async () => {
let markStarted: (() => void) | undefined
let releaseFirst: (() => void) | undefined
const started = new Promise<void>((resolveStarted) => {
markStarted = resolveStarted
})
const firstGate = new Promise<void>((resolve) => {
releaseFirst = resolve
})
let callIndex = 0
const exchange = vi.fn(async () => {
const index = callIndex++
if (index === 0) {
markStarted?.()
await firstGate
}
return validResponse
})
const proxy = new ModelBridgeLoopbackProxy({
exchange
})
const origin = await proxy.listen()
const first = sendHttp(origin, {
path: '/v1/messages',
body: '{}'
})
await started
const second = sendHttp(origin, {
path: '/v1/messages',
body: '{}'
})
try {
await new Promise<void>((resolve) => setTimeout(resolve, 20))
expect(exchange).toHaveBeenCalledOnce()
releaseFirst?.()
await expect(first).resolves.toMatchObject({ status: 201 })
await expect(second).resolves.toMatchObject({ status: 201 })
expect(exchange).toHaveBeenCalledTimes(2)
} finally {
await proxy.close()
}
})
it('aborts an in-flight dispatch when the proxy closes', async () => {
let dispatchSignal: AbortSignal | undefined
let markStarted: (() => void) | undefined
const started = new Promise<void>((resolveStarted) => {
markStarted = resolveStarted
})
const proxy = new ModelBridgeLoopbackProxy({
exchange: async (_request, context) => {
dispatchSignal = context.signal
markStarted?.()
await new Promise(() => undefined)
return validResponse
}
})
const origin = await proxy.listen()
const pending = sendHttp(origin, {
path: '/v1/messages',
body: '{}'
}).catch(() => undefined)
await started
await proxy.close()
await pending
expect(dispatchSignal?.aborted).toBe(true)
})
it.each([
{
protocol: 'anthropic-messages' as const,
providerId: 'goodbuddy-anthropic',
npm: '@ai-sdk/anthropic',
requiresSdkAuthSentinel: true
},
{
protocol: 'openai-chat-completions' as const,
providerId: 'goodbuddy-openai-chat',
npm: '@ai-sdk/openai-compatible',
requiresSdkAuthSentinel: true
},
{
protocol: 'openai-responses' as const,
providerId: 'goodbuddy-openai-responses',
npm: '@ai-sdk/openai',
requiresSdkAuthSentinel: true
}
])(
'creates secret-free $protocol OpenCode metadata',
({ protocol, providerId, npm, requiresSdkAuthSentinel }) => {
const config = createOpenCodeModelBridgeProviderConfig({
protocol,
model: 'private-model',
name: 'Private model',
loopbackOrigin:
`http://127.0.0.1:12345/${modelBridgeRouteToken}`,
supportsImageInput: true,
workMode: 'ask'
})
expect(config).toMatchObject({
model: `${providerId}/private-model`,
permission: 'ask',
agent: {
title: {
disable: true
},
build: {
permission: 'ask'
}
},
provider: {
[providerId]: {
npm,
options: {
baseURL:
`http://127.0.0.1:12345/${modelBridgeRouteToken}/v1`
},
models: {
'private-model': {
attachment: true,
provider: { npm }
}
}
}
}
})
const options = config.provider[providerId]!.options
expect(options.apiKey).toBe(
requiresSdkAuthSentinel
? MODEL_BRIDGE_SDK_AUTH_SENTINEL
: undefined
)
expect(JSON.stringify(config)).not.toContain('provider.example')
}
)
it('explicitly allows Execute permissions instead of inheriting directory prompts', () => {
const config = createOpenCodeModelBridgeProviderConfig({
protocol: 'openai-responses',
model: 'private-model',
loopbackOrigin:
`http://127.0.0.1:12345/${modelBridgeRouteToken}`,
workMode: 'execute'
})
expect(config.permission).toBe('allow')
expect(config.agent.build.permission).toBe('allow')
})
})
describe('model bridge Unix broker', () => {
const runOnUnix = process.platform === 'win32' ? it.skip : it
it('allows a delayed connection with the default unlimited request timeout', async () => {
const socket = new Duplex({
read() {},
write(chunk: Buffer, _encoding, callback) {
const packet = JSON.parse(chunk.subarray(4).toString()) as {
type: string
requestId: string
}
const response = Buffer.from(JSON.stringify(
packet.type === 'model-request'
? { version: 1, type: 'model-response', requestId: packet.requestId, ok: true, response: validResponse }
: { version: 1, type: 'delivery-accepted', requestId: packet.requestId }
))
const frame = Buffer.alloc(4 + response.length)
frame.writeUInt32BE(response.length)
response.copy(frame, 4)
this.push(frame)
callback()
}
})
vi.spyOn(net.Socket.prototype, 'connect').mockReturnValueOnce(socket as net.Socket)
const connect = setTimeout(() => socket.emit('connect'), 30)
try {
const exchange = createUnixModelBridgeExchange({
socketPath: resolve(tmpdir(), 'delayed-model-bridge.sock'),
connectTimeoutMs: 500
})
const result = await exchange({
method: 'POST', path: '/v1/messages', headers: {}, bodyBase64: ''
}, { requestId: 'delayed-connect', signal: new AbortController().signal })
expect('response' in result).toBe(true)
if ('response' in result) {
expect(result.response).toEqual(validResponse)
await result.acknowledgeDelivery()
}
} finally {
clearTimeout(connect)
socket.destroy()
}
})
runOnUnix('rejects a socket path beyond the Unix kernel limit', () => {
const scratch = resolve(
privateTemporaryDirectory(),
'a'.repeat(150)
)
expect(
() =>
new ModelBridgeBrokerServer({
scratchDirectory: scratch,
dispatch: async () => validResponse
})
).toThrow('endpoint path is too long')
})
runOnUnix(
'uses a private owned socket, exchanges one request, and cleans up',
async () => {
const scratch = privateTemporaryDirectory()
const dispatch = vi.fn(async () => validResponse)
const broker = new ModelBridgeBrokerServer({
scratchDirectory: scratch,
dispatch
})
await broker.listen()
const socketPath = resolve(
scratch,
MODEL_BRIDGE_BROKER_SOCKET_NAME
)
expect(lstatSync(socketPath).mode & 0o777).toBe(0o600)
const exchange = createUnixModelBridgeExchange({
socketPath
})
const request: RemoteModelGatewayRequest = {
method: 'POST',
path: '/v1/responses',
headers: { 'content-type': 'application/json' },
bodyBase64: Buffer.from('{}').toString('base64')
}
const exchanged = await exchange(request, {
requestId: 'request-a',
signal: new AbortController().signal
})
expect(
'response' in exchanged ? exchanged.response : exchanged
).toEqual(validResponse)
if ('response' in exchanged) {
await exchanged.acknowledgeDelivery()
}
expect(dispatch).toHaveBeenCalledWith(request, {
requestId: 'request-a',
signal: expect.any(AbortSignal)
})
await broker.close()
expect(existsSync(socketPath)).toBe(false)
}
)
runOnUnix(
'drains a response larger than the Unix socket receive buffer',
async () => {
const scratch = privateTemporaryDirectory()
const responseBody = Buffer.alloc(256 * 1024, 0x61)
const broker = new ModelBridgeBrokerServer({
scratchDirectory: scratch,
dispatch: async () => ({
...validResponse,
bodyBase64: responseBody.toString('base64')
})
})
await broker.listen()
try {
const exchange = createUnixModelBridgeExchange({
socketPath: broker.socketPath,
requestTimeoutMs: 2_000
})
const exchanged = await exchange(
{
method: 'POST',
path: '/v1/responses',
headers: { 'content-type': 'application/json' },
bodyBase64: Buffer.from('{}').toString('base64')
},
{
requestId: 'request-large-response',
signal: new AbortController().signal
}
)
expect('response' in exchanged).toBe(true)
if ('response' in exchanged) {
expect(
Buffer.from(
exchanged.response.bodyBase64,
'base64'
).byteLength
).toBe(responseBody.byteLength)
await exchanged.acknowledgeDelivery()
}
} finally {
await broker.close()
}
}
)
runOnUnix(
'refuses to replace or remove a non-socket endpoint path',
async () => {
const scratch = privateTemporaryDirectory()
const socketPath = join(
scratch,
MODEL_BRIDGE_BROKER_SOCKET_NAME
)
writeFileSync(socketPath, 'user-owned')
chmodSync(socketPath, 0o600)
const broker = new ModelBridgeBrokerServer({
scratchDirectory: scratch,
dispatch: async () => validResponse
})
await expect(broker.listen()).rejects.toThrow(
'not an owned Unix socket'
)
await broker.close()
expect(existsSync(socketPath)).toBe(true)
}
)
runOnUnix(
'bounds dispatch time and forwards only a generic failure code',
async () => {
const scratch = privateTemporaryDirectory()
const privateValue = 'provider-secret-in-error'
let dispatchSignal: AbortSignal | undefined
const broker = new ModelBridgeBrokerServer({
scratchDirectory: scratch,
requestTimeoutMs: 20,
dispatch: async (_request, context) => {
dispatchSignal = context.signal
await new Promise(() => undefined)
throw new Error(privateValue)
}
})
await broker.listen()
try {
const exchange = createUnixModelBridgeExchange({
socketPath: broker.socketPath,
requestTimeoutMs: 1_000
})
await expect(
exchange(
{
method: 'POST',
path: '/v1/messages',
headers: {},
bodyBase64: ''
},
{
requestId: 'request-timeout',
signal: new AbortController().signal
}
)
).rejects.toMatchObject({
code: 'request-timeout',
message: 'Model bridge request timed out'
})
expect(dispatchSignal?.aborted).toBe(true)
} finally {
await broker.close()
}
}
)
})
function privateTemporaryDirectory(): string {
const path = mkdtempSync(join(tmpdir(), 'goodbuddy-model-bridge-'))
temporaryPaths.push(path)
if (process.platform !== 'win32') {
chmodSync(path, 0o700)
}
return resolve(path)
}
async function sendHttp(
origin: string,
options: {
method?: string
path?: string
authenticated?: boolean
headers?: Record<string, string>
body?: string
}
): Promise<{
status: number
headers: Record<string, string | string[] | undefined>
body: string
}> {
const url = new URL(origin)
const body = options.body ?? ''
const requestPath = options.path ?? '/v1/messages'
const basePath =
url.pathname === '/'
? ''
: url.pathname.replace(/\/$/u, '')
const requestOptions: RequestOptions = {
hostname: url.hostname,
port: url.port,
method: options.method ?? 'POST',
path:
options.authenticated === false
? requestPath
: `${basePath}${requestPath}`,
headers: {
...(options.headers ?? {}),
...(!hasHeader(options.headers, 'content-length') &&
!hasHeader(options.headers, 'transfer-encoding')
? { 'content-length': Buffer.byteLength(body) }
: {})
}
}
return await new Promise((resolveResponse, reject) => {
const request = httpRequest(requestOptions, (response) => {
const chunks: Buffer[] = []
response.on('data', (chunk: Buffer) => chunks.push(chunk))
response.once('end', () => {
resolveResponse({
status: response.statusCode ?? 0,
headers: response.headers,
body: Buffer.concat(chunks).toString('utf8')
})
})
})
request.once('error', reject)
request.end(body)
})
}
function hasHeader(
headers: Record<string, string> | undefined,
expected: string
): boolean {
return Object.keys(headers ?? {}).some(
(name) => name.toLowerCase() === expected
)
}