Skip to content

UPP Specification

UPP-1.3: Unified Provider Protocol Specification

Section titled “UPP-1.3: Unified Provider Protocol Specification”

Version: 1.3.1 Status: Draft Authors: UPP Working Group


The Unified Provider Protocol (UPP) is a language-agnostic specification for interacting with AI inference services. This document defines the protocol semantics, data structures, and implementation requirements for building UPP-compliant clients and providers.

UPP establishes uniform interfaces for Large Language Models (LLM), Embedding Models, and Image Generation Models. The protocol enables multi-provider interoperability while preserving provider-native configuration and avoiding abstraction leakage.


  1. Introduction
  2. Design Principles
  3. Core Concepts
  4. Provider Protocol
  5. LLM Interface
  6. Messages
  7. Turns
  8. Threads
  9. Streaming
  10. Middleware
  11. Tools
  12. Structured Outputs
  13. Embedding Interface
  14. Image Interface
  15. Data Types
  16. Conformance
  17. Security Considerations

Modern AI development requires interacting with multiple providers (Anthropic, OpenAI, Google, etc.), each with distinct APIs, authentication schemes, and response formats. UPP establishes a standard protocol that:

  • Provides modality-specific interfaces (llm, embedding, image)
  • Enables provider switching without application code changes
  • Maintains provider-native configuration to avoid abstraction leakage
  • Shares common infrastructure (auth, retry, HTTP) across modalities

This specification covers:

  • The llm() function interface (chat/completion)
  • The embedding() function interface (vector embeddings)
  • The image() function interface (image generation)
  • Provider adapter requirements for each modality
  • Shared infrastructure (ProviderConfig, KeyStrategy, error handling)
  • Message, Turn, and Thread data structures
  • Streaming response handling
  • Tool definition and execution

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “NOT RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.

TermDefinition
ProviderA vendor-specific adapter exposing one or more modality interfaces
ModalityA distinct AI capability: LLM, embedding, or image generation
BoundModelA model instance bound to a specific provider and model ID
MessageA single message in an LLM conversation
TurnThe complete result of one LLM inference call
ThreadA utility class for managing LLM conversation history

This specification uses language-agnostic pseudocode. Implementations SHOULD adapt syntax to their target language’s conventions.

function_name(arg1, arg2) // Function call
{ key: value } // Object/map literal
[item1, item2] // Array/list literal
object.property // Property access
await expression // Async operation
Type? // Optional value

This specification references JSON Schema (draft-07 or later) for parameter and structured output definitions. Implementations MAY use schema validation libraries appropriate to their language ecosystem (e.g., Zod for TypeScript, Pydantic for Python, JSON Schema validators for other languages).

UPP does NOT mandate runtime schema validation. Schemas serve as contracts between components; validation is the responsibility of the application layer.


Configuration MUST pass through to providers unchanged. UPP SHALL NOT impose its own defaults or transform model parameters. When no config is supplied, provider defaults apply.

UPP favors explicit APIs:

  • Separate entry points per modality—no modal switches
  • generate() for complete responses, stream() for streaming
  • Users manage their own conversation history
  • System prompts declared at configuration

Each modality gets a purpose-built interface:

  • LLM: Conversational with messages, turns, streaming, tools
  • Embedding: Batch-oriented, returns vectors
  • Image: Prompt-based, returns images

While interfaces differ, providers share common infrastructure:

  • ProviderConfig (apiKey, baseUrl, timeout, retry)
  • KeyStrategy (RoundRobin, Weighted, Dynamic)
  • Error handling (UPPError, ErrorCode)

Providers SHOULD wrap vendor REST APIs directly using native HTTP primitives rather than depending on first-party vendor SDKs.

Rationale: Minimal dependencies, full control over request/response handling, consistency across providers, transparency, and smaller bundle sizes.


┌─────────────────────────────────────────────────────────────────┐
│ Application Code │
└─────────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ llm() │ │ embedding() │ │ image() │
└─────────────┘ └──────────────┘ └─────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ Provider Adapters │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Vendor APIs │
└─────────────────────────────────────────────────────────────┘

Implementations SHOULD export both a namespace object and individual functions:

// Namespace style
import ai from "upp"
claude = ai.llm({ model: anthropic("claude-sonnet-4-20250514") })
// Direct import style
import { llm } from "upp"
claude = llm({ model: anthropic("claude-sonnet-4-20250514") })

A provider exports a factory function that returns a ModelReference:

import openai from "upp/openai"
llm({ model: openai("gpt-4o") })
embedding({ model: openai("text-embedding-3-small") })
image({ model: openai("dall-e-3") })
LayerPurposeShared Across Modalities
Provider ConfigInfrastructure settingsYes
Model ParamsModel behavior parametersNo
Modality OptionsInterface-specific settingsNo

All providers share common configuration:

FieldTypeDescription
apiKeyString | Function | KeyStrategyAPI key or key strategy
baseUrlStringOverride base API URL
timeoutIntegerRequest timeout in milliseconds
fetchFunctionCustom fetch implementation
retryStrategyRetryStrategyRetry handling strategy
headersMap<String, String>Custom HTTP headers

Providers MUST support custom headers. Custom headers MUST be merged with provider-required headers. If a custom header conflicts with a required header, the custom header SHOULD take precedence.

KeyStrategy Interface:

interface KeyStrategy {
getKey(): String | Promise<String>
}

Standard Implementations:

StrategyDescription
RoundRobinKeys(keys[])Cycles through keys in order
WeightedKeys(entries[])Random selection with weights
DynamicKey(fn)Custom async key selection

RetryStrategy Interface:

interface RetryStrategy {
onRetry(error: UPPError, attempt: Integer): Integer | null | Promise
beforeRequest?(): Integer | Promise<Integer>
reset?(): void
}

The onRetry method MUST return delay in milliseconds before retry, or null to stop retrying.

Standard Implementations:

StrategyDescription
ExponentialBackoff(options)Exponential backoff with jitter
LinearBackoff(options)Fixed delay between retries
NoRetry()No automatic retry

All modalities MUST normalize errors to UPPError:

FieldTypeDescription
messageStringHuman-readable error message
codeErrorCodeStandardized error code
providerStringProvider name
modalityModalityWhich modality: llm, embedding, image
statusCodeInteger?HTTP status code if applicable
causeError?Original underlying error

ErrorCode Values:

AUTHENTICATION_FAILED, RATE_LIMITED, CONTEXT_LENGTH_EXCEEDED, MODEL_NOT_FOUND, INVALID_REQUEST, INVALID_RESPONSE, CONTENT_FILTERED, QUOTA_EXCEEDED, PROVIDER_ERROR, NETWORK_ERROR, TIMEOUT, CANCELLED

HTTP Status Code Mapping:

HTTP StatusError Code
400INVALID_REQUEST
401, 403AUTHENTICATION_FAILED
404MODEL_NOT_FOUND
429RATE_LIMITED
5xxPROVIDER_ERROR
interface ModelReference {
modelId: String
provider: Provider
}

When a modality function receives a ModelReference, it MUST check if the provider supports that modality and throw UPPError with code INVALID_REQUEST if not supported.


llm(options: LLMOptions) -> LLMInstance
FieldTypeRequiredDescription
modelModelReferenceYesModel reference from provider factory
configProviderConfigNoProvider configuration
paramsMapNoModel-specific parameters
systemStringNoSystem prompt
toolsListNoAvailable tools
toolStrategyToolUseStrategyNoTool execution strategy
structureJSONSchemaNoStructured output schema
Method/PropertyDescription
generate(...)Execute inference, return complete Turn
stream(...)Execute streaming inference
modelThe bound model
capabilitiesProvider API capabilities

generate() Signatures:

generate(input: InferenceInput) -> Promise<Turn>
generate(...inputs: InferenceInput[]) -> Promise<Turn>
generate(history: List<Message>, ...inputs: InferenceInput[]) -> Promise<Turn>

InferenceInput: String | Message | ContentBlock

Capabilities declare what the provider’s API supports, not individual model capabilities:

FieldTypeDescription
streamingBooleanSupports streaming responses
toolsBooleanSupports tool/function calling
structuredOutputBooleanSupports native structured output
imageInputBooleanSupports image input
documentInputBooleanSupports document input
videoInputBooleanSupports video input
audioInputBooleanSupports audio input

