Skip to content

Commit af02a93

Browse files
henry701cursoragent
andcommitted
Merge remote-tracking branch 'henry701/dev' into dev
Integrate upstream provider reasoning variant changes and drop fork-only sdk reasoning_options sanitization superseded by upstream transform logic. Co-authored-by: Cursor <cursoragent@cursor.com>
2 parents 42de4b8 + 462410a commit af02a93

13 files changed

Lines changed: 590 additions & 247 deletions

File tree

‎.opencode/tool/github-triage.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { tool } from "@opencode-ai/plugin"
44
const TEAM = {
55
tui: ["kommander", "simonklee"],
66
desktop_web: ["Hona", "Brendonovich"],
7-
core: ["jlongster", "rekram1-node", "nexxeln", "kitlangton", "starptech"],
7+
core: ["jlongster", "rekram1-node", "nexxeln", "kitlangton"],
88
inference: ["fwang", "MrMushrooooom", "starptech"],
99
windows: ["Hona"],
1010
} as const

‎packages/app/src/components/prompt-input.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2410,7 +2410,7 @@ function ModelControlContent(props: { state: ComposerModelControlState; v2?: boo
24102410
/>
24112411
)}
24122412
</Show>
2413-
<span class="truncate">{props.state.modelName}</span>
2413+
<span class="truncate leading-4">{props.state.modelName}</span>
24142414
<span class={props.v2 ? "-ml-0.5 -mr-1 flex shrink-0" : "-ml-1 shrink-0 flex size-fit"}>
24152415
<Icon name="chevron-down" size="small" class="text-v2-icon-icon-muted" />
24162416
</span>

