Simon Willison’s Weblog

Subscribe

Releases

Filters: Sorted by date

Support for branches other than the default branch. Use uvx commit-rewriter --branch other to run against another branch. #3

Alec Garcia added support for OpenTelemetry to Datasette in this release.

I've also refactored all of Datasette's modal dialogs to a single Web Component, which is now documented for other plugins to use.

  • New OpenAI models: gpt-6-sol for GPT-6 Sol and gpt-6-luna for GPT-6 Luna. #1702
  • Model plugins can now declare supports_conversation = False for models that only accept single-turn prompts. LLM raises llm.ConversationNotSupported when these models receive assistant or tool history, and llm chat rejects them before starting a session. See Models that do not support conversations. The first plugin to use this is llm-typesafe. #1692
  • Reasoning traces in the Markdown output of llm logs are now wrapped in <details><summary> tags. #1701

Plus bug fixes from five new contributors.

Adds support for Claude Opus 5.5:

llm -m claude-opus-5.5 "prompt goes here"

I built this new plugin for LLM to add support for TypeSafe AI's new Jev model. Install it like this:

llm install llm-typesafe

Then set an API key (get one here, the waitlist seems to move pretty fast):

llm keys set typesafe
# Paste key

And now you can ask yes/no "noul" questions like this:

llm -m jev 'Please refund my last payment.' \
  -s 'Does this message explicitly request a refund?'

Output:

{"type": "noul", "noul": 0.99}

Or choice questions like this:

cat message.txt | llm -m jev \
  -s 'Which team should handle this message? If billing and technical issues both occur, choose billing.' \
  -o answer_type choice \
  -o criteria '{
    "billing":"Charges, invoices, payments, or refunds",
    "technical":"Problems installing or using the product",
    "other":"Neither category fits"
  }'

Or scoring questions like this:

cat report.txt | llm -m jev \
  -s 'How reproducible is the problem described in this report?' \
  -o answer_type score \
  -o criteria '[
    "No reproduction instructions",
    "Some instructions, but important steps are missing",
    "Complete steps with expected and actual results"
  ]'

See the README for more details.

This plugin solves a very specific problem.

I've started using Codex Remote to run coding agents on various machines while controlling them from my phone.

Sometimes I use those machines to hack on LLM projects, and occasionally that means I need to configure an API key.

I don't like pasting API keys into agent sessions, so I wanted a way to get those keys onto a machine without pasting them into the ChatGPT app directly.

With this plugin, I can tell Codex to run:

uvx --with llm-keys-ui llm keys-ui --all

Then have it tell me the URL - including local network or Tailscale device IPs - for an interface to save additional API keys.

Then later it can use a command like llm keys get anthropic as part of a shell command when it needs to use a key.

Chat conversation requesting uvx --with llm-keys-ui llm keys-ui --all, with a response listing four server URLs on port 8010 and confirming the server is still running. LLM keys web interface listing anthropic, openai, openrouter, and qwen-dummy as stored keys, with a form containing Key name and New value fields and a Save key button. Existing key values are never displayed.
  • Explain plans now work on read-only stored-query pages.

I upgraded datasette.simonwillison.net to Datasette 1.0a40, which inspired me to ship a new version of this explain plugin.

I run this GitHub login plugin on the agent.datasette.io demo site and I noticed that my authenticated sessions weren't lasting very long. It turned out that the plugin was setting cookies without a Max-Age parameter, so they were expiring at the end of a browser session (which in Mobile Safari seems to happen pretty often, independently of how you are using the app.)

I fixed that in #80 and, since this plugin has been around for quite a while and is tested against both Datasette 0.65.x and Datasette 1.0ax, I decided to bump it up to a 1.0 release. I'm trying to get better at promoting stable plugins to 1.0.

Same security fix as 0.65.5, plus some neat new features and bug fixes:

  • Plugins can now launch and manage background tasks using the new datasette.add_background_task() method. Thanks, Alex Garcia.
  • I've migrated Datasette to httpx2 for features like the internal datasette.client.get() method.
  • A whole lot of bug fixes, many of them stemming from a recent effort to triage issues for a 1.0 stable release.

