refactor: unwrap ConfigMCP namespace + self-reexport (#22948)
This commit is contained in:
parent
c03fa36257
commit
5d47ea0918
10 changed files with 104 additions and 92 deletions
|
|
@ -12,7 +12,7 @@ export const { use: useKV, provider: KVProvider } = createSimpleContext({
|
||||||
const [store, setStore] = createStore<Record<string, any>>()
|
const [store, setStore] = createStore<Record<string, any>>()
|
||||||
const filePath = path.join(Global.Path.state, "kv.json")
|
const filePath = path.join(Global.Path.state, "kv.json")
|
||||||
|
|
||||||
Filesystem.readJson(filePath)
|
Filesystem.readJson<Record<string, any>>(filePath)
|
||||||
.then((x) => {
|
.then((x) => {
|
||||||
setStore(x)
|
setStore(x)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -28,10 +28,10 @@ export function FormatError(input: unknown) {
|
||||||
// ProviderModelNotFoundError: { providerID: string, modelID: string, suggestions?: string[] }
|
// ProviderModelNotFoundError: { providerID: string, modelID: string, suggestions?: string[] }
|
||||||
if (NamedError.hasName(input, "ProviderModelNotFoundError")) {
|
if (NamedError.hasName(input, "ProviderModelNotFoundError")) {
|
||||||
const data = (input as ErrorLike).data
|
const data = (input as ErrorLike).data
|
||||||
const suggestions = data?.suggestions as string[] | undefined
|
const suggestions: string[] = Array.isArray(data?.suggestions) ? data.suggestions : []
|
||||||
return [
|
return [
|
||||||
`Model not found: ${data?.providerID}/${data?.modelID}`,
|
`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`,
|
`Try: \`opencode models\` to list available models`,
|
||||||
`Or check your config (opencode.json) provider/model names`,
|
`Or check your config (opencode.json) provider/model names`,
|
||||||
].join("\n")
|
].join("\n")
|
||||||
|
|
@ -64,10 +64,10 @@ export function FormatError(input: unknown) {
|
||||||
const data = (input as ErrorLike).data
|
const data = (input as ErrorLike).data
|
||||||
const path = data?.path
|
const path = data?.path
|
||||||
const message = data?.message
|
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 [
|
return [
|
||||||
`Configuration is invalid${path && path !== "config" ? ` at ${path}` : ""}` + (message ? `: ${message}` : ""),
|
`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")
|
].join("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,70 +1,70 @@
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
|
|
||||||
export namespace ConfigMCP {
|
export const Local = z
|
||||||
export const Local = z
|
.object({
|
||||||
.object({
|
type: z.literal("local").describe("Type of MCP server connection"),
|
||||||
type: z.literal("local").describe("Type of MCP server connection"),
|
command: z.string().array().describe("Command and arguments to run the MCP server"),
|
||||||
command: z.string().array().describe("Command and arguments to run the MCP server"),
|
environment: z
|
||||||
environment: z
|
.record(z.string(), z.string())
|
||||||
.record(z.string(), z.string())
|
.optional()
|
||||||
.optional()
|
.describe("Environment variables to set when running the MCP server"),
|
||||||
.describe("Environment variables to set when running the MCP server"),
|
enabled: z.boolean().optional().describe("Enable or disable the MCP server on startup"),
|
||||||
enabled: z.boolean().optional().describe("Enable or disable the MCP server on startup"),
|
timeout: z
|
||||||
timeout: z
|
.number()
|
||||||
.number()
|
.int()
|
||||||
.int()
|
.positive()
|
||||||
.positive()
|
.optional()
|
||||||
.optional()
|
.describe("Timeout in ms for MCP server requests. Defaults to 5000 (5 seconds) if not specified."),
|
||||||
.describe("Timeout in ms for MCP server requests. Defaults to 5000 (5 seconds) if not specified."),
|
})
|
||||||
})
|
.strict()
|
||||||
.strict()
|
.meta({
|
||||||
.meta({
|
ref: "McpLocalConfig",
|
||||||
ref: "McpLocalConfig",
|
})
|
||||||
})
|
|
||||||
|
|
||||||
export const OAuth = z
|
export const OAuth = z
|
||||||
.object({
|
.object({
|
||||||
clientId: z
|
clientId: z
|
||||||
.string()
|
.string()
|
||||||
.optional()
|
.optional()
|
||||||
.describe("OAuth client ID. If not provided, dynamic client registration (RFC 7591) will be attempted."),
|
.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)"),
|
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"),
|
scope: z.string().optional().describe("OAuth scopes to request during authorization"),
|
||||||
redirectUri: z
|
redirectUri: z
|
||||||
.string()
|
.string()
|
||||||
.optional()
|
.optional()
|
||||||
.describe("OAuth redirect URI (default: http://127.0.0.1:19876/mcp/oauth/callback)."),
|
.describe("OAuth redirect URI (default: http://127.0.0.1:19876/mcp/oauth/callback)."),
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
.meta({
|
.meta({
|
||||||
ref: "McpOAuthConfig",
|
ref: "McpOAuthConfig",
|
||||||
})
|
})
|
||||||
export type OAuth = z.infer<typeof OAuth>
|
export type OAuth = z.infer<typeof OAuth>
|
||||||
|
|
||||||
export const Remote = z
|
export const Remote = z
|
||||||
.object({
|
.object({
|
||||||
type: z.literal("remote").describe("Type of MCP server connection"),
|
type: z.literal("remote").describe("Type of MCP server connection"),
|
||||||
url: z.string().describe("URL of the remote MCP server"),
|
url: z.string().describe("URL of the remote MCP server"),
|
||||||
enabled: z.boolean().optional().describe("Enable or disable the MCP server on startup"),
|
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"),
|
headers: z.record(z.string(), z.string()).optional().describe("Headers to send with the request"),
|
||||||
oauth: z
|
oauth: z
|
||||||
.union([OAuth, z.literal(false)])
|
.union([OAuth, z.literal(false)])
|
||||||
.optional()
|
.optional()
|
||||||
.describe(
|
.describe(
|
||||||
"OAuth authentication configuration for the MCP server. Set to false to disable OAuth auto-detection.",
|
"OAuth authentication configuration for the MCP server. Set to false to disable OAuth auto-detection.",
|
||||||
),
|
),
|
||||||
timeout: z
|
timeout: z
|
||||||
.number()
|
.number()
|
||||||
.int()
|
.int()
|
||||||
.positive()
|
.positive()
|
||||||
.optional()
|
.optional()
|
||||||
.describe("Timeout in ms for MCP server requests. Defaults to 5000 (5 seconds) if not specified."),
|
.describe("Timeout in ms for MCP server requests. Defaults to 5000 (5 seconds) if not specified."),
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
.meta({
|
.meta({
|
||||||
ref: "McpRemoteConfig",
|
ref: "McpRemoteConfig",
|
||||||
})
|
})
|
||||||
|
|
||||||
export const Info = z.discriminatedUnion("type", [Local, Remote])
|
export const Info = z.discriminatedUnion("type", [Local, Remote])
|
||||||
export type Info = z.infer<typeof Info>
|
export type Info = z.infer<typeof Info>
|
||||||
}
|
|
||||||
|
export * as ConfigMCP from "./mcp"
|
||||||
|
|
|
||||||
|
|
@ -440,12 +440,11 @@ export const layer = Layer.effect(
|
||||||
const workspaceSymbol = Effect.fn("LSP.workspaceSymbol")(function* (query: string) {
|
const workspaceSymbol = Effect.fn("LSP.workspaceSymbol")(function* (query: string) {
|
||||||
const results = yield* runAll((client) =>
|
const results = yield* runAll((client) =>
|
||||||
client.connection
|
client.connection
|
||||||
.sendRequest("workspace/symbol", { query })
|
.sendRequest<Symbol[]>("workspace/symbol", { query })
|
||||||
.then((result: any) => result.filter((x: Symbol) => kinds.includes(x.kind)))
|
.then((result) => result.filter((x) => kinds.includes(x.kind)).slice(0, 10))
|
||||||
.then((result: any) => result.slice(0, 10))
|
.catch(() => [] as Symbol[]),
|
||||||
.catch(() => []),
|
|
||||||
)
|
)
|
||||||
return results.flat() as Symbol[]
|
return results.flat()
|
||||||
})
|
})
|
||||||
|
|
||||||
const prepareCallHierarchy = Effect.fn("LSP.prepareCallHierarchy")(function* (input: LocInput) {
|
const prepareCallHierarchy = Effect.fn("LSP.prepareCallHierarchy")(function* (input: LocInput) {
|
||||||
|
|
|
||||||
|
|
@ -124,8 +124,17 @@ export async function install(dir: string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const pkg = await Filesystem.readJson(path.join(dir, "package.json")).catch(() => ({}))
|
type PackageDeps = Record<string, string>
|
||||||
const lock = await Filesystem.readJson(path.join(dir, "package-lock.json")).catch(() => ({}))
|
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([
|
const declared = new Set([
|
||||||
...Object.keys(pkg.dependencies || {}),
|
...Object.keys(pkg.dependencies || {}),
|
||||||
|
|
|
||||||
|
|
@ -547,12 +547,14 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
|
||||||
},
|
},
|
||||||
async getModel(sdk: any, modelID: string, options?: Record<string, any>) {
|
async getModel(sdk: any, modelID: string, options?: Record<string, any>) {
|
||||||
if (modelID.startsWith("duo-workflow-")) {
|
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
|
// Use the static mapping if it exists, otherwise use duo-workflow with selectedModelRef
|
||||||
const sdkModelID = isWorkflowModel(modelID) ? modelID : "duo-workflow"
|
const sdkModelID = isWorkflowModel(modelID) ? modelID : "duo-workflow"
|
||||||
|
const workflowDefinition =
|
||||||
|
typeof options?.workflowDefinition === "string" ? options.workflowDefinition : undefined
|
||||||
const model = sdk.workflowChat(sdkModelID, {
|
const model = sdk.workflowChat(sdkModelID, {
|
||||||
featureFlags,
|
featureFlags,
|
||||||
workflowDefinition: options?.workflowDefinition as string | undefined,
|
workflowDefinition,
|
||||||
})
|
})
|
||||||
if (workflowRef) {
|
if (workflowRef) {
|
||||||
model.selectedModelRef = workflowRef
|
model.selectedModelRef = workflowRef
|
||||||
|
|
|
||||||
|
|
@ -272,16 +272,18 @@ export const getUsage = (input: { model: Provider.Model; usage: LanguageModelUsa
|
||||||
input.usage.inputTokenDetails?.cacheReadTokens ?? input.usage.cachedInputTokens ?? 0,
|
input.usage.inputTokenDetails?.cacheReadTokens ?? input.usage.cachedInputTokens ?? 0,
|
||||||
)
|
)
|
||||||
const cacheWriteInputTokens = safe(
|
const cacheWriteInputTokens = safe(
|
||||||
(input.usage.inputTokenDetails?.cacheWriteTokens ??
|
Number(
|
||||||
input.metadata?.["anthropic"]?.["cacheCreationInputTokens"] ??
|
input.usage.inputTokenDetails?.cacheWriteTokens ??
|
||||||
// google-vertex-anthropic returns metadata under "vertex" key
|
input.metadata?.["anthropic"]?.["cacheCreationInputTokens"] ??
|
||||||
// (AnthropicMessagesLanguageModel custom provider key from 'vertex.anthropic.messages')
|
// google-vertex-anthropic returns metadata under "vertex" key
|
||||||
input.metadata?.["vertex"]?.["cacheCreationInputTokens"] ??
|
// (AnthropicMessagesLanguageModel custom provider key from 'vertex.anthropic.messages')
|
||||||
// @ts-expect-error
|
input.metadata?.["vertex"]?.["cacheCreationInputTokens"] ??
|
||||||
input.metadata?.["bedrock"]?.["usage"]?.["cacheWriteInputTokens"] ??
|
// @ts-expect-error
|
||||||
// @ts-expect-error
|
input.metadata?.["bedrock"]?.["usage"]?.["cacheWriteInputTokens"] ??
|
||||||
input.metadata?.["venice"]?.["usage"]?.["cacheCreationInputTokens"] ??
|
// @ts-expect-error
|
||||||
0) as number,
|
input.metadata?.["venice"]?.["usage"]?.["cacheCreationInputTokens"] ??
|
||||||
|
0,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
// AI SDK v6 normalized inputTokens to include cached tokens across all providers
|
// AI SDK v6 normalized inputTokens to include cached tokens across all providers
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ export type Context<M extends Metadata = Metadata> = {
|
||||||
agent: string
|
agent: string
|
||||||
abort: AbortSignal
|
abort: AbortSignal
|
||||||
callID?: string
|
callID?: string
|
||||||
extra?: { [key: string]: any }
|
extra?: { [key: string]: unknown }
|
||||||
messages: MessageV2.WithParts[]
|
messages: MessageV2.WithParts[]
|
||||||
metadata(input: { title?: string; metadata?: M }): Effect.Effect<void>
|
metadata(input: { title?: string; metadata?: M }): Effect.Effect<void>
|
||||||
ask(input: Omit<Permission.Request, "id" | "sessionID" | "tool">): Effect.Effect<void>
|
ask(input: Omit<Permission.Request, "id" | "sessionID" | "tool">): Effect.Effect<void>
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ export async function readText(p: string): Promise<string> {
|
||||||
return readFile(p, "utf-8")
|
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"))
|
return JSON.parse(await readFile(p, "utf-8"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -757,7 +757,7 @@ test("updates config and writes to file", async () => {
|
||||||
const newConfig = { model: "updated/model" }
|
const newConfig = { model: "updated/model" }
|
||||||
await save(newConfig as any)
|
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")
|
expect(writtenConfig.model).toBe("updated/model")
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue