# A package filling `ai`

The full rules for a provider package's `provides/ai.jadpo`, which maps
`ai`'s operations onto the provider's. An app only names the package's
connection in `ai: { connection: }`; the overview is under
[How a provider fills it](the-ai-capability.md#how-a-provider-fills-it).

- **`provide ai`** in a provider package's `provides/ai.jadpo` takes
  `generate:` and `models:`, both required, and `stream:` and `embed:`
  ([Streams, embeddings and the OpenAI and Anthropic packages](streams-embeddings-and-the-openai-and-anthropic-packages.md)).
  Any other key, like every mistake in the mapping, is
  `package.provider_invalid` where it is written. `ai: { connection: }`
  naming a connection whose package has no `provide ai` is
  `ai.settings_invalid`.
- **`generate:`** takes `call:`, `input:`, `answer:`, `usage:` and
  `schema:`, required, and `refused_when:` and `cut_off_when:`. `call:` is
  one of the package's operations that changes nothing (`changes:
  false`), since generation changes nothing at the provider, and whose
  answer its types read (no `decode:`). Its fields' decoders and encoders
  aren't run: a package function has no site at an `ai` call.
- **`input:`** maps each field of the operation's input, every required
  one, by its name, sent by its `wire_name:`: `request.model`, the
  model's name after its provider (`gpt-4.1-mini` for
  `openai/gpt-4.1-mini`, since the connection is the provider; the
  built-in driver sends the `ModelId` whole, as vLLM names a model),
  onto text; `request.max_output` onto a whole number;
  `request.instructions` onto text; `{ each: request.messages  as: { } }`
  onto a list of objects, `item.role` onto text or an enum and
  `item.content` onto text; `settings.<name>`, a setting the package
  declares, given by each connection; or text, a whole number or a flag
  written there. Where `request.instructions` is mapped the instructions
  go there and `request.messages` holds the rest; otherwise the messages
  open with them as the `system` message, as Chat Completions takes them.
  `request.schema` isn't mapped: the schema goes where `schema: { at: }`
  says. No other nested object is mapped.
- **`answer:`** and **`refused_when:`** are places in the operation's
  output, `response.<field>`, `first` stepping into a list's first item,
  read from the answer decoded by the package's types: `answer:` ends at
  text, and the call is `refused` when `refused_when:`'s place holds
  anything but `none`, blank text or `false`. **`cut_off_when: { path:
  equals: }`** is read on the answer as sent, as `failed_when:` is, its
  `path:` dotted by the members' spellings on the wire and a list's items
  by number, checked against the output type; it ends the call
  `over_budget`.
- **`usage:`** names `input:` and `output:`, required, and `cached:`,
  each a place in the output that is a whole number or a `TokenCount`, or
  `0` for a count the provider never gives; the budgets count them.
- **`schema: { mode: at: }`**: `mode:` is required. With
  `AiSchema.strict_subset` or `AiSchema.json_only`, `at:` is required and
  names, dotted, the member of the request Jadpo writes the mode's
  envelope in, the one the built-in driver sends as `response_format`:
  `{"type": "json_schema", "json_schema": {"name": <prompt>, "strict":
  true, "schema": ...}}`, the schema cut as
  [Generating, its fake and the local driver](generating-its-fake-and-the-local-driver.md)
  says, or `{"type": "json_object"}`; nothing for a `T` built on `Text`. With
  `AiSchema.none`, `at:` is refused. The example above writes `at:
  "response_format"` for that reason. With `json_only` and `none` the
  schema goes in the instructions, under `# Answer format`, whole, and a
  JSON answer in a Markdown code fence is read without the fence.
  Anthropic's envelope is `schema: { envelope: AiEnvelope.anthropic }`; Gemini's waits for a package that
  needs it.
- **`models: { context: }`** names `<library>.<constant>`, a constant of
  the package's own libraries listing each model as an object of
  `model:`, its name after its provider, and `tokens:`, its context
  window; the mapping reading it counts as using the constant. `prices:` is optional, checked to name such a constant, and read by the cost estimate.
- **`too_long`** is checked before sending, before the call takes a step:
  the rendered messages (the schema in the instructions included) are
  taken to be a token for each four bytes of UTF-8, rounded up, OpenAI's
  rule of thumb, and a total over the model's `tokens:` ends the call
  `too_long` with nothing sent. A model the table doesn't list isn't
  checked. `max_output:` isn't added to the total, as the page counts only
  the prompt. A provider's own refusal of a long prompt is one of its
  operation's failures, so `misconfigured`, as every one of them is.
- **The fake.** In `jadpo test` and `jadpo call` the `ai` fake stands in
  the package's connection too, ahead of the package's fake, answering in
  the built-in driver's form whatever the connection, as
  [Generating, its fake and the local driver](generating-its-fake-and-the-local-driver.md)
  says. An answer from the connection's own driver or its sandbox is
  read through the mapping. The request is still made from the mapping,
  so what would be sent is checked and logged.
- **`jadpo dev`** fakes the package's connection, as it fakes every
  provider call
  ([Fakes and the development call log](fakes-and-the-development-call-log.md)).
  Each prompt is answered with a generated value of the call's type
  (source `generated`), or with a condition scripted by
  `jadpo calls script ai.<prompt> <condition>` (source `scripted`). Only
  the five conditions can be scripted, and the prompt is named as a
  test's `fakes:` names it.
- **The sandbox.** `development: { sandbox: [<prompt>] }` on the `ai`
  connection names the app's own prompts beside the package's
  operations. Those go to the provider's sandbox with the developer's
  credential, never live, as a sandboxed operation does, under the app's
  own limits ([How budgets are counted](how-budgets-are-counted.md)). A
  prompt asked of `Driver.openai_compatible` isn't faked, so scripting
  one is `cli.calls_arguments`.
