Carrying the torch: 48 community PRs merged + fork fixes (v0.101.0-v0.109.0) - #1028
Open
wishborn wants to merge 195 commits into
Open
Carrying the torch: 48 community PRs merged + fork fixes (v0.101.0-v0.109.0)#1028wishborn wants to merge 195 commits into
wishborn wants to merge 195 commits into
Conversation
Add comprehensive Replicate provider implementation supporting all core features: text generation, streaming (SSE), structured output, embeddings, image generation, and audio (TTS/STT). Features: - Text generation with system prompts and conversation history - Real-time SSE streaming with automatic fallback to simulated streaming - Structured output with JSON schema validation - Image generation (FLUX, Stable Diffusion XL, etc.) - Text-to-Speech with multiple voices (Kokoro-82m) - Speech-to-Text with Whisper (WAV, MP3, FLAC, OGG, M4A) - Embeddings (single and batch, 768-dimensional vectors) Implementation: - Async prediction management with configurable polling - Sync mode (Prefer: wait header) for lower latency - Comprehensive error handling with typed exceptions - Full PHPStan level 8 compliance - 21 tests with 60 assertions (100% feature coverage) - 455 lines of comprehensive documentation Files changed: 58 files, 4,444+ lines added
…chronously This adds the ability to be able to send a request to a provider to create a transcript where the provider will give you an id and then send a webhook to you in the future when the job is done with that id. This is just supplying the interface that a provider can utilize in the future.
Add comprehensive support for Alibaba Cloud's Qwen models via the DashScope native API (/api/v1), covering text generation, streaming, structured output, embeddings, image generation, and image editing. Key features: - Text generation with multi-step tool calling - Multi-modal (VL) support with automatic endpoint routing - Streaming with DashScope SSE protocol and reasoning/thinking tokens - Structured output with both JSON Object and JSON Schema modes - Embeddings with configurable dimensions - Image generation (qwen-image-max/plus) and editing (qwen-image-edit) - Region-aware configuration (International, China, US deployments) - 52 tests with real API fixtures (176 assertions) Co-authored-by: Cursor <cursoragent@cursor.com>
StreamEndEvent.usage can be null when providers don't include usage data in their final stream chunk, causing a TypeError downstream. Add `?? new Usage(0, 0)` fallback to emitStreamEndEvent() in all providers missing it, matching the existing pattern in the OpenAI stream handler.
Add an `api_format` config option to the OpenAI driver that allows switching from the default `/responses` endpoint to `/chat/completions`. This enables using Prism with OpenAI-compatible backends like vLLM, LiteLLM, and LocalAI that only implement the chat/completions API. Set `OPENAI_API_FORMAT=chat_completions` in your env to use it. Only text, structured, and stream methods dispatch conditionally — other modalities (embeddings, images, moderation, TTS, STT) already use standard endpoints that work with compatible backends as-is.
…nfigured Providers that reject unknown parameters (e.g. Perplexity via LiteLLM) return HTTP 400 when `"tools": []` is sent. Return null instead so Arr::whereNotNull() filters it out entirely.
Providers with integrated search capabilities (e.g. Perplexity, You.com) return top-level `citations` and `search_results` fields in chat/completions responses. These were previously ignored. Add ChatCompletionsCitationsMapper to map these into Prism's existing Citation infrastructure, and extract them once per stream in the ChatCompletions stream handler. Citations are passed through on the StreamEndEvent, matching the existing pattern used by the Anthropic handler.
…ob management and result handling
…h job management, result handling, and error mapping
…-anthropic-and-openai
…lue for data retrieval
… provider methods for batch management
… tool loop ## Context When Anthropic's server-side tools (like `web_search`) are used alongside regular user-defined tools, the model can do both in a single response: perform a web search, write text with citations referencing the search results, and call a regular tool. Because a regular tool was called, Prism enters its multi-step tool loop. It executes the tool, then replays the entire conversation back to the API for the next turn. The problem is that when Prism builds the replayed assistant message, it includes the text with citations but drops the `server_tool_use` and `web_search_tool_result` content blocks that the citations reference. The API validates that every citation points to an existing search result, finds none, and rejects the request with: `invalid_request_error - Could not find search result for citation index.` This only triggers when the model performs a server-side tool call AND a regular tool call in the same response. If either happens alone, everything works fine. ## Changes Both the Text and Stream handlers had the same gap in their tool loop replay logic. **Text handler (`Text.php`):** Added `extractProviderToolContent()` that pulls `server_tool_use` and `*_tool_result` content blocks from the API response and stores them in `additionalContent` as `provider_tool_calls` and `provider_tool_results`, the same keys that `MessageMap::mapAssistantMessage()` already reads and serializes back to the API. This follows the existing pattern of `extractText()`, `extractCitations()`, and `extractThinking()`. **Stream handler (`Stream.php`):** The stream state already tracked provider tool calls, provider tool results, and citations during streaming, but `handleToolCalls()` only included `thinking` and `thinking_signature` in the replayed `AssistantMessage`'s `additionalContent`. Now it also includes `citations`, `provider_tool_calls`, and `provider_tool_results`.
…delete, and metadata retrieval functionalities
…e batch job handling with inputFileId support
…xtRequest
- Use ?? [] on items to avoid passing null to buildAndUploadFile()
- Cast json_encode() result to string to satisfy non-empty-string return type
- Change clientRetry default from [] to [0] to satisfy array{0: int} type constraint
Made-with: Cursor
…s from OpenAI responses
…update tests for empty array responses
…checker composer-require-checker surfaced undeclared direct dependencies, now declared: ext-mbstring, ext-openssl, psr/http-message, and symfony/http-foundation (all already installed transitively via laravel/framework, so this changes nothing for consumers). laravel/mcp moves to suggest for the optional LaravelMcpTool integration; its symbols plus the PHPUnit assertion used by the testing fakes are whitelisted in composer-require-checker.json. The custom ReorderMethodsRector was dev tooling shipping inside src/ (the source of phantom PhpParser/Rector symbols) - moved to dev/ under a Prism\Dev namespace, autoloaded only in autoload-dev and export-ignored from the dist package. New Require Checker CI workflow runs the tool on every push/PR. This is the proper guard for the goal upstream prism-php#1025 aimed at (catching use of undeclared symbols) without splitting laravel/framework into illuminate/* packages. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…penAI verbosity Three bug fixes from the upstream issue tracker plus one found in review: - DeepSeek and Qwen streaming handlers sent `stream: true` in the request body but never set the Guzzle `stream` transport option, so the full response body was buffered before the first event yielded — matching the other 14 providers now (upstream prism-php#990; Qwen found in review). - OpenRouter ToolCallMap eagerly ran json_decode() and passed the result to the ToolCall constructor, so malformed model JSON produced a raw TypeError (json_decode returns null) instead of a handled error. The map now stores the raw argument string; ToolCall::arguments() decodes lazily and wraps a decode failure in PrismException::malformedToolCallArguments(), which the tool-execution loop already converts into a tool result the model sees. The loop's error path is hardened to not re-throw while building that result (upstream prism-php#1006). Also adds OpenAI text_verbosity support to the structured (Responses) path and both chat/completions paths — it was previously only wired on the Responses text path (upstream prism-php#1015). Tests: OpenRouter ToolCallMap mapping + lazy-decode behavior, verbosity pass-through for structured + chat/completions, updated the ToolCall value object test to assert the handled PrismException. 1,891 passing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…stency Per upstream prism-php#1017: promptTokens is normalized to exclude cached tokens everywhere, but completionTokens is inclusive of reasoning tokens on OpenAI/Anthropic/OpenAI-compatible providers and exclusive on Gemini/Vertex. Documents the difference rather than changing billing-relevant numbers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s-vertex feat(vertex): added support for multi region endpoints
Introduce the Tier-1 telemetry substrate for observability (upstream prism-php#935): a provider-agnostic set of plain Laravel events emitted across the generation lifecycle, decoupled from the existing ShouldBroadcast streaming events so telemetry never forces websocket delivery. - TelemetryContext: correlation VO carrying a stable traceId plus explicit stepIndex/toolIndex ordinals, so a consumer can rebuild the span tree deterministically without ambient context surviving the tool loop. - ContextStack: container-bound ambient stack; tolerates out-of-order removal so an abandoned streaming generator cannot corrupt later calls. - Telemetry: static emission helper; a complete no-op when disabled. - Events: GenerationStarted/Completed/Failed, StepCompleted, ToolInvoked. - config: prism.telemetry.{enabled,capture_content}, both off by default (capture_content gates prompt/completion/tool-arg payloads — PII). - Bind ContextStack as a singleton. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ion paths Wire the neutral telemetry events into the provider-agnostic chokepoints so instrumentation lives in one place per path, touching no provider handler. - asText / asStructured / asEmbeddings / generate (images): start on entry, completed on success (with per-step StepCompleted for text/structured), failed on RequestException, context popped in finally. - asStream: wrap the provider generator via Telemetry::instrumentStream, which emits StepCompleted per StepFinishEvent and GenerationCompleted on StreamEndEvent while passing every event through untouched. - CallsTools: executeToolCall now measures and returns its own duration; ToolInvoked is dispatched from the in-process parent (executeToolsWithConcurrency and the approval-resume path) so listeners never fire in a forked child. - Tests: disabled no-op, started/completed, content-capture gating, per-step events, failure, streaming pass-through, and tool-invocation ordinals. Full suite green (1909 passed), PHPStan level 8 clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Security review found the capture_content privacy control was applied to GenerationStarted, StepCompleted, and GenerationCompleted but NOT to ToolInvoked, so tool arguments and tool results (which can carry user PII) were placed into the event payload even when capture_content was disabled — contradicting the documented opt-out and leaking content to whatever sink the operator wired. Fix: ToolInvoked now carries always-present scalar `toolName`/`toolCallId` (safe span metadata) while the content-bearing `toolCall`/`toolResult` are nullable and populated only when capture_content is enabled — matching the nullable-content pattern of the other three events. Added a regression test asserting content is withheld by default and present only when enabled. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ceof) The security-fix commit ran Pint but not Rector; the Formatting CI job runs `rector` + `git diff --exit-code`, which flagged the `=== null` assertions. FlipTypeControlToUseExclusiveTypeRector rewrites them to `! $e->toolCall instanceof ToolCall`, matching the repo's enforced style. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Anthropic streaming and Groq/Mistral/OpenAI audio handlers call json_validate(), a PHP 8.3 built-in, while composer.json declares "php": "^8.2" and the test matrix runs 8.2. Without a polyfill this fatals on real 8.2 in those paths, and composer-require-checker (run on 8.2) flagged json_validate as an unknown symbol — a red check on every push since the checker was added. Add symfony/polyfill-php83 to require: it defines json_validate() on 8.2, fixing the latent runtime bug and satisfying the require-checker while keeping 8.2 support (vs. a whitelist entry, which would only silence the checker). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix: polyfill json_validate() for PHP 8.2 support
docs: point README to the ai.particle.academy docs site
The recursive tool loop runs step N's tools before step N is recorded, so ToolInvoked carried no step ordinal and a consumer could not nest a tool span under its step. Track a per-generation step cursor on the ContextStack, advance it once per executed tool batch, and stamp it onto every ToolInvoked. No-op when telemetry is disabled.
…s + bounded content capture Adopts upstream issue prism-php#935: neutral Laravel telemetry events across the generation lifecycle (context/stack, step/tool ordinals, user/session ids), bounded opt-in content capture, and a step cursor so tool events are tagged with their owning step. Off by default and a complete no-op when disabled. Validated end-to-end (real multi-step tool generation -> OpenTelemetry bridge -> Phoenix) and security-reviewed (PASS WITH WARNINGS).
Pairs with the "main protection" branch ruleset: outside contributions now need a pull request with an approving review from a code owner before they can land on main.
The Gemini API specifies `tools` as `Tool[]`, but four handlers built it in ways
that break that contract once more than one kind of tool is in play.
**Mixed keys → JSON object.** Gemini/Text, Gemini/Stream and Vertex/Text map
provider tools into a numerically keyed list and then assign
`$tools['function_declarations'] = …` on top. The result is a mixed-key array,
which `json_encode` emits as an object:
"tools": {"0": {"google_search": {}}, "function_declarations": [...]}
Gemini and Vertex both reject that, so combining Google Search grounding with
any custom tool fails outright.
**Silent overwrite.** Vertex/Structured reassigns `$tools` instead of appending,
so provider tools vanish whenever custom tools are present.
**Mutual exclusion.** Vertex/Stream chained `elseif`, so custom tools were only
ever sent when there were neither provider tools nor `searchGrounding` — the
combination was impossible rather than merely malformed.
All four now append a separate Tool entry, matching Gemini/Structured, which
already had it right and serves as the reference:
"tools": [{"google_search": {}}, {"function_declarations": [...]}]
Provider-tool precedence in Vertex/Stream (explicit provider tools over the
legacy `searchGrounding` option) is preserved; only the custom-tool branch
becomes additive.
Adds a regression test asserting `array_is_list($data['tools'])` plus both
entries. Verified it fails on the current code and passes with the fix; the 169
Gemini + Vertex tests and Pint stay green.
Extends the fix with runnable coverage for the previously untested paths: Gemini/Text custom-tools-only, Gemini/Stream grounding + custom tools, and Vertex/Text custom-tools-only — each asserting `tools` serializes as a JSON array (array_is_list), the shape Gemini and Vertex require.
Covers enabling telemetry, the config block, the five lifecycle events and their payloads, listening, TelemetryContext, user/session metadata via withTelemetryMetadata(), the content-capture PII bounds, and exporting to OpenTelemetry / Arize Phoenix via prism-opentelemetry. Adds it under Advanced.
Resolves the open high-severity Dependabot alert for postcss (GHSA-r28c-9q8g-f849, path traversal via sourceMappingURL) plus two further high advisories npm audit surfaced in the same tree: brace-expansion (GHSA-mh99-v99m-4gvg, GHSA-rgw5-rvv9-x895) and js-yaml (GHSA-5p4m-2wfm-xmqj). All three are transitive dev dependencies of the VitePress docs site and were already permitted by the existing semver ranges — the lockfile was simply stale. Refreshed with `npm audit fix --package-lock-only`, so no override pins and no package.json changes. `npm audit` now reports zero vulnerabilities.
Two repository-hygiene fixes found during the v0.111.1 release audit. **Line endings.** There was no eol rule, so on Windows (core.autocrlf=true) every checkout writes CRLF while Pint writes LF. `git status` then reports 30+ files as modified with empty `git diff`s, and `git add -A` will stage that noise into an unrelated commit. `* text=auto eol=lf` makes the working tree match what the tools produce. Renormalising touches exactly five files. Every fixture is stored with LF except tests/Fixtures/gemini/stream-with-tools*.json, committed with CRLF and the anomaly — including the .sse stream fixtures, whose CRLF I first took for stored bytes when it was only a checkout artefact. Those five are now LF like the rest; the Gemini suite (146 tests) passes unchanged, so nothing depended on it. **docs.** /docs was the only non-runtime path not export-ignored, so 9.6M of committed .vitepress/dist build output and a dev package-lock.json ship inside every Composer install — dead weight in vendor/, and a lockfile that makes dependency scanners flag consumers for advisories in our docs toolchain. The directory itself must keep shipping: the docs site renders markdown AND parses the VitePress sidebar out of vendor/particle-academy/prism/docs, and installs with --prefer-dist. Excluding /docs wholesale would have taken that site down. So only what nothing reads at runtime is excluded, verified against a real `git archive`: 46 markdown files and .vitepress/config.mts still present, dist output and lockfile gone. 1923 passed, 11 skipped.
"0" is falsy in PHP, so `if ($content)` silently discards a message, delta or payload whose entire text is the single character 0. Rector's ExplicitBoolCompareRector (SetList::CODE_QUALITY) rewrote those checks into the explicit `$content !== '' && $content !== '0'`, which is a faithful translation — and is why the bug survived in plain sight. A lone "0" is ordinary model output: a count, a numeric answer, a JSON number, or one digit landing alone in a stream chunk. Scoped to the call sites where a "0" can actually reach a user: message maps Anthropic, Gemini, OpenAI, OpenRouter, Requesty stream deltas Azure, DeepSeek, Qwen, XAI structured output Gemini, Vertex (a "0" body was reported as empty) file content Media::fromLocalPath and rawContent used `?: ''` Deliberately NOT swept: the same pattern in Anthropic's SSE line parsing, Ollama's line parsing, the TTS voice name and ToolCall::arguments(). A data line or a voice is never the single character 0, and ToolCall already funnels through `is_array($decoded)`, so changing them would edit real files across four more providers to fix nothing. Two findings beyond the mechanical rewrite: - HandlesStructuredJson returned json_decode() straight from a method declared `: array`. The "0" guard was accidentally shielding it, so any scalar JSON from a provider — "12", "\"text\"", "true" — raised a TypeError. Now guarded on the decoded shape. - Anthropic's assistant map built its text block with a bare array_filter(), dropping 'text' => '0' while only meaning to drop a null cache_control. Removing the '0' arm made three thinking-complete conditions provably constant; PHPStan flagged them and they are gone, matching upstream prism-php#1005. Rector's rule stays enabled: it is what made this visible. Suppressing it would only hide the same bug behind `if ($content)`. Covered by tests/Regression/FalsyZeroStringTest.php, verified to fail against the unfixed source with these exact modes (including the TypeError).
Closes #14. All three Vertex handlers refused provider tools alongside custom tools: throw new PrismException('Use of provider tools with custom tools is not currently supported by Vertex.'); Investigated rather than assumed, because #12 left its Structured and Stream fixes unreachable behind these guards. The guards are not evidence of an API restriction. They arrived with the very first Vertex support commit (d81de06, Feb 2026) as a conservative placeholder, not in response to Vertex rejecting anything, and were never revisited. The Vertex handlers are otherwise near-copies of the Gemini ones, which carry no such guard — so the same package already permits on Gemini exactly what it forbade on Vertex, against the same API shape. Google documents the combination as supported: "Gemini 3 models also support combining these built-in tools with custom tools (function calling)". That is precisely the case reported in #12 — Gemini 3 on Vertex with Google Search grounding plus custom function tools. Removing all four guards (the three provider-tool ones plus Stream's separate searchGrounding one, whose message claimed Prism did not support the combination at all — it does, on Gemini). #12 already fixed the payload these paths emit, so the tools array serialises as a proper Tool[]; the code was correct and simply unreachable. Tests assert the combined payload is a list of two entries, google_search then function_declarations, and that the call no longer throws. Not verified against the live Vertex API — that needs Google Cloud credentials I do not have. If Vertex does reject some model/tool combination, the caller now gets Google's own error, which is more accurate than a blanket Prism exception that contradicts Google's docs. Pint · PHPStan clean · 1925 passed, 11 skipped.
Upstream PR by @mrmorgan-i. Browsers report the CONTAINER type, so an audio-only MediaRecorder clip arrives as video/webm or video/mp4 and was being written out as audio.mp3 — the wrong extension on every browser recording, which providers then reject or mis-transcode. Applied upstream's mapping (video/mp4, video/ogg, video/webm) plus one gap it does not cover: MediaRecorder attaches codec parameters, so the very mime types this fix targets arrive as "audio/webm;codecs=opus" and fell straight through to the mp3 default anyway. The mime type is now reduced to its bare type and lowercased before matching, so the fix works on real browser output rather than only on the canonical strings.
Inspired by prism-php#1022 by @cerebrixos, rewritten rather than absorbed. The gap that PR identified is real: `url` appears in the config block but nothing on the page says it can point somewhere other than OpenAI, so the single most common deployment question — "can I put this behind vLLM / a gateway / Azure" — is unanswered. The upstream PR answers it with one vendor's product as the worked example, including their hostname, their env var name, and a paragraph of their positioning. Merging that would put a supplier advertisement in our provider docs and date the page to that supplier. So this documents the capability instead: a neutral placeholder URL, self-hosted runtimes and gateways named as categories, and no vendor endorsed. Also adds what a user actually gets wrong here and the upstream text omits: "OpenAI-compatible" rarely covers the whole surface, so an unsupported feature fails at the endpoint rather than in Prism; and model names drive capability inference, so a llama model behind the OpenAI provider is not treated as structured-output capable. Closes with a pointer to the dedicated providers, which map their APIs' real quirks.
…#16) Follow-up to the falsy-"0" fix. The message maps built their payloads with a bare `array_filter()`, which drops EVERY falsy value. That is correct for a null cache_control or an empty tool_calls array — and wrong for `'content' => '0'`, which is falsy in PHP but is ordinary model output. The recurring shape, present in eight providers: array_filter([ 'role' => 'assistant', 'content' => $message->content, // '0' silently removed 'tool_calls' => $toolCalls, // [] correctly removed ]) Because the same array mixes a content value with a collection that SHOULD vanish when empty, neither a plain `array_filter` nor `Arr::whereNotNull` is right. Added Providers\Support\Payload::compact(), which keeps the original intent — no null, no empty string, no empty array, no false — and keeps scalar zero in string and int form. Converted 21 content-bearing sites across Anthropic, Azure, Groq, Mistral, Ollama, OpenAI (chat completions), OpenRouter, Qwen, Requesty and XAI, covering assistant content, user text, system prompts, tool-result content and citation text. The Anthropic site fixed with Arr::whereNotNull in the previous commit now uses the same helper, so there is one mechanism. Deliberately NOT converted: the remaining array_filter calls carry file ids, model names, provider options, schema fragments and tool names/descriptions — no message content — so changing them would alter provider payloads for no benefit. One two-argument call in Anthropic's CitationsMapper already had an explicit null-only callback and was correct; a first pass converted it by mistake and PHPStan caught it. Tests: tests/Regression/PayloadCompactTest.php, 20 cases. Verified against the unfixed source — the 10 "keeps 0" cases fail, and the 8 "still omits an empty tool_calls array" guards pass, confirming the strip behaviour the array_filter existed for is unchanged. Pint · PHPStan clean · 1950 passed, 11 skipped. Known, not addressed: ElevenLabs' speech-to-text call array_filters boolean provider options, so `diarize: false` is dropped. Same falsy class, but a boolean rather than "0", and dropping it may match the API default. Needs a maintainer decision rather than a blind change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Upstream has been quiet since March 2026 (v0.100.1), so particle-academy/prism — a drop-in fork,
Prism\Prismnamespace unchanged — has been absorbing the open backlog and shipping releases (context: discussion #1027). This PR offers all of that work back upstream in one piece: 166 commits, nine releases (v0.101.0–v0.109.0), gated throughout by Pest + PHPStan + Pint/Rector.If maintainership resumes, merge wholesale or tell us how you'd like it split — we're happy to break it into reviewable chunks. Either way the fork remains active.
Community PRs from this repo merged into the fork (48)
v0.101.0 — correctness fixes (17): #952, #958, #964, #971, #977, #985, #986, #987, #989, #991, #996, #1001, #1004, #1009, #1012, #1013, #1024
v0.103.0 — provider correctness / API drift (16): #949, #954, #961, #965, #975, #976, #980, #992, #993, #995, #997, #1000, #1002, #1008, #1020, #1021
v0.104.0 — features (9): #951 (batches + files APIs), #960 (xAI images), #978 (Vertex AI provider, answers #795), #988 (fine-grained tool streaming), #998 (Anthropic adaptive thinking), #1003 (pause_turn/refusal), #1014 (Mistral FIM), #1018 (provider-agnostic withReasoning()), #1026 (Requesty provider)
v0.105.0 — features + providers (6): #757 (Replicate provider), #810 (async STT interface), #835 (Azure OpenAI provider), #898 (Qwen provider), #907 (OpenAI chat/completions api_format + streaming citations, answers #900), #920 (cost tracking in Usage)
Reimplemented rather than rebased: #932 (client-executed tools + human-in-the-loop approval, answers #921) — clean-room implementation across all providers including streaming; docs at https://ai.particle.academy/docs/core-concepts/human-in-the-loop
Adjudicated, not merged (rationale posted): #950 (duplicate of #977), #937 and #1005 (superseded by an escape-based control-character fix), #999 (superseded), #1025 (rejected — a composer-require-checker CI gate solves the underlying goal properly; analysis in Particle-Academy/prism#3)
Fork-original changes
laravel/framework ^12.61.1|^13.12.0).Tool::requiresApproval(bool|Closure)/Tool::clientExecuted(), deny-by-default resume from message history, streaming approval events — text/structured/stream on all 18 providers.promptTokens= non-cached input everywhere;cacheReadInputTokenspopulated wherever the provider exposes it. Fixed silent double counting in Gemini, Vertex, OpenRouter (v0.108.1) and Z.AI + Requesty streams (v0.109.0); added cache visibility for OpenAI chat/completions, Azure, Groq, Qwen, xAI.anthropic_betaprovider option.src/.Full release notes: https://github.com/Particle-Academy/prism/releases
🤖 Generated with Claude Code