Merge pull request #3862 from code-yeongyu/codex/cpu-usage-optimization
[codex] perf(plugin): trim cold init CPU overhead
This commit is contained in:
+1
-4
@@ -151,9 +151,6 @@ describe("oh-my-openagent plugin module", () => {
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
gateways: {},
|
gateways: {},
|
||||||
hooks: {},
|
hooks: {},
|
||||||
replyListener: {
|
|
||||||
discordBotToken: "discord-token",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
mockLoadPluginConfig.mockReturnValue({
|
mockLoadPluginConfig.mockReturnValue({
|
||||||
openclaw: openclawConfig,
|
openclaw: openclawConfig,
|
||||||
@@ -182,7 +179,7 @@ describe("oh-my-openagent plugin module", () => {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
expect(mockInitializeOpenClaw).not.toHaveBeenCalled()
|
expect(mockInitializeOpenClaw).not.toHaveBeenCalled()
|
||||||
})
|
}, { timeout: 15000 })
|
||||||
|
|
||||||
it("exports a V1 PluginModule shape with id and server", () => {
|
it("exports a V1 PluginModule shape with id and server", () => {
|
||||||
// given the plugin module is loaded
|
// given the plugin module is loaded
|
||||||
|
|||||||
@@ -132,6 +132,54 @@ describe("opencode-version", () => {
|
|||||||
// then returns null without executing command
|
// then returns null without executing command
|
||||||
expect(result).toBe(null)
|
expect(result).toBe(null)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("reads adjacent package version before executing opencode binary", () => {
|
||||||
|
// given an opencode package next to the resolved binary
|
||||||
|
const calls: string[] = []
|
||||||
|
|
||||||
|
// when getting version
|
||||||
|
const result = getOpenCodeVersion({
|
||||||
|
getBinaryPath: () => "/tmp/opencode-ai/bin/opencode",
|
||||||
|
realpath: (filePath) => filePath,
|
||||||
|
exists: (filePath) => filePath === "/tmp/opencode-ai/package.json",
|
||||||
|
readText: (filePath) => {
|
||||||
|
calls.push(`read:${filePath}`)
|
||||||
|
return JSON.stringify({ name: "opencode-ai", version: "1.14.41" })
|
||||||
|
},
|
||||||
|
execCommand: () => {
|
||||||
|
calls.push("exec")
|
||||||
|
return "1.14.41"
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// then the version is resolved without spawning the CLI
|
||||||
|
expect(result).toBe("1.14.41")
|
||||||
|
expect(calls).toEqual(["read:/tmp/opencode-ai/package.json"])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("falls back to opencode binary when package version is unavailable", () => {
|
||||||
|
// given no adjacent package version can be read
|
||||||
|
const calls: string[] = []
|
||||||
|
|
||||||
|
// when getting version
|
||||||
|
const result = getOpenCodeVersion({
|
||||||
|
getBinaryPath: () => "/tmp/custom-opencode",
|
||||||
|
realpath: (filePath) => filePath,
|
||||||
|
exists: () => false,
|
||||||
|
readText: () => {
|
||||||
|
calls.push("read")
|
||||||
|
return ""
|
||||||
|
},
|
||||||
|
execCommand: () => {
|
||||||
|
calls.push("exec")
|
||||||
|
return "opencode 1.14.42"
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// then the original CLI fallback remains intact
|
||||||
|
expect(result).toBe("1.14.42")
|
||||||
|
expect(calls).toEqual(["exec"])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("isOpenCodeVersionAtLeast", () => {
|
describe("isOpenCodeVersionAtLeast", () => {
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { execSync } from "child_process"
|
import { execSync } from "child_process"
|
||||||
|
import { existsSync, readFileSync, realpathSync } from "fs"
|
||||||
|
import { dirname, join } from "path"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Minimum OpenCode version required for this plugin.
|
* Minimum OpenCode version required for this plugin.
|
||||||
@@ -24,6 +26,38 @@ export const OPENCODE_SQLITE_VERSION = "1.1.53"
|
|||||||
const NOT_CACHED = Symbol("NOT_CACHED")
|
const NOT_CACHED = Symbol("NOT_CACHED")
|
||||||
let cachedVersion: string | null | typeof NOT_CACHED = NOT_CACHED
|
let cachedVersion: string | null | typeof NOT_CACHED = NOT_CACHED
|
||||||
|
|
||||||
|
type RuntimeWithBun = typeof globalThis & {
|
||||||
|
Bun?: {
|
||||||
|
which(binary: string): string | null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExecCommandOptions = {
|
||||||
|
encoding: "utf-8"
|
||||||
|
timeout: number
|
||||||
|
stdio: ["pipe", "pipe", "pipe"]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type OpenCodeVersionDeps = {
|
||||||
|
execCommand: (command: string, options: ExecCommandOptions) => string
|
||||||
|
getBinaryPath: () => string | null
|
||||||
|
exists: (filePath: string) => boolean
|
||||||
|
realpath: (filePath: string) => string
|
||||||
|
readText: (filePath: string) => string
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultDeps: OpenCodeVersionDeps = {
|
||||||
|
execCommand: (command, options) => execSync(command, options),
|
||||||
|
getBinaryPath: () => {
|
||||||
|
const envPath = process.env.OPENCODE_BIN_PATH
|
||||||
|
if (envPath) return envPath
|
||||||
|
return (globalThis as RuntimeWithBun).Bun?.which("opencode") ?? null
|
||||||
|
},
|
||||||
|
exists: existsSync,
|
||||||
|
realpath: realpathSync,
|
||||||
|
readText: (filePath) => readFileSync(filePath, "utf-8"),
|
||||||
|
}
|
||||||
|
|
||||||
export function parseVersion(version: string): number[] {
|
export function parseVersion(version: string): number[] {
|
||||||
const cleaned = version.replace(/^v/, "").split("-")[0]
|
const cleaned = version.replace(/^v/, "").split("-")[0]
|
||||||
return cleaned.split(".").map((n) => parseInt(n, 10) || 0)
|
return cleaned.split(".").map((n) => parseInt(n, 10) || 0)
|
||||||
@@ -43,14 +77,54 @@ export function compareVersions(a: string, b: string): -1 | 0 | 1 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||||
|
return typeof value === "object" && value !== null
|
||||||
|
}
|
||||||
|
|
||||||
export function getOpenCodeVersion(): string | null {
|
function parsePackageVersion(content: string): string | null {
|
||||||
|
try {
|
||||||
|
const parsed: unknown = JSON.parse(content)
|
||||||
|
if (!isRecord(parsed)) return null
|
||||||
|
|
||||||
|
const name = parsed.name
|
||||||
|
const version = parsed.version
|
||||||
|
if (typeof name !== "string" || !name.includes("opencode")) return null
|
||||||
|
if (typeof version !== "string" || version.length === 0) return null
|
||||||
|
|
||||||
|
return version
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPackageVersionFromBinary(binaryPath: string, deps: OpenCodeVersionDeps): string | null {
|
||||||
|
try {
|
||||||
|
const realBinaryPath = deps.realpath(binaryPath)
|
||||||
|
const packagePath = join(dirname(dirname(realBinaryPath)), "package.json")
|
||||||
|
if (!deps.exists(packagePath)) return null
|
||||||
|
return parsePackageVersion(deps.readText(packagePath))
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOpenCodeVersion(deps: Partial<OpenCodeVersionDeps> = {}): string | null {
|
||||||
if (cachedVersion !== NOT_CACHED) {
|
if (cachedVersion !== NOT_CACHED) {
|
||||||
return cachedVersion
|
return cachedVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resolvedDeps: OpenCodeVersionDeps = { ...defaultDeps, ...deps }
|
||||||
|
const binaryPath = resolvedDeps.getBinaryPath()
|
||||||
|
if (binaryPath) {
|
||||||
|
const packageVersion = getPackageVersionFromBinary(binaryPath, resolvedDeps)
|
||||||
|
if (packageVersion) {
|
||||||
|
cachedVersion = packageVersion
|
||||||
|
return cachedVersion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = execSync("opencode --version", {
|
const result = resolvedDeps.execCommand("opencode --version", {
|
||||||
encoding: "utf-8",
|
encoding: "utf-8",
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
stdio: ["pipe", "pipe", "pipe"],
|
stdio: ["pipe", "pipe", "pipe"],
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type { ToolContext } from "@opencode-ai/plugin/tool"
|
|||||||
import type { LoadedSkill } from "../../features/opencode-skill-loader/types"
|
import type { LoadedSkill } from "../../features/opencode-skill-loader/types"
|
||||||
import * as skillContent from "../../features/opencode-skill-loader/skill-content"
|
import * as skillContent from "../../features/opencode-skill-loader/skill-content"
|
||||||
import * as commandDiscovery from "../slashcommand/command-discovery"
|
import * as commandDiscovery from "../slashcommand/command-discovery"
|
||||||
|
import type { CommandInfo } from "../slashcommand/types"
|
||||||
|
|
||||||
const discoverCommandsSync = mock(() => [])
|
const discoverCommandsSync = mock(() => [])
|
||||||
|
|
||||||
@@ -128,4 +129,26 @@ describe("createSkillTool", () => {
|
|||||||
expect(clearSkillCache.mock.calls.length).toBe(baselineClearSkillCacheCalls + 2)
|
expect(clearSkillCache.mock.calls.length).toBe(baselineClearSkillCacheCalls + 2)
|
||||||
expect(getAllSkills.mock.calls.length).toBe(baselineGetAllSkillsCalls + 4)
|
expect(getAllSkills.mock.calls.length).toBe(baselineGetAllSkillsCalls + 4)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("executes precomputed commands without rediscovering commands", async () => {
|
||||||
|
// given
|
||||||
|
const baselineDiscoverCommandsSyncCalls = discoverCommandsSync.mock.calls.length
|
||||||
|
const command: CommandInfo = {
|
||||||
|
name: "seeded-command",
|
||||||
|
metadata: {
|
||||||
|
name: "seeded-command",
|
||||||
|
description: "Seeded command",
|
||||||
|
},
|
||||||
|
content: "Seeded command body",
|
||||||
|
scope: "project",
|
||||||
|
}
|
||||||
|
const skillTool = await createSkillTool({ skills: [], commands: [command] })
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result = await skillTool.execute({ name: "seeded-command" }, mockContext)
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(result).toContain("Seeded command body")
|
||||||
|
expect(discoverCommandsSync.mock.calls.length).toBe(baselineDiscoverCommandsSyncCalls)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ export function createSkillTool(options: SkillLoadOptions = {}): ToolDefinition
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getCommands = (): CommandInfo[] => {
|
const getCommands = (): CommandInfo[] => {
|
||||||
|
if (options.commands) return [...options.commands]
|
||||||
|
|
||||||
return commandDiscovery.discoverCommandsSync(undefined, {
|
return commandDiscovery.discoverCommandsSync(undefined, {
|
||||||
pluginsEnabled: options.pluginsEnabled,
|
pluginsEnabled: options.pluginsEnabled,
|
||||||
enabledPluginsOverride: options.enabledPluginsOverride,
|
enabledPluginsOverride: options.enabledPluginsOverride,
|
||||||
|
|||||||
Reference in New Issue
Block a user