Skip to content

Plugin request hooks don't run for experimental.generate.text #62096

Open

Description

@JosXa

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

  1. 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}`));
      },
    };
  2. 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.log only contains setup.

  3. Run opencode run --standalone -m openai/gpt-6-luna-fast "Reply with exactly: ok". The log now shows model.request, http.request and http.response for kind=primary and kind=title.

Operating System

CachyOS Linux (kernel 7.0.8)

Terminal

Not terminal related (reproduced through opencode api)

Activity

  1. opencodejr commented on Nov 5, 2026

    @opencodejr
    Contributor

    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 a sessionID and an agent, and Generate.text calls 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 example ctx.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:

  2. JosXa commented on Nov 5, 2026

    @JosXa
    ContributorAuthor

    #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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions