@stablekernel/opencode-bifrost 0.1.4 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stablekernel/opencode-bifrost",
3
- "version": "0.1.4",
3
+ "version": "0.2.1",
4
4
  "description": "OpenCode plugin that routes models through the Bifrost gateway, resolving per-project virtual keys via gateway-cli.",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
package/src/index.test.ts CHANGED
@@ -153,6 +153,27 @@ describe("config hook", () => {
153
153
  ).toEqual({ input: 1, output: 5, cache_read: 0.1, cache_write: 1.25 });
154
154
  });
155
155
 
156
+ test("omits headers when resolve reports none", async () => {
157
+ const noHeaders: Record<string, unknown> = { ...RESOLUTION };
158
+ delete noHeaders.headers;
159
+ const cfg = await applyConfig({
160
+ ...OK_SHELL,
161
+ "resolve --json": JSON.stringify(noHeaders),
162
+ });
163
+ expect(cfg.provider.bifrost.options.headers).toBeUndefined();
164
+ });
165
+
166
+ test("treats an empty header map the same as no headers", async () => {
167
+ // Go-side mergeHeaders returns nil for an empty merge and the JSON tag
168
+ // omits it, so `{}` cannot reach the plugin in practice. This pins the
169
+ // behavior anyway so a future CLI change cannot quietly flip it.
170
+ const cfg = await applyConfig({
171
+ ...OK_SHELL,
172
+ "resolve --json": JSON.stringify({ ...RESOLUTION, headers: {} }),
173
+ });
174
+ expect(cfg.provider.bifrost.options.headers).toBeUndefined();
175
+ });
176
+
156
177
  test("does not read the key: discovery goes through the CLI", async () => {
157
178
  // The CLI holds the credential and returns only non-secret metadata.
158
179
  const calls: string[] = [];
@@ -319,6 +340,17 @@ describe("auth.loader", () => {
319
340
  expect(opts.baseURL).toBe("https://gw.example/openai/v1");
320
341
  });
321
342
 
343
+ test("forwards the resolved headers alongside the key", async () => {
344
+ // The config hook sets headers at startup, but auth.loader also returns
345
+ // provider options at request time; both must carry the gateway headers.
346
+ const hooks = await load(OK_SHELL);
347
+ const opts = await hooks.auth!.loader!(
348
+ async () => ({ type: "api", key: "stored" }) as never,
349
+ {} as never,
350
+ );
351
+ expect(opts.headers).toEqual({ "X-Org-Route": "prod" });
352
+ });
353
+
322
354
  test("falls back to the stored credential without gateway-cli", async () => {
323
355
  const hooks = await load({});
324
356
  const opts = await hooks.auth!.loader!(
package/src/index.ts CHANGED
@@ -150,7 +150,9 @@ export const BifrostGateway: Plugin = async ({ directory, $ }) => {
150
150
  ...existing,
151
151
  options: {
152
152
  baseURL: info.openai_base_url,
153
- ...(info.headers ? { headers: info.headers } : {}),
153
+ ...(info.headers && Object.keys(info.headers).length > 0
154
+ ? { headers: info.headers }
155
+ : {}),
154
156
  ...(existing.options ?? {}),
155
157
  },
156
158
  models: { ...models, ...(existing.models ?? {}) },
@@ -176,7 +178,9 @@ export const BifrostGateway: Plugin = async ({ directory, $ }) => {
176
178
  return {
177
179
  apiKey,
178
180
  ...(info ? { baseURL: info.openai_base_url } : {}),
179
- ...(info?.headers ? { headers: info.headers } : {}),
181
+ ...(info?.headers && Object.keys(info.headers).length > 0
182
+ ? { headers: info.headers }
183
+ : {}),
180
184
  };
181
185
  },
182
186