Security fix for an issue where a trailing newline in a requested table name could bypass table permissions and expose private rows, reported by dpfkdlemtp in GHSA-h547-rmjf-5m2m.

I built this little web app the other day to help edit the commit messages for the Datasette security releases. The initial commits were full of coding agent cruft and references to issue IDs from our private repository, so they weren't fit for publication.

If you want to edit the commit messages for a repository you can run it like this:

uvx commit-rewriter path/to/repo

Omit the path if you are already in the directory for that repo.

Screenshot of the commit-rewriter web interface. A heading reads commit-rewriter above the repository path and current branch and commit hash, with a short description of the tool. A toolbar shows a pending edits count with Discard drafts and Rewrite commit messages buttons, followed by a search box for message, author, or hash and an Edited only checkbox. A left sidebar titled Navigate commits lists recent commit messages with their short hashes. The main panel shows a card for each commit with its hash, author and timestamp, an editable text area containing the commit message, and a View full formatted diff toggle.

When you submit your edits the tool creates a timestamped branch of your current repo state - to allow you to revert if you need to - and then rewrites every commit from the first one you edited to the most recent.

I've added WebP support to my shot-scraper screenshot automation tool. You can now take a WebP screenshot of a web page like this:

shot-scraper https://simonwillison.net -o screenshot.webp --quality 80

The --quality option sets the quality - without that option the WebP file will be lossless.

In my experience WebP screenshots are almost always significantly smaller in file size than their JPEG or PNG equivalents. See the PR for some examples.

I shipped this feature so I could use it to generate the screenshot for my new commit-rewriter tool.

  • Sets force_https=true in fly.toml. #31
  • Fix for Volume could not be found bug. #32
  • Compatible with app-scoped deploy tokens. #34
  • Performance fix for loading OpenRouter models. Thanks, waveplate. #59

One new feature:

  • llm logs --usage Markdown output now includes the response duration in milliseconds and as a human-readable duration. llm logs --short includes a new duration_ms field. #1653

Plus several contributed bug fixes, and a significant performance improvement to llm logs thanks to waveplate on GitHub, see also llm-openrouter 0.7.1.

Claude Fable 5.1, reasoning traces are now displayed by default for models that support them, plus a new llm_anthropic.ClaudeRefusal exception for when Claude throws a refusal.

