@wess/atlas/ai#
Unified AI provider abstraction with chat, embeddings, streaming, RAG, and agent support. Zero external dependencies -- uses fetch for all API calls.
Exports#
Provider:
createProvider({ provider, key?, baseUrl? })→AiProvider— provider is"openai" | "anthropic" | "ollama".keyis required for openai/anthropic, optional for ollama (sent asAuthorization: Bearer, needed for Ollama Cloud).baseUrlis optional for all three (anthropic defaulthttps://api.anthropic.com, trailing slash trimmed)AiProviderexposes.chat(opts),.chatStream(opts),.embed(opts)
Chat:
createConversation(system?)→ConversationaddMessage(conv, msg)→Conversation(immutable)send(provider, conv, content, opts?)→{ conversation, response }userMessage,assistantMessage,systemMessage,toolMessage
Stream:
parseSSE(text)→ eventscollectStream(stream)→ChatResponsestreamToSse(stream)→ReadableStream(for HTTP responses)
Embeddings:
embed(provider, inputs)→number[][]— inputs:string | string[]cosineSimilarity(a, b)→numbercreateVectorStore()→{ add, search, size }
Structured / tools:
generateJson<T>(provider, prompt, opts?)→Ttool(name, description, parameters)→ToolDef
RAG:
index(rag, id, text)→Promise<void>—rag = { ai, store, topK? }query(rag, question)→{ answer, sources }
Agents:
runAgent({ ai, system?, tools, maxIterations?, approve? }, prompt)→ resultapprove(call) => boolean | Promise<boolean>runs before each tool call;falserefuses it and the model getsError: tool call <name> was not approvedas the result- A handler that throws, an unknown tool, or unparseable arguments (
ToolCall.error) becomes anError: <message>tool result; the loop keeps going and no stack is sent
Server pipe:
withAi(provider)→PipeFn— addsprovidertoconn.assigns.ai
Types#
ProviderConfig,ImageAttachment,Message,ChatOptions,ChatResponse,StreamChunkEmbedOptions,EmbedResponse,ToolDef,ToolCallConversation,VectorStore,VectorEntry,RagOptionsAgentTool,AgentOptions
Provider setup#
import { createProvider } from "@wess/atlas/ai"
const openai = createProvider({ provider: "openai", key: process.env.OPENAI_API_KEY! })
const anthropic = createProvider({ provider: "anthropic", key: process.env.ANTHROPIC_API_KEY! })
const ollama = createProvider({ provider: "ollama" }) // local, no key needed
const cloud = createProvider({ provider: "ollama", baseUrl: "https://ollama.com", key: process.env.OLLAMA_API_KEY }) // Ollama Cloud: sends Authorization: Bearer
Chat#
import { createConversation, send } from "@wess/atlas/ai"
const conv = createConversation("You are a helpful assistant")
const { conversation, response } = await send(openai, conv, "Hello!")
// conversation tracks full message history immutably
Image input#
User messages can include images: [{ mediaType: "image/jpeg", data: base64 }].
The same attachment shape works with chat and streaming. Supported media types
are JPEG, PNG, WebP, and GIF. Pass base64 bytes, without a data-URL prefix; image
URLs are not fetched by the library. Image input requires a compatible model.
System, assistant, and tool messages cannot carry image attachments. Existing
text-only messages keep their original wire format.
Streaming#
import { collectStream, streamToSse } from "@wess/atlas/ai"
const stream = openai.chatStream({ messages: [{ role: "user", content: "Hi" }] })
const full = await collectStream(stream) // collect into ChatResponse
const sse = streamToSse(stream) // convert to ReadableStream for HTTP responses
Embeddings and vector search#
import { embed, cosineSimilarity, createVectorStore } from "@wess/atlas/ai"
const vectors = await embed(openai, ["hello", "world"])
const score = cosineSimilarity(vectors[0]!, vectors[1]!)
const store = createVectorStore()
store.add("id1", vectors[0]!, { text: "hello" })
const results = store.search(vectors[1]!, 5) // top 5 nearest
Structured output#
import { generateJson, tool } from "@wess/atlas/ai"
const user = await generateJson<{ name: string }>(openai, "Generate a user")
const searchTool = tool("search", "Search the web", { type: "object", properties: { query: { type: "string" } } })
RAG#
import { index, query, createVectorStore, createProvider } from "@wess/atlas/ai"
const store = createVectorStore()
const rag = { ai: openai, store, topK: 3 }
await index(rag, "doc1", "Document text here...")
const result = await query(rag, "What does the document say?")
// result.answer, result.sources
Agent loop#
import { runAgent, tool } from "@wess/atlas/ai"
const result = await runAgent({
ai: openai,
system: "You are a calculator assistant",
tools: [{
definition: tool("multiply", "Multiply two numbers", {
type: "object",
properties: { a: { type: "number" }, b: { type: "number" } },
}),
handler: async (args) => String((args.a as number) * (args.b as number)),
}],
maxIterations: 5,
// optional: gate calls, e.g. refuse anything destructive without a human
approve: (call) => call.name !== "delete_account",
}, "What is 6 times 7?")
Tool arguments are model output, and a prompt-injected document can choose them. Never eval, new Function, shell out, or interpolate them into SQL; parse them into the narrow shape the tool needs and reject the rest.
Server pipe#
import { withAi } from "@wess/atlas/ai"
// Add AI provider to conn.assigns.ai in a server pipeline
const aiPipe = withAi(openai)
Architecture#
provider/-- Provider interface and adapters (openai, anthropic, ollama)chat/-- Immutable conversation managementstream/-- SSE parsing, stream collection, SSE response generationembeddings/-- Vector embeddings and in-memory vector storestructured/-- JSON mode and tool definitionsrag/-- Retrieval-augmented generation pipelineagents/-- Tool-use agent loop with iteration limitspipes/-- Server middleware integration
Dependencies#
@wess/atlas/server— only for thewithAipipe; the rest of the package stands alone.- External: none. All provider calls go through
fetch.
Testing#
All tests use mock providers. No real API calls.
bun test packages/ai/