From 8571a922dbb3d8d72f6b743607fafdd82954ac91 Mon Sep 17 00:00:00 2001 From: Matthew Feroz <136640686+MatthewFeroz@users.noreply.github.com> Date: Wed, 12 Aug 2026 11:31:19 -0400 Subject: [PATCH 1/6] fix(provider): add Merge Gateway reasoning variants (#41867) --- packages/opencode/src/provider/transform.ts | 3 +++ .../opencode/test/provider/provider.test.ts | 27 +++++++++++++++++++ .../opencode/test/provider/transform.test.ts | 20 ++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 4a2738e87..fdd03d520 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -84,6 +84,8 @@ function sdkKey(npm: string): string | undefined { return "gateway" case "@openrouter/ai-sdk-provider": return "openrouter" + case "merge-gateway-ai-sdk-provider": + return "mergeGateway" case "ai-gateway-provider": // ai-gateway-provider/unified wraps createOpenAICompatible({ name: "Unified" }), // and @ai-sdk/openai-compatible parses compatibleOptions from one of @@ -1772,6 +1774,7 @@ function reasoningEffort(model: Provider.Model, effort: string) { case "@ai-sdk/togetherai": case "venice-ai-sdk-provider": case "ai-gateway-provider": + case "merge-gateway-ai-sdk-provider": return { reasoningEffort: effort } case "@ai-sdk/cohere": case "@ai-sdk/perplexity": diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts index 44e82c7e1..df23a5c49 100644 --- a/packages/opencode/test/provider/provider.test.ts +++ b/packages/opencode/test/provider/provider.test.ts @@ -1548,6 +1548,33 @@ test("models.dev reasoning options replace generated variants and unsupported to expect(models["gemini-3-pro-fast"].variants).toEqual(models.override.variants) }) +test("MERGE Gateway exposes declared effort variants without model-specific handling", () => { + const provider = { + id: "merge-gateway", + name: "MERGE Gateway", + env: ["MERGE_GATEWAY_API_KEY"], + npm: "merge-gateway-ai-sdk-provider", + models: { + "openai/gpt-5.6-sol": { + id: "openai/gpt-5.6-sol", + name: "GPT-5.6 Sol", + reasoning: true, + reasoning_options: [{ type: "effort", values: ["none", "low", "medium", "high", "xhigh", "max"] }], + limit: { context: 128_000, output: 64_000 }, + }, + }, + } as unknown as ModelsDev.Provider + + expect(Provider.fromModelsDevProvider(provider).models["openai/gpt-5.6-sol"].variants).toEqual({ + none: { reasoningEffort: "none" }, + low: { reasoningEffort: "low" }, + medium: { reasoningEffort: "medium" }, + high: { reasoningEffort: "high" }, + xhigh: { reasoningEffort: "xhigh" }, + max: { reasoningEffort: "max" }, + }) +}) + test("public provider info omits invalid models", () => { const provider = Provider.fromModelsDevProvider({ id: "test", diff --git a/packages/opencode/test/provider/transform.test.ts b/packages/opencode/test/provider/transform.test.ts index d1e437642..701658987 100644 --- a/packages/opencode/test/provider/transform.test.ts +++ b/packages/opencode/test/provider/transform.test.ts @@ -3370,6 +3370,7 @@ describe("ProviderTransform.reasoningVariants", () => { ["@ai-sdk/togetherai", { reasoningEffort: "high" }], ["venice-ai-sdk-provider", { reasoningEffort: "high" }], ["ai-gateway-provider", { reasoningEffort: "high" }], + ["merge-gateway-ai-sdk-provider", { reasoningEffort: "high" }], ["@ai-sdk/amazon-bedrock", { reasoningConfig: { type: "enabled", maxReasoningEffort: "high" } }], ])("converts effort for %s", (npm, expected, ...args) => { const id = args[0] as string | undefined @@ -5555,6 +5556,25 @@ describe("ProviderTransform.providerOptions - ai-gateway-provider", () => { }) }) +describe("ProviderTransform.providerOptions - merge-gateway-ai-sdk-provider", () => { + const model = { + id: "merge-gateway/openai/gpt-5.6-sol", + providerID: "merge-gateway", + api: { + id: "openai/gpt-5.6-sol", + url: "https://api-gateway.merge.dev/v1/ai-sdk", + npm: "merge-gateway-ai-sdk-provider", + }, + capabilities: { reasoning: true }, + } as any + + test("routes normalized effort under the adapter's mergeGateway key", () => { + expect(ProviderTransform.providerOptions(model, { reasoningEffort: "high" })).toEqual({ + mergeGateway: { reasoningEffort: "high" }, + }) + }) +}) + describe("ProviderTransform.options - kimi family adaptive thinking", () => { const createModel = (overrides: Record = {}) => ({ From ca3df21b7f8c2fa0adc07fdf3b7f33f29f5e1385 Mon Sep 17 00:00:00 2001 From: SKY ZHAO Date: Wed, 12 Aug 2026 23:31:47 +0800 Subject: [PATCH 2/6] docs: fix broken DigitalOcean and Daytona links (#42048) Co-authored-by: skyzhao1223 --- packages/web/src/content/docs/ecosystem.mdx | 2 +- packages/web/src/content/docs/providers.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/web/src/content/docs/ecosystem.mdx b/packages/web/src/content/docs/ecosystem.mdx index ce4f3100a..6c13b3004 100644 --- a/packages/web/src/content/docs/ecosystem.mdx +++ b/packages/web/src/content/docs/ecosystem.mdx @@ -17,7 +17,7 @@ You can also check out [awesome-opencode](https://github.com/awesome-opencode/aw | Name | Description | | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | -| [opencode-daytona](https://github.com/daytonaio/daytona/tree/main/libs/opencode-plugin) | Automatically run OpenCode sessions in isolated Daytona sandboxes with git sync and live previews | +| [opencode-daytona](https://github.com/daytona/integrations/tree/main/packages/opencode-plugin) | Automatically run OpenCode sessions in isolated Daytona sandboxes with git sync and live previews | | [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | Automatically inject Helicone session headers for request grouping | | [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | Auto-inject TypeScript/Svelte types into file reads with lookup tools | | [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | Use your ChatGPT Plus/Pro subscription instead of API credits | diff --git a/packages/web/src/content/docs/providers.mdx b/packages/web/src/content/docs/providers.mdx index a5a17de3a..ce40ce5a0 100644 --- a/packages/web/src/content/docs/providers.mdx +++ b/packages/web/src/content/docs/providers.mdx @@ -759,7 +759,7 @@ Cloudflare Workers AI lets you run AI models on Cloudflare's global network dire ### DigitalOcean -DigitalOcean's [Inference Engine](https://docs.digitalocean.com/products/inference/) provides access to open models like GPT-OSS, Llama, Qwen, and DeepSeek, plus custom [Inference Routers](https://docs.digitalocean.com/products/genai-platform/concepts/inference-routers/) that route each request to the cheapest, fastest, or best-fit model for a task. +DigitalOcean's [Inference Engine](https://docs.digitalocean.com/products/inference/) provides access to open models like GPT-OSS, Llama, Qwen, and DeepSeek, plus custom [Inference Routers](https://docs.digitalocean.com/products/inference/how-to/use-inference-router/) that route each request to the cheapest, fastest, or best-fit model for a task. OpenCode supports two authentication methods: From 959c8bd4981fe838df102ddb7a7974e3117e92c6 Mon Sep 17 00:00:00 2001 From: SKY ZHAO Date: Wed, 12 Aug 2026 23:32:23 +0800 Subject: [PATCH 3/6] docs: fix provider display name and PAT typos (#42034) Co-authored-by: skyzhao1223 --- packages/web/src/content/docs/github.mdx | 2 +- packages/web/src/content/docs/providers.mdx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/web/src/content/docs/github.mdx b/packages/web/src/content/docs/github.mdx index a31fe1e7b..e940b616b 100644 --- a/packages/web/src/content/docs/github.mdx +++ b/packages/web/src/content/docs/github.mdx @@ -97,7 +97,7 @@ Or you can set it up manually. issues: write ``` - You can also use a [personal access tokens](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens)(PAT) if preferred. + You can also use a [personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens)(PAT) if preferred. --- diff --git a/packages/web/src/content/docs/providers.mdx b/packages/web/src/content/docs/providers.mdx index ce40ce5a0..1a5d0fd23 100644 --- a/packages/web/src/content/docs/providers.mdx +++ b/packages/web/src/content/docs/providers.mdx @@ -2487,7 +2487,7 @@ You can use any OpenAI-compatible provider with opencode. Most modern AI provide "provider": { "myprovider": { "npm": "@ai-sdk/openai-compatible", - "name": "My AI ProviderDisplay Name", + "name": "My AI Provider Display Name", "options": { "baseURL": "https://api.myprovider.com/v1" }, @@ -2525,7 +2525,7 @@ Here's an example setting the `apiKey`, `headers`, and model `limit` options. "provider": { "myprovider": { "npm": "@ai-sdk/openai-compatible", - "name": "My AI ProviderDisplay Name", + "name": "My AI Provider Display Name", "options": { "baseURL": "https://api.myprovider.com/v1", "apiKey": "{env:ANTHROPIC_API_KEY}", From 7e0353cca93e4fc1e1f93cb58ea51603fbf83cd9 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Wed, 12 Aug 2026 11:36:23 -0500 Subject: [PATCH 4/6] fix(stats): correct r2 daily totals --- .../stats/core/src/domain/inference.test.ts | 38 +++++++++++++++++++ packages/stats/core/src/domain/inference.ts | 33 +++++++++------- 2 files changed, 58 insertions(+), 13 deletions(-) diff --git a/packages/stats/core/src/domain/inference.test.ts b/packages/stats/core/src/domain/inference.test.ts index f58e7deab..858d2ab7f 100644 --- a/packages/stats/core/src/domain/inference.test.ts +++ b/packages/stats/core/src/domain/inference.test.ts @@ -103,6 +103,44 @@ describe("inference stat normalization", () => { expect(queries[1]).toContain("'geo_model' ELSE 'geo'") expect(queries[1]).toContain("0 AS sessions") }) + + test("aligns periods to UTC calendar boundaries", () => { + const queries = buildStatsQueries( + new Date("2026-06-17T15:56:00.000Z"), + new Date("2026-06-19T15:56:00.000Z"), + { + namespace: "inference", + table: "generation", + dataset: "zen", + }, + ) + + expect(queries).toHaveLength(8) + expect(queries[0]).toContain("'2026-W25' AS period_key") + expect(queries[0]).toContain("started_at >= '2026-06-15T00:00:00.000Z'") + expect(queries[2]).toContain("'2026-06-17' AS period_key") + expect(queries[2]).toContain("started_at >= '2026-06-17T00:00:00.000Z'") + expect(queries[2]).toContain("started_at < '2026-06-18T00:00:00.000Z'") + expect(queries[6]).toContain("'2026-06-19' AS period_key") + expect(queries[6]).toContain("started_at < '2026-06-19T15:56:00.000Z'") + }) + + test("uses an exclusive live and legacy source handoff", () => { + const [query] = buildStatsQueries( + new Date("2026-08-11T00:00:00.000Z"), + new Date("2026-08-12T00:00:00.000Z"), + { + namespace: "inference", + table: "generation", + dataset: "zen", + }, + ) + + expect(query).toContain( + "(source = 'inference-legacy' AND started_at < '2026-08-11T10:57:48.186Z')", + ) + expect(query).toContain("(source = 'inference' AND started_at >= '2026-08-11T10:57:48.186Z')") + }) }) function aggregate(model: string, provider: string) { diff --git a/packages/stats/core/src/domain/inference.ts b/packages/stats/core/src/domain/inference.ts index ad2460530..178bfd75d 100644 --- a/packages/stats/core/src/domain/inference.ts +++ b/packages/stats/core/src/domain/inference.ts @@ -10,7 +10,14 @@ import { statProvider, } from "./model-normalization" import type { ProviderStatAggregate } from "./provider" -import { normalizeCountry, normalizeTier, type StatBaseAggregate } from "./stat" +import { + normalizeCountry, + normalizeTier, + periodKeyFor, + startOfIsoWeek, + startOfUtcDay, + type StatBaseAggregate, +} from "./stat" export type StatDimension = "model" | "provider" | "geo" | "geo_model" export type StatsQuerySource = { namespace: string; table: string; dataset: string } @@ -18,6 +25,10 @@ type StatsQueryFamily = "usage" | "geo" const DAY_MS = 86_400_000 const WEEK_MS = 7 * DAY_MS +// The typed production stream began before the legacy backfill's original end +// boundary. Use one exclusive handoff so the overlapping rows are never counted +// from both sources. +const LIVE_SOURCE_START = "2026-08-11T10:57:48.186Z" // R2 SQL limits result sets to 10,000 rows and does not support OFFSET. Two // queries per day/week keep each result bounded and avoid combining the costly @@ -123,6 +134,10 @@ WITH normalized AS ( FROM ${sourceTable} WHERE event_type = 'generation.completed' AND source IN ('inference', 'inference-legacy') + AND ( + (source = 'inference-legacy' AND started_at < ${sqlString(LIVE_SOURCE_START)}) + OR (source = 'inference' AND started_at >= ${sqlString(LIVE_SOURCE_START)}) + ) AND product = 'go' AND model_requested IS NOT NULL AND model_requested <> '' @@ -264,27 +279,19 @@ function sqlString(value: string) { function statPeriods(grain: "day" | "week", periodStart: Date, periodEnd: Date) { const interval = grain === "day" ? DAY_MS : WEEK_MS - const count = Math.max(0, Math.ceil((periodEnd.getTime() - periodStart.getTime()) / interval)) + const first = grain === "day" ? startOfUtcDay(periodStart) : startOfIsoWeek(periodStart) + const count = Math.max(0, Math.ceil((periodEnd.getTime() - first.getTime()) / interval)) return Array.from({ length: count }, (_, index) => { - const start = new Date(periodStart.getTime() + index * interval) + const start = new Date(first.getTime() + index * interval) return { grain, - key: grain === "day" ? start.toISOString().slice(0, 10) : isoWeekKey(start), + key: periodKeyFor(grain, start), start, end: new Date(Math.min(start.getTime() + interval, periodEnd.getTime())), } }) } -function isoWeekKey(date: Date) { - const thursday = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate())) - const day = thursday.getUTCDay() || 7 - thursday.setUTCDate(thursday.getUTCDate() + 4 - day) - const year = thursday.getUTCFullYear() - const week = Math.ceil((thursday.getTime() - Date.UTC(year, 0, 1) + DAY_MS) / WEEK_MS) - return `${year}-W${String(week).padStart(2, "0")}` -} - function statModelSql(model: string, providerModel: string) { return `COALESCE(NULLIF(regexp_replace(CASE WHEN lower(${model}) = 'big-pickle' THEN NULLIF(${providerModel}, '') From 284187ac55b9c38e3831143bed6c64053e8c85cc Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Wed, 12 Aug 2026 11:45:00 -0500 Subject: [PATCH 5/6] fix(ci): authenticate pulumi downloads --- .github/workflows/deploy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 18e6cf7ac..ef977a93b 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -35,6 +35,7 @@ jobs: - run: bun sst deploy --stage=${{ github.ref_name }} env: + GITHUB_TOKEN: ${{ github.token }} CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} PLANETSCALE_SERVICE_TOKEN_NAME: ${{ secrets.PLANETSCALE_SERVICE_TOKEN_NAME }} PLANETSCALE_SERVICE_TOKEN: ${{ secrets.PLANETSCALE_SERVICE_TOKEN }} From 6d3ae4d63d9b4116b97e4cf77516ebf1467e1c48 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 12 Aug 2026 16:47:08 +0000 Subject: [PATCH 6/6] chore: generate --- .../stats/core/src/domain/inference.test.ts | 32 +++++++------------ 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/packages/stats/core/src/domain/inference.test.ts b/packages/stats/core/src/domain/inference.test.ts index 858d2ab7f..57236d294 100644 --- a/packages/stats/core/src/domain/inference.test.ts +++ b/packages/stats/core/src/domain/inference.test.ts @@ -105,15 +105,11 @@ describe("inference stat normalization", () => { }) test("aligns periods to UTC calendar boundaries", () => { - const queries = buildStatsQueries( - new Date("2026-06-17T15:56:00.000Z"), - new Date("2026-06-19T15:56:00.000Z"), - { - namespace: "inference", - table: "generation", - dataset: "zen", - }, - ) + const queries = buildStatsQueries(new Date("2026-06-17T15:56:00.000Z"), new Date("2026-06-19T15:56:00.000Z"), { + namespace: "inference", + table: "generation", + dataset: "zen", + }) expect(queries).toHaveLength(8) expect(queries[0]).toContain("'2026-W25' AS period_key") @@ -126,19 +122,13 @@ describe("inference stat normalization", () => { }) test("uses an exclusive live and legacy source handoff", () => { - const [query] = buildStatsQueries( - new Date("2026-08-11T00:00:00.000Z"), - new Date("2026-08-12T00:00:00.000Z"), - { - namespace: "inference", - table: "generation", - dataset: "zen", - }, - ) + const [query] = buildStatsQueries(new Date("2026-08-11T00:00:00.000Z"), new Date("2026-08-12T00:00:00.000Z"), { + namespace: "inference", + table: "generation", + dataset: "zen", + }) - expect(query).toContain( - "(source = 'inference-legacy' AND started_at < '2026-08-11T10:57:48.186Z')", - ) + expect(query).toContain("(source = 'inference-legacy' AND started_at < '2026-08-11T10:57:48.186Z')") expect(query).toContain("(source = 'inference' AND started_at >= '2026-08-11T10:57:48.186Z')") }) })