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
127 changes: 127 additions & 0 deletions grid-defaults.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
{
"provider": "thegrid",
"bases": {
"inference": "https://api.thegrid.ai/v1",
"messages": "https://messages-beta.api.thegrid.ai"
},
"apiKeyEnv": "THEGRID_API_KEY",
"models": [
{
"id": "agent-max",
"maxTokens": 128000,
"contextWindow": 1000000,
"input": [
"text"
],
"costPer1mIn": 1.5885,
"costPer1mOut": 1.5885
},
{
"id": "agent-prime",
"maxTokens": 30000,
"contextWindow": 196608,
"input": [
"text"
],
"costPer1mIn": 0.1422,
"costPer1mOut": 0.1422
},
{
"id": "agent-standard",
"maxTokens": 16000,
"contextWindow": 128000,
"input": [
"text"
],
"costPer1mIn": 0.02,
"costPer1mOut": 0.02
},
{
"id": "code-max",
"maxTokens": 128000,
"contextWindow": 1000000,
"input": [
"text"
],
"costPer1mIn": 1.7553,
"costPer1mOut": 1.7553
},
{
"id": "code-prime",
"maxTokens": 30000,
"contextWindow": 196608,
"input": [
"text"
],
"costPer1mIn": 0.1493,
"costPer1mOut": 0.1493
},
{
"id": "code-standard",
"maxTokens": 16000,
"contextWindow": 128000,
"input": [
"text"
],
"costPer1mIn": 0.0288,
"costPer1mOut": 0.0288
},
{
"id": "text-max",
"maxTokens": 128000,
"contextWindow": 1000000,
"input": [
"text"
],
"costPer1mIn": 1.68,
"costPer1mOut": 1.68
},
{
"id": "text-prime",
"maxTokens": 30000,
"contextWindow": 196608,
"input": [
"text"
],
"costPer1mIn": 0.1017,
"costPer1mOut": 0.1017
},
{
"id": "text-standard",
"maxTokens": 16000,
"contextWindow": 128000,
"input": [
"text"
],
"costPer1mIn": 0.03,
"costPer1mOut": 0.03
}
],
"tools": [
{
"slug": "opencode",
"surface": "openai-completions",
"baseUrl": "https://api.thegrid.ai/v1"
},
{
"slug": "cline",
"surface": "openai-completions",
"baseUrl": "https://api.thegrid.ai/v1"
},
{
"slug": "kilocode",
"surface": "openai-completions",
"baseUrl": "https://api.thegrid.ai/v1"
},
{
"slug": "aider",
"surface": "openai-completions",
"baseUrl": "https://api.thegrid.ai/v1"
},
{
"slug": "hermes",
"surface": "anthropic-messages",
"baseUrl": "https://messages-beta.api.thegrid.ai"
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ describe("grid doc parity (configure uploads)", () => {
expect(uploaded).toContain("key_env: THEGRID_API_KEY");
expect(uploaded).toContain("provider: custom:thegrid");
expect(uploaded).toContain("default: agent-prime");
expect(uploaded).toContain("context_length: 128000");
expect(uploaded).toContain("context_length: 196608");
expect(uploaded).toContain("supports_vision: false");
expect(uploaded).toContain("model_aliases:");
expect(uploaded).toContain("agent_standard:");
Expand All @@ -214,7 +214,7 @@ describe("grid doc parity (configure uploads)", () => {
expect(uploaded).toContain('"thegrid"');
expect(uploaded).toContain("api.thegrid.ai");
expect(uploaded).toContain("code-prime");
expect(uploaded).toContain('"context": 128000');
expect(uploaded).toContain('"context": 196608');
expect(uploaded).toContain('"modalities"');
expect(uploaded).toContain('"text"');
});
Expand All @@ -224,7 +224,7 @@ describe("grid doc parity (configure uploads)", () => {
const { agents: localAgents } = createCloudAgents(runner);
await localAgents.pi.configure!("test-key");
const uploaded = [...runner.uploads.values()].join("\n");
expect(uploaded).toContain('"contextWindow": 128000');
expect(uploaded).toContain('"contextWindow": 196608');
expect(uploaded).toContain('"input"');
expect(uploaded).toContain('"text"');
expect(uploaded).toContain("code-prime");
Expand Down
62 changes: 62 additions & 0 deletions packages/cli/src/__tests__/grid-defaults-parity.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* grid-defaults.json (repo root) is published by the Grid registry generator and is the source
* of truth for instrument specs. The CLI ships a fallback mirror for offline/bundled runs;
* these tests pin the mirror to the committed file so the two can never drift apart, and cover
* the pure spec builder the runtime refresh uses.
*/

import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "bun:test";
import { resolveGridInstrumentModelSpec, specsFromGridDefaults } from "../shared/grid-instruments.js";
import { OPENCLAW_GRID_MODEL_MAX_TOKENS } from "../shared/vendor-routing.js";

const GRID_DEFAULTS_PATH = join(import.meta.dir, "../../../..", "grid-defaults.json");
const gridDefaults = JSON.parse(readFileSync(GRID_DEFAULTS_PATH, "utf-8")) as {
models: Array<{ id: string; maxTokens: number; contextWindow: number; input: string[] }>;
};

describe("grid-defaults.json parity", () => {
it("has the registry-generator shape", () => {
expect(gridDefaults.models.length).toBeGreaterThan(0);
for (const model of gridDefaults.models) {
expect(model.contextWindow).toBeGreaterThan(0);
expect(model.maxTokens).toBeGreaterThan(0);
expect(model.maxTokens).toBeLessThanOrEqual(model.contextWindow);
expect(model.input.length).toBeGreaterThan(0);
}
});

it("fallback mirror matches the committed catalogue (update both together)", () => {
for (const model of gridDefaults.models) {
const spec = resolveGridInstrumentModelSpec(model.id);
expect(spec.contextWindow).toBe(model.contextWindow);
expect([...spec.input]).toEqual(model.input);
expect(spec.maxOutputTokens).toBe(Math.min(OPENCLAW_GRID_MODEL_MAX_TOKENS, model.maxTokens));
}
});

it("specsFromGridDefaults builds the same map the fallback mirrors", () => {
const specs = specsFromGridDefaults(gridDefaults);
expect(specs).not.toBeNull();
for (const model of gridDefaults.models) {
expect(specs?.[model.id]).toEqual({
contextWindow: model.contextWindow,
maxOutputTokens: Math.min(OPENCLAW_GRID_MODEL_MAX_TOKENS, model.maxTokens),
input: model.input as ("text" | "image")[],
});
}
});

it("specsFromGridDefaults rejects malformed bodies", () => {
expect(specsFromGridDefaults(null)).toBeNull();
expect(specsFromGridDefaults({})).toBeNull();
expect(specsFromGridDefaults({ models: [] })).toBeNull();
expect(
specsFromGridDefaults({ models: [{ id: "x", contextWindow: -1, maxTokens: 1, input: ["text"] }] }),
).toBeNull();
expect(
specsFromGridDefaults({ models: [{ id: "x", contextWindow: 10, maxTokens: 1, input: ["video"] }] }),
).toBeNull();
});
});
8 changes: 4 additions & 4 deletions packages/cli/src/__tests__/grid-instrument-model-spec.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import {
import { OPENCLAW_GRID_MODEL_MAX_TOKENS } from "../shared/vendor-routing.js";

describe("grid instrument model specs", () => {
it("uses Cortex catalogue context windows (not oversized defaults)", () => {
expect(resolveGridInstrumentModelSpec("agent-prime").contextWindow).toBe(128_000);
expect(resolveGridInstrumentModelSpec("code-prime").contextWindow).toBe(128_000);
expect(resolveGridInstrumentModelSpec("text-prime").contextWindow).toBe(128_000);
it("uses Grid catalogue context windows (grid-defaults.json, not oversized defaults)", () => {
expect(resolveGridInstrumentModelSpec("agent-prime").contextWindow).toBe(196_608);
expect(resolveGridInstrumentModelSpec("code-prime").contextWindow).toBe(196_608);
expect(resolveGridInstrumentModelSpec("text-prime").contextWindow).toBe(196_608);
expect(resolveGridInstrumentModelSpec("agent-max").contextWindow).toBe(1_000_000);
expect(resolveGridInstrumentModelSpec("code-max").contextWindow).toBe(1_000_000);
expect(resolveGridInstrumentModelSpec("text-max").contextWindow).toBe(1_000_000);
Expand Down
5 changes: 5 additions & 0 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import {
import { expandEqualsFlags, findUnknownFlag } from "./flags.js";
import { agentKeys, cloudKeys, getCacheAge, loadManifest } from "./manifest.js";
import { expandFastProvisionVariant, getFeatureFlag, initFeatureFlags } from "./shared/feature-flags.js";
import { refreshGridInstrumentSpecs } from "./shared/grid-instruments.js";
import { getInstallRefPath } from "./shared/paths.js";
import { asyncTryCatch, asyncTryCatchIf, isFileError, isNetworkError, tryCatch, tryCatchIf } from "./shared/result.js";
import { captureError, initTelemetry, setTelemetryContext } from "./shared/telemetry.js";
Expand Down Expand Up @@ -976,7 +977,11 @@ async function main(): Promise<void> {
const preOutputIdx = args.indexOf("--output");
const isJsonOutput = preOutputIdx !== -1 && args[preOutputIdx + 1] === "json";

// Refresh instrument specs from grid-defaults.json concurrently with the update check so
// provisioning uses live catalogue limits without adding startup latency. Never throws.
const specsRefresh = refreshGridInstrumentSpecs();
await checkForUpdates(isJsonOutput);
await specsRefresh;

const [prompt, filteredArgs] = await resolvePrompt(args);

Expand Down
Loading
Loading