Capabilities are static for the lifetime of a provider instance.

The llm() core manages the tool execution loop. Providers only handle single request/response cycles.

Loop Flow:

  1. Convert input to UserMessage, append to messages
  2. Call provider’s complete() method
  3. If response has tool calls AND iterations < maxIterations:
    • Execute tools (parallel if multiple)
    • Append AssistantMessage and ToolResultMessage
    • Return to step 2
  4. Build Turn from accumulated messages

Providers MUST:

  1. Transform requests: Convert UPP structures to vendor format
  2. Transform responses: Map vendor responses to AssistantMessage
  3. Handle system prompts: Transform to vendor-specific format
  4. Normalize errors: Wrap vendor errors in UPPError
  5. Namespace metadata: Store vendor-specific data under metadata.{providerName}

Base Message Structure:

FieldTypeDescription
idStringUnique message identifier
typeMessageTypeuser, assistant, or tool_result
timestampTimestampWhen created
metadataMap?Provider-namespaced metadata

Convenience Accessors (on all message types):

AccessorTypeDescription
textStringConcatenated text blocks
imagesListAll image blocks
documentsListAll document blocks
audioListAll audio blocks
videoListAll video blocks
FieldTypeDescription
type”user”Always “user”
contentListContent blocks

UserContent: TextBlock, ImageBlock, DocumentBlock, AudioBlock, VideoBlock, BinaryBlock

FieldTypeDescription
type”assistant”Always “assistant”
contentListContent blocks
toolCallsList?Tool calls requested
hasToolCallsBooleanTrue if toolCalls is non-empty

AssistantContent: TextBlock, ReasoningBlock, ImageBlock, AudioBlock, VideoBlock

FieldTypeDescription
type”tool_result”Always “tool_result”
resultsListTool execution results
Block TypeFieldsDescription
TextBlocktext: StringPlain text content
ReasoningBlocktext: StringModel reasoning/thinking
ImageBlocksource: ImageSource, mimeType: StringImage content
DocumentBlocksource: DocumentSource, mimeType: StringDocument content
AudioBlockdata: Bytes, mimeType: StringAudio content
VideoBlockdata: Bytes, mimeType: StringVideo content
BinaryBlockdata: Bytes, mimeType: StringArbitrary binary (UserContent only)

ImageSource Variants: base64, url, bytes

DocumentSource Variants: base64, url, text

Implementations SHOULD provide helper types with factory methods:

Image:

  • Image.fromPath(path) - Create from file path
  • Image.fromUrl(url, mimeType?) - Create from URL
  • Image.fromBytes(data, mimeType) - Create from bytes
  • Image.fromBase64(base64, mimeType) - Create from base64
  • Image.fromBlock(block) - Create from ImageBlock

Document:

  • Document.fromPath(path, title?) - Create from file path
  • Document.fromUrl(url, title?) - Create from URL
  • Document.fromBase64(base64, mimeType, title?) - Create from base64
  • Document.fromText(text, title?) - Create from plain text

Audio:

  • Audio.fromPath(path) - Create from file path
  • Audio.fromBytes(data, mimeType) - Create from bytes
  • Audio.fromBase64(base64, mimeType) - Create from base64

Video:

  • Video.fromPath(path) - Create from file path
  • Video.fromBytes(data, mimeType) - Create from bytes
  • Video.fromBase64(base64, mimeType) - Create from base64

Implementations SHOULD provide type guards:

isUserMessage(msg: Message) -> Boolean
isAssistantMessage(msg: Message) -> Boolean
isToolResultMessage(msg: Message) -> Boolean

A Turn represents the complete result of one inference call:

FieldTypeDescription
messagesListAll messages produced, chronological
responseAssistantMessageFinal assistant response
toolExecutionsListTool executions that occurred
usageTokenUsageAggregate token usage
cyclesIntegerNumber of inference cycles
dataAny?Structured output data (if structure provided)
FieldTypeDescription
inputTokensIntegerInput tokens across all cycles
outputTokensIntegerOutput tokens across all cycles
totalTokensIntegerTotal tokens
cacheReadTokensIntegerTokens read from cache (0 if unsupported)
cacheWriteTokensIntegerTokens written to cache (0 if unsupported)
FieldTypeDescription
toolNameStringTool that was called
toolCallIdStringTool call ID
argumentsMapArguments passed
resultAnyResult returned
isErrorBooleanWhether execution errored
durationIntegerDuration in milliseconds

