Compare commits

..

6 Commits

Author SHA1 Message Date
Aiden Cline 3b29b7e3af fix(opencode): default xAI OAuth to device flow 2026-08-04 02:36:43 +00:00
opencode 7fe993879f sync release versions for v1.18.12 2026-08-04 00:55:26 +00:00
Frank def14d96ef Revert "fix slow queries" 2026-08-03 20:14:17 -04:00
opencode-agent[bot] b0b1149233 test(opencode): cover Azure completion reasoning (#40340)
Co-authored-by: Aiden Cline <rekram1-node@users.noreply.github.com>
2026-08-04 09:50:17 +10:00
Aarav Sareen 7becbbf711 feat(app): refine diff viewer (#40285)
Co-authored-by: LukeParkerDev <10430890+Hona@users.noreply.github.com>
2026-08-03 22:49:49 +00:00
Frederik 87481f2bb6 fix: gpt-5.5+ in combination with azure fails with reasoningEffort (#40265) 2026-08-03 17:42:30 -05:00
41 changed files with 291 additions and 244 deletions
+28 -28
View File
@@ -29,7 +29,7 @@
},
"packages/app": {
"name": "@opencode-ai/app",
"version": "1.18.11",
"version": "1.18.12",
"dependencies": {
"@corvu/drawer": "catalog:",
"@dnd-kit/abstract": "0.5.0",
@@ -96,7 +96,7 @@
},
"packages/cli": {
"name": "@opencode-ai/cli",
"version": "1.18.11",
"version": "1.18.12",
"bin": {
"lildax": "./bin/lildax.cjs",
},
@@ -144,7 +144,7 @@
},
"packages/codemode": {
"name": "@opencode-ai/codemode",
"version": "1.18.11",
"version": "1.18.12",
"dependencies": {
"acorn": "8.15.0",
"effect": "catalog:",
@@ -158,7 +158,7 @@
},
"packages/console/app": {
"name": "@opencode-ai/console-app",
"version": "1.18.11",
"version": "1.18.12",
"dependencies": {
"@cloudflare/vite-plugin": "1.15.2",
"@ibm/plex": "6.4.1",
@@ -194,7 +194,7 @@
},
"packages/console/core": {
"name": "@opencode-ai/console-core",
"version": "1.18.11",
"version": "1.18.12",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1",
@@ -221,7 +221,7 @@
},
"packages/console/function": {
"name": "@opencode-ai/console-function",
"version": "1.18.11",
"version": "1.18.12",
"dependencies": {
"@ai-sdk/anthropic": "3.0.82",
"@ai-sdk/openai": "3.0.48",
@@ -243,7 +243,7 @@
},
"packages/console/mail": {
"name": "@opencode-ai/console-mail",
"version": "1.18.11",
"version": "1.18.12",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
@@ -267,7 +267,7 @@
},
"packages/console/support": {
"name": "@opencode-ai/console-support",
"version": "1.18.11",
"version": "1.18.12",
"dependencies": {
"@cloudflare/vite-plugin": "1.15.2",
"@opencode-ai/console-core": "workspace:*",
@@ -287,7 +287,7 @@
},
"packages/core": {
"name": "@opencode-ai/core",
"version": "1.18.11",
"version": "1.18.12",
"bin": {
"opencode": "./bin/opencode",
},
@@ -381,7 +381,7 @@
},
"packages/desktop": {
"name": "@opencode-ai/desktop",
"version": "1.18.11",
"version": "1.18.12",
"dependencies": {
"@zip.js/zip.js": "2.7.62",
"drizzle-orm": "catalog:",
@@ -435,7 +435,7 @@
},
"packages/effect-drizzle-sqlite": {
"name": "@opencode-ai/effect-drizzle-sqlite",
"version": "1.18.11",
"version": "1.18.12",
"dependencies": {
"drizzle-orm": "catalog:",
"effect": "catalog:",
@@ -449,7 +449,7 @@
},
"packages/effect-sqlite-node": {
"name": "@opencode-ai/effect-sqlite-node",
"version": "1.18.11",
"version": "1.18.12",
"dependencies": {
"effect": "catalog:",
},
@@ -461,7 +461,7 @@
},
"packages/enterprise": {
"name": "@opencode-ai/enterprise",
"version": "1.18.11",
"version": "1.18.12",
"dependencies": {
"@hono/standard-validator": "catalog:",
"@opencode-ai/core": "workspace:*",
@@ -493,7 +493,7 @@
},
"packages/function": {
"name": "@opencode-ai/function",
"version": "1.18.11",
"version": "1.18.12",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "catalog:",
@@ -509,7 +509,7 @@
},
"packages/http-recorder": {
"name": "@opencode-ai/http-recorder",
"version": "1.18.11",
"version": "1.18.12",
"dependencies": {
"@effect/platform-node": "4.0.0-beta.83",
"@effect/platform-node-shared": "4.0.0-beta.83",
@@ -540,7 +540,7 @@
},
"packages/llm": {
"name": "@opencode-ai/llm",
"version": "1.18.11",
"version": "1.18.12",
"dependencies": {
"@opencode-ai/schema": "workspace:*",
"@smithy/eventstream-codec": "4.2.14",
@@ -559,7 +559,7 @@
},
"packages/opencode": {
"name": "opencode",
"version": "1.18.11",
"version": "1.18.12",
"bin": {
"opencode": "./bin/opencode",
},
@@ -690,7 +690,7 @@
},
"packages/plugin": {
"name": "@opencode-ai/plugin",
"version": "1.18.11",
"version": "1.18.12",
"dependencies": {
"@ai-sdk/provider": "3.0.8",
"@opencode-ai/sdk": "workspace:*",
@@ -766,7 +766,7 @@
},
"packages/sdk/js": {
"name": "@opencode-ai/sdk",
"version": "1.18.11",
"version": "1.18.12",
"dependencies": {
"cross-spawn": "catalog:",
},
@@ -781,7 +781,7 @@
},
"packages/server": {
"name": "@opencode-ai/server",
"version": "1.18.11",
"version": "1.18.12",
"dependencies": {
"@opencode-ai/core": "workspace:*",
"@opencode-ai/protocol": "workspace:*",
@@ -796,7 +796,7 @@
},
"packages/session-ui": {
"name": "@opencode-ai/session-ui",
"version": "1.18.11",
"version": "1.18.12",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/client": "file:../app/vendor/opencode-ai-client-1.17.13-v2.tgz",
@@ -840,7 +840,7 @@
},
"packages/slack": {
"name": "@opencode-ai/slack",
"version": "1.18.11",
"version": "1.18.12",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1",
@@ -853,7 +853,7 @@
},
"packages/stats/app": {
"name": "@opencode-ai/stats-app",
"version": "1.18.11",
"version": "1.18.12",
"dependencies": {
"@ibm/plex": "6.4.1",
"@kobalte/core": "catalog:",
@@ -887,7 +887,7 @@
},
"packages/stats/core": {
"name": "@opencode-ai/stats-core",
"version": "1.18.11",
"version": "1.18.12",
"dependencies": {
"@aws-sdk/client-athena": "3.933.0",
"@planetscale/database": "1.19.0",
@@ -906,7 +906,7 @@
},
"packages/stats/server": {
"name": "@opencode-ai/stats-server",
"version": "1.18.11",
"version": "1.18.12",
"dependencies": {
"@aws-sdk/client-firehose": "3.933.0",
"@effect/platform-node": "catalog:",
@@ -948,7 +948,7 @@
},
"packages/tui": {
"name": "@opencode-ai/tui",
"version": "1.18.11",
"version": "1.18.12",
"dependencies": {
"@opencode-ai/core": "workspace:*",
"@opencode-ai/plugin": "workspace:*",
@@ -975,7 +975,7 @@
},
"packages/ui": {
"name": "@opencode-ai/ui",
"version": "1.18.11",
"version": "1.18.12",
"dependencies": {
"@kobalte/core": "catalog:",
"@pierre/diffs": "catalog:",
@@ -1027,7 +1027,7 @@
},
"packages/web": {
"name": "@opencode-ai/web",
"version": "1.18.11",
"version": "1.18.12",
"dependencies": {
"@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/app",
"version": "1.18.11",
"version": "1.18.12",
"description": "",
"type": "module",
"exports": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/cli",
"version": "1.18.11",
"version": "1.18.12",
"type": "module",
"license": "MIT",
"bin": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/codemode",
"version": "1.18.11",
"version": "1.18.12",
"description": "Effect-native confined code execution over schema-described tools",
"private": true,
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-app",
"version": "1.18.11",
"version": "1.18.12",
"type": "module",
"license": "MIT",
"scripts": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/console-core",
"version": "1.18.11",
"version": "1.18.12",
"private": true,
"type": "module",
"license": "MIT",
+35 -13
View File
@@ -59,7 +59,30 @@ export namespace Referral {
const accountID = Actor.account()
const code = await ensureCode(workspaceID)
const rows = await Database.use(async (tx) => {
const [invites, inviteeReferral] = await Promise.all([
const [rewards, invites, inviteeReferral, inviteeRewards] = await Promise.all([
tx
.select({
referralID: ReferralRewardTable.referralID,
workspaceID: ReferralRewardTable.workspaceID,
referralWorkspaceID: ReferralTable.workspaceID,
inviteeEmail: AuthTable.subject,
amount: ReferralRewardTable.amount,
timeCreated: ReferralRewardTable.timeCreated,
timeApplied: ReferralRewardTable.timeApplied,
})
.from(ReferralRewardTable)
.innerJoin(ReferralTable, eq(ReferralTable.id, ReferralRewardTable.referralID))
.innerJoin(
AuthTable,
and(eq(AuthTable.accountID, ReferralTable.inviteeAccountID), eq(AuthTable.provider, "email")),
)
.where(
and(
eq(ReferralRewardTable.workspaceID, workspaceID),
isNull(ReferralRewardTable.timeDeleted),
isNull(ReferralTable.timeDeleted),
),
),
tx
.select({ id: ReferralTable.id, inviteeEmail: AuthTable.subject, timeCreated: ReferralTable.timeCreated })
.from(ReferralTable)
@@ -83,20 +106,19 @@ export namespace Referral {
.where(and(eq(ReferralTable.inviteeAccountID, accountID), isNull(ReferralTable.timeDeleted)))
.orderBy(asc(UserTable.timeCreated))
.then((rows) => rows.find((row) => row.inviterEmail) ?? rows[0]),
tx
.select({ referralID: ReferralRewardTable.referralID })
.from(ReferralRewardTable)
.innerJoin(ReferralTable, eq(ReferralTable.id, ReferralRewardTable.referralID))
.where(
and(
eq(ReferralTable.inviteeAccountID, accountID),
isNull(ReferralRewardTable.timeDeleted),
isNull(ReferralTable.timeDeleted),
),
),
])
// Hide reward history until the referral_id index can be deployed and this lookup restored.
const rewards: {
referralID: string
workspaceID: string
referralWorkspaceID: string
inviteeEmail: string
amount: number
timeCreated: Date
timeApplied: Date | null
}[] = []
// Hide pending invitee rewards until the referral_id index can be deployed and this lookup restored.
const inviteeRewards = inviteeReferral ? [{ referralID: inviteeReferral.id }] : []
return { inviteeReferral, inviteeRewards, invites, rewards }
})
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-function",
"version": "1.18.11",
"version": "1.18.12",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-mail",
"version": "1.18.11",
"version": "1.18.12",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-support",
"version": "1.18.11",
"version": "1.18.12",
"type": "module",
"license": "MIT",
"scripts": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.18.11",
"version": "1.18.12",
"name": "@opencode-ai/core",
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@opencode-ai/desktop",
"private": true,
"version": "1.18.11",
"version": "1.18.12",
"type": "module",
"license": "MIT",
"homepage": "https://opencode.ai",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.18.11",
"version": "1.18.12",
"name": "@opencode-ai/effect-drizzle-sqlite",
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.18.11",
"version": "1.18.12",
"name": "@opencode-ai/effect-sqlite-node",
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/enterprise",
"version": "1.18.11",
"version": "1.18.12",
"private": true,
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/function",
"version": "1.18.11",
"version": "1.18.12",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.18.11",
"version": "1.18.12",
"name": "@opencode-ai/http-recorder",
"description": "Record and replay Effect HTTP client traffic with deterministic cassettes",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.18.11",
"version": "1.18.12",
"name": "@opencode-ai/llm",
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.18.11",
"version": "1.18.12",
"name": "opencode",
"type": "module",
"license": "MIT",
+26 -34
View File
@@ -551,6 +551,32 @@ export async function XaiAuthPlugin(input: PluginInput, options: XaiAuthPluginOp
{
label: "xAI Grok OAuth (SuperGrok Subscription)",
type: "oauth",
authorize: async () => {
const device = await requestDeviceCode(options)
const browserUrl = device.verification_uri_complete ?? device.verification_uri
return {
url: browserUrl,
instructions: `Open ${device.verification_uri} on any device and enter code: ${device.user_code}`,
method: "auto" as const,
callback: async () => {
try {
const tokens = await pollDeviceCodeToken(device, options)
return {
type: "success" as const,
refresh: tokens.refresh_token,
access: tokens.access_token,
expires: Date.now() + (tokens.expires_in ?? 3600) * 1000,
}
} catch (err) {
return { type: "failed" as const }
}
},
}
},
},
{
label: "xAI Grok OAuth (Local Callback)",
type: "oauth",
authorize: async () => {
await startOAuthServer()
const pkce = await generatePKCE()
@@ -582,40 +608,6 @@ export async function XaiAuthPlugin(input: PluginInput, options: XaiAuthPluginOp
}
},
},
{
// RFC 8628 device-code flow. The CLI prints a verification URL
// and a short user_code that the user enters in a browser on
// any device. No loopback callback server runs on the CLI host,
// so this works on VPS / SSH / Docker / CI / WSL / any
// environment where 127.0.0.1:56121 isn't reachable from the
// user's browser. Defends the only attack surface (the polling
// loop) with the standard authorization_pending / slow_down
// backoff and a hard deadline from xAI's `expires_in`.
label: "xAI Grok OAuth (Headless / Remote / VPS)",
type: "oauth",
authorize: async () => {
const device = await requestDeviceCode(options)
const browserUrl = device.verification_uri_complete ?? device.verification_uri
return {
url: browserUrl,
instructions: `Open ${device.verification_uri} on any device and enter code: ${device.user_code}`,
method: "auto" as const,
callback: async () => {
try {
const tokens = await pollDeviceCodeToken(device, options)
return {
type: "success" as const,
refresh: tokens.refresh_token,
access: tokens.access_token,
expires: Date.now() + (tokens.expires_in ?? 3600) * 1000,
}
} catch (err) {
return { type: "failed" as const }
}
},
}
},
},
{
label: "Manually enter API Key",
type: "api",
+11 -26
View File
@@ -1270,6 +1270,17 @@ export function options(input: {
result["gateway"] = { caching: "auto" }
}
// Any gpt version above 5.4 in combination with azure does not support reasoningEffort
// so we should return early here.
const [, gptMajorVersion, gptMinorVersion] = input.model.api.id.match(/gpt-(\d+)\.(\d+)/) ?? []
const isGpt55OrNewer = Number(gptMajorVersion) > 5 || (Number(gptMajorVersion) === 5 && Number(gptMinorVersion) >= 5)
if (input.model.api.npm === "@ai-sdk/azure" && input.providerOptions?.useCompletionUrls) {
if (!isGpt55OrNewer) {
result["reasoningEffort"] = "medium"
}
return result
}
if (input.model.api.id.includes("gpt-5") && !input.model.api.id.includes("gpt-5-chat")) {
if (!input.model.api.id.includes("gpt-5-pro")) {
result["reasoningEffort"] = "medium"
@@ -1307,32 +1318,6 @@ export function options(input: {
return result
}
export function requestOptions(input: {
model: Provider.Model
providerOptions: Record<string, any>
options: Record<string, any>
}) {
if (
input.model.api.npm !== "@ai-sdk/azure" ||
!(input.model.options.useCompletionUrls ?? input.providerOptions.useCompletionUrls)
)
return input.options
const result = { ...input.options }
delete result.reasoningSummary
delete result.include
if (isGpt55OrNewer(input.model.api.id)) delete result.reasoningEffort
return result
}
function isGpt55OrNewer(modelID: string) {
const match = /gpt-(\d)(?:[.-](\d+))?(?:[.-]|$)/i.exec(modelID)
if (!match) return false
const major = Number(match[1])
const minor = Number(match[2] ?? 0)
return major > 5 || (major === 5 && minor >= 5)
}
export function smallOptions(model: Provider.Model) {
const small = Object.values(model.variants ?? {})[0] ?? {}
if (
+8 -5
View File
@@ -88,11 +88,14 @@ export const prepare = Effect.fn("LLMRequestPrep.prepare")(function* (input: Pre
sessionID: input.sessionID,
providerOptions: input.provider.options,
})
const options = ProviderTransform.requestOptions({
model: input.model,
providerOptions: input.provider.options,
options: mergeOptions(mergeOptions(mergeOptions(base, input.model.options), input.agent.options), variant),
})
const options = mergeOptions(mergeOptions(mergeOptions(base, input.model.options), input.agent.options), variant)
if (
input.model.api.npm === "@ai-sdk/azure" &&
(input.provider.options.useCompletionUrls || input.model.options.useCompletionUrls || options.useCompletionUrls)
) {
delete options.reasoningSummary
delete options.include
}
if (isOpenaiOauth) options.instructions = system.join("\n")
const messages =
+10 -10
View File
@@ -111,7 +111,7 @@ describe("plugin.xai", () => {
).toEqual({})
expect(hooks.auth!.methods.map((m) => [m.type, m.label])).toEqual([
["oauth", "xAI Grok OAuth (SuperGrok Subscription)"],
["oauth", "xAI Grok OAuth (Headless / Remote / VPS)"],
["oauth", "xAI Grok OAuth (Local Callback)"],
["api", "Manually enter API Key"],
])
})
@@ -424,11 +424,11 @@ describe("plugin.xai", () => {
return new Response("unexpected request", { status: 500 })
})
const hooks = await XaiAuthPlugin({} as any, serverOptions(server))
const headless = hooks.auth!.methods.find(
const oauth = hooks.auth!.methods.find(
(m): m is Extract<typeof m, { type: "oauth" }> =>
m.type === "oauth" && m.label === "xAI Grok OAuth (Headless / Remote / VPS)",
m.type === "oauth" && m.label === "xAI Grok OAuth (SuperGrok Subscription)",
)!
const result = await headless.authorize!()
const result = await oauth.authorize!()
expect(result.method).toBe("auto")
expect(result.url).toBe("https://x.ai/device?user_code=ABCD-1234")
@@ -448,11 +448,11 @@ describe("plugin.xai", () => {
}
return new Response("unexpected request", { status: 500 })
})
const headless = (await XaiAuthPlugin({} as any, serverOptions(server))).auth!.methods.find(
const oauth = (await XaiAuthPlugin({} as any, serverOptions(server))).auth!.methods.find(
(m): m is Extract<typeof m, { type: "oauth" }> =>
m.type === "oauth" && m.label === "xAI Grok OAuth (Headless / Remote / VPS)",
m.type === "oauth" && m.label === "xAI Grok OAuth (SuperGrok Subscription)",
)!
expect((await headless.authorize!()).url).toBe("https://x.ai/device")
expect((await oauth.authorize!()).url).toBe("https://x.ai/device")
})
test("requestDeviceCode posts form body, validates fields, and surfaces endpoint errors", async () => {
@@ -610,11 +610,11 @@ describe("plugin.xai", () => {
}
return Response.json({ error: "access_denied" }, { status: 400 })
})
const headless = (await XaiAuthPlugin({} as any, serverOptions(server))).auth!.methods.find(
const oauth = (await XaiAuthPlugin({} as any, serverOptions(server))).auth!.methods.find(
(m): m is Extract<typeof m, { type: "oauth" }> =>
m.type === "oauth" && m.label === "xAI Grok OAuth (Headless / Remote / VPS)",
m.type === "oauth" && m.label === "xAI Grok OAuth (SuperGrok Subscription)",
)!
expect(await ((await headless.authorize!()) as any).callback()).toEqual({ type: "failed" })
expect(await ((await oauth.authorize!()) as any).callback()).toEqual({ type: "failed" })
})
})
})
@@ -252,7 +252,7 @@ describe("ProviderTransform.options - setCacheKey", () => {
expect(result.promptCacheKey).toBeUndefined()
})
test("should keep the Azure cache key for gpt-5.5 Responses defaults", () => {
test("should keep the Azure cache key for gpt-5.5 early return", () => {
const result = ProviderTransform.options({
model: {
...mockModel,
@@ -264,7 +264,6 @@ describe("ProviderTransform.options - setCacheKey", () => {
})
expect(result.store).toBe(false)
expect(result.reasoningSummary).toBe("auto")
expect(result.reasoningEffort).toBe("medium")
expect(result.promptCacheKey).toBe(sessionID)
})
@@ -526,13 +525,13 @@ describe("ProviderTransform.options - gpt-5 textVerbosity", () => {
expect(result.include).toBeUndefined()
})
test("azure gpt-5.5 chat completions omit unsupported reasoning options after variants merge", async () => {
test("azure chat completions omit Responses-only reasoning options after variants merge", async () => {
const model = {
...createGpt5Model("gpt-5.5"),
id: "azure/gpt-5.5",
...createGpt5Model("gpt-5.4"),
id: "azure/gpt-5.4",
providerID: "azure",
api: {
id: "gpt-5.5",
id: "gpt-5.4",
url: "https://azure.com",
npm: "@ai-sdk/azure",
},
@@ -552,7 +551,7 @@ describe("ProviderTransform.options - gpt-5 textVerbosity", () => {
role: "user",
time: { created: Date.now() },
agent: "test",
model: { providerID: "azure", modelID: "gpt-5.5", variant: "high" },
model: { providerID: "azure", modelID: "gpt-5.4", variant: "high" },
} as any,
sessionID,
model,
@@ -581,7 +580,7 @@ describe("ProviderTransform.options - gpt-5 textVerbosity", () => {
isWorkflow: false,
}),
)
expect(result.params.options.reasoningEffort).toBeUndefined()
expect(result.params.options.reasoningEffort).toBe("high")
expect(result.params.options.reasoningSummary).toBeUndefined()
expect(result.params.options.include).toBeUndefined()
expect(result.tools.lookup.strict).toBe(false)
@@ -682,52 +681,34 @@ describe("ProviderTransform.options - gpt-5 reasoningEffort", () => {
expect(result.reasoningEffort).toBeUndefined()
})
test("gpt-5.5 should set Responses reasoning options", () => {
test("gpt-5.5 should NOT set reasoningEffort for the completions API", () => {
const result = ProviderTransform.options({
model: createModel("gpt-5.5"),
sessionID,
providerOptions: { useCompletionUrls: true },
})
expect(result.reasoningEffort).toBeUndefined()
})
test("gpt-5.6 should NOT set reasoningEffort for the completions API", () => {
const result = ProviderTransform.options({
model: createModel("gpt-5.6"),
sessionID,
providerOptions: { useCompletionUrls: true },
})
expect(result.reasoningEffort).toBeUndefined()
})
test("gpt-5.6 should set reasoningEffort for the responses API", () => {
const result = ProviderTransform.options({
model: createModel("gpt-5.6"),
sessionID,
providerOptions: {},
})
expect(result.reasoningEffort).toBe("medium")
expect(result.reasoningSummary).toBe("auto")
})
})
describe("ProviderTransform.requestOptions - Azure endpoints", () => {
const options = {
reasoningEffort: "high",
reasoningSummary: "auto",
include: ["reasoning.encrypted_content"],
}
test.each([
["preserves Responses reasoning options", "gpt-5.5", {}, {}, options],
[
"preserves reasoning effort for gpt-5.4 Chat Completions",
"gpt-5.4",
{ useCompletionUrls: true },
{},
{ reasoningEffort: "high" },
],
["uses the model endpoint override", "gpt-5.5", { useCompletionUrls: true }, { useCompletionUrls: false }, options],
["omits reasoning effort for gpt-5.5 Chat Completions", "gpt-5.5", { useCompletionUrls: true }, {}, {}],
["omits reasoning effort for gpt-5.6 Chat Completions", "gpt-5.6", { useCompletionUrls: true }, {}, {}],
["omits reasoning effort for gpt-6 Chat Completions", "gpt-6", { useCompletionUrls: true }, {}, {}],
[
"does not treat gpt-50 as a versioned GPT model",
"gpt-50",
{ useCompletionUrls: true },
{},
{ reasoningEffort: "high" },
],
])("%s", (_name, apiID, providerOptions, modelOptions, expected) => {
expect(
ProviderTransform.requestOptions({
model: { api: { id: apiID, npm: "@ai-sdk/azure" }, options: modelOptions } as any,
providerOptions,
options,
}),
).toEqual(expected)
})
})
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/plugin",
"version": "1.18.11",
"version": "1.18.12",
"type": "module",
"license": "MIT",
"scripts": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/sdk",
"version": "1.18.11",
"version": "1.18.12",
"type": "module",
"license": "MIT",
"scripts": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/server",
"version": "1.18.11",
"version": "1.18.12",
"private": true,
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/session-ui",
"version": "1.18.11",
"version": "1.18.12",
"private": true,
"type": "module",
"license": "MIT",
+32 -5
View File
@@ -255,8 +255,16 @@ function useFileViewer(config: ViewerConfig) {
if (!dragMoved) {
pendingSelectionEnd = false
const selected = config.buildClickSelection()
if (selected) config.setSelectedLines(selected)
config.onLineSelectionEnd(lastSelection)
const next =
selected &&
lastSelection?.start === selected.start &&
lastSelection.end === selected.end &&
lastSelection.side === selected.side &&
(lastSelection.endSide ?? lastSelection.side) === (selected.endSide ?? selected.side)
? null
: selected
if (next !== undefined) config.setSelectedLines(next)
config.onLineSelectionEnd(next === undefined ? lastSelection : next)
dragStart = undefined
dragEnd = undefined
dragMoved = false
@@ -291,10 +299,29 @@ function useFileViewer(config: ViewerConfig) {
createEffect(() => {
rendered()
const ranges = config.commentedLines()
requestAnimationFrame(() => {
const root = getRoot()
if (!root) return
const root = getRoot()
if (!root) return
if (ranges.length === 0) {
config.markCommented(root, ranges)
return
}
let frame: number | undefined
const mark = () => {
if (frame !== undefined) cancelAnimationFrame(frame)
frame = requestAnimationFrame(() => {
frame = undefined
config.markCommented(root, ranges)
})
}
const observer = new MutationObserver(mark)
observer.observe(root, { childList: true, subtree: true })
mark()
onCleanup(() => {
observer.disconnect()
if (frame !== undefined) cancelAnimationFrame(frame)
})
})
+61 -29
View File
@@ -26,30 +26,19 @@ const unsafeCSS = `
color-mix(in lab, var(--diffs-bg) 33.333%, var(--diffs-deletion-base)),
color-mix(in lab, var(--diffs-bg) 60%, var(--diffs-deletion-base))
);
--diffs-bg-deletion-number-override: var(--diffs-bg-deletion-override);
--diffs-bg-addition-override: light-dark(
color-mix(in lab, var(--diffs-bg) 33.333%, var(--diffs-addition-base)),
color-mix(in lab, var(--diffs-bg) 60%, var(--diffs-addition-base))
);
--diffs-selection-base: var(--surface-warning-strong);
--diffs-selection-border: var(--border-warning-base);
--diffs-selection-number-fg: #1c1917;
--diffs-bg-addition-number-override: var(--diffs-bg-addition-override);
--diffs-selection-base: var(--v2-background-bg-accent);
--diffs-selection-number-fg: var(--v2-text-text-accent);
--diffs-comment-bg: rgb(from var(--v2-background-bg-accent) r g b / 0.06);
/* Use explicit alpha instead of color-mix(..., transparent) to avoid Safari's non-premultiplied interpolation bugs. */
--diffs-bg-selection: var(--diffs-bg-selection-override, rgb(from var(--surface-warning-base) r g b / 0.65));
--diffs-bg-selection-number: var(
--diffs-bg-selection-number-override,
rgb(from var(--surface-warning-base) r g b / 0.85)
);
--diffs-bg-selection-text: rgb(from var(--surface-warning-strong) r g b / 0.2);
}
:host([data-color-scheme='dark']) [data-diff],
:host([data-color-scheme='dark']) [data-file] {
--diffs-selection-number-fg: #fdfbfb;
--diffs-bg-selection: var(--diffs-bg-selection-override, rgb(from var(--solaris-dark-6) r g b / 0.65));
--diffs-bg-selection-number: var(
--diffs-bg-selection-number-override,
rgb(from var(--solaris-dark-6) r g b / 0.85)
);
--diffs-bg-selection: var(--diffs-bg-selection-override, rgb(from var(--diffs-selection-base) r g b / 0.2));
--diffs-bg-selection-number: var(--diffs-bg-selection-number-override, var(--diffs-bg-selection));
--diffs-bg-selection-text: rgb(from var(--diffs-selection-base) r g b / 0.2);
}
[data-diff] ::selection,
@@ -57,6 +46,34 @@ const unsafeCSS = `
background-color: var(--diffs-bg-selection-text);
}
[data-indicators='bars'] [data-column-number][data-line-type='change-addition']::before,
[data-indicators='bars'] [data-column-number][data-line-type='change-deletion']::before {
width: 2px;
}
[data-indicators='bars'] [data-column-number][data-line-type='change-deletion']::before {
background-image: none;
background-color: var(--diffs-deletion-base);
}
[data-background] [data-column-number] {
--mix-light: 88%;
--mix-dark: 80%;
}
[data-diff-type='split'] [data-additions],
[data-diff-type='split'] [data-additions] [data-gutter],
[data-diff-type='split'] [data-deletions],
[data-diff-type='split'] [data-deletions] [data-content] {
border-left: 0;
border-right: 0;
}
[data-content-buffer] {
background-image: none;
background-color: var(--diffs-bg-context-gutter);
}
::highlight(opencode-find) {
background-color: rgb(from var(--surface-warning-base) r g b / 0.35);
}
@@ -83,22 +100,24 @@ const unsafeCSS = `
color: var(--diffs-selection-number-fg);
}
[data-diff] [data-line-annotation][data-comment-selected]:not([data-selected-line]) [data-annotation-content] {
box-shadow: inset 0 0 0 9999px var(--diffs-bg-selection);
}
[data-file] [data-line-annotation][data-comment-selected]:not([data-selected-line]) [data-annotation-content] {
box-shadow: inset 0 0 0 9999px var(--diffs-bg-selection);
[data-diff] [data-line-annotation],
[data-diff] [data-gutter-buffer='annotation'],
[data-file] [data-line-annotation],
[data-file] [data-gutter-buffer='annotation'] {
--diffs-annotation-bg: var(--diffs-comment-bg);
--diffs-computed-decoration-bg: var(--diffs-comment-bg);
--diffs-computed-diff-line-bg: var(--diffs-comment-bg);
--diffs-computed-selected-line-bg: var(--diffs-comment-bg);
--diffs-line-bg: var(--diffs-comment-bg);
background-color: var(--diffs-comment-bg);
}
[data-diff] [data-line][data-selected-line] {
background-color: var(--diffs-bg-selection);
box-shadow: inset 2px 0 0 var(--diffs-selection-border);
}
[data-file] [data-line][data-selected-line] {
background-color: var(--diffs-bg-selection);
box-shadow: inset 2px 0 0 var(--diffs-selection-border);
}
[data-diff] [data-column-number][data-selected-line] {
@@ -118,6 +137,19 @@ const unsafeCSS = `
color: var(--diffs-selection-number-fg);
}
@media (pointer: fine) {
[data-gutter-utility-slot] {
opacity: 0;
pointer-events: none;
}
[data-column-number][data-hovered] [data-gutter-utility-slot],
[data-gutter-utility-slot]:focus-within {
opacity: 1;
pointer-events: auto;
}
}
/* The deletion word-diff emphasis is stronger than additions; soften it while selected so the selection highlight reads consistently. */
[data-diff] [data-line][data-line-type='change-deletion'][data-selected-line] {
--diffs-bg-deletion-emphasis: light-dark(
@@ -133,7 +165,6 @@ const unsafeCSS = `
height: 24px;
}
[data-column-number] {
background-color: var(--diffs-bg);
cursor: default !important;
}
@@ -182,5 +213,6 @@ export const styleVariables = {
"--diffs-font-features": "var(--font-family-mono--font-feature-settings)",
"--diffs-header-font-family": "var(--font-family-sans)",
"--diffs-gap-block": 0,
"--diffs-min-number-column-width": "4ch",
"--diffs-gap-style": 0,
"--diffs-min-number-column-width": "3ch",
}
@@ -151,7 +151,10 @@ export function createLineCommentControllerV2<T extends LineCommentShape>(props:
}
: undefined
},
onMouseEnter: () => note.hoverComment(comment.selection),
onMouseEnter: () => {
if (note.commenting()) return
note.hoverComment(comment.selection)
},
onClick: () => {
if (note.isEditing(comment.id)) return
note.toggleComment(comment.id, comment.selection)
@@ -164,10 +167,14 @@ export function createLineCommentControllerV2<T extends LineCommentShape>(props:
},
selection: formatSelectedLineLabel(range, i18n.t),
onInput: note.setDraft,
onCancel: note.cancelDraft,
onCancel: () => {
note.cancelDraft()
note.select(null)
},
onSubmit: (comment) => {
props.onSubmit({ comment, selection: cloneSelectedLineRange(range) })
note.cancelDraft()
note.select(null)
},
cancelLabel: i18n.t("ui.lineComment.cancel"),
submitLabel: i18n.t("ui.lineComment.submit"),
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/slack",
"version": "1.18.11",
"version": "1.18.12",
"type": "module",
"license": "MIT",
"scripts": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/stats-app",
"version": "1.18.11",
"version": "1.18.12",
"private": true,
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/stats-core",
"version": "1.18.11",
"version": "1.18.12",
"private": true,
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/stats-server",
"version": "1.18.11",
"version": "1.18.12",
"private": true,
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/tui",
"version": "1.18.11",
"version": "1.18.12",
"private": true,
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/ui",
"version": "1.18.11",
"version": "1.18.12",
"type": "module",
"license": "MIT",
"repository": {
+4 -2
View File
@@ -41,7 +41,8 @@
"syntax-type": "var(--v2-purple-800)",
"syntax-constant": "#007b80",
"syntax-critical": "var(--v2-red-800)",
"syntax-diff-delete": "#ff8c00",
"syntax-diff-add": "var(--v2-state-fg-success)",
"syntax-diff-delete": "var(--v2-state-fg-danger)",
"syntax-diff-unknown": "#a753ae",
"surface-critical-base": "#FFF2F0"
},
@@ -275,7 +276,8 @@
"syntax-type": "var(--v2-purple-400)",
"syntax-constant": "#93e9f6",
"syntax-critical": "var(--v2-red-400)",
"syntax-diff-delete": "#fab283",
"syntax-diff-add": "var(--v2-state-fg-success)",
"syntax-diff-delete": "var(--v2-state-fg-danger)",
"syntax-diff-unknown": "#edb2f1",
"surface-critical-base": "#1F0603"
},
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "@opencode-ai/web",
"type": "module",
"license": "MIT",
"version": "1.18.11",
"version": "1.18.12",
"scripts": {
"dev": "astro dev",
"dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",
+10 -14
View File
@@ -2244,9 +2244,9 @@ Some useful routing options:
### xAI
Three ways to authenticate: a SuperGrok subscription via browser OAuth, the same SuperGrok subscription via a headless device-code flow (for VPS / SSH / Docker), or a pay-as-you-go API key from the xAI console.
Three ways to authenticate: a SuperGrok subscription via device-code OAuth, the same SuperGrok subscription via a local callback, or a pay-as-you-go API key from the xAI console.
#### Option A — SuperGrok OAuth (browser login)
#### Option A — SuperGrok OAuth
1. Run the `/connect` command and search for **xAI**.
@@ -2254,9 +2254,11 @@ Three ways to authenticate: a SuperGrok subscription via browser OAuth, the same
/connect
```
2. Select **xAI Grok OAuth (SuperGrok Subscription)**. OpenCode opens xAI's consent screen in your browser and waits for the callback on `http://127.0.0.1:56121/callback`.
2. Select **xAI Grok OAuth (SuperGrok Subscription)**. OpenCode prints a verification URL and a short user code.
3. Run the `/models` command to select a Grok model.
3. Open the URL, enter the code, and approve the consent screen. OpenCode polls xAI's token endpoint and stores the resulting OAuth tokens once you approve.
4. Run the `/models` command to select a Grok model.
```txt
/models
@@ -2264,23 +2266,17 @@ Three ways to authenticate: a SuperGrok subscription via browser OAuth, the same
OpenCode refreshes the OAuth access token automatically. Any Grok or X Premium plan that includes Grok API access works; you do not need a separate `XAI_API_KEY`.
#### Option B — SuperGrok device-code (headless / remote server / VPS)
#### Option B — SuperGrok local callback
Use this when OpenCode is running somewhere a browser can't reach the loopback redirect: a VPS, a remote dev box over SSH, inside Docker, in CI, etc. No callback port is opened on the host running OpenCode — instead xAI hands the CLI a short code that you type into a browser on any other device (laptop, phone, …).
Use this only if you prefer a local browser callback and the browser can reach OpenCode on the same machine.
1. Run the `/connect` command on the remote host and search for **xAI**.
1. Run the `/connect` command and search for **xAI**.
```txt
/connect
```
2. Select **xAI Grok OAuth (Headless / Remote / VPS)**. OpenCode prints a verification URL and a short user code.
```txt
Open https://x.ai/device on any device and enter code: ABCD-1234
```
3. Open the URL on a device that has a browser (your laptop or phone), enter the code, and approve the consent screen. OpenCode polls xAI's token endpoint and stores the resulting OAuth tokens once you approve. Token refresh works the same as Option A.
2. Select **xAI Grok OAuth (Local Callback)**. OpenCode opens xAI's consent screen and waits for the callback on `http://127.0.0.1:56121/callback`. Token refresh works the same as Option A.
#### Option C — API key
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "opencode",
"displayName": "opencode",
"description": "opencode for VS Code",
"version": "1.18.11",
"version": "1.18.12",
"publisher": "sst-dev",
"repository": {
"type": "git",