Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion packages/zen/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,9 @@ export {
} from "./constants.js";
export { resolveZenEndpoint } from "./endpoint.js";
export { isZenProvider, isZenProviderId } from "./identity.js";
export { isKnownZenModel, ZEN_DEFAULT_MODEL, ZEN_MODEL_IDS } from "./models.js";
export {
isKnownZenModel,
protocolForZenModel,
ZEN_DEFAULT_MODEL,
ZEN_MODEL_IDS,
} from "./models.js";
22 changes: 22 additions & 0 deletions packages/zen/src/models.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { describe, expect, test } from "bun:test";

import { resolveZenEndpoint } from "./endpoint.js";
import { isKnownZenModel, protocolForZenModel } from "./models.js";

describe("protocolForZenModel", () => {
test("maps grok-4.7 to responses like grok-4.6", () => {
expect(protocolForZenModel("grok-4.7")).toBe("responses");
expect(protocolForZenModel("grok-4.6")).toBe("responses");
expect(isKnownZenModel("grok-4.7")).toBe(true);
expect(isKnownZenModel("grok-4.6")).toBe(true);
});
});

describe("resolveZenEndpoint", () => {
test("routes grok-4.7 the same as grok-4.6", () => {
expect(resolveZenEndpoint("grok-4.7")).toEqual(
resolveZenEndpoint("grok-4.6"),
);
expect(resolveZenEndpoint("grok-4.7").adapter).toBe("openai-responses");
});
});
1 change: 1 addition & 0 deletions packages/zen/src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ const ZEN_MODELS: readonly ZenModel[] = [
{ id: "gpt-5", protocol: "responses" },
{ id: "gpt-5-codex", protocol: "responses" },
{ id: "gpt-5-nano", protocol: "responses" },
{ id: "grok-4.7", protocol: "responses" },
{ id: "grok-4.6", protocol: "responses" },
{ id: "grok-4.5", protocol: "responses" },
{ id: "grok-build-0.1", protocol: "responses" },
Expand Down
35 changes: 29 additions & 6 deletions src/auth/xai/constants.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,38 @@
import { XAI_REDIRECT_URI } from "@corbits/xai-provider";

// Local alias over the vendor fallback list (CL-5691): XAI_DEFAULT_MODELS
// backs live xAI catalog calls and stays in the vendor package — this alias
// only renames the proxy URL. identity-divergence.test.ts pins the agreement.
export {
XAI_DEFAULT_MODELS,
import {
XAI_DEFAULT_MODELS as VENDOR_XAI_DEFAULT_MODELS,
XAI_OAUTH_PROXY_BASE_URL as XAI_BASE_URL,
XAI_REDIRECT_URI,
XAI_REFRESH_SKEW_MS,
} from "@corbits/xai-provider";

export { XAI_BASE_URL, XAI_REDIRECT_URI, XAI_REFRESH_SKEW_MS };

const GROK_47 = "grok-4.7";

// Local extension over the vendor fallback list (CL-5691): grok-4.7 rides the
// same OAuth proxy as the older Grok generations but the vendored catalog has
// not caught up yet. Insert after the second vendor entry and skip when the
// vendor list already includes it so a vendor bump cannot duplicate. The
// default stays the first vendor entry.
export function extendVendorXaiDefaultModels(
vendorModels: readonly string[],
): readonly [string, ...string[]] {
if (vendorModels.some((id) => id === GROK_47)) {
const [first, ...rest] = vendorModels;
if (first === undefined) return [GROK_47];
return [first, ...rest];
}
const [first, second, ...rest] = vendorModels;
if (first === undefined) return [GROK_47];
if (second === undefined) return [first, GROK_47];
return [first, second, GROK_47, ...rest];
}

export const XAI_DEFAULT_MODELS = extendVendorXaiDefaultModels(
VENDOR_XAI_DEFAULT_MODELS,
);

const xaiRedirect = new URL(XAI_REDIRECT_URI);
export const XAI_CALLBACK_PORT = Number(xaiRedirect.port);
export const XAI_CALLBACK_PATH = xaiRedirect.pathname;
Expand Down
62 changes: 59 additions & 3 deletions src/config/xai-providers.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { describe, expect, test } from "bun:test";

import { XAI_BASE_URL, XAI_DEFAULT_MODELS } from "../auth/xai/constants.js";
import {
extendVendorXaiDefaultModels,
XAI_BASE_URL,
XAI_DEFAULT_MODELS,
} from "../auth/xai/constants.js";
import type { XaiProfile } from "../auth/xai/store.js";
import { providerCatalogToSettings } from "./index.js";
import {
Expand All @@ -12,12 +16,64 @@ import {
} from "./xai-providers.js";

describe("xAI OAuth provider projection", () => {
test("default models include Grok 4.6 while defaulting to the CLI coding model", () => {
test("default models include Grok 4.7 while defaulting to the CLI coding model", () => {
const models: string[] = [...XAI_DEFAULT_MODELS];
expect(models).toEqual(["grok-4.5", "grok-4.6", "grok-composer-2.5-fast"]);
expect(models).toEqual([
"grok-4.5",
"grok-4.6",
"grok-4.7",
"grok-composer-2.5-fast",
]);
expect(models.filter((m) => m === "grok-4.7")).toHaveLength(1);
expect(models[0]).toBe("grok-4.5");
});

test("skips grok-4.7 insert when the vendor list already includes it", () => {
const vendor = [
"grok-4.5",
"grok-4.6",
"grok-4.7",
"grok-composer-2.5-fast",
] as const;
const models = extendVendorXaiDefaultModels(vendor);
expect(models).toEqual(vendor);
expect(models.filter((m) => m === "grok-4.7")).toHaveLength(1);
});

test("skips grok-4.7 insert when the vendor list starts with it", () => {
const vendor = [
"grok-4.7",
"grok-4.5",
"grok-4.6",
"grok-composer-2.5-fast",
] as const;
const models = extendVendorXaiDefaultModels(vendor);
expect(models).toEqual(vendor);
expect(models.filter((m) => m === "grok-4.7")).toHaveLength(1);
});

test("skips grok-4.7 insert when the vendor list ends with it", () => {
const vendor = [
"grok-4.5",
"grok-4.6",
"grok-composer-2.5-fast",
"grok-4.7",
] as const;
const models = extendVendorXaiDefaultModels(vendor);
expect(models).toEqual(vendor);
expect(models.filter((m) => m === "grok-4.7")).toHaveLength(1);
});

test("inserts grok-4.7 after the second vendor model when absent", () => {
expect(
extendVendorXaiDefaultModels([
"grok-4.5",
"grok-4.6",
"grok-composer-2.5-fast",
]),
).toEqual(["grok-4.5", "grok-4.6", "grok-4.7", "grok-composer-2.5-fast"]);
});

const profile: XaiProfile = {
name: "work",
tokens: {
Expand Down
3 changes: 2 additions & 1 deletion src/provider/context-window.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ describe("contextWindowFor", () => {
expect(contextWindowFor("xai/thegreataxios:grok-4.5")).toBe(500_000);
});

it("falls back to a 500k grok-4.5/4.6 heuristic window when the registry has no entry", () => {
it("falls back to a 500k grok-4.5/4.6/4.7 heuristic window when the registry has no entry", () => {
setModelContextWindows(undefined);
expect(contextWindowFor("xai/thegreataxios:grok-4.5")).toBe(500_000);
expect(contextWindowFor("xai/thegreataxios:grok-4.6")).toBe(500_000);
expect(contextWindowFor("xai/thegreataxios:grok-4.7")).toBe(500_000);
expect(contextWindowFor("xai/thegreataxios:grok-4.3")).toBe(1_000_000);
});

Expand Down
7 changes: 6 additions & 1 deletion src/provider/context-window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,12 @@ function heuristicWindow(model: string): number {
if (m.includes("glm-5.3")) return 1_000_000;
if (m.includes("glm")) return 200_000;
if (m.includes("o3") || m.includes("o4")) return 200_000;
if (m.includes("grok-4.6") || m.includes("grok-4.5")) return 500_000;
if (
m.includes("grok-4.7") ||
m.includes("grok-4.6") ||
m.includes("grok-4.5")
)
return 500_000;
if (m.includes("grok-4.3")) return 1_000_000;
if (m.includes("grok") || m.includes("xai")) return 256_000;
return DEFAULT_CONTEXT_WINDOW;
Expand Down
7 changes: 7 additions & 0 deletions src/provider/identity-divergence.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
ZEN_DEFAULT_MODEL,
ZEN_MODEL_IDS,
isKnownZenModel,
protocolForZenModel,
} from "../../packages/zen/src/index.js";
import {
CODEX_BASE_URL,
Expand Down Expand Up @@ -131,6 +132,12 @@ describe("provider identity divergence", () => {
}
});

test("Zen protocol map pins grok-4.7 to responses like grok-4.6", () => {
expect(protocolForZenModel("grok-4.7")).toBe("responses");
expect(protocolForZenModel("grok-4.6")).toBe("responses");
expect(isKnownZenModel("grok-4.7")).toBe(true);
});

test("OpenCode Go registry entry tracks the packaged seed catalog", () => {
const go = firstClassProviderById("opencode-go");
expect([...(go?.models ?? [])]).toEqual([...OPENCODE_GO_MODEL_IDS]);
Expand Down
21 changes: 21 additions & 0 deletions src/provider/reasoning-effort.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,15 @@ describe("supportedEfforts", () => {
]);
});

test("grok-4.7 includes xhigh", () => {
expect(supportedEfforts("grok-4.7")).toEqual([
"low",
"medium",
"high",
"xhigh",
]);
});

test("grok-4.5 stays on the unknown-model subset without xhigh", () => {
expect(supportedEfforts("grok-4.5")).toEqual(["low", "medium", "high"]);
});
Expand Down Expand Up @@ -223,6 +232,11 @@ describe("validateEffort", () => {
expect(validateEffort("grok-4.5", "xhigh").ok).toBe(false);
});

test("accepts xhigh on grok-4.7", () => {
expect(validateEffort("grok-4.7", "xhigh")).toEqual({ ok: true });
expect(validateEffort("grok-4.7", "minimal").ok).toBe(false);
});

test("accepts minimal on Muse Spark and rejects none", () => {
expect(validateEffort("muse-spark-1.3-contributor", "minimal")).toEqual({
ok: true,
Expand Down Expand Up @@ -295,6 +309,12 @@ describe("cycleReasoningEffort", () => {
expect(cycleReasoningEffort("grok-4.6", "high")).toBe("xhigh");
expect(cycleReasoningEffort("grok-4.6", "xhigh")).toBe("low");
});

test("wraps high to xhigh to low on grok-4.7", () => {
expect(cycleReasoningEffort("grok-4.7", undefined)).toBe("xhigh");
expect(cycleReasoningEffort("grok-4.7", "high")).toBe("xhigh");
expect(cycleReasoningEffort("grok-4.7", "xhigh")).toBe("low");
});
});

describe("reasoning capability gate", () => {
Expand Down Expand Up @@ -504,6 +524,7 @@ describe("defaultEffortForModel", () => {
afterEach(() => setModelReasoningCapabilities({}));

test("grok family defaults to high", () => {
expect(defaultEffortForModel("grok-4.7")).toBe("high");
expect(defaultEffortForModel("grok-4.6")).toBe("high");
expect(defaultEffortForModel("grok-4.5")).toBe("high");
});
Expand Down
10 changes: 5 additions & 5 deletions src/provider/reasoning-effort.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,10 @@ function isMuseSparkModel(model: string): boolean {
return /^muse-spark/i.test(model.trim());
}

// grok-4.6 accepts xhigh — the same ladder as CODEX_EFFORTS above;
// grok-4.6 and grok-4.7 accept xhigh — the same ladder as CODEX_EFFORTS above;
// grok-4.5 and composer stay on the unknown-model subset.
const GROK_46_EFFORTS: readonly ReasoningEffort[] = CODEX_EFFORTS;
const GROK_46_MODELS: readonly string[] = ["grok-4.6"];
const GROK_46_AND_47_EFFORTS: readonly ReasoningEffort[] = CODEX_EFFORTS;
const GROK_46_AND_47_MODELS: readonly string[] = ["grok-4.6", "grok-4.7"];

// GPT-6 Astra accepts low through max on both the OpenAI API and Codex surfaces.
// Not ultra (gpt-5.6 Codex-only), not minimal, not none.
Expand Down Expand Up @@ -153,8 +153,8 @@ export function supportedEfforts(
if (isKnownOpenAIReasoningModel(model)) {
return [...DEFAULT_EFFORTS];
}
if (GROK_46_MODELS.includes(model)) {
return [...GROK_46_EFFORTS];
if (GROK_46_AND_47_MODELS.includes(model)) {
return [...GROK_46_AND_47_EFFORTS];
}
if (GLM_53_MODELS.includes(model)) {
return [...GLM_53_EFFORTS];
Expand Down
3 changes: 3 additions & 0 deletions src/subagent/provider-family.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ describe("detectModelFamily", () => {
expect(
detectModelFamily({ providerName: "xai/default", model: "grok-4.6" }),
).toBe("grok");
expect(
detectModelFamily({ providerName: "xai/default", model: "grok-4.7" }),
).toBe("grok");
expect(
detectModelFamily({ providerName: "moonshot", model: "kimi-k2" }),
).toBe("kimi");
Expand Down
Loading