summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorKit Langton <[email protected]>2026-04-16 19:52:04 -0400
committerGitHub <[email protected]>2026-04-16 19:52:04 -0400
commit5d47ea091879b026b8efb9d09af06deb0643e46a (patch)
treec5e523d2c2a9e3e6c571d7845e77cc6e81e83a53
parentc03fa362572d8108d2d76c2a18bbf616a7345dac (diff)
downloadopencode-5d47ea091879b026b8efb9d09af06deb0643e46a.tar.gz
opencode-5d47ea091879b026b8efb9d09af06deb0643e46a.zip
refactor: unwrap ConfigMCP namespace + self-reexport (#22948)
-rw-r--r--packages/opencode/src/cli/cmd/tui/context/kv.tsx2
-rw-r--r--packages/opencode/src/cli/error.ts8
-rw-r--r--packages/opencode/src/config/mcp.ts130
-rw-r--r--packages/opencode/src/lsp/lsp.ts9
-rw-r--r--packages/opencode/src/npm/index.ts13
-rw-r--r--packages/opencode/src/provider/provider.ts6
-rw-r--r--packages/opencode/src/session/session.ts22
-rw-r--r--packages/opencode/src/tool/tool.ts2
-rw-r--r--packages/opencode/src/util/filesystem.ts2
-rw-r--r--packages/opencode/test/config/config.test.ts2
10 files changed, 104 insertions, 92 deletions
diff --git a/packages/opencode/src/cli/cmd/tui/context/kv.tsx b/packages/opencode/src/cli/cmd/tui/context/kv.tsx
index 39e976b0e..803752e76 100644
--- a/packages/opencode/src/cli/cmd/tui/context/kv.tsx
+++ b/packages/opencode/src/cli/cmd/tui/context/kv.tsx
@@ -12,7 +12,7 @@ export const { use: useKV, provider: KVProvider } = createSimpleContext({
const [store, setStore] = createStore<Record<string, any>>()
const filePath = path.join(Global.Path.state, "kv.json")
- Filesystem.readJson(filePath)
+ Filesystem.readJson<Record<string, any>>(filePath)
.then((x) => {
setStore(x)
})
diff --git a/packages/opencode/src/cli/error.ts b/packages/opencode/src/cli/error.ts
index 89b557e2d..f286b5166 100644
--- a/packages/opencode/src/cli/error.ts
+++ b/packages/opencode/src/cli/error.ts
@@ -28,10 +28,10 @@ export function FormatError(input: unknown) {
// ProviderModelNotFoundError: { providerID: string, modelID: string, suggestions?: string[] }
if (NamedError.hasName(input, "ProviderModelNotFoundError")) {
const data = (input as ErrorLike).data
- const suggestions = data?.suggestions as string[] | undefined
+ const suggestions: string[] = Array.isArray(data?.suggestions) ? data.suggestions : []
return [
`Model not found: ${data?.providerID}/${data?.modelID}`,
- ...(Array.isArray(suggestions) && suggestions.length ? ["Did you mean: " + suggestions.join(", ")] : []),
+ ...(suggestions.length ? ["Did you mean: " + suggestions.join(", ")] : []),
`Try: \`opencode models\` to list available models`,
`Or check your config (opencode.json) provider/model names`,
].join("\n")
@@ -64,10 +64,10 @@ export function FormatError(input: unknown) {
const data = (input as ErrorLike).data
const path = data?.path
const message = data?.message
- const issues = data?.issues as Array<{ message: string; path: string[] }> | undefined
+ const issues: Array<{ message: string; path: string[] }> = Array.isArray(data?.issues) ? data.issues : []
return [
`Configuration is invalid${path && path !== "config" ? ` at ${path}` : ""}` + (message ? `: ${message}` : ""),
- ...(issues?.map((issue) => "↳ " + issue.message + " " + issue.path.join(".")) ?? []),
+ ...issues.map((issue) => "↳ " + issue.message + " " + issue.path.join(".")),
].join("\n")
}
diff --git a/packages/opencode/src/config/mcp.ts b/packages/opencode/src/config/mcp.ts
index fb8f8caa4..fda933b42 100644
--- a/packages/opencode/src/config/mcp.ts
+++ b/packages/opencode/src/config/mcp.ts
@@ -1,70 +1,70 @@
import z from "zod"
-export namespace ConfigMCP {
- export const Local = z
- .object({
- type: z.literal("local").describe("Type of MCP server connection"),
- command: z.string().array().describe("Command and arguments to run the MCP server"),
- environment: z
- .record(z.string(), z.string())
- .optional()
- .describe("Environment variables to set when running the MCP server"),
- enabled: z.boolean().optional().describe("Enable or disable the MCP server on startup"),
- timeout: z
- .number()
- .int()
- .positive()
- .optional()
- .describe("Timeout in ms for MCP server requests. Defaults to 5000 (5 seconds) if not specified."),
- })
- .strict()
- .meta({
- ref: "McpLocalConfig",
- })
+export const Local = z
+ .object({
+ type: z.literal("local").describe("Type of MCP server connection"),
+ command: z.string().array().describe("Command and arguments to run the MCP server"),
+ environment: z
+ .record(z.string(), z.string())
+ .optional()
+ .describe("Environment variables to set when running the MCP server"),
+ enabled: z.boolean().optional().describe("Enable or disable the MCP server on startup"),
+ timeout: z
+ .number()
+ .int()
+ .positive()
+ .optional()
+ .describe("Timeout in ms for MCP server requests. Defaults to 5000 (5 seconds) if not specified."),
+ })
+ .strict()
+ .meta({
+ ref: "McpLocalConfig",
+ })
- export const OAuth = z
- .object({
- clientId: z
- .string()
- .optional()
- .describe("OAuth client ID. If not provided, dynamic client registration (RFC 7591) will be attempted."),
- clientSecret: z.string().optional().describe("OAuth client secret (if required by the authorization server)"),
- scope: z.string().optional().describe("OAuth scopes to request during authorization"),
- redirectUri: z
- .string()
- .optional()
- .describe("OAuth redirect URI (default: http://127.0.0.1:19876/mcp/oauth/callback)."),
- })
- .strict()
- .meta({
- ref: "McpOAuthConfig",
- })
- export type OAuth = z.infer<typeof OAuth>
+export const OAuth = z
+ .object({
+ clientId: z
+ .string()
+ .optional()
+ .describe("OAuth client ID. If not provided, dynamic client registration (RFC 7591) will be attempted."),
+ clientSecret: z.string().optional().describe("OAuth client secret (if required by the authorization server)"),
+ scope: z.string().optional().describe("OAuth scopes to request during authorization"),
+ redirectUri: z
+ .string()
+ .optional()
+ .describe("OAuth redirect URI (default: http://127.0.0.1:19876/mcp/oauth/callback)."),
+ })
+ .strict()
+ .meta({
+ ref: "McpOAuthConfig",
+ })
+export type OAuth = z.infer<typeof OAuth>
- export const Remote = z
- .object({
- type: z.literal("remote").describe("Type of MCP server connection"),
- url: z.string().describe("URL of the remote MCP server"),
- enabled: z.boolean().optional().describe("Enable or disable the MCP server on startup"),
- headers: z.record(z.string(), z.string()).optional().describe("Headers to send with the request"),
- oauth: z
- .union([OAuth, z.literal(false)])
- .optional()
- .describe(
- "OAuth authentication configuration for the MCP server. Set to false to disable OAuth auto-detection.",
- ),
- timeout: z
- .number()
- .int()
- .positive()
- .optional()
- .describe("Timeout in ms for MCP server requests. Defaults to 5000 (5 seconds) if not specified."),
- })
- .strict()
- .meta({
- ref: "McpRemoteConfig",
- })
+export const Remote = z
+ .object({
+ type: z.literal("remote").describe("Type of MCP server connection"),
+ url: z.string().describe("URL of the remote MCP server"),
+ enabled: z.boolean().optional().describe("Enable or disable the MCP server on startup"),
+ headers: z.record(z.string(), z.string()).optional().describe("Headers to send with the request"),
+ oauth: z
+ .union([OAuth, z.literal(false)])
+ .optional()
+ .describe(
+ "OAuth authentication configuration for the MCP server. Set to false to disable OAuth auto-detection.",
+ ),
+ timeout: z
+ .number()
+ .int()
+ .positive()
+ .optional()
+ .describe("Timeout in ms for MCP server requests. Defaults to 5000 (5 seconds) if not specified."),
+ })
+ .strict()
+ .meta({
+ ref: "McpRemoteConfig",
+ })
- export const Info = z.discriminatedUnion("type", [Local, Remote])
- export type Info = z.infer<typeof Info>
-}
+export const Info = z.discriminatedUnion("type", [Local, Remote])
+export type Info = z.infer<typeof Info>
+
+export * as ConfigMCP from "./mcp"
diff --git a/packages/opencode/src/lsp/lsp.ts b/packages/opencode/src/lsp/lsp.ts
index d4d1e7563..d895e7325 100644
--- a/packages/opencode/src/lsp/lsp.ts
+++ b/packages/opencode/src/lsp/lsp.ts
@@ -440,12 +440,11 @@ export const layer = Layer.effect(
const workspaceSymbol = Effect.fn("LSP.workspaceSymbol")(function* (query: string) {
const results = yield* runAll((client) =>
client.connection
- .sendRequest("workspace/symbol", { query })
- .then((result: any) => result.filter((x: Symbol) => kinds.includes(x.kind)))
- .then((result: any) => result.slice(0, 10))
- .catch(() => []),
+ .sendRequest<Symbol[]>("workspace/symbol", { query })
+ .then((result) => result.filter((x) => kinds.includes(x.kind)).slice(0, 10))
+ .catch(() => [] as Symbol[]),
)
- return results.flat() as Symbol[]
+ return results.flat()
})
const prepareCallHierarchy = Effect.fn("LSP.prepareCallHierarchy")(function* (input: LocInput) {
diff --git a/packages/opencode/src/npm/index.ts b/packages/opencode/src/npm/index.ts
index 174df1297..425b27f42 100644
--- a/packages/opencode/src/npm/index.ts
+++ b/packages/opencode/src/npm/index.ts
@@ -124,8 +124,17 @@ export async function install(dir: string) {
return
}
- const pkg = await Filesystem.readJson(path.join(dir, "package.json")).catch(() => ({}))
- const lock = await Filesystem.readJson(path.join(dir, "package-lock.json")).catch(() => ({}))
+ type PackageDeps = Record<string, string>
+ type PackageJson = {
+ dependencies?: PackageDeps
+ devDependencies?: PackageDeps
+ peerDependencies?: PackageDeps
+ optionalDependencies?: PackageDeps
+ }
+ const pkg: PackageJson = await Filesystem.readJson<PackageJson>(path.join(dir, "package.json")).catch(() => ({}))
+ const lock: { packages?: Record<string, PackageJson> } = await Filesystem.readJson<{
+ packages?: Record<string, PackageJson>
+ }>(path.join(dir, "package-lock.json")).catch(() => ({}))
const declared = new Set([
...Object.keys(pkg.dependencies || {}),
diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts
index 43ae9a5e9..a7297634e 100644
--- a/packages/opencode/src/provider/provider.ts
+++ b/packages/opencode/src/provider/provider.ts
@@ -547,12 +547,14 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
},
async getModel(sdk: any, modelID: string, options?: Record<string, any>) {
if (modelID.startsWith("duo-workflow-")) {
- const workflowRef = options?.workflowRef as string | undefined
+ const workflowRef = typeof options?.workflowRef === "string" ? options.workflowRef : undefined
// Use the static mapping if it exists, otherwise use duo-workflow with selectedModelRef
const sdkModelID = isWorkflowModel(modelID) ? modelID : "duo-workflow"
+ const workflowDefinition =
+ typeof options?.workflowDefinition === "string" ? options.workflowDefinition : undefined
const model = sdk.workflowChat(sdkModelID, {
featureFlags,
- workflowDefinition: options?.workflowDefinition as string | undefined,
+ workflowDefinition,
})
if (workflowRef) {
model.selectedModelRef = workflowRef
diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts
index 8c5fc29e4..a453b1981 100644
--- a/packages/opencode/src/session/session.ts
+++ b/packages/opencode/src/session/session.ts
@@ -272,16 +272,18 @@ export const getUsage = (input: { model: Provider.Model; usage: LanguageModelUsa
input.usage.inputTokenDetails?.cacheReadTokens ?? input.usage.cachedInputTokens ?? 0,
)
const cacheWriteInputTokens = safe(
- (input.usage.inputTokenDetails?.cacheWriteTokens ??
- input.metadata?.["anthropic"]?.["cacheCreationInputTokens"] ??
- // google-vertex-anthropic returns metadata under "vertex" key
- // (AnthropicMessagesLanguageModel custom provider key from 'vertex.anthropic.messages')
- input.metadata?.["vertex"]?.["cacheCreationInputTokens"] ??
- // @ts-expect-error
- input.metadata?.["bedrock"]?.["usage"]?.["cacheWriteInputTokens"] ??
- // @ts-expect-error
- input.metadata?.["venice"]?.["usage"]?.["cacheCreationInputTokens"] ??
- 0) as number,
+ Number(
+ input.usage.inputTokenDetails?.cacheWriteTokens ??
+ input.metadata?.["anthropic"]?.["cacheCreationInputTokens"] ??
+ // google-vertex-anthropic returns metadata under "vertex" key
+ // (AnthropicMessagesLanguageModel custom provider key from 'vertex.anthropic.messages')
+ input.metadata?.["vertex"]?.["cacheCreationInputTokens"] ??
+ // @ts-expect-error
+ input.metadata?.["bedrock"]?.["usage"]?.["cacheWriteInputTokens"] ??
+ // @ts-expect-error
+ input.metadata?.["venice"]?.["usage"]?.["cacheCreationInputTokens"] ??
+ 0,
+ ),
)
// AI SDK v6 normalized inputTokens to include cached tokens across all providers
diff --git a/packages/opencode/src/tool/tool.ts b/packages/opencode/src/tool/tool.ts
index 0ea0435fb..179149afd 100644
--- a/packages/opencode/src/tool/tool.ts
+++ b/packages/opencode/src/tool/tool.ts
@@ -19,7 +19,7 @@ export type Context<M extends Metadata = Metadata> = {
agent: string
abort: AbortSignal
callID?: string
- extra?: { [key: string]: any }
+ extra?: { [key: string]: unknown }
messages: MessageV2.WithParts[]
metadata(input: { title?: string; metadata?: M }): Effect.Effect<void>
ask(input: Omit<Permission.Request, "id" | "sessionID" | "tool">): Effect.Effect<void>
diff --git a/packages/opencode/src/util/filesystem.ts b/packages/opencode/src/util/filesystem.ts
index 3ff2c6e3f..6c4d45522 100644
--- a/packages/opencode/src/util/filesystem.ts
+++ b/packages/opencode/src/util/filesystem.ts
@@ -39,7 +39,7 @@ export async function readText(p: string): Promise<string> {
return readFile(p, "utf-8")
}
-export async function readJson<T = any>(p: string): Promise<T> {
+export async function readJson<T = unknown>(p: string): Promise<T> {
return JSON.parse(await readFile(p, "utf-8"))
}
diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts
index c41f395e5..3e90842e1 100644
--- a/packages/opencode/test/config/config.test.ts
+++ b/packages/opencode/test/config/config.test.ts
@@ -757,7 +757,7 @@ test("updates config and writes to file", async () => {
const newConfig = { model: "updated/model" }
await save(newConfig as any)
- const writtenConfig = await Filesystem.readJson(path.join(tmp.path, "config.json"))
+ const writtenConfig = await Filesystem.readJson<{ model: string }>(path.join(tmp.path, "config.json"))
expect(writtenConfig.model).toBe("updated/model")
},
})