Repository navigation
Releases: Particle-Academy/prism-ts
Release list
v0.5.1
OpenAI and Mistral embeddings decode base64 strictly
- Fixed: OpenAI and Mistral embeddings returned as base64 were decoded to an empty vector, so callers silently received
no data. Valid base64 now decodes to the same float32 values as the PHP reference. - Malformed embeddings (non-canonical base64, a byte length that is not a whole number of float32 values, non-finite
values, or a value that is neither a vector nor a string) now raisePrismErrorwith codeinvalid_embedding, instead
of returning an empty or partial vector.
No breaking changes.
v0.5.0
prism 0.5.0: an Anthropic stream that ends early is reported, not passed off as finished
BREAKING CHANGE: an Anthropic stream now always ends with exactly one StreamEndEvent, which reports FinishReason Error if anything went wrong, and its usage is null unless the final usage arrived. Before, a stream cut off before message_stop simply stopped, with no error and no end event; a provider error event was followed by no end event; a later stop could overwrite an earlier error with Stop; and a stream missing its final usage reported the start-of-message counters (the input, no output) as its total. If you treat the end event or its usage as a complete, billable call, check finish_reason and treat a null usage as unknown, not free.
What changed, matching the PHP reference (particle-academy/prism 0.135.0):
- A stream that ends before Anthropic's
message_stop(a dropped connection, a proxy cut) emits anincomplete_streamerror, non-recoverable, then a StreamEndEvent with finish Error. - After a provider
errorevent, the stream still ends with a StreamEndEvent, finish Error, and an unfinished text block is closed with its text. A later stop or final usage never turns Error back into Stop. - The StreamEndEvent usage is null when Anthropic never sent the final usage (
output_tokensinmessage_delta), so a partial count is never reported as the total. - Tool input cut off mid-stream is discarded: it never becomes a completed, executable tool call.
- ErrorEvent gains an optional
recoverableflag.
Verified by a new cross-language streaming suite (prism-parity corpus 0.1.11, ten cases including a truncated tool call and keepalive/unknown events), generated from the published PHP reference and run against both ports. The event vocabulary still differs from the reference (no step events; tracked as G-125); the properties above hold in all three languages.
v0.4.0
prism 0.4.0: replay provider state across turns
No breaking changes.
A stored assistant message can now be sent back exactly as the provider produced it, matching the PHP reference (particle-academy/prism). Before this, the provider state that multi-turn conversations depend on was dropped, so a resumed conversation could lose it.
- Anthropic responses carry
content_blocks,stop_reasonand, when present,thinking_blocksinadditional_content(on the response, each step and the assistant message). When that message is sent back, itscontent_blocksare replayed in order, signatures included, following the reference's rules after a model fallback. The order of preference iscontent_blocks, thenthinking_blocks, then the olderthinkingandthinking_signaturepair. Anthropic requires signed thinking blocks to come back during a tool loop with extended thinking. - OpenAI Responses carry
output_itemsinadditional_content. When the message is sent back, those items are replayed as Responses input, keeping reasoning state across turns. - Thinking signatures and encrypted reasoning are opaque. They are passed through untouched and never logged.
- Checked against the shared cross-language corpus (prism-parity), generated from the published PHP reference 0.135.0, for both the response shape and the replayed request.
v0.3.0
v0.3.0 - Node 22 is the floor
Breaking changes: the declared Node floor moves from 20 to 22. Node 20
reached end of life on 2026-04-30, and engines >=20 told consumers that running
there was sanctioned. Be exact about what this does: npm checks engines and
emits EBADENGINE, but it is a WARNING and the install still proceeds. It is a
refusal only where you set engine-strict=true; pnpm refuses by default. No
source changed, so the code would most likely still run on Node 20 -- what
changed is that it is no longer claimed to.
Tested on Node 22, 24 and 26, on Linux and Windows.
v0.2.1
v0.2.1 - Keep credentials out of a failed fetch's message
A Media fetch that fails put the FULL url into the exception message, and an
exception message is logged and shipped to an error tracker as a matter of
course. A url carries credentials often enough that it belongs in neither:
userinfo holds them outright (https://user:pass@host/…), and a presigned
object-storage url puts a bearer-equivalent token in the query string.
The message now carries scheme, host, port and path — which say WHICH fetch
failed, the whole job of the message — and nothing else. A query string that was
present is marked ?[redacted] rather than dropped silently: "there was one and
it is not shown" is a different fact from "there was none".
Matches Media::safeUrl() in the PHP reference (prism#45).
A patch rather than a minor, deliberately: ^0.2.0 resolves it, so a consumer
already on 0.2.x picks the fix up without changing a constraint.
v0.2.0
v0.2.0 - Anthropic thinking as the reference sends it; truncated answers returned
Anthropic thinking
Provider options now build the same Anthropic request here as in the PHP reference.
thinking: { enabled: true, budgetTokens: n }is sent as{ type: 'enabled', budget_tokens: n }. It used to go out verbatim, which Anthropic refuses. A budget that is not an integer becomes 1024.effortis sent asoutput_config: { effort }. It used to be dropped.withReasoning(false)wins over a thinking option.{ enabled: false },{}andfalsesend no thinking.- Any other value is sent as given:
{ type: 'adaptive' },display, and Anthropic's own{ type: 'enabled', budget_tokens: n }.
For current Claude models use:
.withProviderOptions({ thinking: { type: 'adaptive' }, effort: 'medium' })A parsed Anthropic response keeps the first thinking block's text and signature, as additionalContent.thinking and additionalContent.thinking_signature. An AssistantMessage carrying both sends the thinking block back first. Anthropic requires that block on a tool-use turn with thinking on. A thinking block whose text is empty keeps its signature.
Behaviour change: additionalContent.thinking is the first thinking block's text. It used to be every block's text joined, which one signature cannot cover.
Text documents to Anthropic
A text document is sent with media_type: text/plain whatever text/* type it declares. Anthropic accepts nothing else there, so text/markdown, text/csv, text/html or a charset parameter failed the whole request (Particle-Academy/prism#49).
Truncated answers are returned
An Anthropic or Mistral response that stops at the token limit returns its text and usage with finishReason length, instead of throwing. That matches the reference, and keeps the usage of the most expensive call. OpenAI still throws, as the reference does.
Behaviour change: code that ignores finishReason will treat a truncated Anthropic or Mistral answer as complete.
Hosted image generation
An OpenAI response that generated an image through the hosted image_generation tool returns it as additionalContent.generatedImages, in the images endpoint's shape. What the provider actually drew is in additionalContent.imageGenerationCalls: status, size, quality, output format and revised prompt.
Also
- Installing from git (
npm install github:Particle-Academy/prism-ts) builds the package, so it can be imported (#5). - A provider URL's trailing slashes and a fenced JSON block in structured output are found in linear time. Both used regular expressions that took seconds on long crafted input.
- CI checks every direct dependency against the organisation's allowlist, including at publish time.
v0.1.0
v0.1.0 - the TypeScript port of Prism
@particle-academy/prism is a native TypeScript port of the PHP reference, not
a transliteration of it. Promises, discriminated unions and strict null checking
rather than PHP idiom rendered in TypeScript.
WHAT IS HERE, STATED AS A FRACTION because a capability count on its own
flatters: all 12 capabilities, and 3 of the reference's 18 providers (anthropic,
mistral, openai). The remaining fifteen are mapping work, not architecture work
-- Mistral proved the contract holds for a third wire format without changing
the provider interface, the transport, or any builder.
Text, structured and stream reach all three providers. Embeddings and
speech-to-text reach OpenAI and Mistral. Images, moderation, text-to-speech,
files and batch are OpenAI-only here. fim is Mistral-only because Mistral is
the only provider that has it, in the reference too.
CONFORMANCE. Four cross-language suites agree byte-for-byte with the PHP
reference: OpenAI request construction, OpenAI response parsing, coded errors,
and value-object round trips. Two are partial and say why in the corpus rather
than in a footnote -- json-container-identity, where JavaScript cannot
represent the distinction one row asks about, and anthropic-text-response,
where Anthropic response citations are not ported yet.
Those suites run ONE fixture through PHP, TypeScript and Python and compare the
bytes, and every one of them found a real disagreement when it was written.
This is the first published version, and 0.1.0 is meant literally: a focused,
working slice rather than a 1.0 in disguise.
It is published from CI through npm trusted publishing, with a provenance
attestation you can verify. The 0.0.0 that claimed this name was hand-published
and carries none, which is exactly why it was burned rather than used.