Skip to content

Releases: Particle-Academy/prism-ts

v0.5.1

Choose a tag to compare

@github-actions github-actions released this 30 Oct 03:54

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 raise PrismError with code invalid_embedding, instead
    of returning an empty or partial vector.

No breaking changes.

v0.5.0

Choose a tag to compare

@github-actions github-actions released this 24 Oct 17:36
df6f9c1

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 an incomplete_stream error, non-recoverable, then a StreamEndEvent with finish Error.
  • After a provider error event, 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_tokens in message_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 recoverable flag.

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

Choose a tag to compare

@github-actions github-actions released this 24 Oct 15:49
0a32ed5

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_reason and, when present, thinking_blocks in additional_content (on the response, each step and the assistant message). When that message is sent back, its content_blocks are replayed in order, signatures included, following the reference's rules after a model fallback. The order of preference is content_blocks, then thinking_blocks, then the older thinking and thinking_signature pair. Anthropic requires signed thinking blocks to come back during a tool loop with extended thinking.
  • OpenAI Responses carry output_items in additional_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

Choose a tag to compare

@github-actions github-actions released this 07 Oct 19:16

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

Choose a tag to compare

@github-actions github-actions released this 22 Sep 02:37

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

Choose a tag to compare

@github-actions github-actions released this 14 Sep 16:20

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.
  • effort is sent as output_config: { effort }. It used to be dropped.
  • withReasoning(false) wins over a thinking option.
  • { enabled: false }, {} and false send 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

Choose a tag to compare

@github-actions github-actions released this 06 Sep 05:35

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.