Thread is an OPTIONAL utility class for managing conversation history.

Property/MethodDescription
idUnique thread identifier
messagesAll messages (read-only)
lengthNumber of messages
append(turn)Append messages from a turn
push(...messages)Add raw messages
clear()Clear all messages
toJSON()Serialize to JSON
Thread.fromJSON(json)Deserialize from JSON
thread = Thread()
turn = await claude.generate(thread, "Hello!")
thread.append(turn)

Threads are optional—plain arrays work:

messages = []
turn = await claude.generate(messages, "Hello!")
messages.push(...turn.messages)

Streaming returns a StreamResult:

Property/MethodDescription
(async iterable)Yields StreamEvent objects
turnPromise resolving to complete Turn
abort()Abort the stream
FieldTypeDescription
typeStreamEventTypeEvent type
indexIntegerContent block index
deltaEventDeltaEvent data

StreamEventType Values:

text_delta, reasoning_delta, image_delta, audio_delta, video_delta, object_delta, tool_call_delta, tool_execution_start, tool_execution_end, message_start, message_stop, content_block_start, content_block_stop

stream = claude.stream(history, "Write a haiku.")
for await (event in stream) {
if (event.type == "text_delta") {
print(event.delta.text)
}
}
turn = await stream.turn

When aborted during tool execution:

  • The abort signal MUST propagate to in-flight tool execution
  • Pending tool calls MUST be skipped
  • The generation MUST throw CANCELLED error

Middleware provides a composable mechanism to intercept and transform requests, responses, and stream events. Middleware is configured per-instance and executes in a defined order.

claude = llm({
model: anthropic("claude-sonnet-4-20250514"),
middleware: [
loggingMiddleware(),
parsedObjectMiddleware()
]
})
FieldTypeRequiredDescription
nameStringYesMiddleware name for debugging
onStartFunctionNoCalled when generate/stream starts
onEndFunctionNoCalled when generate/stream completes
onErrorFunctionNoCalled on any error
onRequestFunctionNoCalled before provider execution
onResponseFunctionNoCalled after provider execution
onStreamEventFunctionNoCalled for each stream event
onStreamEndFunctionNoCalled when stream completes
onToolCallFunctionNoCalled before tool execution
onToolResultFunctionNoCalled after tool execution

Context passed to lifecycle hooks:

FieldTypeDescription
modalityModalityllm, embedding, or image
modelIdStringModel identifier
providerStringProvider name
streamingBooleanWhether this is a streaming request
requestAnyRequestThe request object
responseAnyResponse?Response (populated after execution)
stateMapShared state across middleware
startTimeIntegerRequest start timestamp
endTimeInteger?Request end timestamp

Context passed to stream event hooks:

FieldTypeDescription
stateMapShared state across middleware

Middleware that need to accumulate text or other data SHOULD manage their own state using the provided state map with namespaced keys (e.g., myMiddleware:accumulator).

generate() / stream() called
│
▼
┌─────────────────────────────────────────┐
│ onStart (all middleware, in order) │
└─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ onRequest (all middleware, in order) │
└─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Provider execution │
│ ├─ For streaming: onStreamEvent() │
│ │ called per event (pipeline) │
│ └─ Tool loop: onToolCall/onToolResult │
└─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ onResponse (all middleware, reverse) │
└─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ onEnd (all middleware, reverse order) │
└─────────────────────────────────────────┘
│
▼ (on error at any point)
┌─────────────────────────────────────────┐
│ onError (all middleware that have it) │
└─────────────────────────────────────────┘

Lifecycle hooks (onStart, onRequest) execute in middleware array order. Response hooks (onResponse, onEnd) execute in reverse order.

The onStreamEvent hook MAY transform, filter, or expand events:

Return ValueBehavior
StreamEventReplace event with returned event
StreamEvent[]Expand into multiple events
nullFilter out (suppress) the event
// Example: Filter out reasoning events
onStreamEvent(event, ctx) {
if (event.type == "reasoning_delta") return null
return event
}

parsedObjectMiddleware(options?)

Parses incremental JSON from object_delta and tool_call_delta events. Adds a parsed field to the event delta containing the incrementally parsed value.

OptionTypeDefaultDescription
parseObjectsBooleantrueParse ObjectDelta events
parseToolCallsBooleantrueParse ToolCallDelta events

loggingMiddleware(options?)

Logs request lifecycle events for debugging.

OptionTypeDefaultDescription
levelLogLevel”info”Minimum log level
loggerFunctionconsoleCustom logger function

Middleware that adds fields to events SHOULD export extended types:

// parsedObjectMiddleware exports:
interface ParsedEventDelta extends EventDelta {
parsed?: Any
}
interface ParsedStreamEvent extends StreamEvent {
delta: ParsedEventDelta
}

Consumers accessing middleware-added fields MUST cast to the extended type.


FieldTypeRequiredDescription
nameStringYesTool name (unique within instance)
descriptionStringYesDescription for the model
parametersJSONSchemaYesParameter schema
runFunctionYesExecution function
approvalFunctionNoApproval handler
metadataMapNoProvider-specific metadata

By default, llm() handles tool execution automatically:

  1. Model returns AssistantMessage with toolCalls
  2. If approval defined and returns false, error result sent to model
  3. Tool’s run function executed
  4. Result sent back as ToolResultMessage
  5. Loop continues until no tool calls OR max iterations

IMPORTANT: llm() SHALL NOT validate tool arguments against the schema. Tool implementations MUST treat arguments as untrusted input.

FieldTypeDescription
maxIterationsIntegerMaximum tool rounds (default: 10)
onToolCallFunctionCalled when tool requested
onBeforeCallFunctionCalled before execution
onAfterCallFunctionCalled after execution
onErrorFunctionCalled on execution error
onMaxIterationsFunctionCalled when max reached

Some providers offer built-in server-side tools (web search, code interpreter, etc.). These differ from UPP function tools:

AspectUPP Function ToolsProvider-Native Tools
Passed viatools parameterparams.tools
ExecutionClient-sideServer-side
// Provider-native tools go in params
gpt = llm({
model: openai("gpt-4o"),
params: {
tools: [{ type: "web_search" }]
},
// UPP function tools
tools: [myCustomTool]
})

Structured outputs constrain model responses to a JSON schema:

claude = llm({
model: anthropic("claude-sonnet-4-20250514"),
structure: {
type: "object",
properties: {
name: { type: "string" },
age: { type: "integer" }
},
required: ["name", "age"]
}
})
turn = await claude.generate("John Doe is 30 years old.")
print(turn.data) // { name: "John Doe", age: 30 }

If a provider’s API does not support native structured outputs (capabilities.structuredOutput is false), the llm() core MUST throw INVALID_REQUEST when structure is provided.

UPP SHALL NOT validate responses against the schema. Schema validation is the application’s responsibility.


embedding(options: EmbeddingOptions) -> EmbeddingInstance
FieldTypeRequiredDescription
modelModelReferenceYesModel reference
configProviderConfigNoProvider configuration
paramsMapNoProvider-specific parameters
MethodDescription
embed(input, options?)Embed one or more inputs

EmbeddingInput: String | TextBlock | ImageBlock

FieldTypeDescription
embeddingsListEmbeddings in input order
usageEmbeddingUsageUsage statistics
metadataMap?Provider-specific metadata

Embedding:

FieldTypeDescription
vectorListThe embedding vector
dimensionsIntegerVector dimensionality
indexIntegerInput array position

For large-scale embedding, use chunked mode:

stream = embedder.embed(documents, { chunked: true, batchSize: 100 })
for await (progress in stream) {
print("Progress:", progress.percent + "%")
await storeInVectorDB(progress.embeddings)
}

image(options: ImageOptions) -> ImageInstance
FieldTypeRequiredDescription
modelModelReferenceYesModel reference
configProviderConfigNoProvider configuration
paramsMapNoProvider-specific parameters

All provider-specific options (size, quality, style, etc.) flow through params.

