Files
HomerandClaude Opus 5.5 28ff205ed0 Custom connections are gone: the CLI talks to the LLeMbas server it is logged in to
LLeMbas CLI 2.0 is LLeMbas-server-specific (CLI rule 2). Removed: the
anthropic, gemini, ollama and responses clients, context discovery and
llama-swap unloading (discover.ts), the Connection schema with its dialect,
auth, key_cmd, discover, unload_url and one_model_at_a_time, the model
`fallback` (it only ever moved to another server), effort_map and cache, and
the multi-connection refs. Kept: openai-chat, http, sse, think, effort,
tokens and all of src/lembas.

- connections.yaml holds the login (`type: webui`, as lembas login writes
  it). A custom entry from 1.x is read past with a warning that says how to
  log in, so an old file still starts; with several logins left, the latest
  is used and the others named.
- A model's ref is the id the server serves it under; the older forms
  (`<login>/<id>`, `<login>/<model>`) still resolve and config.yaml is
  rewritten once. Without a login there is no model, and the error (and the
  first-run screen, now the sign-in alone) says `lembas login <address>`.
- A login to another server signs this machine out of the previous one.
- The window is the server's word (no discovery); a capacity group comes
  from the server's `capacity.group` alone; embeddings use the server's
  /v1/embeddings.
- `lembas models` lists the server's models; install.sh's starter files, the
  README and the JSON schemas say the same.

Tests: every fixture is a login now (the fake server lists models, answers
other GETs with 404, and can hand one model to another script, as a server
with several backends); the dialect suites and the cross-connection tests are
gone with their code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 21:00:05 +00:00

46 lines
2.1 KiB
TypeScript

import { afterAll, beforeAll, expect, test } from "bun:test"
import { mkdtempSync, readFileSync, writeFileSync } from "node:fs"
import { tmpdir } from "node:os"
import { join } from "node:path"
import { OpenAIChatClient } from "../src/provider/openai-chat.ts"
import type { ResolvedModel } from "../src/provider/types.ts"
// A private CA and a leaf for 127.0.0.1, made with openssl — the shape of a LAN endpoint behind a private CA.
const dir = mkdtempSync(join(tmpdir(), "lembas-tls-"))
const sh = (cmd: string) => {
const r = Bun.spawnSync(["sh", "-c", cmd], { cwd: dir, stderr: "pipe" })
if (r.exitCode !== 0) throw new Error(r.stderr.toString())
}
let server: ReturnType<typeof Bun.serve>
beforeAll(() => {
sh("openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -keyout ca.key -out ca.crt -days 1 -subj '/CN=Test CA' 2>/dev/null")
writeFileSync(join(dir, "ext"), "subjectAltName=IP:127.0.0.1\nbasicConstraints=CA:FALSE\n")
sh("openssl req -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -keyout leaf.key -out leaf.csr -subj '/CN=127.0.0.1' 2>/dev/null")
sh("openssl x509 -req -in leaf.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out leaf.crt -days 1 -extfile ext 2>/dev/null")
server = Bun.serve({
port: 0,
tls: { cert: readFileSync(join(dir, "leaf.crt"), "utf8"), key: readFileSync(join(dir, "leaf.key"), "utf8") },
fetch: () => Response.json({ data: [{ id: "m" }] }),
})
})
afterAll(() => server?.stop(true))
const model = (tls?: { ca?: string; insecure?: boolean }): ResolvedModel => ({
ref: "t/m",
connectionName: "t",
id: "m",
spec: {},
connection: { base_url: `https://127.0.0.1:${server.port}/v1`, tls, models: {} },
})
test("a private CA is refused by default, with advice", async () => {
await expect(new OpenAIChatClient(model()).listModels()).rejects.toThrow("tls.ca")
})
test("tls.ca trusts it", async () => {
expect(await new OpenAIChatClient(model({ ca: join(dir, "ca.crt") })).listModels()).toEqual([{ id: "m", context: undefined }])
})
test("tls.insecure skips verification", async () => {
expect(await new OpenAIChatClient(model({ insecure: true })).listModels()).toHaveLength(1)
})