@sema-agent/core
Version:
Stateless, task-oriented AI agent core
61 lines (60 loc) • 4.14 kB
TypeScript
import type { Brain } from "../core/types.js";
import { type StreamEngineConfig } from "./stream-engine.js";
export interface OpenAIBrainConfig extends StreamEngineConfig {
/** Base URL of your OpenAI-compatible gateway (without /chat/completions). Overridden by model.baseUrl if set. */
baseUrl?: string;
/** API key. Overridden by per-call options.apiKey / getApiKeyAndHeaders. */
apiKey?: string;
/** Extra headers merged into every request. */
headers?: Record<string, string>;
/** Inject a custom fetch (tests / proxies). Absent ⇒ the engine's default transport; the contract for
* both cases is `resolveFetch` (transport.ts). */
fetchImpl?: typeof fetch;
/**
* For weaker models that emit tool calls as TEXT (`<tool_call>{...}</tool_call>` / fenced json)
* instead of native tool calls: promote them to real tool calls. Off by default. Native models
* don't need this.
*/
repairTextToolCalls?: boolean;
/**
* Replay assistant `reasoning_content` back to the provider on follow-up requests (design/46).
* **On by default**, and **conditional**: thinking is replayed only on prior assistant **tool-call**
* turns — DeepSeek V4 **requires** it there (else it errors/degrades), while non-tool turns ignore it,
* so replaying there only bloats the context window (prefix-cache cuts the *cost* of that bloat but not
* the *window occupancy* → earlier compaction). Replaying just the tool-call turns is correctness-
* equivalent and leaner. Set `false` to never replay (e.g. a provider that rejects the field).
* Anthropic uses `createAnthropicBrain`, which always replays thinking blocks with their signature.
*/
replayThinking?: boolean;
/**
* Detect **degenerate repetition** (the model looping on the same char/phrase forever) mid-stream
* and cut it off — the turn ends `error` ("degenerate repetition") so the loop stops paying and a
* team member's looped output is treated as a failed turn, not a real statement. Default true.
*/
detectRepetition?: boolean;
}
/**
* Wire-field inference for the per-reply output cap when the model declares no explicit
* `compat.maxTokensField`. Newer stock-OpenAI model families REJECT `max_tokens` with a 400
* ("Unsupported parameter: 'max_tokens' is not supported with this model. Use 'max_completion_tokens'
* instead."): the o-series reasoning models (o1/o3/o4 and their -mini/-pro/dated variants), the gpt-5
* family, and chatgpt-4o. Everything else — older OpenAI models AND the long tail of third-party
* OpenAI-COMPATIBLE endpoints (vLLM, DeepSeek, llama.cpp, gateways) — keeps `max_tokens`, because many
* compat endpoints only recognize that spelling. Heuristic rules (deliberately conservative — an id the
* table can't confidently classify keeps the widely-supported `max_tokens`):
* - `gpt-5…` → max_completion_tokens (gpt-5, gpt-5-mini, gpt-5.x, …)
* - `chatgpt-4o…` → max_completion_tokens (chatgpt-4o-latest)
* - `o1|o3|o4` (+ `-`/`.` suffix or bare) → max_completion_tokens (o1-preview, o3-mini, o4-mini, …)
* - anything else → max_tokens (gpt-4o/gpt-4.x accept both; third-party ids often ONLY this)
* An explicit `compat.maxTokensField` always wins over this inference. Exported so shells constructing
* Models from bare ids (e.g. the TB entry) can bake the same inference into compat metadata.
*/
export declare function inferMaxTokensField(modelId: string): "max_completion_tokens" | "max_tokens";
/**
* Create a Brain that talks to an OpenAI-compatible chat-completions endpoint (your model gateway).
* Translates the unified Context into an OpenAI request and the streamed response back into the unified
* AssistantMessageEvent protocol, including native tool calls. The connect/retry/timeout/read-loop
* machinery lives in the shared {@link runStreamingBrain} engine (design/32); this adapter owns the
* OpenAI request shaping + chat-completions SSE parsing + finalization.
*/
export declare function createOpenAIBrain(config?: OpenAIBrainConfig): Brain;