Skip to content

Commit 485a981

Browse files
Merge remote-tracking branch 'upstream/dev' into ocv
2 parents 22d7de8 + 55ecb06 commit 485a981

9 files changed

Lines changed: 122 additions & 26 deletions

File tree

packages/opencode/src/provider/provider.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1135,6 +1135,13 @@ const layer: Layer.Layer<
11351135

11361136
for (const [modelID, model] of Object.entries(provider.models ?? {})) {
11371137
const existingModel = parsed.models[model.id ?? modelID]
1138+
const apiID = model.id ?? existingModel?.api.id ?? modelID
1139+
const apiNpm =
1140+
model.provider?.npm ??
1141+
provider.npm ??
1142+
existingModel?.api.npm ??
1143+
modelsDev[providerID]?.npm ??
1144+
"@ai-sdk/openai-compatible"
11381145
const name = iife(() => {
11391146
if (model.name) return model.name
11401147
if (model.id && model.id !== modelID) return modelID
@@ -1143,13 +1150,8 @@ const layer: Layer.Layer<
11431150
const parsedModel: Model = {
11441151
id: ModelID.make(modelID),
11451152
api: {
1146-
id: model.id ?? existingModel?.api.id ?? modelID,
1147-
npm:
1148-
model.provider?.npm ??
1149-
provider.npm ??
1150-
existingModel?.api.npm ??
1151-
modelsDev[providerID]?.npm ??
1152-
"@ai-sdk/openai-compatible",
1153+
id: apiID,
1154+
npm: apiNpm,
11531155
url: model.provider?.api ?? provider?.api ?? existingModel?.api.url ?? modelsDev[providerID]?.api ?? "",
11541156
},
11551157
status: model.status ?? existingModel?.status ?? "active",
@@ -1177,7 +1179,12 @@ const layer: Layer.Layer<
11771179
model.modalities?.output?.includes("video") ?? existingModel?.capabilities.output.video ?? false,
11781180
pdf: model.modalities?.output?.includes("pdf") ?? existingModel?.capabilities.output.pdf ?? false,
11791181
},
1180-
interleaved: model.interleaved ?? existingModel?.capabilities.interleaved ?? false,
1182+
interleaved:
1183+
model.interleaved ??
1184+
existingModel?.capabilities.interleaved ??
1185+
(!existingModel && apiNpm === "@ai-sdk/openai-compatible" && apiID.includes("deepseek")
1186+
? { field: "reasoning_content" }
1187+
: false),
11811188
},
11821189
cost: {
11831190
input: model?.cost?.input ?? existingModel?.cost?.input ?? 0,

packages/opencode/src/server/routes/instance/httpapi/config.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,9 @@ export const configHandlers = Layer.unwrap(
6767
})
6868

6969
const update = Effect.fn("ConfigHttpApi.update")(function* (ctx) {
70-
const payload = Config.Info.zod.parse(ctx.payload)
71-
yield* configSvc.update(payload, { dispose: false })
70+
yield* configSvc.update(ctx.payload, { dispose: false })
7271
yield* markInstanceForDisposal(yield* InstanceState.context)
73-
return payload
72+
return ctx.payload
7473
})
7574

7675
const providers = Effect.fn("ConfigHttpApi.providers")(function* () {

packages/opencode/src/server/routes/instance/httpapi/project.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ export const projectHandlers = Layer.unwrap(
9999
params: { projectID: ProjectID }
100100
payload: Project.UpdatePayload
101101
}) {
102-
return yield* svc.update({ ...Project.UpdatePayload.zod.parse(ctx.payload), projectID: ctx.params.projectID })
102+
return yield* svc.update({ ...ctx.payload, projectID: ctx.params.projectID })
103103
})
104104

105105
return HttpApiBuilder.group(ProjectApi, "project", (handlers) =>

packages/opencode/src/server/routes/instance/httpapi/session.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ export const SessionApi = HttpApi.make("session")
203203
}),
204204
),
205205
HttpApiEndpoint.post("create", SessionPaths.create, {
206-
payload: Session.CreateInput,
206+
payload: [HttpApiSchema.NoContent, Session.CreateInput],
207207
success: Session.Info,
208208
}).annotateMerge(
209209
OpenApi.annotations({
@@ -513,7 +513,7 @@ export const sessionHandlers = Layer.unwrap(
513513
)
514514
})
515515

516-
const create = Effect.fn("SessionHttpApi.create")(function* (ctx: { payload: Session.CreateInput }) {
516+
const create = Effect.fn("SessionHttpApi.create")(function* (ctx: { payload?: Session.CreateInput }) {
517517
const instance = yield* InstanceState.context
518518
return yield* Effect.promise(() =>
519519
Instance.restore(instance, () =>
@@ -524,6 +524,22 @@ export const sessionHandlers = Layer.unwrap(
524524
)
525525
})
526526

527+
const createRaw = Effect.fn("SessionHttpApi.createRaw")(function* (ctx: {
528+
request: HttpServerRequest.HttpServerRequest
529+
}) {
530+
const body = yield* Effect.orDie(ctx.request.text)
531+
if (body.trim().length === 0) return yield* create({})
532+
533+
const json = yield* Effect.try({
534+
try: () => JSON.parse(body) as unknown,
535+
catch: () => new HttpApiError.BadRequest({}),
536+
})
537+
const payload = yield* Schema.decodeUnknownEffect(Session.CreateInput)(json).pipe(
538+
Effect.mapError(() => new HttpApiError.BadRequest({})),
539+
)
540+
return yield* create({ payload })
541+
})
542+
527543
const remove = Effect.fn("SessionHttpApi.remove")(function* (ctx: { params: { sessionID: SessionID } }) {
528544
const instance = yield* InstanceState.context
529545
yield* Effect.promise(() =>
@@ -894,7 +910,7 @@ export const sessionHandlers = Layer.unwrap(
894910
.handle("diff", diff)
895911
.handle("messages", messages)
896912
.handle("message", message)
897-
.handle("create", create)
913+
.handleRaw("create", createRaw)
898914
.handle("remove", remove)
899915
.handle("update", update)
900916
.handle("fork", fork)

packages/opencode/src/server/routes/instance/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { McpPaths } from "./httpapi/mcp"
2525
import { SessionPaths } from "./httpapi/session"
2626
import { SyncPaths } from "./httpapi/sync"
2727
import { TuiPaths } from "./httpapi/tui"
28+
import { WorkspacePaths } from "./httpapi/workspace"
2829
import { ProjectRoutes } from "./project"
2930
import { SessionRoutes } from "./session"
3031
import { PtyRoutes } from "./pty"
@@ -144,6 +145,12 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => {
144145
app.post(TuiPaths.selectSession, (c) => handler(c.req.raw, context))
145146
app.get(TuiPaths.controlNext, (c) => handler(c.req.raw, context))
146147
app.post(TuiPaths.controlResponse, (c) => handler(c.req.raw, context))
148+
app.get(WorkspacePaths.adaptors, (c) => handler(c.req.raw, context))
149+
app.post(WorkspacePaths.list, (c) => handler(c.req.raw, context))
150+
app.get(WorkspacePaths.list, (c) => handler(c.req.raw, context))
151+
app.get(WorkspacePaths.status, (c) => handler(c.req.raw, context))
152+
app.delete(WorkspacePaths.remove, (c) => handler(c.req.raw, context))
153+
app.post(WorkspacePaths.sessionRestore, (c) => handler(c.req.raw, context))
147154
}
148155

149156
return app

packages/opencode/test/provider/provider.test.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,67 @@ test("custom provider with npm package", async () => {
312312
})
313313
})
314314

315+
test("custom DeepSeek openai-compatible model defaults interleaved reasoning field", async () => {
316+
await using tmp = await tmpdir({
317+
init: async (dir) => {
318+
await Bun.write(
319+
path.join(dir, "opencode.json"),
320+
JSON.stringify({
321+
$schema: "https://opencode.ai/config.json",
322+
provider: {
323+
"custom-provider": {
324+
name: "Custom Provider",
325+
npm: "@ai-sdk/openai-compatible",
326+
api: "https://api.custom.com/v1",
327+
models: {
328+
"deepseek-r1": {
329+
name: "DeepSeek R1",
330+
},
331+
"deepseek-details": {
332+
name: "DeepSeek Details",
333+
interleaved: { field: "reasoning_details" },
334+
},
335+
"custom-model": {
336+
name: "Custom Model",
337+
},
338+
},
339+
options: {
340+
apiKey: "custom-key",
341+
},
342+
},
343+
"custom-anthropic-provider": {
344+
name: "Custom Anthropic Provider",
345+
npm: "@ai-sdk/anthropic",
346+
api: "https://api.custom.com/v1",
347+
models: {
348+
"deepseek-r1": {
349+
name: "DeepSeek R1",
350+
},
351+
},
352+
options: {
353+
apiKey: "custom-key",
354+
},
355+
},
356+
},
357+
}),
358+
)
359+
},
360+
})
361+
await Instance.provide({
362+
directory: tmp.path,
363+
fn: async () => {
364+
const providers = await list()
365+
const provider = providers[ProviderID.make("custom-provider")]
366+
expect(provider.models["deepseek-r1"].capabilities.interleaved).toEqual({ field: "reasoning_content" })
367+
expect(provider.models["deepseek-details"].capabilities.interleaved).toEqual({ field: "reasoning_details" })
368+
expect(provider.models["custom-model"].capabilities.interleaved).toBe(false)
369+
expect(
370+
providers[ProviderID.make("custom-anthropic-provider")].models["deepseek-r1"].capabilities.interleaved,
371+
).toBe(false)
372+
},
373+
})
374+
})
375+
315376
test("env variable takes precedence, config merges options", async () => {
316377
await using tmp = await tmpdir({
317378
init: async (dir) => {

packages/opencode/test/server/httpapi-session.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,14 @@ describe("session HttpApi", () => {
151151
await using tmp = await tmpdir({ git: true, config: { formatter: false, lsp: false, share: "disabled" } })
152152
const headers = { "x-opencode-directory": tmp.path, "content-type": "application/json" }
153153

154+
const createdEmpty = await json<Session.Info>(
155+
await app().request(SessionPaths.create, {
156+
method: "POST",
157+
headers,
158+
}),
159+
)
160+
expect(createdEmpty.id).toBeTruthy()
161+
154162
const created = await json<Session.Info>(
155163
await app().request(SessionPaths.create, {
156164
method: "POST",

packages/opencode/test/server/httpapi-workspace.test.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { afterEach, describe, expect, test } from "bun:test"
22
import { mkdir } from "node:fs/promises"
33
import path from "node:path"
4-
import { Context, Effect } from "effect"
4+
import { Effect } from "effect"
5+
import type { UpgradeWebSocket } from "hono/ws"
56
import { Flag } from "@opencode-ai/core/flag/flag"
67
import { registerAdaptor } from "../../src/control-plane/adaptors"
78
import type { WorkspaceAdaptor } from "../../src/control-plane/types"
89
import { Workspace } from "../../src/control-plane/workspace"
9-
import { ExperimentalHttpApiServer } from "../../src/server/routes/instance/httpapi/server"
1010
import { WorkspacePaths } from "../../src/server/routes/instance/httpapi/workspace"
11+
import { InstanceRoutes } from "../../src/server/routes/instance"
1112
import { Session } from "../../src/session"
1213
import { Log } from "../../src/util"
1314
import { resetDatabase } from "../fixture/db"
@@ -16,19 +17,15 @@ import { Instance } from "../../src/project/instance"
1617

1718
void Log.init({ print: false })
1819

19-
const context = Context.empty() as Context.Context<unknown>
2020
const originalWorkspaces = Flag.OPENCODE_EXPERIMENTAL_WORKSPACES
21+
const originalHttpApi = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI
22+
const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket
2123

2224
function request(path: string, directory: string, init: RequestInit = {}) {
25+
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
2326
const headers = new Headers(init.headers)
2427
headers.set("x-opencode-directory", directory)
25-
return ExperimentalHttpApiServer.webHandler().handler(
26-
new Request(`http://localhost${path}`, {
27-
...init,
28-
headers,
29-
}),
30-
context,
31-
)
28+
return InstanceRoutes(websocket).request(path, { ...init, headers })
3229
}
3330

3431
function runSession<A, E>(fx: Effect.Effect<A, E, Session.Service>) {
@@ -61,6 +58,7 @@ function localAdaptor(directory: string): WorkspaceAdaptor {
6158

6259
afterEach(async () => {
6360
Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = originalWorkspaces
61+
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = originalHttpApi
6462
await Instance.disposeAll()
6563
await resetDatabase()
6664
})

packages/web/src/content/docs/it/cli.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,6 @@ Queste variabili d'ambiente abilitano funzionalità sperimentali che potrebbero
598598
| `OPENCODE_EXPERIMENTAL_LSP_TOOL` | boolean | Abilita strumento LSP sperimentale |
599599
| `OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER` | boolean | Disabilita file watcher |
600600
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | Abilita funzionalità Exa sperimentali |
601-
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | Abilita Abilita TY LSP per i file python |
601+
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | Abilita TY LSP per i file python |
602602
| `OPENCODE_EXPERIMENTAL_MARKDOWN` | boolean | Abilita markdown sperimentale |
603603
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Abilita plan mode |

0 commit comments

Comments
 (0)