Model backend

Sculpting uses pipeline/sculpt/llm.js to send one OpenAI-compatible /v1/chat/completions request per model step. complete({ system, messages, maxTokens }) returns { text, stopReason }. The pipeline supports two routes:

  • Brama (models.brama) signs each request with its agent identity.
  • OpenAI-compatible provider (models.openai_compatible) sends a bearer request without Brama signing. This can be a provider the user runs or rents.

The config declares exactly one of these objects. There is no preference key, fallback, or automatic provider selection. models.backend, models.openrouter, models.anthropic, models.openai, and other backend names are refused with the unsupported path. Declaring both is refused. A missing required field is named in the error.

Configure one route

Brama requires key, bearer, agent_id, and an explicit model alias. Its address comes from models.brama.url when declared, otherwise from Stado's published ~/.stado/forwards/brama.local file. An absent address or model is refused with the missing source named. The key and bearer must be role://<role>/<field> references in an original config; the identity can be a reference or plain string. The base URL has /v1/chat/completions appended. glina config check --config pipeline.config.json resolves references and validates the route without sending a model request.

For a provider without Brama, replace the whole models.brama object:

{
  "models": {
    "openai_compatible": {
      "url": "https://provider.example",
      "bearer": "role://model-provider/token",
      "model": "example-model"
    }
  }
}

All three provider fields are required. The URL is a base URL, not a /v1 endpoint; Glina appends /v1/chat/completions. The bearer must be a role reference, resolved through Stado and Skarbiec or the selected owner-only GLINA_CREDENTIALS_FILE store. An inline bearer is refused before a request. See configuration for the credential format and local validation.

Request and errors

Brama sends authorization: Bearer <bearer>, x-agent-id, x-agent-timestamp, and x-agent-signature = HMAC-SHA256(key, "<agent_id>:<ts>:<sha256(body)>"). The unsigned route sends only its bearer authorization. Each model step makes one request. A transport error names the route and URL. A response-read failure keeps its cause and HTTP status. An HTTP refusal preserves the complete response body, including plain-text and HTML refusals; malformed JSON and missing or non-text answer content are separate errors with the received body. CLI errors and the desktop operation output expose the same cause. Glina does not silently retry or switch routes.

Anthropic-style content blocks are flattened to plain OpenAI messages. Image blocks become [viewport screenshot attached]; raw image bytes are not sent.

parseJsonFrom(text) extracts the sculpt step from a fenced JSON block first, then scans balanced braces for the first valid object. A malformed or absent JSON object is a model reply error, not a route fallback.