Corbie

Plugins

llm

This plugin holds a conversation with a model, or puts questions with fixed answers to a System One model.

  llm.chat(conversation, { service, model, address, key, system, tools, shape, effort, reply_tokens, timeout, own })
      gives { text, calls, stop, usage, kept }
  llm.decide(state, { service, model, address, key, questions, timeout })
      gives { answers, model, usage }
  llm.takes(blob, { service })    returns true when the service accepts the blob in files

chat holds a conversation with a model. decide puts questions with fixed answers to a System One model, which answers them all at once with probabilities and writes no text.

conversation is a list of parts, each one of these.

{ role: "user", content, files }what the model is told { role: "assistant", content, calls, kept }a reply of the model's { role: "tool", results: [{ id, name, result, error, files }] }the results of its tool calls

files maps a name to a blob: an image (png, jpeg, gif or webp, up to 5 MiB) or a PDF (up to 32 MiB). The plugin sends each to the model after a line that gives its name. Any other blob fails the call before anything is sent.

Options:

serviceanthropic, openai (the Responses API), openai-chat (the Chat Completions API) or claude-code (the claude program on the runner's computer); default anthropic modelthe model's name; required addressthe service's URL, when it is not https://api.anthropic.com or https://api.openai.com keya handle to the secret that holds the service's key systemthe system prompt toolsthe tools the model may call: [{ name, does, args }], with args a JSON Schema with type: "object" shapea reply that is data, as a JSON Schema with type: "object"; text is then JSON. A field with format: "blob" and no description is described as a blob's name effortlow, medium or high; claude-code also takes xhigh and max reply_tokensthe most tokens a reply may hold timeoutseconds, or text such as "5m"; default 3m ownfor claude-code: the program's own tools that it may use, by name

Returns:

textthe model's reply; also emitted as "text" while it arrives, for a client that watches the run callsthe tool calls the model made: [{ id, name, args }] stop"end", "tools" when it made tool calls, or "length" when the reply was cut at its limit usage{ input, output }, in tokens keptwhat the service needs back unchanged, or null; put it in the assistant part

For decide, state is the text or data the questions are about, and questions is written in the System One format, which TypeSafe's Jev set: each question under its name, with these fields.

typenoul, choice or score instructionswhat is asked criteriafor choice, { option: description }; for score, the levels from lowest to highest; noul has none

Options for decide:

servicesystemone; default systemone modelthe model's name; required addressthe service's URL, when it is not https://api.typesafe.ai: a model served on the runner's computer, or by a cloud keya handle to the secret that holds the service's key; a model on the runner's computer has none questionsthe questions, as above; required timeoutas for chat

Returns, for decide:

answerseach answer under its question's name, as the service wrote it: { type, noul } for noul, the probability that the statement holds; { type, choice, probabilities, confidence } for choice; and { type, score, legend, probabilities, confidence } for score. confidence is how concentrated the probabilities are, not how likely the answer is right: a condition reads a probability modelthe version of the model that answered; jev-latest names a different version after each release usage{ input, output }, in tokens

Errors:

bad_inputthe service does not accept a blob in files, or does not make the call refusedthe service could not be reached, returned an error, or stopped before the reply was complete timeoutthe reply was not complete within the timeout not_foundthe claude program could not be started failedthe claude program ended without a reply

Needs net:host for the service and secret:NAME@net:host for its key. claude-code needs these in their place.

exec:claudeto start the program env:HOMEwith no key: the program is signed in as the runner's user env:USERPROFILEthe same, on Windows secret:NAME@exec:claudewith a key: an API key, or the token that claude setup-token prints networkfor WebSearch or WebFetch in own: diskfor Read, Glob, Grep, Edit, Write or NotebookEdit in own: allfor any other tool in own:, such as Bash or Skill

claude-code ignores address and reply. Agent lists the types, and says how Corbie starts the claude program.

This script asks for a reply as data.

allow: [net:api.anthropic.com, "secret:ANTHROPIC_KEY@net:api.anthropic.com"]

const reply = llm.chat([{ role: "user", content: "Say hello." }], {
  model: "claude-sonnet-5-5", key: secrets.get("ANTHROPIC_KEY"),
  shape: { type: "object", properties: { greeting: { type: "string" } } },
})
return JSON.parse(reply.text)       // gives { greeting: "Hello." }

This script sends an image with its question.

allow: [fs.read:/srv/reports/**, net:api.anthropic.com, "secret:ANTHROPIC_KEY@net:api.anthropic.com"]

const chart = blobs.file("/srv/reports/chart.png")
llm.takes(chart, { service: "anthropic" })       // gives true
const reply = llm.chat([{ role: "user", content: "What is this?", files: { chart } }], {
  model: "claude-sonnet-5-5", key: secrets.get("ANTHROPIC_KEY"),
})
return reply.text                                // gives "A picture, named chart."

This script calls the claude program, signed in as the runner's user.

allow: [exec:claude, env:HOME]

const reply = llm.chat([{ role: "user", content: "Say hello." }], { service: "claude-code", model: "sonnet" })
return reply.text                   // gives "Hello."

This script puts two questions to a System One model.

allow: [net:api.typesafe.ai, "secret:TYPESAFE_KEY@net:api.typesafe.ai"]

const r = llm.decide("Checkout has been failing for every customer for the last hour.", {
  model: "jev-latest", key: secrets.get("TYPESAFE_KEY"),
  questions: {
    team: { type: "choice", instructions: "Which team should handle this?",
            criteria: { billing: "Payments, invoices and refunds", technical: "Outages, errors and configuration" } },
    urgent: { type: "noul", instructions: "The customer cannot use the product." },
  },
})
r.answers.team.choice               // gives "technical"
r.answers.urgent.noul > 0.9         // gives true
return r.model                      // gives "jev-1.13.0"