Skip to content

[FEATURE]: Add logging of LLM HTTP Requests (v2) #56066

Closed as duplicate
Closed as duplicate

Description

@martincmb

Feature hasn't been suggested before.

  • I have verified this feature I'm about to request hasn't been suggested before.

Describe the enhancement you want to request

Troubleshooting failed requests to an LLM is difficult when there is no log of the actual URL, etc. that was called.

There are a few plugins that are supposed to help with this, but have not been updated for v2.

It would be great if this can be added as a built-in feature to be able to inspect URLs, headers and possibly payloads of requests made to LLMs.

Activity

  1. opencode-agent commented on Oct 16, 2026

    @opencode-agent
    Contributor

    Thanks for the request! You'd like OpenCode v2 to log outbound LLM HTTP requests (URL, headers and optionally payloads) so failed provider calls are easier to debug. The maintainers will consider it.

    Related issues:

    In the meantime, you can do this yourself with a plugin. The v2 plugin API has http.request and http.response session hooks that see every HTTP request sent to a model provider. Here is a small plugin that appends each request and response as one JSON line to a file. It hides auth headers, logs request bodies, and logs response bodies only for error responses, because successful responses are streams and are left alone. I've checked that it type-checks against the plugin API, but I haven't run it inside OpenCode yet.

    Save it as .opencode/plugins/llm-http-log/index.ts in your project (plugins there are loaded automatically), or put it anywhere and add its path to plugins in opencode.json:

    import { appendFile, mkdir } from "node:fs/promises"
    import { homedir } from "node:os"
    import { dirname, join } from "node:path"
    import { Plugin } from "@opencode/plugin"
    
    const SECRET = /^(authorization|x-api-key|api-key|x-goog-api-key|cookie|proxy-authorization)$/i
    
    function headers(input: Headers) {
      const out: Record<string, string> = {}
      input.forEach((value, key) => {
        out[key] = SECRET.test(key) ? "[redacted]" : value
      })
      return out
    }
    
    export default Plugin.define({
      id: "llm-http-log",
      async setup(ctx) {
        const file =
          typeof ctx.options.file === "string" ? ctx.options.file : join(homedir(), ".local/share/opencode/llm-http.jsonl")
        const bodies = ctx.options.bodies !== false
        await mkdir(dirname(file), { recursive: true })
        const write = (entry: Record<string, unknown>) =>
          appendFile(file, JSON.stringify({ time: new Date().toISOString(), ...entry }) + "\n").catch(() => {})
    
        await ctx.session.hook("http.request", async (event) => {
          const req = event.request
          await write({
            type: "request",
            sessionID: event.sessionID,
            kind: event.kind,
            model: event.model,
            method: req.method,
            url: req.url,
            headers: headers(req.headers),
            body: bodies && req.body ? await req.clone().text() : undefined,
          })
        })
    
        await ctx.session.hook("http.response", async (event) => {
          const res = event.response
          await write({
            type: "response",
            sessionID: event.sessionID,
            kind: event.kind,
            model: event.model,
            url: event.request.url,
            status: res.status,
            headers: headers(res.headers),
            // Only error bodies are read; successful bodies are streams and are left untouched.
            body: !res.ok ? await res.clone().text() : undefined,
          })
        })
      },
    })

    The options are optional. Use them to change the log file or turn off request bodies:

    {
      "plugins": [
        { "package": "./.opencode/plugins/llm-http-log", "options": { "file": "/tmp/llm-http.jsonl", "bodies": false } }
      ]
    }

    Then run tail -f ~/.local/share/opencode/llm-http.jsonl while you reproduce the failing request. Request bodies can contain your prompts and file contents, so check the log before sharing it. Providers that use WebSocket transport won't show up here, because these hooks only cover HTTP.

  2. neriousy commented on Oct 20, 2026

    @neriousy
    Member

    Thanks for the suggestion. We're tracking built-in LLM request and response logging for debugging in #29186, so I'm closing this as a duplicate. Please follow that issue for updates.

  3. mschfh commented on Nov 2, 2026

    @mschfh

    @neriousy The linked issue was closed as stale by you. Will there be further updates? Is this available in v2?

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