Google released Gemini 3.8 Flash (and 3.8 Flash Cyber, but that's available to "trusted defenders" only) today.

Here are the pelicans for high, medium, and low. This is high:

Description by Gemini 3.8 Flash: Digital illustration of a cartoon pelican wearing a red and white polka-dot scarf riding a teal cruiser bicycle along a wooden boardwalk by the beach, with a small blue fish in the front basket and a glowing sun over the ocean.

For comparison, here are the same pelicans generated using Gemini 3.7 Flash.

Something I appreciate about Gemini Flash is that it's fast, cheap, and competent at things like HTML and JavaScript. I was messing around with it and prompted "make me a cool thing in html" and it built this, which is certainly a cool thing in HTML! Took 13 seconds, cost 1.8 cents.

If you click through to the demo you'll see one more thing I built with Gemini 3.8 Flash.

My markdown-svg-renderer tool lets me feed in the URL to a Gist with Markdown in and renders that markdown with fenced code blocks for SVG correctly rendered.

I used Gemini 3.8 Flash (with my very basic llm-coding-agent coding agent plugin) to add support for HTML as well, so now any HTML blocks in the Markdown are rendered using a sandboxed iframe. Here's the transcript.

  • "rows" from execute_sql is now an array of objects. Previously it was an array of arrays. This should help weaker models avoid losing track of which positional array element maps to which column. #1
  • Now depends on mcp>=2.1.1.

This is the first non-alpha release of the plugin. I'm confident it's ready as I've been using it quite a bit myself.

This release of the Anthropic plugin for LLM mainly provides compatibility with the recently released anthropic v1.0.0 Python library, which switches from httpx to httpx2. OpenAI made the same change in their v3.0.0 release two weeks ago.

Anthropic provide this migration guide for upgrading to 1.0, so I prompted Fable 5 in Claude Code with:

Upgrade to anthropic>=1 - read https://raw.githubusercontent.com/anthropics/anthropic-sdk-python/refs/heads/main/MIGRATION.md and get the tests passing

Here's the resulting PR.

My highlights from this release:

  • Upgraded to the OpenAI Python library 3.x and switched the HTTP client dependency from httpx to httpx2. #1608, #1631

I shipped a quick 0.32.1 fix for this yesterday, but this is the more comprehensive fix.

  • llm embed and llm embed-multi now accept --key. The Python EmbeddingModel.embed(), EmbeddingModel.embed_multi(), Collection.embed() and Collection.embed_multi() methods accept key= too, passing the resolved per-call key to embedding plugins without changing shared model state. Existing plugins that read self.key continue to work through a compatibility fallback. Thanks, ChrisJr404. #757, #1620

The embedding models now use the same pattern for keys that regular LLM models do.

  • llm prompt -t/--template can now be repeated to combine templates in order. This allows model configuration and options from one template to be used with a prompt from another.

This unlocks a neat pattern where you can create templates that package a model with a set of default options:

llm -m gpt-5.6-luna -o reasoning_effort high --save lhigh
llm "Generate an SVG of a pelican riding a bicycle" --save pelican
# Combine and run the templates
llm -t lhigh -t pelican
  • Reasoning-capable Responses API models now support a reasoning_summary option with auto, concise, and detailed values. This can be used with llm openai endpoint --responses. #1600

This is particularly useful for exercising different models that provide their own imitation of the OpenAI Responses API.

Fresh installs of LLM stopped working the other day because the OpenAI Python library dropped its usage of httpx, and it turned out LLM depended on that library but only installed it via a transitive openai dependency.

This dot-release fixes that for the moment by pinning to openai<3, and a soon-to-drop 0.33 release will switch from httpx to httpx2.

Now that this plugin is compatible with LLM 0.32 it can display the reasoning traces for LLMs available through OpenRouter.

Fixes a crashing bug in sqlite-utils 4.2. I'd introduced code that looks like this:

from typing_extensions import Self

It turned out the typing-extensions package was not listed as a dependency for sqlite-utils - it was installed by one of the other dependencies in the dev dependency group, but when you uvx sqlite-utils directly you don't get those dependencies.

As part of fixing this I figured out how to run a smoke test to ensure the CLI tool still works even without those dev dependencies, which can be run from the project checkout:

uv run --isolated --no-default-groups sqlite-utils --help

The --no-default-groups argument prevents it from installing that default dev group, and --isolated means that even if there is a .venv/ folder containing extra dependencies they will be ignored for the duration of that uv run command.

Lots of improvements in this one relating to the table.transform() feature, which adds support for complex alter table operations by creating a fresh table, copying across the data and then dropping and replacing the old one.

transform() now preserves a much larger array of edge-case schema definitions, including check constraints, unique constraints and even comments describing the columns.

There are also new introspection properties for check constraints, and a whole lot of other smaller changes.

Includes contributions from Bunlong Heng, ethanhawkes-gif, Rami Abdelrazzaq, nyxst4ck, and ikatyal2110.

(It later turned out 4.2 had a crashing bug, fixed in 4.2.1.)

It's been a while since the last llm-gemini release. This version of the plugin adds support for today's Gemini 3.7 Flash release, plus gemini-3.6-flash, gemini-3.5-flash-lite and two embedding models gemini-embedding-2 and gemini-embedding-001.

The plugin is also upgraded for compatibility with LLM 0.32, which means you can now see reasoning traces and you can also enable server-side tools using this pattern:

llm -m gemini-3.7-flash -T CodeExecution \
  'use python to calculate (factorial of 13) * 3'

I had Gemini 3.7 Flash draw me some pelicans riding bicycles at high, medium, and low thinking efforts (minimal, which was an option in 3.6 Flash, has been removed in 3.7.) Here's the high level one, which is pretty great:

This pelican has  a very cool curved green bicycle, a fish in its basket, a lovely red and white spotted scarf and a captain's hat

Update 14th August 2026: I had originally said that the SVG rendered incorrectly in Chrome and Firefox, and blamed Gemini 3.7 Flash for producing invalid SVG. That was entirely incorrect: the rendering glitch was my fault, caused by a bug In my rendering tool. I've now fixed that bug.

Performance boost for DuckDB exports and CSV imports, see here.