Skip to main content
This guide covers breaking changes in @outputai/llm. Generation APIs drop native AI SDK call arguments. Skills load only from prompt frontmatter. Call-argument tools merge with prompt YAML tools. Prompt file config is a strict key list. Loaded messages carry resolved providerOptions instead of tag attributes. LLM traces use a single loaded prompt on start and cost / merged sources on end.

Skills load only from the prompt file

generateText, streamText, generateTextWithStreaming, and Agent no longer accept a skills argument. Dynamic skill resolvers (sync or async functions) are gone with it. Passing skills throws:
skill() is no longer exported. Colocated skills/ auto-discovery is gone: a skills/ folder next to the prompt is not loaded unless you list it in frontmatter.

Move call-argument and inline skills into the prompt

Before

After

Put the instructions in a markdown file and list the path in frontmatter. Paths are relative to the prompt file.
prompts/skills/audience.md
prompts/writer@v1.prompt
A directory path loads every .md file under it (recursive):
A single string is still valid YAML and is coerced to an array at load time:

Restore colocated skills that used auto-discovery

Before

No skills: key in the prompt. Output discovered ./skills automatically.

After

Keep the folder. Add an explicit path:

Prompt tools and call-argument tools merge

Call-argument tools no longer replace the whole prompt YAML tools map.
  • Prompt YAML tools and call-argument tools are merged.
  • The same key: the caller wins.
  • When skills are present, load_skill is added last and cannot be overridden.

Before

googleSearch was dropped. Only lookup was sent.

After

Both are sent: { googleSearch, lookup }. If you meant to disable YAML tools, remove them from the prompt (or override that key on the call).

Loaded prompt shape

loadPrompt returns the parsed prompt object.

Before

After

The SkillsArg and Skill types are removed. Skills are now internal loaded values; declare file paths in prompt frontmatter instead of constructing or typing skill objects.

Per-message options resolve at load

v0.11 kept the role tag’s attributes on the loaded message and compiled options="..." into AI SDK providerOptions at generate time. v0.12 compiles that at loadPrompt. LLM traces (input.prompt.messages) use the same loaded shape.

Before

After

The only supported role-tag attribute is options. Any other attribute throws at load (previously this could fail later, at generate):
Unknown options names, options without a value, and options set while config.messageOptions is missing or empty also throw at load.

Prompt message roles are narrowed

PromptMessage.role is now typed as 'system' | 'user' | 'assistant' instead of string, matching the authored role blocks accepted by loadPrompt(). Code that constructs a PromptMessage from a dynamic string must validate or narrow the value before assigning it.

Prompt bodies use explicit parsing modes

v0.11 searched the rendered body for supported role blocks wherever they appeared. This could silently ignore text outside those blocks, unknown top-level tags, and malformed attributes. v0.12 selects one mode from the first meaningful token after leading whitespace and HTML comments:
  • Plain text selects instruction mode. The whole trimmed body becomes prompt.instructions, including any tags that appear later.
  • A tag selects message mode. The complete body is validated as top-level role blocks and prompt.instructions is null.
These modes describe loadPrompt() output; API requirements are unchanged. generateText, generateTextWithStreaming, streamText, and Agent require message mode, while generateImage requires instruction mode. For example, v0.11 extracted the <user> block here and discarded the surrounding text:
In v0.12 this is one instruction string because it starts with text. To keep message mode, move the text inside the role block:
Message mode now enforces these rules:
  • Top-level blocks must use system, user, or assistant.
  • Only whitespace and HTML comments may appear between blocks.
  • Root self-closing tags, unmatched closing tags, and unclosed blocks throw.
  • Different-name tags inside a message remain literal message content.
  • A nested non-self-closing tag with the same name as its message throws instead of prematurely closing the outer block. Escape literal examples, such as &lt;user&gt;example&lt;/user&gt;.
  • Attribute names, separators, and quotes are validated. Spaces around = and > inside quoted values are supported; malformed fragments no longer pass silently.
Prompt files no longer accept authored <tool> blocks. Their string content never matched AI SDK’s structured tool-result message contract, so text generation rejected them. AI SDK continues to create tool messages during execution, and Agent callers may supply structured tool messages through messages or messageStore.

