* Copyright (c) 2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import fs from "fs"
import { createRequire } from "node:module"
import os from "node:os"
import path from "path"
import { findDevEcoHome } from "./env"
import * as Log from "@opencode-ai/core/util/log"
import { DEVECO_PROVIDER_CONFIG } from "@/plugin/deveco-models"
import { devecoAuth, ACCESS_TOKEN_EXPIRES_MS } from "@/plugin/deveco"
function addon() {
const execDir = path.dirname(process.execPath)
const vendorMarker = path.join(execDir, "..", "vendor", "mcp-bridge-native", "package.json")
if (fs.existsSync(vendorMarker)) {
const r = createRequire(vendorMarker)
return r("./napi_bridge.node") as typeof import("@deveco-codegenie/mcp-bridge")
}
const legacyMarker = path.join(execDir, "mcp-bridge-native", "package.json")
if (fs.existsSync(legacyMarker)) {
const r = createRequire(legacyMarker)
return r("./napi_bridge.node") as typeof import("@deveco-codegenie/mcp-bridge")
}
return createRequire(import.meta.url)("@deveco-codegenie/mcp-bridge")
}
const bridge = addon()
const log = Log.create({ service: "harmony-napi" })
let gate: Promise<void> = Promise.resolve()
let bound = ""
export async function resolveUIVerifyParams(worktree: string) {
try {
const { Effect } = await import("effect")
const { AppRuntime } = await import("@/effect/app-runtime")
const { Config } = await import("@/config/config")
const { Provider } = await import("@/provider/provider")
const { InstanceStore } = await import("@/project/instance-store")
const result = await AppRuntime.runPromise(
InstanceStore.Service.use((store) =>
store.provide({ directory: worktree }, Effect.gen(function* () {
const config = yield* Config.Service
const cfg = yield* config.get()
const modelStr = cfg.agent?.["ui_verification"]?.model
if (!modelStr) {
return null as unknown as { baseURL: string | null; apiKey: string | null; modelName: string | null }
}
const { providerID, modelID } = Provider.parseModel(modelStr)
const svc = yield* Provider.Service
const provider = yield* svc.getProvider(providerID).pipe(Effect.catch(() => Effect.succeed(null)))
const model = yield* svc.getModel(providerID, modelID).pipe(Effect.catch(() => Effect.succeed(null)))
if (provider && model) {
const baseURL = (provider.options?.baseURL as string | undefined) ?? model.api.url ?? null
const apiKey = provider.key ?? (provider.options?.apiKey as string | undefined) ?? null
const modelName = model.api.id ?? null
return { baseURL, apiKey, modelName }
}
return null as unknown as { baseURL: string | null; apiKey: string | null; modelName: string | null }
}))
)
)
if (result) { return result }
} catch {}
if (process.env.UI_VERIFY_BASE_URL && process.env.UI_VERIFY_API_KEY && process.env.UI_VERIFY_MODEL_NAME) {
return {
baseURL: process.env.UI_VERIFY_BASE_URL ?? null,
apiKey: process.env.UI_VERIFY_API_KEY ?? null,
modelName: process.env.UI_VERIFY_MODEL_NAME ?? null,
}
}
try {
const { Effect } = await import("effect")
const { AppRuntime } = await import("@/effect/app-runtime")
const { Auth } = await import("@/auth")
const getAuth = () => AppRuntime.runPromise(Effect.gen(function* () { const svc = yield* Auth.Service; return yield* svc.get("deveco") })).catch(() => undefined)
let auth = await getAuth()
if (auth instanceof Auth.Oauth && auth.access) {
if (!auth.access || auth.expires < Date.now()) {
const tokens = await devecoAuth.refreshToken()
if (tokens) {
await AppRuntime.runPromise(Effect.gen(function* () {
const svc = yield* Auth.Service
yield* svc.set("deveco", new Auth.Oauth({
type: "oauth",
access: tokens.accessToken,
refresh: tokens.refreshToken,
expires: Date.now() + ACCESS_TOKEN_EXPIRES_MS,
}))
}))
auth = await getAuth()
}
}
if (auth instanceof Auth.Oauth && auth.access && auth.expires > Date.now()) {
return {
baseURL: DEVECO_PROVIDER_CONFIG.api + "/no-stream",
apiKey: auth.access,
modelName: "Qwen2.5-VL-72B",
}
}
}
} catch {}
return {
baseURL: null,
apiKey: null,
modelName: null,
}
}
async function runInit(worktree: string) {
const devecoHome = await findDevEcoHome()
if (!devecoHome) {
throw new Error("DevEco Studio not found. Please set DEVECO_HOME to your DevEco installation directory.")
}
const logDir = path.join(process.env.XDG_DATA_HOME || path.join(os.homedir(), '.local', 'share', 'deveco'), 'log', 'deveco-mcp')
fs.mkdirSync(logDir, { recursive: true })
const { baseURL, apiKey, modelName } = await resolveUIVerifyParams(worktree)
log.info("ui_verification model", { baseURL, modelName })
await bridge.init(logDir, worktree, devecoHome, baseURL, apiKey, modelName)
}
export async function ensureInitialized(worktree: string): Promise<void> {
gate = gate.then(async () => {
if (bound === worktree) {
const { Effect } = await import("effect")
const { AppRuntime } = await import("@/effect/app-runtime")
const { Auth } = await import("@/auth")
const auth = await AppRuntime.runPromise(Effect.gen(function* () { const svc = yield* Auth.Service; return yield* svc.get("deveco") })).catch(() => undefined)
if (!(auth instanceof Auth.Oauth) || auth.expires > Date.now()) return
}
bound = worktree
await runInit(worktree)
})
await gate
}
export async function callHarmonyNapiTool(params: {
worktree: string
toolName: string
args: Record<string, unknown>
}): Promise<unknown> {
await ensureInitialized(params.worktree)
const resultJson = await bridge.callTool(params.toolName, JSON.stringify(params.args))
return JSON.parse(resultJson as string)
}
export async function listTools(worktree: string): Promise<unknown> {
await ensureInitialized(worktree)
return JSON.parse(bridge.listTools())
}
export async function callTool(
worktree: string,
toolName: string,
args: Record<string, unknown>,
): Promise<unknown> {
return callHarmonyNapiTool({ worktree, toolName, args })
}
export async function napiBridgeStop(): Promise<void> {
try {
await bridge?.stop?.();
} catch (error) {
}
}