Repository navigation
Description
Description
ctx.experimental.generate.text (also ctx.generate.text and POST /api/experimental/generate/text) never runs the model.request, http.request or http.response hooks. Those hooks are the only place a plugin can rewrite outbound model requests, so any provider that depends on them works in sessions and fails in transient generation.
My case: a local plugin registers a claude provider (a copy of the Anthropic catalog) and uses http.request to turn requests into Claude subscription requests (auth headers, the required system block). claude/haiku-5.5 works in sessions. A plugin that builds prompt autocomplete on generate.text with the same model gets the unmodified request, and Anthropic rejects it:
{"_tag":"ProviderRequestError","providerID":"claude","reason":"rate_limit","message":"Error","status":429}
Session hooks also can't change the model, so plugins have no workaround.
Expected: provider-level request hooks also run for transient generation, for example http.request/http.response with kind: "generate" and no session, or a provider-scoped request hook that doesn't depend on a session.
Plugins
A minimal probe plugin (below). Original case: a local Claude subscription provider plugin and opencode-recall autocomplete.
OpenCode version
2.2.5
Steps to reproduce
-
Register this plugin in an otherwise empty config:
import { appendFileSync } from "node:fs"; const log = (line) => appendFileSync("/tmp/hooks.log", line + "\n"); export default { id: "probe.http-hooks", async setup(ctx) { log("setup"); await ctx.session.hook("model.request", (e) => log(`model.request kind=${e.kind}`)); await ctx.session.hook("http.request", (e) => log(`http.request kind=${e.kind}`)); await ctx.session.hook("http.response", (e) => log(`http.response kind=${e.kind} status=${e.response.status}`)); }, };
-
Run
opencode api --standalone post /api/experimental/generate/text -d '{"prompt":"Reply with exactly: ok","model":{"providerID":"openai","id":"gpt-6-luna-fast"}}'. It returns{"data":{"text":"ok"}}, and/tmp/hooks.logonly containssetup. -
Run
opencode run --standalone -m openai/gpt-6-luna-fast "Reply with exactly: ok". The log now showsmodel.request,http.requestandhttp.responseforkind=primaryandkind=title.
Operating System
CachyOS Linux (kernel 7.0.8)
Terminal
Not terminal related (reproduced through opencode api)
Activity
opencodejr commentedon Nov 5, 2026
Thanks for the detailed report and the probe plugin, that makes the gap very clear.
To restate the request: provider-level request hooks (
model.request,http.request,http.response) should also run for stateless generation (ctx.generate.text,ctx.experimental.generate.text,POST /api/experimental/generate/text). That way a provider plugin that rewrites outbound requests works outside sessions too.The current behaviour matches how the code is built. Those hooks can only be registered with
ctx.session.hook, their events require asessionIDand anagent, andGenerate.textcalls the model directly without going through the session request path. So there's currently no way to do this from a plugin, as you found. The missing piece is a hook that doesn't depend on a session: for examplectx.generate.hook("http.request" | "http.response", ...)with the model and a request ID, or one provider-scoped hook used by both sessions and stateless generation. I've passed that on to the maintainers, who will consider it.Related:
ctx.generate.hook("http.request" | "http.response")for the same Anthropic OAuth Haiku 5.5 429 case. It was closed for a process reason (no linked issue), not on its merits./api/experimental/generateaccepts only{prompt, model}whileLLM.requestalready acceptssystem,messagesandgeneration#49549: requests to pass more options to statelessgenerate.text.JosXa commentedon Nov 5, 2026
#53990 implemented exactly this (
ctx.generate.hook("http.request" | "http.response")) and was closed only because it had no linked issue. Could it be reopened against this one?