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
44 changes: 44 additions & 0 deletions .github/releases/v1.0.62.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
## opencode 1.0.62

Stable release from `main` branch. Keeps existing Ctrl-T model variants in reasoning-effort order.

---

### 🐛 Bug Fixes

- Sort existing Ctrl-T model variants by reasoning effort: `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`, then `ultra`.
- Keep custom non-integer variant names after the known names, preserving their relative order. This changes display order only; it does not add or enable variants or change their saved identity, payload, or default-cycle behavior.

---

### ⚙️ CI / Engineering

- Run GitHub Actions workflows on GitHub-hosted runners.
- Run workspace tests one task at a time on hosted runners to reduce CPU contention and timeout risk.

---

### 🧪 Test Summary

```text
Provider, transform, direct-run variant, runtime.boot, and release helpers: 486 passed, 0 failed
Workspace typecheck: 31 tasks passed
Lint: 4834 warnings, 0 errors; unchanged limit of 4850
Direct footer rendering, layout, and menus: 27 passed, 5 existing skips, 0 failed
app cycle helper: 7 passed, 0 failed
Isolated TUI PTY cycle: default -> none -> minimal -> low -> medium -> high -> xhigh -> max -> ultra -> custom-large -> custom-small -> default; screen and persisted selection matched at every step
```

---

### 🔍 Verification

An isolated source TUI session confirmed the displayed and persisted selection after every Ctrl-T step. The session made no model requests and created no sessions or messages. The test process left no running child process.

Configuration merging preserves variant parameters, disabled entries, and saved selections. Integer-style custom names keep JavaScript's existing object enumeration behavior.

Astra's final review of the patch passed. Native CI remains the merge gate.

---

**Full changelog:** [`graphagent-v1.0.61`...`graphagent-v1.0.62`](https://github.com/LeXwDeX/OpenCode-GraphAgent/compare/graphagent-v1.0.61...graphagent-v1.0.62)
1 change: 1 addition & 0 deletions packages/opencode/src/provider/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1648,6 +1648,7 @@ export const layer = Layer.effect(
(v) => omit(v, ["disabled"]),
)
}
model.variants = ProviderTransform.sortVariants(model.variants)
}

if (Object.keys(provider.models).length === 0) {
Expand Down
11 changes: 11 additions & 0 deletions packages/opencode/src/provider/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,17 @@ export function topK(model: Provider.Model) {

const WIDELY_SUPPORTED_EFFORTS = ["low", "medium", "high"]
const OPENAI_EFFORTS = ["none", "minimal", ...WIDELY_SUPPORTED_EFFORTS, "xhigh"]
const VARIANT_EFFORT_ORDER = [...OPENAI_EFFORTS, "max", "ultra"]

export function sortVariants<T>(variants: Record<string, T>): Record<string, T> {
const rank = (name: string) => {
const index = VARIANT_EFFORT_ORDER.indexOf(name)
return index === -1 ? VARIANT_EFFORT_ORDER.length : index
}
// Custom names have no standard effort ranking. Keep their relative order.
return Object.fromEntries(Object.entries(variants).sort(([a], [b]) => rank(a) - rank(b)))
}

const OPENAI_GPT5_1_EFFORTS = ["none", ...WIDELY_SUPPORTED_EFFORTS]
const OPENAI_GPT5_2_PLUS_EFFORTS = [...OPENAI_GPT5_1_EFFORTS, "xhigh"]
const OPENAI_GPT5_PRO_EFFORTS = ["high"]
Expand Down
49 changes: 49 additions & 0 deletions packages/opencode/test/provider/provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1428,6 +1428,55 @@ it.instance(
},
)

it.instance(
"model variants are ordered by reasoning effort after config merging",
Effect.gen(function* () {
yield* set("ANTHROPIC_API_KEY", "test-api-key")
const providers = yield* list
const model = providers[ProviderV2.ID.anthropic].models["claude-sonnet-4-20250514"]
expect(Object.keys(model.variants!)).toEqual([
"none",
"low",
"medium",
"high",
"xhigh",
"max",
"ultra",
"custom-large",
"custom-small",
])
expect(model.variants!.high.thinking.budgetTokens).toBe(20000)
expect(model.variants!.max.thinking.budgetTokens).toBe(31999)
expect(model.variants!.minimal).toBeUndefined()
expect(model.variants!.low.reasoningEffort).toBe("low")
expect(model.variants!["custom-large"].budgetTokens).toBe(8000)
expect(model.variants!["custom-small"].budgetTokens).toBe(2000)
}),
{
config: {
provider: {
anthropic: {
models: {
"claude-sonnet-4-20250514": {
variants: {
ultra: { effort: "ultra" },
"custom-large": { budgetTokens: 8000 },
xhigh: { effort: "xhigh" },
medium: { effort: "medium" },
minimal: { disabled: true },
low: { reasoningEffort: "low" },
none: { thinking: { type: "disabled" } },
high: { thinking: { budgetTokens: 20000 } },
"custom-small": { budgetTokens: 2000 },
},
},
},
},
},
},
},
)

it.instance(
"model variants can be customized via config",
Effect.gen(function* () {
Expand Down
35 changes: 35 additions & 0 deletions packages/opencode/test/provider/transform.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,41 @@ import { ModelsDev } from "@opencode-ai/core/models-dev"
import { generateText, jsonSchema, type ModelMessage } from "ai"
import { createAmazonBedrock } from "@ai-sdk/amazon-bedrock"

describe("ProviderTransform.sortVariants", () => {
test("orders known reasoning efforts independently of insertion order", () => {
const variants = Object.fromEntries(
["max", "medium", "ultra", "high", "low", "xhigh", "none", "minimal"].map((name) => [name, { effort: name }]),
)
const result = ProviderTransform.sortVariants(variants)

expect(Object.keys(result)).toEqual(["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"])
expect(Object.keys(variants)).toEqual(["max", "medium", "ultra", "high", "low", "xhigh", "none", "minimal"])
for (const name of Object.keys(variants)) expect(result[name]).toBe(variants[name])
})

test("keeps custom names in their relative order after known efforts", () => {
const variants = {
"custom-large": { budgetTokens: 8000 },
high: { effort: "high" },
"custom-small": { budgetTokens: 2000 },
low: { effort: "low" },
}

expect(Object.keys(ProviderTransform.sortVariants(variants))).toEqual([
"low",
"high",
"custom-large",
"custom-small",
])
})

test("supports empty, single and custom-only variant sets", () => {
expect(ProviderTransform.sortVariants({})).toEqual({})
expect(ProviderTransform.sortVariants({ high: {} })).toEqual({ high: {} })
expect(Object.keys(ProviderTransform.sortVariants({ quality: {}, fast: {} }))).toEqual(["quality", "fast"])
})
})

describe("ProviderTransform.options - setCacheKey", () => {
const sessionID = "test-session-123"

Expand Down
Loading