LLM trace details

Start-trace input on generateText, streamText, generateTextWithStreaming, generateImage, and Agent (generate, generateWithStreaming, stream) is the loaded prompt object only. Filename, interpolation values, and rendered config live on that object. Agent traces use the same shape as the text APIs (v0.11 recorded only the filename as prompt). End-trace output adds cost (the same payload as the cost attribute / cost:llm:request event; null when pricing is missing) and replaces tool-only sourcesFromTools with merged sources (tool results plus native provider sources; always an array).

Start (input)

Before

After

If you read input.prompt as a filename, use input.prompt.name. If you read input.loadedPrompt, switch to input.prompt. If you read sibling input.variables, use input.prompt.variables.

End (output)

Before

After

If you read output.sourcesFromTools, switch to output.sources. If you relied on cost only as a trace attribute, it is also on output.cost.

Response source and cost types

ExtractedSource now matches the AI SDK source union. A source can be a URL or a document, so narrow on sourceType before reading url:
LLMCallCost and LLMUsageEvent now represent a live Tracing.Attribute.LLMUsage instance. The old components and message fields are gone; read the priced dimensions from usage. A missing calculation is represented by response.cost === null, not an object with total: null. If you type a serialized event, omit the instance method that is not present in JSON:

AI SDK helpers come from aiSdk

@outputai/llm no longer re-exports tool, Output, smoothStream, stepCountIs, hasToolCall, or jsonSchema as named exports. The namespace re-export ai is renamed to aiSdk.

Before

After

import { ai } from '@outputai/llm' becomes import { aiSdk } from '@outputai/llm'. Output APIs (generateText, Agent, loadPrompt, …) stay named exports. Cherry-picked AI SDK type re-exports (ToolSet, FinishReason, ModelMessage, StreamTextOnChunkCallback, …) are also gone. Import those from ai, or as aiSdk.ToolSet.

Replace removed Output option types

The Output-owned AI SDK option aliases were removed with the unrestricted native arguments. Use the corresponding public parameter type:
OutputAgentGenerateWithStreamingParameters no longer accepts an output type argument. Remove the generic:

Call arguments are a fixed list

Dropped native AI SDK call arguments from generateText(), generateTextWithStreaming(), streamText(), generateImage(), and Agent. Calls no longer accept temperature, maxTokens, maxSteps, providerOptions, image n/size/seed, experimental_transform, onStepFinish, and similar. Unknown keys throw. Set model and image config (including maxSteps, default 10) on the prompt file; call-argument stopWhen still overrides it. generateImage mask still requires images. Agent.stream() now appends to messageStore in its wrapped onFinish when finishReason is not 'error'. streamText() and Agent.stream() now treat onError as a fire-and-forget observer. Output maps and forwards the provider error, but exceptions and rejected promises from the callback are ignored. To fail a workflow step with the original error, capture it in onError and throw it after consuming the stream.

Agent message store

conversationStore is renamed to messageStore. The type is MessageStore. createMemoryConversationStore() is removed; implement the store yourself.

Before

After

Move model config onto the prompt

Before

After

prompts/writer@v1.prompt
Any merged tools, including prompt-only Vertex googleSearch / urlContext, now get stopWhen: stepCountIs(maxSteps) from that prompt value. Previously the ceiling applied only when call-argument tools or load_skill were present; YAML-only grounding stayed at the AI SDK default of one step. If you need the old one-step grounding behavior, set maxSteps: 1 on the prompt, or pass stopWhen on the call:

Agent constructor validation

new Agent( {} ) no longer throws Agent requires a prompt. Invalid constructor args use the same schema as generateText and throw Invalid Agent() arguments. That includes a missing/empty prompt, an empty promptDir, and call-argument skills or maxSteps fields.

Prompt config is a strict key list

