diff --git a/internal/plugin/host.js b/internal/plugin/host.js index 16c764f7..3d211cf4 100644 --- a/internal/plugin/host.js +++ b/internal/plugin/host.js @@ -709,6 +709,19 @@ function sdkHeaders(npm, key) { return { authorization: `Bearer ${key}` } } +// bodyOf is the body as OpenCode hands it to a plugin's fetch: the string +// the AI SDK built, as the plugins reshape only a string body. One that +// isn't UTF-8 text stays bytes. +function bodyOf(b64) { + if (!b64) return undefined + const b = Buffer.from(b64, "base64") + try { + return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(b) + } catch { + return b + } +} + async function doFetch(id, params) { const key = accountKey(params.provider, params.account) return inScope(params.provider, key, () => fetchAs(id, key, params)) @@ -750,7 +763,7 @@ async function fetchAs(id, key, { provider, model, npm, url, method, headers, bo const res = await f(url, { method: method ?? "POST", headers: h, - body: body ? Buffer.from(body, "base64") : undefined, + body: bodyOf(body), signal: ctl.signal, }) const rh = {} diff --git a/internal/plugin/plugin_test.go b/internal/plugin/plugin_test.go index 9c1ac5d2..cc2e1f19 100644 --- a/internal/plugin/plugin_test.go +++ b/internal/plugin/plugin_test.go @@ -145,6 +145,10 @@ func TestFakePlugin(t *testing.T) { if h.Get("Authorization") != "Bearer fresh-r-blue" || h.Get("X-Plugin-Model") != "fake-1" || h.Get("X-Models") != "fake-1,fake-claude,fake-gemini" { t.Fatalf("headers = %v", h) } + // the plugin's fetch got the body as a string, as OpenCode hands it + if h.Get("X-Body-Type") != "string" { + t.Fatalf("the plugin got a %s body", h.Get("X-Body-Type")) + } b, _ = os.ReadFile(AuthPath()) json.Unmarshal(b, &saved) if saved["fakeco"]["access"] != "fresh-r-blue" { diff --git a/internal/plugin/testdata/fake/index.js b/internal/plugin/testdata/fake/index.js index 59bbc7e3..8640ec12 100644 --- a/internal/plugin/testdata/fake/index.js +++ b/internal/plugin/testdata/fake/index.js @@ -50,6 +50,7 @@ export const FakePlugin = async ({ client }) => ({ const h = new Headers(init.headers) h.set("authorization", "Bearer " + (a.type === "oauth" ? a.access : a.key)) h.set("x-models", Object.keys(provider.models).sort().join(",")) + h.set("x-body-type", typeof init.body) return fetch(url, { ...init, headers: h }) }, }),