MethodDescription
generate(input, options?)Generate images from prompt
stream(input)Generate with streaming (if supported)
edit(input)Edit existing image (if supported)
capabilitiesModel capabilities
FieldTypeDescription
generateBooleanSupports text-to-image
streamingBooleanSupports streaming previews
editBooleanSupports image editing
maxImagesInteger?Maximum images per request
FieldTypeDescription
imagesListGenerated images
metadataMap?Provider-specific metadata
usageImageUsage?Usage information
dalle = image({
model: openai("dall-e-3"),
params: { size: "1024x1024", quality: "hd" }
})
result = await dalle.generate("A sunset over mountains")
imageData = result.images[0].image.toBytes()

TypeDescription
StringUTF-8 text
IntegerSigned integer (at least 64-bit)
FloatIEEE 754 double precision
BooleanTrue or false
BytesRaw binary data
TimestampPoint in time (ISO 8601)
Map<K,V>Key-value mapping
List<T>Ordered collection
T?Optional value
AnyAny JSON value

Implementations MUST use platform-appropriate equivalents:

TypeDescription
AbortSignalAllows aborting async operations
AsyncIterable<T>Async iteration protocol
Promise<T>Promise/future resolving to T
Modality = "llm" | "embedding" | "image"

Implementations SHOULD export named constants for discriminated union types (e.g., StreamEventType.TextDelta, ErrorCode.RateLimited, MessageRole.User). Using constants instead of raw strings enables better tooling support and type safety.


Providers MAY implement one or more modalities. For each implemented modality:

LLM Conformance:

LevelRequirements
CoreText I/O, LLMCapabilities declaration, error normalization, system prompt handling
Streamingstream() method, proper StreamEvent emission
ToolsTool definition transformation, tool call detection, result handling
Structured OutputSchema transformation, JSON parsing
MultimodalInput handling for declared capabilities

Embedding Conformance:

LevelRequirements
Coreembed() method, vector return, error normalization
MetadataPreserve per-embedding and response metadata

Image Conformance:

LevelRequirements
Coregenerate() method, ImageCapabilities declaration, error normalization
Editingedit() method (if capabilities.edit)
Streamingstream() method (if capabilities.streaming)

All providers MUST:

  • Normalize vendor errors to UPPError
  • Set appropriate ErrorCode based on HTTP status
  • Include provider name and modality in all errors
  • Preserve original error as cause when available

All providers MUST:

  • Pass params to vendor API without modification
  • Support custom baseUrl
  • Support custom fetch implementation
  • Respect timeout setting
  • Use provided retryStrategy

