@outputai/llm. The package now uses AI SDK 7 and its matching provider majors. 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, keep raw usage and merged sources on end, and store normalized usage and cost as attributes.
Upgrade AI SDK and provider packages
@outputai/llm v0.12 requires these dependency majors:
ai: 7.x@ai-sdk/openai,@ai-sdk/anthropic,@ai-sdk/azure, and@ai-sdk/perplexity: 4.x@ai-sdk/amazon-bedrockand@ai-sdk/google-vertex: 5.x
@perplexity-ai/ai-sdk search-tool package is not the official @ai-sdk/perplexity provider. Its latest release, 0.1.3, declares support for AI SDK 5 and 6 only. Projects using perplexitySearch() should replace it or verify its behavior with AI SDK 7 before upgrading; package managers may report an unmet peer dependency.
Rename stream callbacks and properties
streamText() and Agent.stream() now use onEnd instead of onFinish:
WrappedStreamTextOnFinishEvent->WrappedStreamTextOnEndEventWrappedStreamTextOnFinishCallback->WrappedStreamTextOnEndCallback
result, cost, and merged sources.
AI SDK 7 also renamed the full event stream:
fullStream remains available as a deprecated alias, but migrate to stream.
textStream is unchanged. Direct AI SDK onStepFinish usage becomes onStepEnd.
onChunk now receives every AI SDK 7 stream part, including start, finish, error, step boundaries, text boundaries, and reasoning boundaries. Update exhaustive handlers to ignore part types they do not use.
Review native AI SDK response usage
Output returns proxied AI SDK 7 results. If your application reads native response fields beyond Output’sresult, cost, and merged sources, follow the official AI SDK 7 migration guide linked above instead of treating this guide as a replacement for it.
Output’s merged sources now includes native sources accumulated across every AI SDK step as well as URLs extracted from tools.
Normalized usage and cost metadata
Cost calculation uses AI SDK 7’s detailed token counts when they reconcile with the aggregate input or output count. Incomplete breakdowns use the aggregate count. Missing cache pricing uses the regular input price, and missing reasoning pricing uses the regular output price. Traces now store separatellm:generation:usage and llm:generation:cost attributes. Both include providerId, modelId, nullable aggregate input, output, and total fields, plus detailed items. Priced calls also retain the deprecated llm:usage attribute for existing trace consumers.
Usage items have an amount, a group (input or output), and one of these labels:
- Input:
no_cache,cache_read, orcache_write - Output:
textorreasoning
label: null. LLMGenerationUsage.status is complete when both input and output usage are available; otherwise it is incomplete.
Cost items repeat the usage identity and amount, then add ppm, item total, and a status:
ok: the matching price was available.fallback: cache or reasoning usage used the regular input or output price.missing: no applicable price was available;ppmand itemtotalarenull.
LLMGenerationCost.status is precise, imprecise, or incomplete based on those item statuses and usage completeness. Missing model or dimension pricing therefore produces a non-null, incomplete LLMGenerationCost whose total may be null. The complete cost object is null only when the pricing catalog itself could not be loaded.
The new llm:generation:metering event is emitted whenever raw AI SDK usage can be normalized. Its payload is { usage: LLMGenerationUsage, cost: LLMGenerationCost | null }. Prefer it for new integrations because it preserves reported usage independently from pricing and records fallback and missing prices explicitly.
The existing cost:llm:request event remains supported with its legacy payload shape, so existing handlers do not need a structural migration for v0.12. Its deprecated LLMUsageEvent retains the input, input_cached, output, and reasoning line types. Values can differ where AI SDK 7 reports a different breakdown or where v0.12 corrects legacy reasoning double-counting.
Raw AI SDK usage remains available on the response and trace output.
Update Agent assumptions
Agent no longer inherits from AI SDK’s ToolLoopAgent. Its Output APIs retain their existing behavior, but code must not depend on Agent instanceof ToolLoopAgent or other inherited ToolLoopAgent members.
The intended MessageStore conversation behavior is unchanged. Output reads AI SDK 7’s accumulated responseMessages so intermediate tool-call and tool-result messages continue to be persisted once.
Custom stores must preserve all AI SDK 7 ModelMessage content parts. File data may require binary-safe serialization, and assistant content can contain the new reasoning-file part.
Review provider behavior
- OpenAI reasoning requests now return detailed reasoning summaries by default when reasoning is enabled. Set
providerOptions.openai.reasoningSummarytonullin prompt frontmatter to disable them. - Function-valued tool descriptions are now accepted in addition to string descriptions.
toolApproval, runtimeContext, and toolsContext are not exposed by the v0.12 Output wrapper. AI SDK 7’s deprecated tool-level needsApproval field remains accepted.
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
.md file under it (recursive):
Restore colocated skills that used auto-discovery
Before
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-argumenttools 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_skillis 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
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’sattributes 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
options. Any other attribute throws at load (previously this could fail later, at generate):
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.instructionsisnull.
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:
- Top-level blocks must use
system,user, orassistant. - 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
<user>example</user>. - Attribute names, separators, and quotes are validated. Spaces around
=and>inside quoted values are supported; malformed fragments no longer pass silently.
<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-traceinput 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 keeps the raw AI SDK usage and replaces tool-only sourcesFromTools with merged sources (tool results plus native provider sources; always an array). Normalized usage and cost are stored as trace attributes.
Start (input)
Before
After
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
output.sourcesFromTools, switch to output.sources.
Attributes
The raw AI SDKoutput.usage shape remains on the trace output. Normalized usage and cost live under the LLM node’s attributes object:
attributes["llm:usage"] payload remains available for compatibility, so existing trace readers do not need an immediate migration. New integrations should use attributes["llm:generation:usage"] and attributes["llm:generation:cost"]; these preserve normalized usage independently from pricing and represent detailed dimensions explicitly. The CLI accepts all three keys and prefers the normalized attributes.
Response source, cost, and metering 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:
response.cost and direct-stream onEnd cost now use LLMGenerationCost | null. The old LLMCallCost type is removed.
@outputai/core no longer exports the LLM-specific Attribute.LLMUsage class or Attribute.Usage interface. Core now exposes only the generic Attribute.BaseAttribute for package-owned trace attributes. Import LLMGenerationUsage, LLMGenerationCost, and the deprecated legacy LLMUsageEvent type from @outputai/llm.
In v0.11, response.cost used the legacy llm:usage shape with priced lines under usage. In v0.12, read aggregate costs directly and detailed dimensions from items:
type: "llm:generation:cost", providerId, modelId, nullable input, output, and total, a calculation status, and one cost item for each normalized usage item.
Do not use only response.cost === null to detect unavailable totals. Missing model or dimension pricing returns an incomplete LLMGenerationCost; check response.cost?.total == null or inspect its status and item statuses. response.cost itself is null when the pricing catalog could not be loaded.
LLMUsageEvent remains exported from @outputai/llm for cost:llm:request. Its legacy payload shape remains compatible, but the type is deprecated to guide new integrations toward the more complete metering event.
llm:generation:metering for new integrations when you need normalized usage and cost data. It fires when usage can be normalized; event.payload.cost may be null or incomplete. Existing cost:llm:request handlers can remain until you choose to adopt the more detailed format.
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 fromgenerateText(), generateTextWithStreaming(), streamText(), generateImage(), and Agent. Calls no longer accept temperature, maxOutputTokens, maxTokens, maxSteps, providerOptions, image n/size/seed, experimental_transform, onStepFinish, and similar. Unknown keys throw. Set model and generation or image config (including maxSteps, default 10) on the prompt file; call-argument stopWhen still overrides it.
generateImage mask still requires images. Agent.generate(), Agent.generateWithStreaming(), and Agent.stream() append to messageStore only when finishReason is not 'error'. Errored turns are not persisted.
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
googleSearch / urlContext, now get stopWhen: isStepCount(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. When provided, maxOutputTokens and deprecated maxTokens must each be a positive integer. Nested providerOptions (including thinking) stays open.
Allowed top-level keys: provider, model, temperature, maxOutputTokens, deprecated maxTokens, topP, topK, presencePenalty, frequencyPenalty, stopSequences, seed, maxSteps, skills, tools, providerOptions, messageOptions, n, maxImagesPerCall, size, aspectRatio.
maxTokens remains available for backward compatibility and is retained on the loaded prompt config. When maxOutputTokens is absent, loadPrompt also copies the maxTokens value to maxOutputTokens. When both keys are set, maxOutputTokens takes precedence. Text generation APIs read the normalized maxOutputTokens value.
Snake_case aliases of those keys fail with a suggestion:
providerOptions. effort and reasoningEffort at the top level are unknown keys; they belong under providerOptions.anthropic and providerOptions.openai.
Before
After
Checklist
- Delete
skillsfromgenerateText/streamText/generateTextWithStreaming/Agentcalls. - Remove
skill(),Skill, andSkillsArgimports; move inline skills into files listed under promptskills:. - 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,maxOutputTokens,maxTokens,maxSteps,providerOptions,maxRetries,experimental_transform,onStepFinish, imagen/size/seed, and any other AI SDK-only keys). Unknown keys throw. Put model and generation or image config on the prompt file; call-argumentstopWhenstill overridesmaxSteps. - Update installed AI SDK packages to
ai7.x, OpenAI / Anthropic / Azure / Perplexity 4.x, and Amazon Bedrock / Google Vertex 5.x. - Rename
onFinishtoonEndinstreamText()andAgent.stream(). Rename the corresponding wrapped callback and event types. - Replace
fullStreamwithstream, and update exhaustiveonChunkhandlers for the expanded AI SDK 7 part union. - Replace
stepCountIs()withisStepCount(). - Review direct AI SDK result-field access against the official AI SDK 7 migration guide.
- Use
groupandlabelwhen reading normalizedLLMGenerationUsageandLLMGenerationCostitems. Legacy event lines retain their existing type names. - Remove assumptions that Output’s
Agentinherits from AI SDK’sToolLoopAgent. - Preserve binary file and
reasoning-filecontent parts in customMessageStoreserializers. - Set
providerOptions.openai.reasoningSummary: nullon reasoning prompts that should not return summaries. - Set
maxSteps: 1on YAML-only grounding prompts that must stay one-shot, or passstopWhen: aiSdk.isStepCount(1)on the call. - Rename
promptFileDirtofileDir. Read interpolation values fromprompt.variables. - Treat
config.skillsas alwaysstring[]afterloadPrompt. - Treat
config.maxStepsas always a positive integer afterloadPrompt(default 10). - Treat
prompt.instructionsas alwaysstring | nullafterloadPrompt(chat prompts arenull). - Read
message.providerOptionsinstead ofmessage.attributesonloadPromptresults and LLM traceinput.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, …). Onlyoptionsis allowed; extras throw atloadPrompt. - Remove authored
<tool>blocks from prompt files; pass structured tool history through AgentmessagesormessageStore. - 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 (
<user>...</user>). Different-name semantic tags remain valid content. - Give every
optionsattribute a value and fix malformed attribute names or quotes; prompt markup now fails explicitly at load. - Read LLM trace
input.promptas the loaded prompt object (input.prompt.name,input.prompt.variables). Do not treatinput.promptas a filename or readinput.loadedPrompt. - Read LLM trace
output.sourcesinstead ofoutput.sourcesFromTools. - Keep existing
llm:usagetrace readers for compatibility, but preferllm:generation:usageandllm:generation:costfor normalized trace metadata. - Narrow
ExtractedSourceonsourceTypebefore readingurl. ReplaceLLMCallCostwithLLMGenerationCost, replaceresponse.cost.usagereads withresponse.cost.items, and handle an incomplete cost whosetotalisnull. Import LLM generation usage and cost types from@outputai/llm, not@outputai/core. - Keep existing
cost:llm:requesthandlers for structural compatibility, while allowing corrected reasoning values. Preferllm:generation:meteringwithLLMGenerationMeteringEventfor new normalized usage/cost integrations. - Import AI SDK helpers and types from
aiSdk(aiSdk.Output,aiSdk.tool,aiSdk.isStepCount,aiSdk.ToolSet, …). Replaceimport { ai }withimport { aiSdk }. Do not import cherry-picked AI SDK types from@outputai/llm. - Replace
GenerateTextAiSdkOptions,StreamTextAiSdkOptions, andGenerateImageAiSdkOptionswith their*Parametersequivalents. Remove the generic fromOutputAgentGenerateWithStreamingParameters. - Expect
Agent.generate(),Agent.generateWithStreaming(), andAgent.stream()to persist message-store history only whenfinishReasonis not'error'. - Replace
conversationStorewithmessageStore. ReplaceConversationStorewithMessageStore. RemovecreateMemoryConversationStore()and pass your own store. - Update Agent tests and error matchers that expected
Agent requires a prompt. - Ensure prompt
providerandmodelvalues are non-empty. If set,maxOutputTokensand deprecatedmaxTokensmust each be positive integers. - Review prompt frontmatter against the supported configuration and remove unsupported fields. Any remaining fields cause an Invalid prompt file error.