u
This commit is contained in:
86
AGENTS.md
86
AGENTS.md
@@ -1,12 +1,5 @@
|
||||
# AGENTS.md
|
||||
|
||||
Orientation notes for AI coding assistants working in this repo. Read this first.
|
||||
|
||||
## What this is
|
||||
|
||||
`llmorbiter` — a small (v0.1.0, 0BSD) **LLM tool-calling orchestrator with provider
|
||||
abstraction**. Pure ES modules (`"type": "module"`). Only runtime dependency is `dotenv`.
|
||||
No build step, no test runner, no README (this file is the orientation doc).
|
||||
`llmorbiter` **LLM tool-calling orchestrator with provider
|
||||
abstraction**.
|
||||
|
||||
## Layout
|
||||
|
||||
@@ -29,46 +22,67 @@ examples/
|
||||
|
||||
Exports: `LLMOrbiter`, `OpenRouterProvider`, `defineTool`, `ToolError`, `DirectReturnError`.
|
||||
|
||||
- `defineTool({ name, description, parameters, strict=true, invoke })` -> `{ spec, invoke, name }`.
|
||||
`spec` is the OpenAI-compatible function schema sent to the LLM; `invoke(args, { signal })`
|
||||
is the local implementation.
|
||||
- `defineTool({ name, description, parameters, strict=false, invoke })` -> `{ spec, invoke, name }`.
|
||||
- `spec`: OpenAI-compatible function schema (`{ type: 'function', function: { name, description, parameters, strict } }`).
|
||||
- `parameters`: JSON Schema object (defaults to `{ type: 'object', properties: {}, additionalProperties: false }`).
|
||||
- `invoke(args, { signal })`: local async implementation.
|
||||
- `ToolError(message)` — throw from a tool to signal a recoverable error back to the LLM.
|
||||
- `DirectReturnError(data)` — throw from a tool to stop the loop immediately; `run()` resolves
|
||||
with `{ directReturn: true, data, ... }`.
|
||||
with `{ directReturn: true, data, ... }`. Any remaining tool calls in the same turn are closed
|
||||
with synthetic skipped tool responses so the conversation transcript remains valid.
|
||||
|
||||
### `new LLMOrbiter(provider, opts)`
|
||||
|
||||
`opts` are lifecycle callbacks: `onLLMCall`, `onUsage`, `onToolCall`, `onToolError`,
|
||||
`onToolResult`, `onStatus`, `onContent`, `onReasoning`.
|
||||
`opts` are lifecycle callbacks:
|
||||
- `onLLMCall({ model, toolCount, tools, promptSize, messageCount, messages, iteration })` — called before each LLM call; `messages` is a comma-separated summary of roles (e.g. `'user,assistant,tool_response'`).
|
||||
- `onUsage({ cached, input, output, cost })` — called after each LLM call with that call's usage; provider's raw usage object is attached on non-enumerable `.raw`.
|
||||
- `onToolCall({ name, args })` — called before executing a tool.
|
||||
- `onToolResult({ name, result })` — called on successful tool execution.
|
||||
- `onToolError({ name, error, args, rawError })` — called when tool execution throws, returns `{ error }`, has invalid JSON args, or references an unknown tool.
|
||||
- `onStatus(status)` — forwarded from provider: `'connected'` (HTTP headers received), `'streaming'` (first SSE chunk received), or `'working'` (tool calls started streaming).
|
||||
- `onContent(chunk)` — forwarded from provider: incremental output content string (`choice.delta.content`).
|
||||
- `onReasoning(text)` — forwarded from provider: **accumulated** reasoning string so far (aggregated from `delta.reasoning`, `delta.reasoning_content`, and `delta.reasoning_details`).
|
||||
|
||||
### `orbiter.run(opts)`
|
||||
|
||||
`{ prompt, messages, model, tools, maxIterations=10, tool_choice, extraBody, signal }`.
|
||||
|
||||
- `prompt`: user message string to append (omit or pass `null` to continue without adding one).
|
||||
- `messages`: transcript array to seed from; a leading `{ role: 'system' }` is the system prompt.
|
||||
- `tools`: array of tool objects created with `defineTool()`.
|
||||
- `maxIterations`: safety limit on tool-calling loop iterations (default: `10`).
|
||||
- `tool_choice`: tool choice setting (defaults to `'auto'` when tools are present).
|
||||
- `extraBody`: extra parameters merged into provider request payload.
|
||||
- `signal`: external `AbortSignal`.
|
||||
|
||||
Loop: send messages + tool specs -> if `tool_calls` come back, invoke locals and feed results
|
||||
back -> repeat until no more tool calls or `maxIterations`. Returns
|
||||
`{ content, toolResults, usage, rawUsage, iterations, messages, ... }`; the returned `messages`
|
||||
can seed the next `run()` for conversation continuity (a leading `{role:'system'}` entry is the
|
||||
system prompt). `orbiter.abort(reason)` cancels the active run; aborts surface as
|
||||
`DOMException('...', 'AbortError')`.
|
||||
back -> repeat until no more tool calls or `maxIterations`.
|
||||
|
||||
Returns:
|
||||
- `content`: final assistant message text (or `null`).
|
||||
- `toolResults`: `{ [toolName]: [result, ...] }` map of all tool results executed across iterations.
|
||||
- `usage`: aggregated usage object `{ input, output, cached, tokenIn, tokenOut, tokenCached, costTotal, cost, raw }`.
|
||||
- `rawUsage`: single provider usage object (if 1 iteration) or array of usage objects (if multiple iterations).
|
||||
- `rawUsages`: array of all iteration usage objects from the provider.
|
||||
- `iterations`: count of iterations performed.
|
||||
- `messages`: conversation transcript array for continuity in subsequent runs.
|
||||
- `directReturn`: `true` if terminated by `DirectReturnError`.
|
||||
- `data`: data payload passed to `DirectReturnError`.
|
||||
|
||||
`orbiter.abort(reason)` cancels the active run; aborts surface as `DOMException('...', 'AbortError')`.
|
||||
|
||||
### `OpenRouterProvider`
|
||||
|
||||
`new OpenRouterProvider({ apiKey, apiUrl, siteUrl, siteName })`. Method:
|
||||
`new OpenRouterProvider({ apiKey, apiUrl, siteUrl, siteName })`.
|
||||
- `apiUrl` defaults to `https://openrouter.ai/api/v1/chat/completions`.
|
||||
- `siteUrl` sets `HTTP-Referer` header.
|
||||
- `siteName` sets `X-Title` header.
|
||||
|
||||
Method:
|
||||
`chatCompletions({ model, messages, tools, tool_choice, signal, extraBody, onStatus, onContent, onReasoning })`
|
||||
— streams SSE, assembles `{ content, tool_calls, finish_reason, usage, reasoning_details, reasoning }`.
|
||||
`apiUrl` defaults to `https://openrouter.ai/api/v1/chat/completions`.
|
||||
|
||||
## Gotchas / notes
|
||||
|
||||
- Examples need `OPENROUTER_API_KEY` (in `examples/.env` or via `--key`). Default model in the
|
||||
examples: `deepseek/deepseek-v4-flash-0731`.
|
||||
- `.gitignore` ignores `node_modules/`, `examples/.env`, `examples/invoice.pdf`.
|
||||
- `pdf-pages.js` shells out to `pdftotext -layout`, splits pages on `\f`, and appends prompts
|
||||
to `examples/prompts.log`.
|
||||
- `OpenRouterProvider` streams reasoning cumulatively via `onReasoning`.
|
||||
|
||||
## Style conventions observed
|
||||
|
||||
- 4-space indent, single quotes, semicolons, `'use strict';` header.
|
||||
- Comments use em dashes (`—`) and `───` section dividers in examples.
|
||||
- If reasoning was returned, automatically appends `{ role: 'assistant', content: result.reasoning }` to `messages`.
|
||||
- Delta handling:
|
||||
- `delta.content` -> passes chunk to `onContent(chunk)`.
|
||||
- `delta.reasoning` / `delta.reasoning_content` / `delta.reasoning_details` -> passes **accumulated** string to `onReasoning(text)`.
|
||||
- `delta.tool_calls` -> triggers `onStatus('working')` once when tool calls begin streaming (tool calls are assembled into `result.tool_calls` without per-chunk streaming callbacks).
|
||||
|
||||
11
package-lock.json
generated
11
package-lock.json
generated
@@ -9,14 +9,17 @@
|
||||
"version": "0.1.0",
|
||||
"license": "0BSD",
|
||||
"dependencies": {
|
||||
"dotenv": "^17.4.2"
|
||||
"dotenv": "^18.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "17.4.2",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz",
|
||||
"integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==",
|
||||
"version": "18.0.4",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-18.0.4.tgz",
|
||||
"integrity": "sha512-jGt9uI5BIxpVO15kvy+iEFqNAwxYClKWnkptLjSNPnFLC003Imd0ofr3aEVsbpHdSBdSsbcApNIlz4nbyGMwdA==",
|
||||
"license": "BSD-2-Clause",
|
||||
"bin": {
|
||||
"dotenv": "dist/index.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
|
||||
@@ -5,7 +5,10 @@
|
||||
"main": "src/index.js",
|
||||
"type": "module",
|
||||
"license": "0BSD",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"dotenv": "^17.4.2"
|
||||
"dotenv": "^18.0.4"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user