OpenAI-compatible endpoints plus a small set of documented proprietary extensions for the features that have no OpenAI equivalent (voice speech, LLM switching, platform capabilities), plus a native MCP JSON-RPC connector. Any OpenAI SDK, script, standalone client or MCP client can drive the AI agents without modification to the agent core.
| Endpoint | Purpose |
|---|---|
POST /v1/chat/completions |
Chat with the agents (streaming SSE, sessions, LLM switching) |
POST /v1/files |
Multipart upload + server-side Markdown conversion |
GET /v1/files · GET /v1/files/{id} |
List / retrieve converted files |
GET /v1/files/{id}/content |
Raw uploaded bytes (OpenAI Files API) |
DELETE /v1/files/{id} |
Delete an uploaded file |
GET /v1/models |
Agent sets and LLM providers with their characteristics |
GET /v1/models/{id} |
Single model details |
POST /v1/audio/speech |
Text-to-speech → WAV bytes (Kokoro neural TTS) |
GET /health |
Liveness probe |
| Endpoint | Purpose |
|---|---|
POST /v1/control |
Pilot/steering: switch the LLM in use, toggle features, reset history, create sessions |
GET /v1/control |
Session state + platform capabilities (what is available here and now) |
POST /v1/voice/listen |
One-shot speech recognition from the server microphone (Windows only) |
GET /v1/audio/voices |
TTS voices available on this platform |
POST /mcp |
Native MCP JSON-RPC endpoint (initialize, tools/list, tools/call) |
GET /OfficeManager |
The OfficeManager web app (static files; see OfficeManager) |
GET /ws/office |
OfficeManager duplex WebSocket hub (agent lifecycle + chat protocol) |
POST /v1/office/events |
Ingest agent lifecycle events forwarded by OTHER processes (AIOffice app, voice panels) |
Telegram is an in-process medium and exposes no HTTP endpoints — messages travel directly through the WTelegramClient library; configuration is done from the TUI (
/telegram) or intelegram.json(see Telegram chat).
The rule for platform-dependent features: the server reports them unavailable (501) when
the platform or the assets are missing, and GET /v1/control / GET /v1/audio/voices always
tell the client what is actually available — a chat client activates voice/TTS only where they
really run.
AgentBridge exposes a native MCP connector in the same process as the agent runtime, so MCP, OpenAI API and TUI all drive the same orchestrator state.
Current minimal profile (intentionally small for immediate interoperability):
initializetools/listtools/call
The initial tool catalog exposes one high-level tool:
agent_run— runs an autonomous AgentBridge execution for the provided prompt.
Example request:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "agent_run",
"arguments": {
"prompt": "Create a concise weekly report from the latest sales data",
"model": "default-agent",
"llm_provider": "Zai",
"max_iterations": 120,
"session_id": "sess-..."
}
}
}agent_run returns MCP content blocks plus a structured payload (success, code,
iterations, optional session_id, optional attachments).
OpenAI Chat Completions compatible. model selects which agent set is used
(see GET /v1/models); stream: true returns Server-Sent Events (SSE).
curl -N http://localhost:5290/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "web-agent",
"messages": [{"role": "user", "content": "What is the weather today?"}],
"file_ids": ["file-..."],
"stream": true
}'| Field | Meaning |
|---|---|
model |
Agent set (see GET /v1/models): default-agent, web-agent, search-agent, research-agent, document-files, spreadsheet-files, email-agent, office-files, multi-files, all-files. |
tools |
Extension — explicit tool-name list (e.g. ["FileTool", "OfficeTool", "EMailTool"]) that overrides the preset from model. Unknown names are skipped; an empty list falls back to the preset. The core tools (FileTool, GitTool) are always part of the presets and of the TUI custom combinations — the TUI cannot remove them; the only way to change their status is tools.json (see below). |
messages |
OpenAI messages; the last user message is the prompt. |
file_ids |
Optional ids from POST /v1/files — attached as context (Markdown, server-side). |
max_tokens |
Roughly maps to agent loop iterations (max_tokens / 100, clamped 1–50). |
stream |
true → SSE chunks; false (default) → single JSON response with usage. |
session_id |
Extension — multi-turn session id (see Sessions). |
llm_provider |
Extension — LLM provider for this request (see LLM switching). |
Per-tool configuration (
tools.json). A JSON file next to the executable overrides a tool's default status —{"tools": {"OfficeTool": true}}enables the class-BOfficeTool(default OFF),{"tools": {"FileTool": false}}disables a core tool. The rule is "unspecified ⇒ ON": a tool with no explicit entry uses its default (class-A tools ON, class-B tools OFF). An absent file means all defaults. The file is never overwritten by updates (same pattern astelegram.json). The dynamicall-filespreset resolves to every loaded tool the config leaves enabled.
Responses carry an additive session_id field when a session was used.
Streaming caveat: LLM-native streaming (
SendQueryStream) does not support anonymization and throws for Gemini — the/v1/chat/completionsSSE endpoint here is response-side only (the agent result is computed with non-streamingSendQuery).
By default every request is stateless (fresh orchestrator, fresh history). Passing a
session_id keeps the conversation history across requests:
- Create a session:
POST /v1/control {"create": true}→ returns thesession_id(or omitsession_idon the first chat request — the response returns the new id; forstream: true, create the session via/v1/controlfirst). - Send chat requests with
"session_id": "sess-..."— the agent remembers previous turns. - Inspect/reset:
GET /v1/control?session_id=...andPOST /v1/controlwithreset_history: true.
Sessions are in-memory, expire after 1 hour of inactivity (the AIOrchestrator suggested
conversation timeout, AgentHarness.SuggestedConversationTimeout — the same value the AIOffice
voice panel uses), and are serialized (one chat at a time per session). Unknown session_id → 404.
session_id is a proprietary additive extension — a strict third-party OpenAI client may
never send it. To avoid a multi-message chat (no session_id) degrading into a sequence of
unrelated one-shot runs, the server correlates stateless requests by content:
- after every exchange the full transcript (roles + texts, including the assistant reply)
is hashed (SHA-256) into a
hash → sessiondictionary (bounded, idle-TTL, dropped when the session is disposed — seeStatelessConversation.cs); - the next request's transcript minus its last message (the "previous part" the client
resends) is hashed the same way; on a hit the request is routed to that conversation — a
session is created and seeded with the resent transcript when the first message was
processed one-shot (
AgentHarness.SeedHistory).
The correlation only applies when the client resends the accumulated transcript (most OpenAI-style SDKs do). A request carrying no prior assistant reply stays a true one-shot: fresh orchestrator, disposed when the request completes.
The LLM provider is not a server-wide constant: it can be changed on the fly, like
switching models in a code editor — per request, or per session. There is no OpenAI-standard
way to do this, so the server exposes the POST /v1/control pilot endpoint (proprietary
but stable and extensible):
// switch the LLM currently in use for a session
{ "session_id": "sess-...", "llm_provider": "Zai" }
// toggle feature flags (extensible for future features)
{ "session_id": "sess-...", "features": { "voice": true, "tts": true } }
// start a fresh conversation
{ "session_id": "sess-...", "reset_history": true }
// create a session
{ "create": true }GET /v1/control?session_id=... returns the full session state: provider in use, model name,
context window, history size and estimated history tokens, feature flags and platform
capabilities.
Context-window guard. A switch is refused with 409 context_window_exceeded when the
accumulated conversation overflows the target provider's context window — the exact
"on-the-fly switch conflicts with the context window of the model in use" case:
{
"error": "context_window_exceeded",
"detail": "The conversation needs ≈44744 tokens but provider 'ExllamaV2' has a context window of 8192 tokens. Reset the conversation (POST /v1/control with reset_history: true) or switch to a provider with a larger context window.",
"estimated_tokens": 44744,
"context_window": 8192,
"provider": "ExllamaV2"
}The same check applies to a per-request llm_provider on a session chat. The switch itself
preserves the conversation (history is moved to the new provider's utility). Note that some
providers block while being activated — e.g. ExllamaV2 auto-starts the local
ExLlamaV2 server and waits for it to become ready (up to 3 minutes, then it fails).
Per-request switching without a session works too: "llm_provider": "Zai" on any
/v1/chat/completions body.
In-process Kokoro neural TTS (the same engine/voices as the Windows VoiceAgent, but cross-platform — it runs on Windows and Linux). Request:
curl http://localhost:5290/v1/audio/speech \
-H "Content-Type: application/json" \
-d '{"input":"Ciao! Oggi è una bella giornata.","voice":"alloy","speed":1.0}' \
-o speech.wavinput(required),voice(OpenAI namesalloy,echo,fable,onyx,nova,shimmer,coral,sage,ash,ballad,verseor raw Kokoro ids likeif_sara,af_heart— seeGET /v1/audio/voices),speed(0.25–4.0, default 1.0).lang(extension): two-letter ISO language. Kokoro voices are per-language (if_*Italian,af_*/am_*English,ef_*Spanish,ff_*French,jf_*Japanese, ...). Whenlangis omitted the server's system language selects the voice — an Italian machine speaks Italian (if_sara), not accented English. A namedvoiceof a different language is overridden bylang(e.g.alloy+lang: it→ anif_*voice).- Response:
audio/wav(24 kHz mono 16-bit PCM). response_formatacceptswav(default); others →400.modelis accepted for compatibility and ignored.- 501
tts_unavailablewhen the model assets are missing (see Build / assets).
One-shot speech recognition from the server microphone through the
AIOffice.VoiceAgent.Win.exe subprocess — the same chain as the AIOffice Voice panel.
curl http://localhost:5290/v1/voice/listen \
-H "Content-Type: application/json" \
-d '{"lang":"it","timeout_seconds":15}'
# → {"text":"quanto fa sette per otto","lang":"it","provider":"voiceagent-win"}lang: two-letter ISO code (defaultit);timeout_seconds: 1–60 (default 15).- 501
voice_unavailableon non-Windows or when the executable is missing (Voice:ExePath, default: next to the server). The microphone is exclusive — one listener at a time.408on timeout.
Typical voice chat flow: voice/listen → transcript → chat/completions → audio/speech →
audio back to the client.
curl http://localhost:5290/v1/files -F "file=@report.csv" -F "purpose=assistants"- Upload: original binary + server-side Markdown conversion (AllToMarkdown for documents,
Z.ai GLM-OCR for images). Response: OpenAI metadata + additive
extracted_content/content_format;statusisprocessed/unsupported. GET /v1/files/{id}/contentreturns the original bytes;DELETE /v1/files/{id}removes the file ({"deleted": true},404when unknown). Chat references files viafile_ids.- Limits: 25 MB per upload; in-memory cache, lost on restart (volatile by design).
Two kinds of entries:
- Agent sets (
owned_by: "ai-orchestrator"): select the agent tools via the chatmodelfield. - LLM providers (
owned_by: "llm-provider"): the actual LLMs behind the agents, each with its characteristics —provider,model_name,protocol(OpenAI/Gemini),context_window,base_address,interaction_mode(APIorCLI— the effective agent interaction mode; see below). This is the "read the LLM characteristics" surface: a client can pick a provider whose context window fits the task.
Agent interaction mode. Each provider drives the agent tools either through the JSON
tool-calling API (interaction_mode: "API" — one tool per method) or through the
application CLI (interaction_mode: "CLI" — the agent issues ClassName subcommand args
commands against the terminal). It is configured per provider in the Models & Providers UI
or in providers.json (AgentInteractionMode, options API/CLI/Default); Default
delegates to the model size — CLI for small models (context window < 128 000 tokens), API
for large ones. interaction_mode always reports the effective value (the explicit
setting or the size default). The same field appears on GET /v1/control session state.
GET /v1/models/{id} returns a single entry (404 for unknown ids).
The Telegram chat medium — a WTelegramClient
4.4.8 userbot that acts as a chat client (text + file attachments), not a voice medium
(the Telegram Client API has no audio-call support; see docs/telegram.md) —
is purely in-process: messages flow directly through the WTelegramClient library into
the agent harness, and configuration is driven from the TUI (/telegram), which calls
TelegramBridge directly. There are no /v1/telegram/* HTTP endpoints — Telegram is
not a web client, so nothing about it is exposed over HTTP.
Configuration lives in telegram.json next to the executable (excluded from updates) — set
it by hand, with the setup scripts (scripts/setup-telegram.bat on Windows,
scripts/setup-telegram.sh on Linux/macOS), or from the TUI /telegram command. Config
keys (case-insensitive): Enabled, ApiId, ApiHash, PhoneNumber, SessionPath,
AllowedUsers (comma-separated list of ids / @usernames), Agent. Changing a
connection-affecting key (Enabled, ApiId, ApiHash, PhoneNumber, SessionPath)
restarts the bridge from the TUI.
Without a session id it returns what this platform can do right now:
{
"capabilities": {
"platform": "windows",
"default_provider": "DeepSeekBridge",
"providers": [ { "name": "Zai", "model_name": "glm-4.7-flash", "protocol": "OpenAI", "context_window": 128000, "base_address": "https://api.z.ai/", "interaction_mode": "API" }, ... ],
"tts": { "available": true, "engine": "kokoro", "voices": [ ... ], "detail": "" },
"voice": { "available": true, "engine": "voiceagent-win", "detail": "" },
"telegram": { "available": true, "connected": true, "status": { "phase": "connected", ... } },
"sessions": 3
}
}See also: README · Terminal UI · Architecture (developers, not shipped)