Skip to content

Releases

lm15 2.3.1 for TypeScript

Choose a tag to compare

@MaximeRivest MaximeRivest released this 11 Nov 03:06

A conversation that changes provider keeps working (lm15-contract 81b519f, new rule MAP-24, issue lm15-dev/lm15-contract#3; 4,269 of 4,269 checks, the same in every lm15 language; the rule is decided and waits for ratification).

  • Fixed: a conversation answered by an OpenAI Responses model and continued on Claude failed on every request with messages: text content blocks must be non-empty. OpenAI's hidden reasoning is kept in the history as a thinking part with empty text and OpenAI's own state. lm15 wrote it to Claude as an empty text block, which Claude refuses, and since the history is stored every retry failed the same way. A part with nothing for the provider it is sent to now writes nothing there, and a message left with nothing is not sent.
  • Your messages are not changed. Only the request leaves those parts out. Send the same history back to OpenAI and its reasoning goes with it. A stored conversation that was stuck works again as it is.
  • The same on every wire, for every such part: an empty text part (what lm15 reads for a reply with no parts), a citation that quotes nothing, an empty system prompt given as parts. Anthropic, Gemini, OpenAI Responses and Chat Completions requests no longer carry an empty text block or an empty message. Checked live on 14 servers: Moonshot refuses an empty text block on all three of its endpoints, DeepSeek and Moonshot refuse an assistant row with nothing in it, Gemini refuses a message with no parts; all 14 accept the message being left out.
  • What must be sent still is. Claude's own thinking with display omitted (empty text and its signature) goes back as a thinking block. A tool result is always sent; an empty output is the empty string.
  • Nothing is recorded as an adaptation for this: no word anyone wrote is withheld.
  • Fixed: a structured answer kept in the history was dropped on OpenAI. An assistant data part (a judgment or structured answer) reached Anthropic and Gemini as its JSON and the OpenAI Responses and Chat Completions wires not at all. It is sent as its compact JSON there too.
  • Checked live with this build on claude-opus-5-5 and claude-haiku-4-5-20251001: the history of the report (a recorded hidden OpenAI reasoning item before a tool call and before text, reasoning effort medium) is answered.

Small wire changes for requests that worked before: text joined into one string has no blank line for an empty part; a message that is only an empty string is not sent, so a request whose only message is empty now reaches the provider with no message and is refused there (13 of 14 servers); a stored conversation that carried empty blocks may miss the prompt cache once.

Not changed, and still refused by the provider when it happens (each has an issue with its receipts): thinking signed by another server of the same API family, for example DeepSeek's or Meta's Anthropic-compatible endpoint sent to Anthropic, or Azure's reasoning items sent to OpenAI (lm15-dev/lm15-contract#5); a tool loop moved to Gemini 3 or DeepSeek between a call and its result (lm15-dev/lm15-contract#6); a message whose only text is whitespace sent to Anthropic (lm15-dev/lm15-contract#4). Change model between turns, not inside one.

Nothing is removed and no API changes.

lm15 2.3.0 for TypeScript

Choose a tag to compare

@MaximeRivest MaximeRivest released this 10 Nov 02:17

In TypeScript: baseModels: { "tinker://…": "Qwen/Qwen3.5-4B" } on the router config, then "tinker:tinker://…" as the model.

Tinker (Thinking Machines): base models and the models you trained there (lm15-contract 18b3788; 2,266 of 2,266 checks, the same in every lm15 language; the new rules MAP-21, MAP-22 and MAP-23 are decided and wait for ratification).

  • Two new providers, one key (TINKER_API_KEY). tinker speaks Chat Completions (also spelled thinkingmachines); tinker-anthropic speaks the Anthropic Messages wire. Both serve Tinker's base models (tinker:Qwen/Qwen3.5-4B) and any sampler checkpoint by its path.
  • A URI is one model name. tinker://<run>:train:0/sampler_weights/<n> routes to Tinker whole. Before, any name://… string was read as provider name with model //….
  • Reasoning is switched the way each model family takes it. On tinker, Qwen 3/3.5/3.6, Kimi K2.6 and Nemotron take an on/off switch (an effort word turns reasoning on, and the substitution is recorded); Inkling takes the effort words. A checkpoint follows the family of the model it was trained from when you name that model (the line above). Listing models on tinker shows your checkpoints as fine-tunes with their base model.
  • Error messages from FastAPI servers are kept. An error body that is {"detail": "…"} used to raise with an empty message on every OpenAI-family and Anthropic-family adapter; the detail is now the message (MAP-21).
  • stop means the same everywhere. Tinker returns the stop sequence it stopped at; lm15 cuts it off (MAP-22).
  • New compat values: thinking format reasoning_effort_switch, reasoning off switch, JSON schema none (a server that applies no response_format: dropped with a record), and stop_in_text. Registry entries may carry aliases.
  • Known, not changed in this release: the reader for a Chat Completions response body that did not come through lm15 (response_from_openai_chat in Python; the same door under each language's spelling) is given no request, so a Tinker body that stopped at a stop sequence keeps the sequence at its end. Ordinary calls and streams cut it. On the Messages door, Tinker itself refuses reasoning off and effort words for the on/off families; use tinker for those.

Nothing is removed and no field changes meaning. Tinker calls both of its doors beta.

lm15 2.2.0 for TypeScript

Choose a tag to compare

@MaximeRivest MaximeRivest released this 07 Nov 01:16

In TypeScript: config: { reasoning: { effort, summary: "auto" } }.

Claude shows its thinking when you ask for it (lm15-contract 9d8e1ce, MAP-7 rule 7 amended; 2,140 of 2,140 checks, the same in every lm15 language).

  • A reasoning summary on Claude sends thinking.display: "summarized". Since Opus 4.7, Anthropic returns a thinking block with empty text unless the request asks for the summary. On Opus 4.7, Opus 4.8 and every Claude 5 model, asking lm15 for reasoning with summary: "auto" gave an empty thinking part (checked live on 14 Claude models). Any summary now adds the setting, with adaptive thinking and with a thinking budget alike; every Claude model accepts it. Without a summary nothing changes: each model's own default.
  • DeepSeek, Meta and Moonshot over the Anthropic Messages wire are unchanged: they return their thinking without the setting.
  • Known, not changed in this release: effort: "off" sends no thinking object to Claude, and Claude 5 thinks anyway. See the contract's change entry, changes/2026-11-07-anthropic-thinking-display.md.

Nothing is removed and no field changes meaning.

lm15 2.1.0 for TypeScript

Choose a tag to compare

@MaximeRivest MaximeRivest released this 05 Nov 16:52

In TypeScript: ProviderLM.streamReader(request) returns the reader for one stream (feed(raw)), with the exported StreamReader type; parseStreamEvents still maps one event on its own.

Streams now yield what the provider said, all of it, once (MAP-20). Nothing is removed and no field changes meaning. Contract 77f22f4, 2,132 of 2,132 checks, the same in all six lm15 languages.

  • Parallel tool calls on the Codex subscription keep their inputs. The Codex backend sends the arguments of parallel calls only in each call's final response.output_item.done, never in pieces, and lm15 read only the pieces, so every call arrived with input {} (from stream and from complete alike). A stream's final copy of an item now completes whatever its pieces did not deliver: arguments, message text, a reasoning summary. A piece that contradicts the final copy is refused (StreamAssemblyError, code stream_assembly, with the part's index) instead of either value being chosen.
  • A streamed reasoning summary reads like the complete one: its parts are joined with a newline, as the complete call joins them. They used to run together (**First idea****Second idea**).
  • Gemini parallel calls sent one per chunk stay separate calls. gemini-2.5-pro and gemini-3-flash-preview send each parallel call in a chunk of its own. Every call landed in the same slot and their arguments ran into one unreadable input.
  • A Responses stream cut short by the output limit ends with length. It ends with response.incomplete, which was ignored: on OpenAI, Azure, Codex and Moonshot the stream then failed with StreamAssemblyError; on Meta it ended stop without its usage.

How this is checked from now on: the contract harness compares every OpenAI Responses stream with the stream's own final copy of the whole response (no hand-written answer needed), replays legal re-shapings of every recorded stream (fragments cut elsewhere, joined, re-chunked, dropped where a final copy restates them, contradicted), and every kind of event a recorded stream carries must be classified (mapping/stream-events.json). See lm15-contract/changes/2026-11-05-stream-final-copy.md.

lm15 2.0.0 for TypeScript

Choose a tag to compare

@MaximeRivest MaximeRivest released this 01 Nov 10:58

In TypeScript: InferencePricing.estimate(usage) returns a cost or undefined; PricingTier for long prompts; reasoningUsage compat.

A usage counter means one thing on every provider (MAP-19). Breaking for code that reads token counts or estimates cost.

  • input_tokens is every token the model read, cached tokens included; output_tokens is every token it wrote, reasoning included. The cache, reasoning and audio counts are parts of one of those two, and total_tokens is input + output. In 1.x each counter was the provider's own, so the same field meant different things.
  • What changes: Anthropic input (and DeepSeek, Meta and Moonshot on the Anthropic wire) now includes cache reads and writes; a call that read 4,873 cached tokens and 20 new ones reports 4,893, not 20. Gemini and Vertex output now includes the thinking, and input includes the tokens Gemini's own tools added. xAI output now includes reasoning. OpenAI and other Chat Completions servers: unchanged. The provider's own numbers stay in provider_data, for checking a bill.
  • A counter is exact or empty, never a partial sum. When a provider's numbers do not add up the way its wire is documented to count, input and/or output are left empty rather than wrong.
  • Building a usage record checks that parts fit inside wholes and that the total is input + output. A usage record saved by 1.x from Anthropic, Gemini or xAI can fail to load for this reason; the message says so.

Cost estimates are exact or unknown. The estimate takes a usage record and returns the list price, or nothing when an unknown count could change it (and for calls with audio tokens). 1.x charged cached tokens twice on OpenAI, Gemini and xAI (a call with 90,000 of its 100,000 input tokens cached cost $0.039 and was estimated at $0.219) and missed Gemini's and xAI's reasoning. Pricing tiers reprice a whole call above a prompt length, as OpenAI does above 272,000 input tokens and Claude Haiku 5.5 above 100,000; an absent price is unknown, never zero. A reasoning_usage compat setting covers servers that count reasoning beside the completion (the xai preset).

Major version 2 in every language. The major version is now the contract's generation, the same in every lm15 language (lm15-contract playbooks/api-family.md, rule 8); generation 2 begins with this change.

Contract cfc3edc (ratified on 2026-11-01 in 6b801f9, wording only): 1,968 of 1,968 checks, the same commit as Python 2.0.0, TypeScript 2.0.0, Rust 2.0.0-rc.1, Go v2.0.0-rc.1, Julia 2.0.0 (LanguageModel15, joining Julia's registry) and R 2.0.0. Rules in lm15-contract changes/2026-10-31-usage-one-meaning.md.

npm install @lm15/lm15@2

lm15 1.0.0

Choose a tag to compare

@MaximeRivest MaximeRivest released this 31 Oct 12:05

The first stable release of lm15 for TypeScript and JavaScript (Node 22+ and browsers, @lm15/lm15/browser).

npm install @lm15/lm15

Same code as 1.0.0-rc.11 (bounding boxes, image judgments, Muse sign-in, saved cloud settings under managed routing), now marked stable. The chat core (requests, responses, streaming, tools, structured output, media in messages, reasoning, errors, credentials, model listing) follows semantic versioning through 1.x. Files, batches, media generation, stored caches, realtime sessions, Chat Completions ingest and sign-in stay provisional; see the README's Stability section.

Also in the source: the optional in-page TLS module (tls/, built separately and not part of the npm package) pins rustls-webpki 0.103.16 and webpki roots 1.0.10, which distrust the compromised eMudhra intermediates (October 2026). Nothing in the published package changes from this.

Conformance: lm15-contract 6dc1c5c, 1,921 of 1,921.

lm15 1.0.0-rc.9

lm15 1.0.0-rc.9 Pre-release
Pre-release

Choose a tag to compare

@MaximeRivest MaximeRivest released this 15 Oct 00:28

A release candidate. Includes rc.8, which was tagged but never published.

Jev reads pictures. Put a PNG or JPEG image in the one user message sent to TypeSafe's Jev, on its own or beside text and data (sent as an ordered list), and ask judgments about it ("Is there a person in the image?", "Do the two images show the same product?"). lm15 sends the bytes where TypeSafe expects them and adds its TypeSafe-Danger-Zone-Images: true opt-in header for you. TypeSafe calls images beta: check answers on pictures you know before relying on them. A picture given by URL or file id, another format (WebP, GIF), audio and video are refused before anything is sent. Box questions are not supported yet. Tested live on jev-1.13.0; rules in lm15-contract changes/2026-10-14-jev-images.md.

Also new since the last published version: the Meta login reaches the Muse Code subscription, Muse Wonder Full included. Signing in with Meta (method device) sends requests to the address Meta names when it hands out the key, and only to an https meta.ai address. Read before using it: the method stays unverified (allow_unverified), because Meta's Model API terms (2026-10-09, § 3.2) reserve this key for Muse Code; using it from lm15 puts the Meta account at risk, and subscription traffic is in Contributor mode by default (Meta may train on it).

Contract 10645e5: 1,911 of 1,911 checks, the same commit as Python 1.4.0, TypeScript and Rust 1.0.0-rc.9, Go v1.1.0-rc.8, Julia 1.0.0 and R 1.3.0.

lm15 1.0.0-rc.11

lm15 1.0.0-rc.11 Pre-release
Pre-release

Choose a tag to compare

@MaximeRivest MaximeRivest released this 15 Oct 23:15

A release candidate.

Bounding boxes: ask where something is in a picture. A new judgment helper (bounding_box in Python, Rust, Julia and R; boundingBox in TypeScript; BoundingBox in Go) declares a question whose answer is four numbers, top, bottom, left and right, each a fraction of the picture measured from its top-left corner. Multiply by the picture's size in pixels to draw or crop. On TypeSafe it is Jev 1.14's new Box question (use jev-preview or jev-1.14.0; jev-latest is still 1.13 and answers "Invalid request."), and lm15 sends TypeSafe's TypeSafe-Danger-Zone-Images: true opt-in header for you. On OpenAI, Anthropic, Gemini and the others the same request is ordinary structured output, so one program runs everywhere; how close the box is depends on the model (on one test picture: Jev 1.14 and GPT-5.6 Luna within 0.04, Claude Sonnet 4.5 and Gemini 2.5 Flash about 0.15, Claude Haiku 4.5 off by 0.3 to 0.4). A box is always four edges, even when the thing is absent: ask a yes/no beside it. A box has no probabilities, so probabilities concerns only the other judgments. TypeSafe calls images and boxes beta. Rules in lm15-contract changes/2026-10-15-bounding-box.md.

Contract 6dc1c5c: 1,921 of 1,921 checks, the same commit as Python 1.5.0, TypeScript and Rust 1.0.0-rc.11, Go v1.1.0-rc.10, Julia 1.0.0 and R 1.4.0.

lm15 1.0.0-rc.10

lm15 1.0.0-rc.10 Pre-release
Pre-release

Choose a tag to compare

@MaximeRivest MaximeRivest released this 15 Oct 16:28

A release candidate.

A saved cloud login's settings are used. A cloud login saved with its host settings (for example configure("vertex-anthropic", method="cloud", answers={"named": "cli"}, settings={"project": "my-project", "location": "us-east5"})) now sends that project and location. Before, the router ignored them and fell back to the environment and the defaults, so a Vertex login saved with its project failed with "setting 'project' is required" or quietly used GOOGLE_CLOUD_PROJECT and the global location. A router settings entry for the same provider changes only the settings it names; an explicit key or named identity uses no saved login, so none of its settings apply. Found and fixed in lm15-python by Rolf Fredheim (lm15-dev/lm15-python#21). Rules in lm15-contract changes/2026-10-15-saved-cloud-settings.md.

Contract 1cbdc12: 1,912 of 1,912 checks, the same commit as Python 1.4.1, TypeScript and Rust 1.0.0-rc.10, Go v1.1.0-rc.9, Julia 1.0.0 and R 1.3.1.

lm15 1.0.0-rc.8

lm15 1.0.0-rc.8 Pre-release
Pre-release

Choose a tag to compare

@MaximeRivest MaximeRivest released this 12 Oct 23:30

A release candidate.

The Meta login reaches the Muse Code subscription, Muse Wonder Full included. Signing in with Meta (method device) now sends requests to the address Meta names when it hands out the key, and only to an https meta.ai address: any other address is refused before the key is sent. The connection's account label names the plan ("Muse Code Power Usage"), or says "no Muse Code subscription (pay-as-you-go)". Tested live on 2026-10-12 on a Power Usage plan: login, key, renewal, then meta:muse-wonder-full-1.0.

Read before using it: the method stays unverified (allow_unverified), because Meta's Model API terms (2026-10-09, § 3.2) reserve this key for Muse Code. Using it from lm15 is outside those terms and puts the Meta account at risk; subscription traffic is in Contributor mode by default (Meta may train on it).

Contract 88bb560: 1,905 of 1,905 checks, the same commit as Python 1.3.0, TypeScript and Rust 1.0.0-rc.8, Go v1.1.0-rc.7, Julia 1.0.0 and R 1.2.0.