‎packages/app/src/components/prompt-workspace-selector.tsx‎

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { For, Show } from "solid-js"
22
import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2"
3+
import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
34
import { Icon } from "@opencode-ai/ui/icon"
45
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
56
import { getFilename } from "@opencode-ai/core/util/path"
@@ -96,10 +97,17 @@ export function PromptWorkspaceSelector(props: {
9697
{(branch) => (
9798
<>
9899
<span class="hidden select-none opacity-50 sm:inline mx-1">/</span>
99-
<div class="flex h-7 min-w-0 max-w-[220px] items-center gap-1.5 px-2 text-[13px] font-[440] leading-5 tracking-[-0.04px]">
100-
<Icon name="branch" size="small" class="shrink-0 text-v2-icon-icon-muted" />
101-
<span class="min-w-0 truncate">{branch()}</span>
102-
</div>
100+
<TooltipV2
101+
placement="top"
102+
value={branch()}
103+
class="min-w-0 max-w-[220px]"
104+
contentClass="max-w-[calc(100vw-32px)] break-all"
105+
>
106+
<div class="flex h-7 min-w-0 max-w-[220px] items-center gap-1.5 px-2 text-[13px] font-[440] leading-5 tracking-[-0.04px]">
107+
<Icon name="branch" size="small" class="shrink-0 text-v2-icon-icon-muted" />
108+
<span class="min-w-0 truncate">{branch()}</span>
109+
</div>
110+
</TooltipV2>
103111
</>
104112
)}
105113
</Show>

‎packages/core/src/models-dev.ts‎

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,18 +44,31 @@ const Cost = Schema.Struct({
4444
),
4545
})
4646

47+
const ReasoningOption = Schema.Union([
48+
Schema.Struct({
49+
type: Schema.Literal("effort"),
50+
values: Schema.Array(Schema.NullOr(Schema.String)),
51+
}),
52+
Schema.Struct({
53+
type: Schema.Literal("toggle"),
54+
}),
55+
Schema.Struct({
56+
type: Schema.Literal("budget_tokens"),
57+
min: Schema.optional(Schema.Finite),
58+
max: Schema.optional(Schema.Finite),
59+
}),
60+
])
61+
4762
export const Model = Schema.Struct({
4863
id: Schema.String,
4964
name: Schema.String,
5065
family: Schema.optional(Schema.String),
5166
release_date: Schema.String,
5267
attachment: Schema.Boolean,
5368
reasoning: Schema.Boolean,
54-
// models.dev is external metadata and reasoning controls are expected to evolve.
55-
// Provider normalization extracts the subset understood by this client.
56-
reasoning_options: Schema.optional(Schema.Unknown),
5769
temperature: Schema.Boolean,
5870
tool_call: Schema.Boolean,
71+
reasoning_options: Schema.optional(Schema.Array(ReasoningOption)),
5972
interleaved: Schema.optional(
6073
Schema.Union([
6174
Schema.Literal(true),

‎packages/core/test/models.test.ts‎

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { describe, expect, beforeAll, beforeEach, afterAll, test } from "bun:test"
2-
import { Effect, Layer, Ref, Schema } from "effect"
1+
import { describe, expect, beforeAll, beforeEach, afterAll } from "bun:test"
2+
import { Effect, Layer, Ref } from "effect"
33
import { HttpClient, HttpClientResponse } from "effect/unstable/http"
44
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
55
import { LayerNodePlatform } from "@opencode-ai/core/effect/app-node-platform"
@@ -126,21 +126,6 @@ const initialState: MockState = {
126126
calls: [],
127127
}
128128

129-
test("models.dev model schema keeps reasoning options permissive", () => {
130-
const model = Schema.decodeUnknownSync(ModelsDev.Model)({
131-
id: "acme-1",
132-
name: "Acme One",
133-
release_date: "2026-01-01",
134-
attachment: false,
135-
reasoning: true,
136-
reasoning_options: [{ type: "future_control", value: { nested: true } }, "future-shape"],
137-
temperature: true,
138-
tool_call: true,
139-
limit: { context: 128000, output: 8192 },
140-
})
141-
expect(model.reasoning_options).toEqual([{ type: "future_control", value: { nested: true } }, "future-shape"])
142-
})
143-
144129
describe("ModelsDev Service", () => {
145130
it.live("get() returns providers from disk when cache file exists", () =>
146131
Effect.gen(function* () {

‎packages/opencode/src/plugin/openai/codex.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ export async function CodexAuthPlugin(input: PluginInput, options: CodexAuthPlug
285285
.filter(([, model]) => {
286286
if (ALLOWED_MODELS.has(model.api.id)) return true
287287
if (DISALLOWED_MODELS.has(model.api.id)) return false
288+
if (model.api.id === "gpt-5.6") return false
288289
const match = model.api.id.match(/^gpt-(\d+\.\d+)/)
289290
return match ? parseFloat(match[1]) > 5.4 : false
290291
})

‎packages/opencode/src/provider/provider.ts‎

Lines changed: 10 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -982,21 +982,6 @@ const ProviderInterleaved = Schema.Union([
982982
}),
983983
])
984984

985-
const ProviderReasoningOption = Schema.Union([
986-
Schema.Struct({
987-
type: Schema.Literal("effort"),
988-
values: Schema.Array(Schema.NullOr(Schema.String)),
989-
}),
990-
Schema.Struct({
991-
type: Schema.Literal("toggle"),
992-
}),
993-
Schema.Struct({
994-
type: Schema.Literal("budget_tokens"),
995-
min: optional(Schema.Finite),
996-
max: optional(Schema.Finite),
997-
}),
998-
])
999-
1000985
const ProviderCapabilities = Schema.Struct({
1001986
temperature: Schema.Boolean,
1002987
reasoning: Schema.Boolean,
@@ -1055,7 +1040,6 @@ export const Model = Schema.Struct({
10551040
options: Schema.Record(Schema.String, Schema.Any),
10561041
headers: Schema.Record(Schema.String, Schema.String),
10571042
release_date: Schema.String,
1058-
reasoning_options: optional(Schema.Array(ProviderReasoningOption)),
10591043
variants: optional(Schema.Record(Schema.String, Schema.Record(Schema.String, Schema.Any))),
10601044
}).annotate({ identifier: "Model" })
10611045
export type Model = Types.DeepMutable<Schema.Schema.Type<typeof Model>>
@@ -1091,11 +1075,7 @@ export function toPublicInfo(provider: Info): Info {
10911075
JSON.stringify(
10921076
{
10931077
...provider,
1094-
models: Object.fromEntries(
1095-
Object.entries(provider.models)
1096-
.filter(([, model]) => Schema.is(Model)(model))
1097-
.map(([id, model]) => [id, publicModel(model)]),
1098-
),
1078+
models: Object.fromEntries(Object.entries(provider.models).filter(([, model]) => Schema.is(Model)(model))),
10991079
},
11001080
(_, value) => {
11011081
if (typeof value === "function" || typeof value === "symbol" || value === undefined) return undefined
@@ -1223,66 +1203,6 @@ function cost(c: ModelsDev.Model["cost"]): Model["cost"] {
12231203
return result
12241204
}
12251205

1226-
type ReasoningOption = NonNullable<Model["reasoning_options"]>[number]
1227-
1228-
function reasoningOptions(input: unknown): Model["reasoning_options"] {
1229-
if (!Array.isArray(input)) return []
1230-
return input.flatMap((option) => {
1231-
const normalized = normalizeReasoningOption(option)
1232-
return normalized ? [normalized] : []
1233-
})
1234-
}
1235-
1236-
function normalizeReasoningOption(option: unknown): ReasoningOption | undefined {
1237-
if (!isRecord(option)) return
1238-
if (option.type === "effort") {
1239-
if (!Array.isArray(option.values)) return
1240-
return {
1241-
type: "effort",
1242-
values: option.values.filter((value): value is string | null => value === null || typeof value === "string"),
1243-
}
1244-
}
1245-
if (option.type === "toggle") return { type: "toggle" }
1246-
if (option.type !== "budget_tokens") return
1247-
const min = typeof option.min === "number" && Number.isFinite(option.min) ? option.min : undefined
1248-
const max = typeof option.max === "number" && Number.isFinite(option.max) ? option.max : undefined
1249-
return {
1250-
type: "budget_tokens",
1251-
...(min === undefined ? {} : { min }),
1252-
...(max === undefined ? {} : { max }),
1253-
}
1254-
}
1255-
1256-
type PublicReasoningOption = NonNullable<import("@opencode-ai/sdk/v2").Model["reasoning_options"]>[number]
1257-
1258-
function publicReasoningOptions(options: Model["reasoning_options"]): PublicReasoningOption[] | undefined {
1259-
if (!options) return undefined
1260-
const next: PublicReasoningOption[] = []
1261-
for (const option of options) {
1262-
if (option.type === "effort") {
1263-
const values = option.values.filter((value): value is string => typeof value === "string")
1264-
if (values.length > 0) next.push({ type: "effort", values })
1265-
continue
1266-
}
1267-
if (option.type === "toggle") {
1268-
next.push({ type: "toggle" })
1269-
continue
1270-
}
1271-
next.push({
1272-
type: "budget_tokens",
1273-
...(option.min === undefined ? {} : { min: option.min }),
1274-
...(option.max === undefined ? {} : { max: option.max }),
1275-
})
1276-
}
1277-
return next.length > 0 ? next : undefined
1278-
}
1279-
1280-
export function publicModel(model: Model): Model {
1281-
const reasoning_options = publicReasoningOptions(model.reasoning_options)
1282-
if (reasoning_options === undefined) return model
1283-
return { ...model, reasoning_options: reasoning_options as Model["reasoning_options"] }
1284-
}
1285-
12861206
function fromModelsDevModel(provider: ModelsDev.Provider, model: ModelsDev.Model): Model {
12871207
const base: Model = {
12881208
id: ModelV2.ID.make(model.id),
@@ -1325,13 +1245,14 @@ function fromModelsDevModel(provider: ModelsDev.Provider, model: ModelsDev.Model
13251245
interleaved: model.interleaved ?? false,
13261246
},
13271247
release_date: model.release_date ?? "",
1328-
reasoning_options: reasoningOptions(model.reasoning_options),
13291248
variants: {},
13301249
}
13311250

1251+
const variants = ProviderTransform.reasoningVariants(model, base) ?? ProviderTransform.variants(base)
1252+
13321253
return {
13331254
...base,
1334-
variants: mapValues(ProviderTransform.variants(base), (v) => v),
1255+
variants: mapValues(variants, (v) => v),
13351256
}
13361257
}
13371258

@@ -1572,10 +1493,13 @@ const layer = Layer.effect(
15721493
headers: mergeDeep(existingModel?.headers ?? {}, model.headers ?? {}),
15731494
family: model.family ?? existingModel?.family ?? "",
15741495
release_date: model.release_date ?? existingModel?.release_date ?? "",
1575-
reasoning_options: existingModel?.reasoning_options,
15761496
variants: {},
15771497
}
1578-
const merged = mergeDeep(ProviderTransform.variants(parsedModel), model.variants ?? {})
1498+
const variants =
1499+
existingModel?.api.npm === parsedModel.api.npm
1500+
? (existingModel.variants ?? ProviderTransform.variants(parsedModel))
1501+
: ProviderTransform.variants(parsedModel)
1502+
const merged = mergeDeep(variants, model.variants ?? {})
15791503
parsedModel.variants = mapValues(
15801504
pickBy(merged, (v) => !v.disabled),
15811505
(v) => omit(v, ["disabled"]),
@@ -1703,7 +1627,7 @@ const layer = Layer.effect(
17031627
)
17041628
delete provider.models[modelID]
17051629

1706-
if (!model.variants || Object.keys(model.variants).length === 0) {
1630+
if (model.variants === undefined) {
17071631
model.variants = mapValues(ProviderTransform.variants(model), (v) => v)
17081632
}
17091633

0 commit comments

Comments
 (0)