Unknown top-level keys on a .prompt file now throw Invalid prompt file. Previously they were kept on config and ignored. provider and model must be non-empty strings, and maxTokens must be a positive integer. Nested providerOptions (including thinking) stays open. Allowed top-level keys: provider, model, temperature, maxTokens, maxSteps, skills, tools, providerOptions, messageOptions, n, maxImagesPerCall, size, aspectRatio, seed. Snake_case aliases of those keys fail with a suggestion:
Move provider-specific fields under providerOptions. effort and reasoningEffort at the top level are unknown keys; they belong under providerOptions.anthropic and providerOptions.openai.

Before

After

Checklist

  • Delete skills from generateText / streamText / generateTextWithStreaming / Agent calls.
  • Remove skill(), Skill, and SkillsArg imports; move inline skills into files listed under prompt skills:.
  • Add skills: ./skills (or explicit file paths) to prompts that relied on colocated auto-discovery.
  • Expect YAML tools and call-argument tools to merge; remove YAML tools if you previously relied on replacement.
  • Strip dropped call arguments from generateText / generateTextWithStreaming / streamText / generateImage / Agent (temperature, maxTokens, maxSteps, providerOptions, maxRetries, experimental_transform, onStepFinish, image n / size / seed, and any other AI SDK-only keys). Unknown keys throw. Put model and image config on the prompt file; call-argument stopWhen still overrides maxSteps.
  • Set maxSteps: 1 on YAML-only grounding prompts that must stay one-shot, or pass stopWhen: aiSdk.stepCountIs(1) on the call.
  • Rename promptFileDir to fileDir. Read interpolation values from prompt.variables.
  • Treat config.skills as always string[] after loadPrompt.
  • Treat config.maxSteps as always a positive integer after loadPrompt (default 10).
  • Treat prompt.instructions as always string | null after loadPrompt (chat prompts are null).
  • Read message.providerOptions instead of message.attributes on loadPrompt results and LLM trace input.prompt.messages.
  • Narrow dynamic role strings before assigning them to PromptMessage.role; the type now accepts only 'system', 'user', and 'assistant'.
  • Remove unknown attributes from role tags (name, id, pinned, …). Only options is allowed; extras throw at loadPrompt.
  • Remove authored <tool> blocks from prompt files; pass structured tool history through Agent messages or messageStore.
  • Audit prompt bodies that put prose before the first role tag. They now load as instructions; move the prose inside a role block to keep message mode.
  • Remove text between or after top-level role blocks. Only whitespace and HTML comments are allowed there.
  • Escape literal same-name role tags inside messages (&lt;user&gt;...&lt;/user&gt;). Different-name semantic tags remain valid content.
  • Give every options attribute a value and fix malformed attribute names or quotes; prompt markup now fails explicitly at load.
  • Read LLM trace input.prompt as the loaded prompt object (input.prompt.name, input.prompt.variables). Do not treat input.prompt as a filename or read input.loadedPrompt.
  • Read LLM trace output.sources instead of output.sourcesFromTools. Expect output.cost on successful LLM nodes (null when pricing is missing).
  • Narrow ExtractedSource on sourceType before reading url. Treat LLMCallCost / LLMUsageEvent as live usage instances and omit addUsage when typing serialized JSON.
  • Import AI SDK helpers and types from aiSdk (aiSdk.Output, aiSdk.tool, aiSdk.stepCountIs, aiSdk.ToolSet, …). Replace import { ai } with import { aiSdk }. Do not import cherry-picked AI SDK types from @outputai/llm.
  • Replace GenerateTextAiSdkOptions, StreamTextAiSdkOptions, and GenerateImageAiSdkOptions with their *Parameters equivalents. Remove the generic from OutputAgentGenerateWithStreamingParameters.
  • Expect Agent.stream() to persist message-store history on success.
  • Replace conversationStore with messageStore. Replace ConversationStore with MessageStore. Remove createMemoryConversationStore() and pass your own store.
  • Update Agent tests and error matchers that expected Agent requires a prompt.
  • Ensure prompt provider and model values are non-empty, and set maxTokens to a positive integer.
  • Move unknown prompt frontmatter keys (topP, effort, reasoningEffort, max_tokens) onto the allowlist or under providerOptions. Expect Invalid prompt file for leftover extras.