chore: merge dev into v2 (#36144)

Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: usrnk1 <7547651+usrnk1@users.noreply.github.com>
Co-authored-by: James Long <longster@gmail.com>
Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com>
Co-authored-by: Jack <jack@anoma.ly>
Co-authored-by: opencode <opencode@sst.dev>
Co-authored-by: Frank <frank@anoma.ly>
Co-authored-by: Jay <53023+jayair@users.noreply.github.com>
Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com>
Co-authored-by: Aarav Sareen <96787824+arvsrn@users.noreply.github.com>
Co-authored-by: Julian Coy <julian@ex-machina.co>
Co-authored-by: Brendan Allan <git@brendonovich.dev>
Co-authored-by: Vladimir Glafirov <vglafirov@gitlab.com>
Co-authored-by: Adam <2363879+adamdotdevin@users.noreply.github.com>
Co-authored-by: Dustin Deus <deusdustin@gmail.com>
Co-authored-by: Kit Langton <kit.langton@gmail.com>
Co-authored-by: Simon Klee <hello@simonklee.dk>
Co-authored-by: Jay <air@live.ca>
Co-authored-by: David Hill <1879069+iamdavidhill@users.noreply.github.com>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
Co-authored-by: James Long <jlongster@users.noreply.github.com>
Co-authored-by: 冯基魁 <56265583+fengjikui@users.noreply.github.com>
Co-authored-by: Aiden Cline <rekram1-node@users.noreply.github.com>
This commit is contained in:
opencode-agent[bot]
2026-07-09 16:32:44 -05:00
committed by GitHub
co-authored by opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com> usrnk1 James Long opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com> Aiden Cline Brendan Allan Jack opencode Frank Jay Luke Parker Aarav Sareen Julian Coy Brendan Allan Vladimir Glafirov Adam Dustin Deus Kit Langton Simon Klee Jay David Hill Aiden Cline James Long 冯基魁 Aiden Cline
parent a72992e00f
commit a7746379d5
129 changed files with 3734 additions and 945 deletions
@@ -187,6 +187,60 @@ test("converts Copilot AIC token prices to USD per million tokens", async () =>
expect(models["ignored-non-chat-record"]).toBeUndefined()
})
test("uses zero cost when Copilot reports a zero billing batch size", async () => {
globalThis.fetch = mock(() =>
Promise.resolve(
new Response(
JSON.stringify({
data: [
{
model_picker_enabled: true,
id: "mercury-alpha",
name: "Mercury Alpha",
version: "mercury-alpha-2026-07-09",
billing: {
token_prices: {
batch_size: 0,
default: {
input_price: 0,
output_price: 0,
cache_price: 0,
},
},
},
capabilities: {
family: "mercury",
limits: {
max_context_window_tokens: 128000,
max_output_tokens: 16384,
max_prompt_tokens: 128000,
},
supports: {
streaming: true,
tool_calls: true,
},
},
},
],
}),
{ status: 200 },
),
),
) as unknown as typeof fetch
const model = (await CopilotModels.get("https://api.githubcopilot.com")).models["mercury-alpha"]
expect(model.cost).toEqual({
input: 0,
output: 0,
cache: {
read: 0,
write: 0,
},
})
expect(JSON.stringify(model)).not.toContain("null")
})
test("records Copilot advertised responses endpoint for non-GPT model IDs", async () => {
globalThis.fetch = mock(() =>
Promise.resolve(
@@ -24,6 +24,8 @@ import { testEffect } from "../lib/effect"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { ModelV2 } from "@opencode-ai/core/model"
type ModelsDevProvider = Parameters<typeof Provider.fromModelsDevProvider>[0]
const originalEnv = new Map<string, string | undefined>()
const rememberEnv = (k: string) => {
@@ -1386,8 +1388,7 @@ test("mode cost preserves over-200k pricing from base model", () => {
},
},
},
// @ts-expect-error dead V1 fixture uses the removed pre-normalized ModelsDev provider type.
} as unknown as ModelsDev.Provider
} as unknown as ModelsDevProvider
const model = Provider.fromModelsDevProvider(provider).models["gpt-5.4-fast"]
expect(model.cost.input).toEqual(5)
@@ -1416,8 +1417,7 @@ test("models.dev normalization fills required response fields", () => {
limit: { context: 1_050_000, input: 922_000, output: 128_000 },
},
},
// @ts-expect-error dead V1 fixture uses the removed pre-normalized ModelsDev provider type.
} as unknown as ModelsDev.Provider
} as unknown as ModelsDevProvider
const model = Provider.fromModelsDevProvider(provider).models["gpt-5.4"]
expect(model.api.url).toBe("")
@@ -1428,6 +1428,32 @@ test("models.dev normalization fills required response fields", () => {
expect(model.release_date).toBe("")
})
test("public provider info omits invalid models", () => {
const provider = Provider.fromModelsDevProvider({
id: "test",
name: "Test",
env: [],
models: {
valid: {
id: "valid",
name: "Valid",
cost: { input: 1, output: 1 },
limit: { context: 128_000, output: 16_000 },
},
},
} as unknown as ModelsDevProvider)
provider.models.invalid = {
...provider.models.valid,
id: ModelV2.ID.make("invalid"),
cost: { ...provider.models.valid.cost, input: Number.NaN },
}
const result = Provider.toPublicInfo(provider)
expect(result.models.valid).toBeDefined()
expect(result.models.invalid).toBeUndefined()
})
it.instance("model variants are generated for reasoning models", () =>
Effect.gen(function* () {
yield* set("ANTHROPIC_API_KEY", "test-api-key")
@@ -73,7 +73,7 @@ describe("ProviderTransform.options - setCacheKey", () => {
expect(result.promptCacheKey).toBeUndefined()
})
test("should set promptCacheKey for openai provider regardless of setCacheKey", () => {
test("should set promptCacheKey for openai provider by default", () => {
const openaiModel = {
...mockModel,
providerID: "openai",
@@ -87,6 +87,56 @@ describe("ProviderTransform.options - setCacheKey", () => {
expect(result.promptCacheKey).toBe(sessionID)
})
test("should not set promptCacheKey for openai when explicitly disabled", () => {
const openaiModel = {
...mockModel,
providerID: "openai",
api: {
id: "gpt-4",
url: "https://api.openai.com",
npm: "@ai-sdk/openai",
},
}
const result = ProviderTransform.options({
model: openaiModel,
sessionID,
providerOptions: { setCacheKey: false },
})
expect(result.promptCacheKey).toBeUndefined()
})
test("should set promptCacheKey for the xAI SDK by default regardless of provider ID", () => {
const xaiModel = {
...mockModel,
providerID: "custom-xai",
api: {
id: "grok-4",
url: "https://api.x.ai",
npm: "@ai-sdk/xai",
},
}
const result = ProviderTransform.options({ model: xaiModel, sessionID, providerOptions: {} })
expect(result.promptCacheKey).toBe(sessionID)
})
test("should not set promptCacheKey for the xAI SDK when explicitly disabled", () => {
const xaiModel = {
...mockModel,
providerID: "xai",
api: {
id: "grok-4",
url: "https://api.x.ai",
npm: "@ai-sdk/xai",
},
}
const result = ProviderTransform.options({
model: xaiModel,
sessionID,
providerOptions: { setCacheKey: false },
})
expect(result.promptCacheKey).toBeUndefined()
})
test("should set store=false for openai provider", () => {
const openaiModel = {
...mockModel,
@@ -3289,7 +3339,7 @@ describe("ProviderTransform.variants", () => {
expect(Object.keys(result)).toEqual(["low", "medium", "high"])
})
test("grok-4 returns empty object", () => {
test("grok-4 uses the provider's standard efforts", () => {
const model = createMockModel({
id: "openrouter/grok-4",
providerID: "openrouter",
@@ -3300,7 +3350,8 @@ describe("ProviderTransform.variants", () => {
},
})
const result = ProviderTransform.variants(model)
expect(result).toEqual({})
expect(Object.keys(result)).toEqual(["low", "medium", "high"])
expect(result.medium).toEqual({ reasoning: { effort: "medium" } })
})
test("grok-3-mini returns low and high with reasoning", () => {
@@ -3716,18 +3767,19 @@ describe("ProviderTransform.variants", () => {
})
describe("@ai-sdk/xai", () => {
test("grok-3 returns empty object", () => {
test("grok-4.5 uses standard reasoning efforts", () => {
const model = createMockModel({
id: "xai/grok-3",
id: "xai/grok-4.5",
providerID: "xai",
api: {
id: "grok-3",
id: "grok-4.5",
url: "https://api.x.ai",
npm: "@ai-sdk/xai",
},
})
const result = ProviderTransform.variants(model)
expect(result).toEqual({})
expect(Object.keys(result)).toEqual(["low", "medium", "high"])
expect(result.medium).toEqual({ reasoningEffort: "medium" })
})
test("grok-3-mini returns low and high with reasoningEffort", () => {
@@ -1,10 +1,11 @@
import { describe, expect } from "bun:test"
import { describe, expect, test } from "bun:test"
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
import { Effect, Layer } from "effect"
import type { Agent } from "../../src/agent/agent"
import { NamedError } from "@opencode-ai/core/util/error"
import { Skill } from "../../src/skill"
import { Permission } from "../../src/permission"
import type { Provider } from "../../src/provider/provider"
import { SystemPrompt } from "../../src/session/system"
import { MCP } from "../../src/mcp"
import { testEffect } from "../lib/effect"
@@ -83,6 +84,12 @@ const it = testEffect(
)
describe("session.system", () => {
test("selects the Meta prompt for Muse Spark model IDs", () => {
expect(SystemPrompt.provider({ api: { id: "meta/muse-spark-preview" } } as Provider.Model)[0]).toContain(
"Meta Muse Spark",
)
})
it.effect("skills output is sorted by name and stable across calls", () =>
Effect.gen(function* () {
const prompt = yield* SystemPrompt.Service