mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-17 12:56:41 +02:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| db2e1f1dfb | |||
| c4c0be106b |
@@ -29,7 +29,7 @@
|
||||
},
|
||||
"packages/app": {
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.14.24",
|
||||
"version": "1.14.22",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -83,7 +83,7 @@
|
||||
},
|
||||
"packages/console/app": {
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.14.24",
|
||||
"version": "1.14.22",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@ibm/plex": "6.4.1",
|
||||
@@ -117,7 +117,7 @@
|
||||
},
|
||||
"packages/console/core": {
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.14.24",
|
||||
"version": "1.14.22",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sts": "3.782.0",
|
||||
"@jsx-email/render": "1.1.1",
|
||||
@@ -144,7 +144,7 @@
|
||||
},
|
||||
"packages/console/function": {
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.14.24",
|
||||
"version": "1.14.22",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "3.0.64",
|
||||
"@ai-sdk/openai": "3.0.48",
|
||||
@@ -168,7 +168,7 @@
|
||||
},
|
||||
"packages/console/mail": {
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.14.24",
|
||||
"version": "1.14.22",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
@@ -192,7 +192,7 @@
|
||||
},
|
||||
"packages/desktop": {
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "1.14.24",
|
||||
"version": "1.14.22",
|
||||
"dependencies": {
|
||||
"@opencode-ai/app": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
@@ -225,7 +225,7 @@
|
||||
},
|
||||
"packages/desktop-electron": {
|
||||
"name": "@opencode-ai/desktop-electron",
|
||||
"version": "1.14.24",
|
||||
"version": "1.14.22",
|
||||
"dependencies": {
|
||||
"drizzle-orm": "catalog:",
|
||||
"effect": "catalog:",
|
||||
@@ -269,7 +269,7 @@
|
||||
},
|
||||
"packages/enterprise": {
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.14.24",
|
||||
"version": "1.14.22",
|
||||
"dependencies": {
|
||||
"@opencode-ai/shared": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
@@ -298,7 +298,7 @@
|
||||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.14.24",
|
||||
"version": "1.14.22",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "catalog:",
|
||||
@@ -314,7 +314,7 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "opencode",
|
||||
"version": "1.14.24",
|
||||
"version": "1.14.22",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -459,7 +459,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.14.24",
|
||||
"version": "1.14.22",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"effect": "catalog:",
|
||||
@@ -494,7 +494,7 @@
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.14.24",
|
||||
"version": "1.14.22",
|
||||
"dependencies": {
|
||||
"cross-spawn": "catalog:",
|
||||
},
|
||||
@@ -509,7 +509,7 @@
|
||||
},
|
||||
"packages/shared": {
|
||||
"name": "@opencode-ai/shared",
|
||||
"version": "1.14.24",
|
||||
"version": "1.14.22",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -533,7 +533,7 @@
|
||||
},
|
||||
"packages/slack": {
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.14.24",
|
||||
"version": "1.14.22",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@slack/bolt": "^3.17.1",
|
||||
@@ -568,7 +568,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.14.24",
|
||||
"version": "1.14.22",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -617,7 +617,7 @@
|
||||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode-ai/web",
|
||||
"version": "1.14.24",
|
||||
"version": "1.14.22",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.14.24",
|
||||
"version": "1.14.22",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.14.24",
|
||||
"version": "1.14.22",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.14.24",
|
||||
"version": "1.14.22",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.14.24",
|
||||
"version": "1.14.22",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.14.24",
|
||||
"version": "1.14.22",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop-electron",
|
||||
"private": true,
|
||||
"version": "1.14.24",
|
||||
"version": "1.14.22",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"homepage": "https://opencode.ai",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop",
|
||||
"private": true,
|
||||
"version": "1.14.24",
|
||||
"version": "1.14.22",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.14.24",
|
||||
"version": "1.14.22",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
id = "opencode"
|
||||
name = "OpenCode"
|
||||
description = "The open source coding agent."
|
||||
version = "1.14.24"
|
||||
version = "1.14.22"
|
||||
schema_version = 1
|
||||
authors = ["Anomaly"]
|
||||
repository = "https://github.com/anomalyco/opencode"
|
||||
@@ -11,26 +11,26 @@ name = "OpenCode"
|
||||
icon = "./icons/opencode.svg"
|
||||
|
||||
[agent_servers.opencode.targets.darwin-aarch64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.24/opencode-darwin-arm64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.22/opencode-darwin-arm64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.darwin-x86_64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.24/opencode-darwin-x64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.22/opencode-darwin-x64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-aarch64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.24/opencode-linux-arm64.tar.gz"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.22/opencode-linux-arm64.tar.gz"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-x86_64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.24/opencode-linux-x64.tar.gz"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.22/opencode-linux-x64.tar.gz"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.windows-x86_64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.24/opencode-windows-x64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.22/opencode-windows-x64.zip"
|
||||
cmd = "./opencode.exe"
|
||||
args = ["acp"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.14.24",
|
||||
"version": "1.14.22",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.14.24",
|
||||
"version": "1.14.22",
|
||||
"name": "opencode",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -412,12 +412,10 @@ Current instance route inventory:
|
||||
- `workspace` - `bridged`
|
||||
best small reads: `GET /experimental/workspace/adaptor`, `GET /experimental/workspace`, `GET /experimental/workspace/status`
|
||||
defer create/remove mutations first
|
||||
- `file` - `bridged` (partial)
|
||||
bridged endpoints: `GET /file`, `GET /file/content`, `GET /file/status`
|
||||
defer search endpoints first
|
||||
- `mcp` - `bridged` (partial)
|
||||
bridged endpoints: `GET /mcp`
|
||||
defer interactive OAuth/auth flows first
|
||||
- `file` - `later`
|
||||
good JSON-only candidate set, but larger than the current first-wave slices
|
||||
- `mcp` - `later`
|
||||
has JSON-only endpoints, but interactive OAuth/auth flows make it a worse early fit
|
||||
- `session` - `defer`
|
||||
large, stateful, mixes CRUD with prompt/shell/command/share/revert flows and a streaming route
|
||||
- `event` - `defer`
|
||||
@@ -451,8 +449,7 @@ Recommended near-term sequence:
|
||||
- [x] port `project` read endpoints (`GET /project`, `GET /project/current`)
|
||||
- [x] port `GET /config` full read endpoint
|
||||
- [x] port `workspace` read endpoints
|
||||
- [x] port `file` JSON read endpoints
|
||||
- [x] port `mcp` status read endpoint
|
||||
- [ ] port `file` JSON read endpoints
|
||||
- [ ] decide when to remove the flag and make Effect routes the default
|
||||
|
||||
## Rule of thumb
|
||||
|
||||
@@ -8,7 +8,7 @@ import { UI } from "@/cli/ui"
|
||||
import { Log } from "@/util"
|
||||
import { errorMessage } from "@/util/error"
|
||||
import { withTimeout } from "@/util/timeout"
|
||||
import { withNetworkOptions, resolveNetworkOptionsNoConfig } from "@/cli/network"
|
||||
import { withNetworkOptions, resolveNetworkOptions } from "@/cli/network"
|
||||
import { Filesystem } from "@/util"
|
||||
import type { GlobalEvent } from "@opencode-ai/sdk/v2"
|
||||
import type { EventSource } from "./context/sdk"
|
||||
@@ -182,7 +182,7 @@ export const TuiThreadCommand = cmd({
|
||||
const prompt = await input(args.prompt)
|
||||
const config = await TuiConfig.get()
|
||||
|
||||
const network = resolveNetworkOptionsNoConfig(args)
|
||||
const network = await resolveNetworkOptions(args)
|
||||
const external =
|
||||
process.argv.includes("--port") ||
|
||||
process.argv.includes("--hostname") ||
|
||||
|
||||
@@ -9,63 +9,69 @@ import { formatPatch, structuredPatch } from "diff"
|
||||
import fuzzysort from "fuzzysort"
|
||||
import ignore from "ignore"
|
||||
import path from "path"
|
||||
import z from "zod"
|
||||
import { Global } from "../global"
|
||||
import { Instance } from "../project/instance"
|
||||
import { Log } from "../util"
|
||||
import { Protected } from "./protected"
|
||||
import { Ripgrep } from "./ripgrep"
|
||||
import { zod } from "@/util/effect-zod"
|
||||
import { type DeepMutable, withStatics } from "@/util/schema"
|
||||
|
||||
export const Info = Schema.Struct({
|
||||
path: Schema.String,
|
||||
added: Schema.Int,
|
||||
removed: Schema.Int,
|
||||
status: Schema.Literals(["added", "deleted", "modified"]),
|
||||
})
|
||||
.annotate({ identifier: "File" })
|
||||
.pipe(withStatics((s) => ({ zod: zod(s) })))
|
||||
export type Info = DeepMutable<Schema.Schema.Type<typeof Info>>
|
||||
export const Info = z
|
||||
.object({
|
||||
path: z.string(),
|
||||
added: z.number().int(),
|
||||
removed: z.number().int(),
|
||||
status: z.enum(["added", "deleted", "modified"]),
|
||||
})
|
||||
.meta({
|
||||
ref: "File",
|
||||
})
|
||||
|
||||
export const Node = Schema.Struct({
|
||||
name: Schema.String,
|
||||
path: Schema.String,
|
||||
absolute: Schema.String,
|
||||
type: Schema.Literals(["file", "directory"]),
|
||||
ignored: Schema.Boolean,
|
||||
})
|
||||
.annotate({ identifier: "FileNode" })
|
||||
.pipe(withStatics((s) => ({ zod: zod(s) })))
|
||||
export type Node = DeepMutable<Schema.Schema.Type<typeof Node>>
|
||||
export type Info = z.infer<typeof Info>
|
||||
|
||||
const Hunk = Schema.Struct({
|
||||
oldStart: Schema.Number,
|
||||
oldLines: Schema.Number,
|
||||
newStart: Schema.Number,
|
||||
newLines: Schema.Number,
|
||||
lines: Schema.Array(Schema.String),
|
||||
})
|
||||
export const Node = z
|
||||
.object({
|
||||
name: z.string(),
|
||||
path: z.string(),
|
||||
absolute: z.string(),
|
||||
type: z.enum(["file", "directory"]),
|
||||
ignored: z.boolean(),
|
||||
})
|
||||
.meta({
|
||||
ref: "FileNode",
|
||||
})
|
||||
export type Node = z.infer<typeof Node>
|
||||
|
||||
const Patch = Schema.Struct({
|
||||
oldFileName: Schema.String,
|
||||
newFileName: Schema.String,
|
||||
oldHeader: Schema.optional(Schema.String),
|
||||
newHeader: Schema.optional(Schema.String),
|
||||
hunks: Schema.Array(Hunk),
|
||||
index: Schema.optional(Schema.String),
|
||||
})
|
||||
|
||||
export const Content = Schema.Struct({
|
||||
type: Schema.Literals(["text", "binary"]),
|
||||
content: Schema.String,
|
||||
diff: Schema.optional(Schema.String),
|
||||
patch: Schema.optional(Patch),
|
||||
encoding: Schema.optional(Schema.Literal("base64")),
|
||||
mimeType: Schema.optional(Schema.String),
|
||||
})
|
||||
.annotate({ identifier: "FileContent" })
|
||||
.pipe(withStatics((s) => ({ zod: zod(s) })))
|
||||
export type Content = DeepMutable<Schema.Schema.Type<typeof Content>>
|
||||
export const Content = z
|
||||
.object({
|
||||
type: z.enum(["text", "binary"]),
|
||||
content: z.string(),
|
||||
diff: z.string().optional(),
|
||||
patch: z
|
||||
.object({
|
||||
oldFileName: z.string(),
|
||||
newFileName: z.string(),
|
||||
oldHeader: z.string().optional(),
|
||||
newHeader: z.string().optional(),
|
||||
hunks: z.array(
|
||||
z.object({
|
||||
oldStart: z.number(),
|
||||
oldLines: z.number(),
|
||||
newStart: z.number(),
|
||||
newLines: z.number(),
|
||||
lines: z.array(z.string()),
|
||||
}),
|
||||
),
|
||||
index: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
encoding: z.literal("base64").optional(),
|
||||
mimeType: z.string().optional(),
|
||||
})
|
||||
.meta({
|
||||
ref: "FileContent",
|
||||
})
|
||||
export type Content = z.infer<typeof Content>
|
||||
|
||||
export const Event = {
|
||||
Edited: BusEvent.define(
|
||||
|
||||
@@ -30,8 +30,6 @@ import { EffectBridge } from "@/effect"
|
||||
import { InstanceState } from "@/effect"
|
||||
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
|
||||
import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner"
|
||||
import { zod as effectZod } from "@/util/effect-zod"
|
||||
import { withStatics } from "@/util/schema"
|
||||
|
||||
const log = Log.create({ service: "mcp" })
|
||||
const DEFAULT_TIMEOUT = 30_000
|
||||
@@ -71,33 +69,50 @@ export const Failed = NamedError.create(
|
||||
|
||||
type MCPClient = Client
|
||||
|
||||
const StatusConnected = Schema.Struct({ status: Schema.Literal("connected") }).annotate({
|
||||
identifier: "MCPStatusConnected",
|
||||
})
|
||||
const StatusDisabled = Schema.Struct({ status: Schema.Literal("disabled") }).annotate({
|
||||
identifier: "MCPStatusDisabled",
|
||||
})
|
||||
const StatusFailed = Schema.Struct({ status: Schema.Literal("failed"), error: Schema.String }).annotate({
|
||||
identifier: "MCPStatusFailed",
|
||||
})
|
||||
const StatusNeedsAuth = Schema.Struct({ status: Schema.Literal("needs_auth") }).annotate({
|
||||
identifier: "MCPStatusNeedsAuth",
|
||||
})
|
||||
const StatusNeedsClientRegistration = Schema.Struct({
|
||||
status: Schema.Literal("needs_client_registration"),
|
||||
error: Schema.String,
|
||||
}).annotate({ identifier: "MCPStatusNeedsClientRegistration" })
|
||||
|
||||
export const Status = Schema.Union([
|
||||
StatusConnected,
|
||||
StatusDisabled,
|
||||
StatusFailed,
|
||||
StatusNeedsAuth,
|
||||
StatusNeedsClientRegistration,
|
||||
])
|
||||
.annotate({ identifier: "MCPStatus", discriminator: "status" })
|
||||
.pipe(withStatics((s) => ({ zod: effectZod(s) })))
|
||||
export type Status = Schema.Schema.Type<typeof Status>
|
||||
export const Status = z
|
||||
.discriminatedUnion("status", [
|
||||
z
|
||||
.object({
|
||||
status: z.literal("connected"),
|
||||
})
|
||||
.meta({
|
||||
ref: "MCPStatusConnected",
|
||||
}),
|
||||
z
|
||||
.object({
|
||||
status: z.literal("disabled"),
|
||||
})
|
||||
.meta({
|
||||
ref: "MCPStatusDisabled",
|
||||
}),
|
||||
z
|
||||
.object({
|
||||
status: z.literal("failed"),
|
||||
error: z.string(),
|
||||
})
|
||||
.meta({
|
||||
ref: "MCPStatusFailed",
|
||||
}),
|
||||
z
|
||||
.object({
|
||||
status: z.literal("needs_auth"),
|
||||
})
|
||||
.meta({
|
||||
ref: "MCPStatusNeedsAuth",
|
||||
}),
|
||||
z
|
||||
.object({
|
||||
status: z.literal("needs_client_registration"),
|
||||
error: z.string(),
|
||||
})
|
||||
.meta({
|
||||
ref: "MCPStatusNeedsClientRegistration",
|
||||
}),
|
||||
])
|
||||
.meta({
|
||||
ref: "MCPStatus",
|
||||
})
|
||||
export type Status = z.infer<typeof Status>
|
||||
|
||||
// Store transports for OAuth servers to allow finishing auth
|
||||
type TransportWithAuth = StreamableHTTPClientTransport | SSEClientTransport
|
||||
|
||||
@@ -1177,7 +1177,7 @@ const layer: Layer.Layer<
|
||||
model.modalities?.output?.includes("video") ?? existingModel?.capabilities.output.video ?? false,
|
||||
pdf: model.modalities?.output?.includes("pdf") ?? existingModel?.capabilities.output.pdf ?? false,
|
||||
},
|
||||
interleaved: model.interleaved ?? existingModel?.capabilities.interleaved ?? false,
|
||||
interleaved: model.interleaved ?? false,
|
||||
},
|
||||
cost: {
|
||||
input: model?.cost?.input ?? existingModel?.cost?.input ?? 0,
|
||||
|
||||
@@ -175,24 +175,6 @@ function normalizeMessages(
|
||||
return result
|
||||
}
|
||||
|
||||
// Deepseek requires all assistant messages to have reasoning on them
|
||||
if (model.api.id.includes("deepseek")) {
|
||||
msgs = msgs.map((msg) => {
|
||||
if (msg.role !== "assistant") return msg
|
||||
if (Array.isArray(msg.content)) {
|
||||
if (msg.content.some((part) => part.type === "reasoning")) return msg
|
||||
return { ...msg, content: [...msg.content, { type: "reasoning", text: "" }] }
|
||||
}
|
||||
return {
|
||||
...msg,
|
||||
content: [
|
||||
...(msg.content ? [{ type: "text" as const, text: msg.content }] : []),
|
||||
{ type: "reasoning" as const, text: "" },
|
||||
],
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (typeof model.capabilities.interleaved === "object" && model.capabilities.interleaved.field) {
|
||||
const field = model.capabilities.interleaved.field
|
||||
return msgs.map((msg) => {
|
||||
@@ -203,19 +185,24 @@ function normalizeMessages(
|
||||
// Filter out reasoning parts from content
|
||||
const filteredContent = msg.content.filter((part: any) => part.type !== "reasoning")
|
||||
|
||||
// Include reasoning_content | reasoning_details directly on the message for all assistant messages.
|
||||
// Always set the field even when empty — some providers (e.g. DeepSeek) may return empty
|
||||
// reasoning_content which still needs to be sent back in subsequent requests.
|
||||
// Include reasoning_content | reasoning_details directly on the message for all assistant messages
|
||||
if (reasoningText) {
|
||||
return {
|
||||
...msg,
|
||||
content: filteredContent,
|
||||
providerOptions: {
|
||||
...msg.providerOptions,
|
||||
openaiCompatible: {
|
||||
...msg.providerOptions?.openaiCompatible,
|
||||
[field]: reasoningText,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...msg,
|
||||
content: filteredContent,
|
||||
providerOptions: {
|
||||
...msg.providerOptions,
|
||||
openaiCompatible: {
|
||||
...msg.providerOptions?.openaiCompatible,
|
||||
[field]: reasoningText,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -418,10 +405,7 @@ export function variants(model: Provider.Model): Record<string, Record<string, a
|
||||
const id = model.id.toLowerCase()
|
||||
const adaptiveEfforts = anthropicAdaptiveEfforts(model.api.id)
|
||||
if (
|
||||
id.includes("deepseek-chat") ||
|
||||
id.includes("deepseek-reasoner") ||
|
||||
id.includes("deepseek-r1") ||
|
||||
id.includes("deepseek-v3") ||
|
||||
id.includes("deepseek") ||
|
||||
id.includes("minimax") ||
|
||||
id.includes("glm") ||
|
||||
id.includes("kimi") ||
|
||||
@@ -547,11 +531,7 @@ export function variants(model: Provider.Model): Record<string, Record<string, a
|
||||
case "venice-ai-sdk-provider":
|
||||
// https://docs.venice.ai/overview/guides/reasoning-models#reasoning-effort
|
||||
case "@ai-sdk/openai-compatible":
|
||||
const efforts = [...WIDELY_SUPPORTED_EFFORTS]
|
||||
if (model.api.id.includes("deepseek-v4")) {
|
||||
efforts.push("max")
|
||||
}
|
||||
return Object.fromEntries(efforts.map((effort) => [effort, { reasoningEffort: effort }]))
|
||||
return Object.fromEntries(WIDELY_SUPPORTED_EFFORTS.map((effort) => [effort, { reasoningEffort: effort }]))
|
||||
|
||||
case "@ai-sdk/azure":
|
||||
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/azure
|
||||
|
||||
@@ -117,7 +117,7 @@ export const FileRoutes = lazy(() =>
|
||||
description: "Files and directories",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(File.Node.zod.array()),
|
||||
schema: resolver(File.Node.array()),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -146,7 +146,7 @@ export const FileRoutes = lazy(() =>
|
||||
description: "File content",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(File.Content.zod),
|
||||
schema: resolver(File.Content),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -175,7 +175,7 @@ export const FileRoutes = lazy(() =>
|
||||
description: "File status",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(File.Info.zod.array()),
|
||||
schema: resolver(File.Info.array()),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
import { File } from "@/file"
|
||||
import { Effect, Layer, Schema } from "effect"
|
||||
import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
|
||||
const FileQuery = Schema.Struct({
|
||||
path: Schema.String,
|
||||
})
|
||||
|
||||
export const FilePaths = {
|
||||
list: "/file",
|
||||
content: "/file/content",
|
||||
status: "/file/status",
|
||||
} as const
|
||||
|
||||
export const FileApi = HttpApi.make("file")
|
||||
.add(
|
||||
HttpApiGroup.make("file")
|
||||
.add(
|
||||
HttpApiEndpoint.get("list", FilePaths.list, {
|
||||
query: FileQuery,
|
||||
success: Schema.Array(File.Node),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "file.list",
|
||||
summary: "List files",
|
||||
description: "List files and directories in a specified path.",
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.get("content", FilePaths.content, {
|
||||
query: FileQuery,
|
||||
success: File.Content,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "file.read",
|
||||
summary: "Read file",
|
||||
description: "Read the content of a specified file.",
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.get("status", FilePaths.status, {
|
||||
success: Schema.Array(File.Info),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "file.status",
|
||||
summary: "Get file status",
|
||||
description: "Get the git status of all files in the project.",
|
||||
}),
|
||||
),
|
||||
)
|
||||
.annotateMerge(
|
||||
OpenApi.annotations({
|
||||
title: "file",
|
||||
description: "Experimental HttpApi file routes.",
|
||||
}),
|
||||
),
|
||||
)
|
||||
.annotateMerge(
|
||||
OpenApi.annotations({
|
||||
title: "opencode experimental HttpApi",
|
||||
version: "0.0.1",
|
||||
description: "Experimental HttpApi surface for selected instance routes.",
|
||||
}),
|
||||
)
|
||||
|
||||
export const fileHandlers = Layer.unwrap(
|
||||
Effect.gen(function* () {
|
||||
const svc = yield* File.Service
|
||||
|
||||
const list = Effect.fn("FileHttpApi.list")(function* (ctx: { query: { path: string } }) {
|
||||
return yield* svc.list(ctx.query.path)
|
||||
})
|
||||
|
||||
const content = Effect.fn("FileHttpApi.content")(function* (ctx: { query: { path: string } }) {
|
||||
return yield* svc.read(ctx.query.path)
|
||||
})
|
||||
|
||||
const status = Effect.fn("FileHttpApi.status")(function* () {
|
||||
return yield* svc.status()
|
||||
})
|
||||
|
||||
return HttpApiBuilder.group(FileApi, "file", (handlers) =>
|
||||
handlers.handle("list", list).handle("content", content).handle("status", status),
|
||||
)
|
||||
}),
|
||||
).pipe(Layer.provide(File.defaultLayer))
|
||||
@@ -1,48 +0,0 @@
|
||||
import { MCP } from "@/mcp"
|
||||
import { Effect, Layer, Schema } from "effect"
|
||||
import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
|
||||
export const McpPaths = {
|
||||
status: "/mcp",
|
||||
} as const
|
||||
|
||||
export const McpApi = HttpApi.make("mcp")
|
||||
.add(
|
||||
HttpApiGroup.make("mcp")
|
||||
.add(
|
||||
HttpApiEndpoint.get("status", McpPaths.status, {
|
||||
success: Schema.Record(Schema.String, MCP.Status),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "mcp.status",
|
||||
summary: "Get MCP status",
|
||||
description: "Get the status of all Model Context Protocol (MCP) servers.",
|
||||
}),
|
||||
),
|
||||
)
|
||||
.annotateMerge(
|
||||
OpenApi.annotations({
|
||||
title: "mcp",
|
||||
description: "Experimental HttpApi MCP routes.",
|
||||
}),
|
||||
),
|
||||
)
|
||||
.annotateMerge(
|
||||
OpenApi.annotations({
|
||||
title: "opencode experimental HttpApi",
|
||||
version: "0.0.1",
|
||||
description: "Experimental HttpApi surface for selected instance routes.",
|
||||
}),
|
||||
)
|
||||
|
||||
export const mcpHandlers = Layer.unwrap(
|
||||
Effect.gen(function* () {
|
||||
const mcp = yield* MCP.Service
|
||||
|
||||
const status = Effect.fn("McpHttpApi.status")(function* () {
|
||||
return yield* mcp.status()
|
||||
})
|
||||
|
||||
return HttpApiBuilder.group(McpApi, "mcp", (handlers) => handlers.handle("status", status))
|
||||
}),
|
||||
).pipe(Layer.provide(MCP.defaultLayer))
|
||||
@@ -10,8 +10,6 @@ import { Instance } from "@/project/instance"
|
||||
import { lazy } from "@/util/lazy"
|
||||
import { Filesystem } from "@/util"
|
||||
import { ConfigApi, configHandlers } from "./config"
|
||||
import { FileApi, fileHandlers } from "./file"
|
||||
import { McpApi, mcpHandlers } from "./mcp"
|
||||
import { PermissionApi, permissionHandlers } from "./permission"
|
||||
import { ProjectApi, projectHandlers } from "./project"
|
||||
import { ProviderApi, providerHandlers } from "./provider"
|
||||
@@ -116,13 +114,9 @@ const ProjectSecured = ProjectApi.middleware(Authorization)
|
||||
const ProviderSecured = ProviderApi.middleware(Authorization)
|
||||
const ConfigSecured = ConfigApi.middleware(Authorization)
|
||||
const WorkspaceSecured = WorkspaceApi.middleware(Authorization)
|
||||
const FileSecured = FileApi.middleware(Authorization)
|
||||
const McpSecured = McpApi.middleware(Authorization)
|
||||
|
||||
export const routes = Layer.mergeAll(
|
||||
HttpApiBuilder.layer(ConfigSecured).pipe(Layer.provide(configHandlers)),
|
||||
HttpApiBuilder.layer(FileSecured).pipe(Layer.provide(fileHandlers)),
|
||||
HttpApiBuilder.layer(McpSecured).pipe(Layer.provide(mcpHandlers)),
|
||||
HttpApiBuilder.layer(ProjectSecured).pipe(Layer.provide(projectHandlers)),
|
||||
HttpApiBuilder.layer(QuestionSecured).pipe(Layer.provide(questionHandlers)),
|
||||
HttpApiBuilder.layer(PermissionSecured).pipe(Layer.provide(permissionHandlers)),
|
||||
|
||||
@@ -16,8 +16,6 @@ import { QuestionRoutes } from "./question"
|
||||
import { PermissionRoutes } from "./permission"
|
||||
import { Flag } from "@/flag/flag"
|
||||
import { ExperimentalHttpApiServer } from "./httpapi/server"
|
||||
import { FilePaths } from "./httpapi/file"
|
||||
import { McpPaths } from "./httpapi/mcp"
|
||||
import { ProjectRoutes } from "./project"
|
||||
import { SessionRoutes } from "./session"
|
||||
import { PtyRoutes } from "./pty"
|
||||
@@ -50,10 +48,6 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => {
|
||||
app.post("/provider/:providerID/oauth/callback", (c) => handler(c.req.raw, context))
|
||||
app.get("/project", (c) => handler(c.req.raw, context))
|
||||
app.get("/project/current", (c) => handler(c.req.raw, context))
|
||||
app.get(FilePaths.list, (c) => handler(c.req.raw, context))
|
||||
app.get(FilePaths.content, (c) => handler(c.req.raw, context))
|
||||
app.get(FilePaths.status, (c) => handler(c.req.raw, context))
|
||||
app.get(McpPaths.status, (c) => handler(c.req.raw, context))
|
||||
}
|
||||
|
||||
return app
|
||||
|
||||
@@ -21,7 +21,7 @@ export const McpRoutes = lazy(() =>
|
||||
description: "MCP server status",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.record(z.string(), MCP.Status.zod)),
|
||||
schema: resolver(z.record(z.string(), MCP.Status)),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -44,7 +44,7 @@ export const McpRoutes = lazy(() =>
|
||||
description: "MCP server added successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.record(z.string(), MCP.Status.zod)),
|
||||
schema: resolver(z.record(z.string(), MCP.Status)),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -121,7 +121,7 @@ export const McpRoutes = lazy(() =>
|
||||
description: "OAuth authentication completed",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(MCP.Status.zod),
|
||||
schema: resolver(MCP.Status),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -153,7 +153,7 @@ export const McpRoutes = lazy(() =>
|
||||
description: "OAuth authentication completed",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(MCP.Status.zod),
|
||||
schema: resolver(MCP.Status),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
import { afterEach, describe, expect, test } from "bun:test"
|
||||
import { Context } from "effect"
|
||||
import path from "path"
|
||||
import { ExperimentalHttpApiServer } from "../../src/server/routes/instance/httpapi/server"
|
||||
import { FilePaths } from "../../src/server/routes/instance/httpapi/file"
|
||||
import { Instance } from "../../src/project/instance"
|
||||
import { Log } from "../../src/util"
|
||||
import { resetDatabase } from "../fixture/db"
|
||||
import { tmpdir } from "../fixture/fixture"
|
||||
|
||||
void Log.init({ print: false })
|
||||
|
||||
const context = Context.empty() as Context.Context<unknown>
|
||||
|
||||
function request(route: string, directory: string, query?: Record<string, string>) {
|
||||
const url = new URL(`http://localhost${route}`)
|
||||
for (const [key, value] of Object.entries(query ?? {})) {
|
||||
url.searchParams.set(key, value)
|
||||
}
|
||||
return ExperimentalHttpApiServer.webHandler().handler(
|
||||
new Request(url, {
|
||||
headers: {
|
||||
"x-opencode-directory": directory,
|
||||
},
|
||||
}),
|
||||
context,
|
||||
)
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
await Instance.disposeAll()
|
||||
await resetDatabase()
|
||||
})
|
||||
|
||||
describe("file HttpApi", () => {
|
||||
test("serves read endpoints", async () => {
|
||||
await using tmp = await tmpdir({ git: true })
|
||||
await Bun.write(path.join(tmp.path, "hello.txt"), "hello")
|
||||
|
||||
const [list, content, status] = await Promise.all([
|
||||
request(FilePaths.list, tmp.path, { path: "." }),
|
||||
request(FilePaths.content, tmp.path, { path: "hello.txt" }),
|
||||
request(FilePaths.status, tmp.path),
|
||||
])
|
||||
|
||||
expect(list.status).toBe(200)
|
||||
expect(await list.json()).toContainEqual(
|
||||
expect.objectContaining({ name: "hello.txt", path: "hello.txt", type: "file" }),
|
||||
)
|
||||
|
||||
expect(content.status).toBe(200)
|
||||
expect(await content.json()).toMatchObject({ type: "text", content: "hello" })
|
||||
|
||||
expect(status.status).toBe(200)
|
||||
expect(await status.json()).toContainEqual({ path: "hello.txt", added: 1, removed: 0, status: "added" })
|
||||
})
|
||||
})
|
||||
@@ -1,48 +0,0 @@
|
||||
import { afterEach, describe, expect, test } from "bun:test"
|
||||
import { Context } from "effect"
|
||||
import { ExperimentalHttpApiServer } from "../../src/server/routes/instance/httpapi/server"
|
||||
import { McpPaths } from "../../src/server/routes/instance/httpapi/mcp"
|
||||
import { Instance } from "../../src/project/instance"
|
||||
import { Log } from "../../src/util"
|
||||
import { resetDatabase } from "../fixture/db"
|
||||
import { tmpdir } from "../fixture/fixture"
|
||||
|
||||
void Log.init({ print: false })
|
||||
|
||||
const context = Context.empty() as Context.Context<unknown>
|
||||
|
||||
function request(route: string, directory: string) {
|
||||
return ExperimentalHttpApiServer.webHandler().handler(
|
||||
new Request(`http://localhost${route}`, {
|
||||
headers: {
|
||||
"x-opencode-directory": directory,
|
||||
},
|
||||
}),
|
||||
context,
|
||||
)
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
await Instance.disposeAll()
|
||||
await resetDatabase()
|
||||
})
|
||||
|
||||
describe("mcp HttpApi", () => {
|
||||
test("serves status endpoint", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
config: {
|
||||
mcp: {
|
||||
demo: {
|
||||
type: "local",
|
||||
command: ["echo", "demo"],
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const response = await request(McpPaths.status, tmp.path)
|
||||
expect(response.status).toBe(200)
|
||||
expect(await response.json()).toEqual({ demo: { status: "disabled" } })
|
||||
})
|
||||
})
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.14.24",
|
||||
"version": "1.14.22",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.14.24",
|
||||
"version": "1.14.22",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.14.24",
|
||||
"version": "1.14.22",
|
||||
"name": "@opencode-ai/shared",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.14.24",
|
||||
"version": "1.14.22",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.14.24",
|
||||
"version": "1.14.22",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@opencode-ai/web",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"version": "1.14.24",
|
||||
"version": "1.14.22",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "opencode",
|
||||
"displayName": "opencode",
|
||||
"description": "opencode for VS Code",
|
||||
"version": "1.14.24",
|
||||
"version": "1.14.22",
|
||||
"publisher": "sst-dev",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
Reference in New Issue
Block a user