All providers MUST:

  • Namespace metadata under metadata.{providerName}
  • Preserve unknown metadata fields during round-trips

  • API keys MUST NOT be logged
  • API keys SHOULD NOT appear in error messages
  • Implementations SHOULD support secure key storage
  • Tool arguments MUST be treated as untrusted input
  • Implementations MUST NOT validate arguments against schema automatically
  • Tool implementations SHOULD validate and sanitize inputs
  • Sensitive operations SHOULD use approval handlers
  • Binary data may come from untrusted sources
  • Implementations SHOULD validate MIME types
  • Large content SHOULD be size-limited
  • Implementations SHOULD default to TLS/SSL
  • Custom base URLs allow MITM if misconfigured
  • Timeout settings prevent resource exhaustion

  • Added Middleware system (Section 10) with composable request/response/stream interception
  • Added Middleware interface with lifecycle hooks (onStart, onEnd, onRequest, onResponse, onError)
  • Added Stream event transformation via onStreamEvent hook
  • Added Tool execution hooks (onToolCall, onToolResult)
  • Added MiddlewareContext and StreamContext types for hook parameters
  • Added Built-in parsedObjectMiddleware() for incremental JSON parsing
  • Added Built-in loggingMiddleware() for request lifecycle logging
  • Added ParsedEventDelta and ParsedStreamEvent extended types
  • Simplified StreamContext to only contain shared state map; middleware manage their own accumulation
  • Breaking Removed parsed field from base EventDelta type; use parsedObjectMiddleware() for incremental JSON parsing during streaming
  • Updated Section numbering (Sections 10-16 renumbered to 11-17)
  • Reformatted specification for improved clarity and usability
  • Updated RFC 2119 compliance with BCP 14 and RFC 8174 references
  • Simplified schema definitions to reference JSON Schema directly
  • Removed verbose appendix JSON schemas (duplicative of prose definitions)
  • Consolidated helper types into single section
  • Streamlined conformance requirements into tabular format
  • Removed provider implementation guide (to be published as separate document)
  • Simplified type constant documentation
  • Clarified MUST/SHOULD/MAY usage throughout per RFC 2119/8174
  • Reorganized table of contents for logical flow
  • Reduced specification length while maintaining semantic completeness
  • Added Document helper type (Section 6.6) with factory methods for PDF and text documents
  • Added Audio helper type (Section 6.7) with factory methods for audio content
  • Added Video helper type (Section 6.8) with factory methods for video content
  • Added Image.fromBlock() factory method to create Image from existing ImageBlock
  • Simplified specification to pure protocol architecture; removed all vendor-specific implementation details including: metadata option tables (6.1, 10.1), system prompt mappings (5.10.4), structured output wiring (11.3), mask conventions (13.6), provider tool reference (10.9.5), embedding params (12.10), image params (13.14), capability matrix (13.14.9), and example model names (1.5)
  • Removed Image conformance levels for vary/upscale/outpaint (16.1.3) to match interface; renumbered Streaming to Level 3
  • Added Section 10.9 Provider-Native Tools documenting server-side built-in tools (web search, image generation, code interpreter, etc.)
  • Added images, audio, video convenience accessors to base Message structure (Section 6.1) to match existing text accessor pattern
  • Reformatted specification to be language-agnostic
  • Replaced language-specific syntax with pseudocode notation
  • Added JSON Schema definitions in appendices
  • Added implementation guidance for multiple programming languages
  • Added security considerations section
  • Added HTTP status code mapping table
  • Added ImageProviderStreamResult type definition and export
  • Added cross-reference note in Section 10.4 linking tool and structured output validation behavior
  • Added EmbeddingInputType enumeration (document/query) for provider-specific input type hints
  • Added MessageOptions structure definition with constructor signatures for all message types
  • Added Section 1.7 clarifying package naming conventions and import syntax
  • Fixed terminology: “fragments” → “events” in Image conformance (Section 16.1.3)
  • Fixed RFC 2119 compliance: “will throw” → “MUST throw” in Section 11.3
  • Fixed iterator notation to use language-agnostic (iterable) format instead of [iterator]
  • Corrected Section 16.4 capability declaration wording (1.1 incorrectly stated “no explicit capability interface”)
  • Updated example model IDs to use dated format (e.g., claude-haiku-4-20250514) for accuracy; this is an example update, not a format requirement
  • Updated example package name to upp as a language-agnostic placeholder (implementations choose their own names)
  • Clarified provider implementation patterns across language paradigms
  • Clarified BinaryBlock constraint: only valid in UserContent, not AssistantContent
  • Maintained semantic compatibility with UPP 1.1 (additive clarifications only, no breaking changes)
  • Renamed from “useAI Provider Protocol” to “Unified Provider Protocol”
  • Renamed package from useAI to @providerprotocol/ai
  • Renamed entry points: useAI() → llm(), useEmbedding() → embedding(), useImage() → image()
  • Added ai namespace export for grouped imports
  • Added embedding() interface for vector embeddings
  • Added image() interface for image generation
  • Added unified provider factories (single export per provider)
  • Added ModelReference type for portable model references
  • Added createProvider() helper for provider implementations
  • Added EmbeddingHandler, ImageHandler interfaces
  • Added BoundEmbeddingModel, BoundImageModel types
  • Added ImageCapabilities for runtime feature detection
  • Added unified embed() interface for single, batch, and chunked embedding
  • Added EmbeddingStream for large-scale embedding with progress
  • Added image editing, variation, and upscaling interfaces
  • Added RetryStrategy interface for pluggable retry/rate-limit handling
  • Replaced RetryConfig with RetryStrategy for flexibility
  • Updated UPPError with modality field
  • Updated provider structure to use unified factories
  • Added Conformance section
  • Initial draft specification (LLM-focused)