opencompanyapp/prism-relay is the shared provider-runtime layer used around Prism PHP in the OpenCompany ecosystem.
It is not just a bundle of custom providers. The package currently handles:
- provider registration into Prism
- a canonical provider and model metadata registry
- provider aliases and model aliases
- prompt-caching strategy selection
- cache-aware Laravel AI -> Prism gateway bridging
- tool-call and tool-result message preservation
- OpenAI-compatible message mapping
- tool-call normalization
- structured provider error normalization
- provider capability lookup
- provider reasoning strategy lookup
In practice, this package is the place where provider-specific behavior should live when that behavior is not unique to the OpenCompany app itself.
Use prism-relay when you want one shared place to answer questions like:
- What providers do we know about?
- What aliases map to what canonical provider names?
- What is the default model for a provider?
- What context window, max output, or pricing data do we have for a model?
- Does this provider support prompt caching, and if so, how?
- Does this provider support
temperature,top_p,stream_usage, or explicit reasoning effort? - How should Laravel AI messages be converted so tool-call and tool-result history survives?
- How should Prism responses or provider exceptions be normalized before app code consumes them?
This package is especially useful when multiple applications or runtimes need consistent provider behavior instead of each app reinventing its own provider table, cache rules, and gateway patches.
prism-relay is not:
- a full application framework
- a complete replacement for Prism PHP
- a persistence layer
- an integration settings UI
- an API key store
- an app-specific provider resolver
For example, OpenCompany's workspace-aware DynamicProviderResolver belongs in the app because it decides how workspace config is applied. The shared provider metadata, aliasing, and cache behavior belong in prism-relay.
Current source layout:
src/PrismRelayServiceProvider.phpsrc/RelayManager.phpsrc/Relay.phpsrc/Registry/*src/Meta/*src/Caching/*src/Bridge/*src/Providers/*src/Capabilities/*src/Reasoning/*src/Normalizers/*src/Errors/*src/Support/*config/relay.phpconfig/relay.generated.phpconfig/relay.manual.phpscripts/sync-registry.mjs
The package is easier to understand if you treat it as five layers:
- Registry and metadata
- Provider registration
- Gateway and prompt-cache bridging
- Message / tool / error normalization
- Provider capability and reasoning strategy helpers
Add the package in Composer:
composer require opencompanyapp/prism-relayThe package declares a Laravel service provider in composer.json, so in a Laravel app it can be auto-discovered:
OpenCompany\\PrismRelay\\PrismRelayServiceProvider
The package requires:
- PHP
^8.2 prism-php/prismpsr/log
Important note:
- The bridge classes under
src/Bridge/are intended for Laravel + Laravel AI consumers. - If you use
CachingPrismGatewayorSystemPromptBag, your app should already be using Laravel AI and Prism in the same runtime.
RelayRegistry is the canonical in-memory representation of provider metadata.
It knows:
- canonical provider names
- provider aliases
- registration names
- model metadata
- model aliases
- default models
- auth modes
- supported capabilities
- provider and model modalities
Representative API:
use OpenCompany\PrismRelay\Registry\RelayRegistryBuilder;
$registry = (new RelayRegistryBuilder)->build();
$registry->canonicalProvider('z.ai'); // "z-api"
$registry->driver('kimi-coding'); // "kimi-coding"
$registry->defaultModel('openai'); // via provider metadata
$registry->model('anthropic', 'claude-sonnet-4-5-20250929');
$registry->registrationNames(); // canonical names + eligible aliasesRelayRegistryBuilder constructs a registry by merging multiple sources.
Current merge flow:
- bundled generated snapshot
- bundled manual overrides
- live provider data from
models.devwhen enabled - app overrides passed into the builder
The builder also supports import-style overrides through models_dev_provider.
This means consumers can use:
- a fully bundled snapshot
- a bundled snapshot plus live metadata
- app-specific overlays without forking the package
Examples:
use OpenCompany\PrismRelay\Registry\RelayRegistryBuilder;
$builder = new RelayRegistryBuilder;
$bundled = $builder->buildBundled();
$full = $builder->build();
$custom = $builder->build([
'my-provider' => [
'models_dev_provider' => 'openai',
'url' => 'https://example.com/v1',
'driver' => 'openai-compatible',
'auth' => 'api_key',
],
]);ProviderMeta is the read-friendly API over the registry.
It exposes:
- default model lookup
- provider URL lookup
- context window lookup
- max output lookup
- detailed model info
- pricing
- cache capability
- thinking support
- model lists
Example:
use OpenCompany\PrismRelay\Meta\ProviderMeta;
$meta = new ProviderMeta;
$meta->defaultModel('openai');
$meta->url('anthropic');
$meta->contextWindow('gemini', 'gemini-2.0-flash');
$meta->maxOutput('openai', 'gpt-4o');
$meta->supportsThinking('deepseek', 'deepseek-reasoner');
$info = $meta->modelInfo('anthropic', 'claude-sonnet-4-5-20250929');
$info->contextWindow;
$info->inputPricePerMillion;
$info->cacheCapability;In Laravel, the service provider does three main things:
- binds
RelayRegistryBuilder - binds a singleton
RelayRegistry - binds a singleton
RelayManager - binds a singleton
Relay - registers providers into Prism after
PrismManagerresolves
That means the package is designed to become the shared provider-registration layer instead of every app hand-registering provider variants.
RelayManager is responsible for taking registry entries and registering them with Prism.
It handles several categories of provider drivers:
- custom direct adapters
glmglm-codingkimikimi-codingminimaxminimax-cn
- model router providers
- unsupported or externally mediated transports
codexexternal-processgoogle-vertexamazon-bedrock
- native Prism-compatible providers
openaianthropicdeepseekgeminigroqmistralollamaopenrouterperplexityxaiz
This is one of the most important current facts about the package:
prism-relayis no longer only about custom OpenCompany providers- it now also acts as a normalized registration layer around native providers and aliases
That is useful, but it also means consumers should be careful not to treat isRelayProvider() as a synonym for “OpenCompany custom provider only.”
Prompt caching is one of the main reasons this package exists.
CacheStrategy answers:
- does the provider support caching?
- what kind of caching?
- what provider options should be added?
- what per-message annotations should be added?
- does the provider report cache metrics?
- does the provider require explicit opt-in?
Current cache capability mapping:
anthropic-> ephemeralopenrouter-> ephemeralopenai-> autogemini-> dedicated- everything else -> none
The package models this using:
CacheCapabilityCacheStrategyPromptCachePlannerPromptCacheOrchestratorPromptCachePlan
src/Bridge/CachingPrismGateway.php is the main Laravel AI bridge entrypoint.
It is a drop-in PrismGateway replacement that:
- resolves system prompts from a
SystemPromptBagwhen present - falls back to the standard single instructions string
- converts Laravel AI messages into Prism messages
- preserves assistant tool calls and tool-result messages via
ToolAwarePrismMessages - asks
Relay::planPromptCache()how to annotate the request - writes system prompts and provider options onto the Prism request
This is the package-level answer to “how do we make prompt caching work without app-specific gateway hacks?”
Example use in a Laravel AI provider:
use Illuminate\Contracts\Events\Dispatcher;
use Laravel\Ai\Providers\OpenAiProvider;
use OpenCompany\PrismRelay\Bridge\CachingPrismGateway;
$gateway = new CachingPrismGateway(app(Dispatcher::class));
$provider = new OpenAiProvider(
$gateway,
['driver' => 'openai', 'key' => env('OPENAI_API_KEY')],
app(Dispatcher::class),
);SystemPromptBag exists so applications can split system prompts into multiple stable/volatile parts before the gateway plans caching.
That lets the cache planner reason over structured system prompt segments instead of a single opaque string.
Use it when your app has prompt frames like:
- stable product instructions
- workspace instructions
- project instructions
- volatile task instructions
This package now contains ToolAwarePrismMessages, which extends the Laravel AI Prism message bridge so that it preserves:
- assistant tool calls
- tool-result messages
This matters because the default Laravel AI static bridge path can be lossy for tool-history-rich runtimes.
If your runtime relies on:
- retries
- checkpoint replay
- prompt cache replay
- continuation after tool use
then losing tool-call structure during conversion is a real correctness bug, not just a cosmetic mismatch.
OpenAiCompatibleMessageMapper converts Prism messages into OpenAI-style payload arrays for providers or transports that expect that schema.
It handles:
- system messages
- user messages
- assistant messages
- assistant tool calls
- tool-result messages
- image content
- OpenRouter cache-control annotations
This is useful when a provider is not a native Prism transport but still wants OpenAI-compatible message payloads.
ToolCallNormalizer exists to sanitize tool calls coming back from providers so downstream code has one normalized shape.
This reduces provider-specific weirdness leaking into application code.
Relay::normalizeError() delegates to ErrorNormalizer and returns a structured ProviderError.
This allows applications to reason over provider failures in a more coherent way than “catch everything and parse strings.”
Package pieces involved:
Errors/ProviderError.phpErrors/ErrorCategory.phpNormalizers/ErrorNormalizer.php
The package also supports listener hooks so consumers can observe requests, responses, and normalized errors through RelayListener.
ProviderCapabilities answers whether a provider supports request parameters such as:
temperaturetop_pmax_tokens- streaming
- stream usage reporting
This is useful for apps that want to strip unsupported parameters before sending requests.
Why it matters:
- some providers reject
temperature - some reject
top_p - some support streaming but not usage-in-stream metadata
- some local backends behave badly if you send unsupported options
Example:
use OpenCompany\PrismRelay\Capabilities\ProviderCapabilities;
$caps = ProviderCapabilities::for('codex');
if ($caps->supportsTemperature()) {
// send temperature
}ReasoningStrategy is the provider-level companion to model metadata.
It answers questions like:
- is reasoning unsupported?
- is reasoning always-on?
- does the provider accept an explicit reasoning effort parameter?
- how do we extract reasoning text from the provider response?
Current examples:
openaiandxaiuse explicit effort-style reasoning parameters- several providers are modeled as always-on reasoning providers
- unsupported providers return no reasoning params
This prevents every application from reinventing provider-specific reasoning logic.
The registry is built from a combination of static and live sources.
config/relay.phpconfig/relay.generated.phpconfig/relay.manual.php
relay.php merges generated metadata with manual overrides.
ModelsDevClient can fetch provider metadata from:
https://models.dev/api.json
The builder can merge that live data into the final registry.
This allows the package to:
- keep a bundled snapshot for deterministic use
- layer in newer metadata when desired
- avoid hardcoding every provider detail directly in app code
The package includes:
composer run sync-registryThis runs:
scripts/sync-registry.mjs
That script currently pulls and merges model/provider data from:
- an opencode snapshot
- Hermes model/auth sources
- package-specific alias and driver maps
- package-specific capability overrides
The goal is to keep relay.generated.php updated without manually editing every provider/model entry by hand.
This is best thought of as a metadata build step, not a runtime dependency.
You can extend prism-relay in several ways.
use OpenCompany\PrismRelay\Registry\RelayRegistryBuilder;
$registry = (new RelayRegistryBuilder)->build([
'my-proxy' => [
'driver' => 'openai-compatible',
'url' => 'https://proxy.example.com/v1',
'default_model' => 'gpt-4o',
'models' => [
'gpt-4o' => [
'context' => 128000,
'max_output' => 16384,
],
],
],
]);If the provider needs to ship with the package itself, add or override it in:
config/relay.manual.php
Applications can bind RelayListener to observe:
- before request
- after response
- normalized error handling
If the behavior is generic across consumers, prefer putting it here rather than into app code.
Examples of good package ownership:
- message conversion correctness
- cache strategy rules
- provider alias handling
- error classification
- OpenAI-compatible payload mapping
Examples of app ownership:
- workspace-specific credential lookup
- app-specific fallback rules
- app-specific policy for choosing providers
Typical Laravel usage looks like:
- install the package
- let the service provider register into the container
- use
RelayManager/ProviderMeta/Relay - wire
CachingPrismGatewayinto your Laravel AI provider creation
Example:
use Laravel\Ai\AiManager;
use Laravel\Ai\Providers\OpenAiProvider;
use OpenCompany\PrismRelay\Bridge\CachingPrismGateway;
app()->afterResolving(AiManager::class, function (AiManager $aiManager, $app) {
$gateway = new CachingPrismGateway($app['events']);
$aiManager->extend('openai', fn ($app, array $config) => new OpenAiProvider(
$gateway,
$config,
$app['events'],
));
});The registry, metadata, strategy, normalization, and relay classes are also usable directly without Laravel auto-discovery.
Example:
use OpenCompany\PrismRelay\Meta\ProviderMeta;
use OpenCompany\PrismRelay\Relay;
use OpenCompany\PrismRelay\Registry\RelayRegistryBuilder;
$registry = (new RelayRegistryBuilder)->build();
$meta = new ProviderMeta($registry);
$relay = new Relay(providerMeta: $meta);
$meta->models('openai');
$meta->modelInfo('anthropic', 'claude-sonnet-4-5-20250929');The Laravel-specific bridge pieces are the main area that assume a Laravel runtime.
prism-relayis the shared provider-runtime layer; it should not absorb app-specific credential or workspace policy logic.- Some bridge classes depend on Laravel AI conventions even though the package itself is centered on Prism.
- The registry can include native and custom providers, so “known to relay” does not necessarily mean “custom provider only.”
- Live metadata and bundled metadata can diverge; decide intentionally whether your app wants bundled-only or live-augmented builds.
- This package is best when treated as infrastructure, not as a dumping ground for app-specific fixes.
Change prism-relay when the issue is about:
- provider metadata
- provider aliases
- model aliases
- cache strategy
- message conversion
- shared gateway behavior
- tool-call normalization
- provider capability lookup
- reasoning strategy
- OpenAI-compatible payload mapping
Change the app when the issue is about:
- how credentials are loaded
- how a workspace chooses a provider
- how a user or tenant is mapped to provider config
- app-specific fallbacks
- app-specific runtime orchestration
MIT