@hiai-gg/hiai-opencode 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +14 -8
- package/AGENTS.md +19 -8
- package/ARCHITECTURE.md +7 -6
- package/LICENSE.md +0 -1
- package/README.md +48 -17
- package/assets/cli/hiai-opencode.mjs +590 -7
- package/assets/mcp/mempalace.mjs +159 -25
- package/config/hiai-opencode.schema.json +82 -148
- package/dist/agents/dynamic-agent-core-sections.d.ts +4 -1
- package/dist/agents/dynamic-agent-prompt-builder.d.ts +1 -1
- package/dist/config/defaults.d.ts +1 -0
- package/dist/config/platform-schema.d.ts +275 -10
- package/dist/config/schema/categories.d.ts +2 -2
- package/dist/config/schema/commands.d.ts +1 -0
- package/dist/config/schema/oh-my-opencode-config.d.ts +1 -3
- package/dist/config/types.d.ts +22 -5
- package/dist/create-tools.d.ts +2 -0
- package/dist/features/builtin-commands/templates/doctor.d.ts +1 -0
- package/dist/features/builtin-commands/types.d.ts +1 -1
- package/dist/features/builtin-skills/skills/hiai-opencode-setup.d.ts +2 -0
- package/dist/features/builtin-skills/skills/index.d.ts +1 -0
- package/dist/index.js +870 -1711
- package/dist/mcp/types.d.ts +1 -1
- package/dist/plugin/tool-registry.d.ts +2 -0
- package/dist/shared/mcp-static-export.d.ts +22 -0
- package/dist/tools/ast-grep/constants.d.ts +1 -1
- package/dist/tools/ast-grep/environment-check.d.ts +1 -5
- package/dist/tools/ast-grep/language-support.d.ts +0 -1
- package/dist/tools/ast-grep/types.d.ts +1 -2
- package/dist/tools/skill-mcp/tools.d.ts +2 -0
- package/hiai-opencode.json +39 -171
- package/package.json +6 -4
- package/src/agents/bob/default.ts +6 -1
- package/src/agents/bob/gpt-pro.ts +1 -0
- package/src/agents/bob.ts +1 -0
- package/src/agents/coder/gpt-codex.ts +1 -0
- package/src/agents/coder/gpt-pro.ts +1 -0
- package/src/agents/coder/gpt.ts +1 -0
- package/src/agents/dynamic-agent-core-sections.ts +36 -0
- package/src/agents/dynamic-agent-prompt-builder.ts +1 -0
- package/src/config/defaults.ts +171 -28
- package/src/config/loader.test.ts +16 -1
- package/src/config/loader.ts +4 -2
- package/src/config/model-slots-and-export.test.ts +55 -0
- package/src/config/platform-schema.ts +37 -5
- package/src/config/schema/commands.ts +1 -0
- package/src/config/schema/oh-my-opencode-config.ts +0 -3
- package/src/config/types.ts +34 -5
- package/src/create-tools.ts +4 -1
- package/src/features/builtin-commands/commands.ts +7 -0
- package/src/features/builtin-commands/templates/doctor.ts +43 -0
- package/src/features/builtin-commands/types.ts +1 -1
- package/src/features/builtin-skills/skills/hiai-opencode-setup.ts +69 -0
- package/src/features/builtin-skills/skills/index.ts +1 -0
- package/src/features/builtin-skills/skills.ts +10 -1
- package/src/index.ts +4 -38
- package/src/lsp/index.ts +1 -0
- package/src/mcp/registry.ts +6 -1
- package/src/plugin/tool-registry.ts +4 -0
- package/src/shared/mcp-static-export.ts +121 -0
- package/src/tools/ast-grep/constants.ts +1 -1
- package/src/tools/ast-grep/environment-check.ts +2 -32
- package/src/tools/ast-grep/language-support.ts +0 -3
- package/src/tools/ast-grep/types.ts +1 -2
- package/src/tools/skill-mcp/tools.test.ts +44 -0
- package/src/tools/skill-mcp/tools.ts +45 -7
- package/dist/ast-grep-napi.win32-x64-msvc-67c0y8nc.node +0 -0
- package/dist/config/loader.test.d.ts +0 -1
- package/dist/config/models.d.ts +0 -13
- package/dist/internals/plugins/websearch-cited/google.d.ts +0 -38
- package/dist/internals/plugins/websearch-cited/index.d.ts +0 -11
- package/dist/internals/plugins/websearch-cited/openai.d.ts +0 -9
- package/dist/internals/plugins/websearch-cited/openrouter.d.ts +0 -2
- package/dist/internals/plugins/websearch-cited/types.d.ts +0 -5
- package/src/internals/plugins/websearch-cited/LICENSE +0 -214
- package/src/internals/plugins/websearch-cited/codex_prompt.txt +0 -79
- package/src/internals/plugins/websearch-cited/google.ts +0 -749
- package/src/internals/plugins/websearch-cited/index.ts +0 -301
- package/src/internals/plugins/websearch-cited/openai.ts +0 -407
- package/src/internals/plugins/websearch-cited/openrouter.ts +0 -190
- package/src/internals/plugins/websearch-cited/types.ts +0 -7
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { BuiltinSkill } from "../types"
|
|
2
|
+
|
|
3
|
+
export const hiaiOpencodeSetupSkill: BuiltinSkill = {
|
|
4
|
+
name: "hiai-opencode-setup",
|
|
5
|
+
description:
|
|
6
|
+
"Use when install/setup/onboarding or MCP debug mentions: install, setup, bootstrap, doctor, mcp-status, MCP not found, mcp list empty, MemPalace, RAG, firecrawl, stitch, sequential-thinking, playwright, DCP, agents, skills, or LSP.",
|
|
7
|
+
template: `# hiai-opencode Setup And Runtime Operations
|
|
8
|
+
|
|
9
|
+
Use this skill for hiai-opencode installation, diagnostics, and integration repair.
|
|
10
|
+
|
|
11
|
+
## Architecture
|
|
12
|
+
|
|
13
|
+
- hiai-opencode is an OpenCode plugin, not a standalone app.
|
|
14
|
+
- MCP servers are external upstream tools launched by hiai-opencode wiring.
|
|
15
|
+
- The user-facing config is \`hiai-opencode.json\` or \`.opencode/hiai-opencode.json\`.
|
|
16
|
+
- Model provider credentials belong to OpenCode Connect. Do not ask for \`OPENROUTER_API_KEY\`, \`OPENAI_API_KEY\`, or \`ANTHROPIC_API_KEY\` for normal model usage.
|
|
17
|
+
- Service credentials are separate: \`FIRECRAWL_API_KEY\`, \`STITCH_AI_API_KEY\`, \`CONTEXT7_API_KEY\`.
|
|
18
|
+
|
|
19
|
+
## First Diagnostic Commands
|
|
20
|
+
|
|
21
|
+
\`\`\`bash
|
|
22
|
+
hiai-opencode doctor
|
|
23
|
+
hiai-opencode mcp-status
|
|
24
|
+
opencode debug config
|
|
25
|
+
\`\`\`
|
|
26
|
+
|
|
27
|
+
If \`opencode mcp list\` is empty but doctor/mcp-status sees servers, explain that OpenCode's list may read only static \`.mcp.json\`. Refresh static visibility:
|
|
28
|
+
|
|
29
|
+
\`\`\`bash
|
|
30
|
+
hiai-opencode export-mcp .mcp.json
|
|
31
|
+
opencode mcp list --print-logs --log-level INFO
|
|
32
|
+
\`\`\`
|
|
33
|
+
|
|
34
|
+
## Plugin vs MCP
|
|
35
|
+
|
|
36
|
+
Install OpenCode plugins with:
|
|
37
|
+
|
|
38
|
+
\`\`\`bash
|
|
39
|
+
opencode plugin @hiai-gg/hiai-opencode@latest --global
|
|
40
|
+
opencode plugin @tarquinen/opencode-dcp@latest --global
|
|
41
|
+
\`\`\`
|
|
42
|
+
|
|
43
|
+
Do not add MCP packages to the OpenCode plugin array. MCP packages are launched through \`hiai-opencode.json\` and helper scripts.
|
|
44
|
+
|
|
45
|
+
## MCP Runtime Notes
|
|
46
|
+
|
|
47
|
+
- \`playwright\`: node/npx; browser binaries may need \`HIAI_PLAYWRIGHT_INSTALL_BROWSERS=1\`; Linux system deps may require admin rights.
|
|
48
|
+
- \`sequential-thinking\`: node/npx; use for complex planning, revision, and branching.
|
|
49
|
+
- \`firecrawl\`: requires \`FIRECRAWL_API_KEY\`.
|
|
50
|
+
- \`mempalace\`: prefers \`uv\`; otherwise Python 3.9+ with \`mempalace\`. Use \`mempalace_status\` first, search before answering memory questions, and never invent memories.
|
|
51
|
+
- \`rag\`: requires \`OPENCODE_RAG_URL\` or a reachable default \`http://localhost:9002/tools/search\`.
|
|
52
|
+
- \`stitch\`: requires \`STITCH_AI_API_KEY\`.
|
|
53
|
+
- \`context7\`: remote docs/search; key optional but recommended for limits.
|
|
54
|
+
|
|
55
|
+
## Calling MCP
|
|
56
|
+
|
|
57
|
+
- Use native MCP tools if OpenCode exposes them.
|
|
58
|
+
- Use \`skill_mcp\` for skill-embedded MCP or enabled hiai-opencode MCP.
|
|
59
|
+
- If \`skill_mcp\` says a server is not found, check whether the skill is loaded, whether the MCP is enabled in \`hiai-opencode.json\`, and whether \`.mcp.json\` needs export.
|
|
60
|
+
|
|
61
|
+
## Safety Rules
|
|
62
|
+
|
|
63
|
+
- Report missing keys by env var name only. Never print key values.
|
|
64
|
+
- Prefer project-local or user-level installs. Do not use sudo/admin rights unless the user explicitly asks.
|
|
65
|
+
- Do not edit unrelated OpenCode/Claude/Agents global skill folders unless the user opts in.
|
|
66
|
+
- Keep DCP separate: it is an optional OpenCode plugin, not part of the hiai-opencode package.
|
|
67
|
+
`,
|
|
68
|
+
allowedTools: ["Bash(*)", "Read(*)", "Edit(*)", "Glob(*)", "Grep(*)", "skill_mcp(*)"],
|
|
69
|
+
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
devBrowserSkill,
|
|
11
11
|
reviewWorkSkill,
|
|
12
12
|
aiSlopRemoverSkill,
|
|
13
|
+
hiaiOpencodeSetupSkill,
|
|
13
14
|
} from "./skills/index"
|
|
14
15
|
|
|
15
16
|
export interface CreateBuiltinSkillsOptions {
|
|
@@ -29,7 +30,15 @@ export function createBuiltinSkills(options: CreateBuiltinSkillsOptions = {}): B
|
|
|
29
30
|
browserSkill = playwrightSkill
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
const skills = [
|
|
33
|
+
const skills = [
|
|
34
|
+
browserSkill,
|
|
35
|
+
hiaiOpencodeSetupSkill,
|
|
36
|
+
frontendUiUxSkill,
|
|
37
|
+
gitMasterSkill,
|
|
38
|
+
devBrowserSkill,
|
|
39
|
+
reviewWorkSkill,
|
|
40
|
+
aiSlopRemoverSkill,
|
|
41
|
+
]
|
|
33
42
|
|
|
34
43
|
if (!disabledSkills) {
|
|
35
44
|
return skills
|
package/src/index.ts
CHANGED
|
@@ -18,18 +18,15 @@ import { injectServerAuthIntoClient, log } from "./shared"
|
|
|
18
18
|
import { hydratePluginConfigWithPlatformDefaults } from "./shared/runtime-plugin-config"
|
|
19
19
|
import { detectExternalSkillPlugin, getSkillPluginConflictWarning } from "./shared/external-plugin-detector"
|
|
20
20
|
import { PLUGIN_NAME } from "./shared/plugin-identity"
|
|
21
|
+
import { autoExportStaticMcpJson } from "./shared/mcp-static-export"
|
|
21
22
|
import { warnIfListPluginEntry, warnMissingRequiredMcpEnv } from "./shared/startup-diagnostics"
|
|
22
23
|
import { startBackgroundCheck as startTmuxCheck } from "./tools/interactive-bash"
|
|
23
24
|
import { lspManager } from "./tools/lsp/client"
|
|
24
25
|
|
|
25
|
-
import { loadConfig
|
|
26
|
+
import { loadConfig } from "./config/loader"
|
|
26
27
|
import type { HiaiOpencodeConfig } from "./config/types"
|
|
27
28
|
|
|
28
29
|
import { createPlugin as createSubtask2Plugin } from "./internals/plugins/subtask2/core/plugin"
|
|
29
|
-
import WebsearchCitedPlugin, {
|
|
30
|
-
WebsearchCitedGooglePlugin,
|
|
31
|
-
WebsearchCitedOpenAIPlugin
|
|
32
|
-
} from "./internals/plugins/websearch-cited/index"
|
|
33
30
|
import { createBuiltinSkills } from "./features/builtin-skills"
|
|
34
31
|
import {
|
|
35
32
|
materializeBuiltinSkills,
|
|
@@ -89,6 +86,7 @@ const HiaiOpenCodePlugin: Plugin = async (ctx) => {
|
|
|
89
86
|
pluginConfig,
|
|
90
87
|
platformConfig: internalConfig,
|
|
91
88
|
})
|
|
89
|
+
autoExportStaticMcpJson(ctx.directory, internalConfig)
|
|
92
90
|
|
|
93
91
|
materializeBuiltinSkills(
|
|
94
92
|
createBuiltinSkills({
|
|
@@ -129,6 +127,7 @@ const HiaiOpenCodePlugin: Plugin = async (ctx) => {
|
|
|
129
127
|
const toolsResult = await createTools({
|
|
130
128
|
ctx,
|
|
131
129
|
pluginConfig,
|
|
130
|
+
platformConfig: internalConfig,
|
|
132
131
|
managers,
|
|
133
132
|
})
|
|
134
133
|
|
|
@@ -169,10 +168,6 @@ const HiaiOpenCodePlugin: Plugin = async (ctx) => {
|
|
|
169
168
|
} catch (err) {
|
|
170
169
|
console.error("[hiai-opencode] PTYPlugin failed to load:", err);
|
|
171
170
|
}
|
|
172
|
-
const websearchResult = await WebsearchCitedPlugin(ctx)
|
|
173
|
-
const websearchGoogleResult = await WebsearchCitedGooglePlugin(ctx)
|
|
174
|
-
const websearchOpenAIResult = await WebsearchCitedOpenAIPlugin(ctx)
|
|
175
|
-
|
|
176
171
|
const combinedResult = {
|
|
177
172
|
name: PLUGIN_NAME,
|
|
178
173
|
...pluginInterface,
|
|
@@ -181,7 +176,6 @@ const HiaiOpenCodePlugin: Plugin = async (ctx) => {
|
|
|
181
176
|
tool: {
|
|
182
177
|
...pluginInterface.tool,
|
|
183
178
|
...ptyResult.tool,
|
|
184
|
-
...websearchResult.tool,
|
|
185
179
|
},
|
|
186
180
|
|
|
187
181
|
// Chain hooks: command.execute.before
|
|
@@ -212,7 +206,6 @@ const HiaiOpenCodePlugin: Plugin = async (ctx) => {
|
|
|
212
206
|
config: async (input: any) => {
|
|
213
207
|
await pluginInterface.config?.(input);
|
|
214
208
|
await (subtask2Result as any).config?.(input);
|
|
215
|
-
await (websearchResult as any).config?.(input);
|
|
216
209
|
},
|
|
217
210
|
|
|
218
211
|
// Merge Events
|
|
@@ -222,33 +215,6 @@ const HiaiOpenCodePlugin: Plugin = async (ctx) => {
|
|
|
222
215
|
await (ptyResult as any).event?.(input);
|
|
223
216
|
},
|
|
224
217
|
|
|
225
|
-
// Auth (Consolidated)
|
|
226
|
-
auth: {
|
|
227
|
-
provider: "hiai-opencode",
|
|
228
|
-
methods: [
|
|
229
|
-
{ type: "api" as const, label: "Google Search API key" },
|
|
230
|
-
],
|
|
231
|
-
loader: async (getAuth: any) => {
|
|
232
|
-
const authData = await getAuth();
|
|
233
|
-
const { registerGetAuth, GOOGLE_PROVIDER_ID, OPENAI_PROVIDER_ID, OPENROUTER_PROVIDER_ID } = await import("./internals/plugins/websearch-cited/index");
|
|
234
|
-
|
|
235
|
-
const getConfiguredKey = (configKey?: string) => {
|
|
236
|
-
if (configKey) return resolveEnvVars(configKey);
|
|
237
|
-
return undefined;
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
const googleKey = authData["Google Search API key"] || getConfiguredKey(internalConfig.auth?.googleSearch);
|
|
241
|
-
const openaiKey = getConfiguredKey(internalConfig.auth?.openai);
|
|
242
|
-
const openRouterKey = getConfiguredKey(internalConfig.auth?.openrouter);
|
|
243
|
-
|
|
244
|
-
if (googleKey) registerGetAuth(GOOGLE_PROVIDER_ID, () => Promise.resolve({ type: "api", key: googleKey }));
|
|
245
|
-
if (openaiKey) registerGetAuth(OPENAI_PROVIDER_ID, () => Promise.resolve({ type: "api", key: openaiKey }));
|
|
246
|
-
if (openRouterKey) registerGetAuth(OPENROUTER_PROVIDER_ID, () => Promise.resolve({ type: "api", key: openRouterKey }));
|
|
247
|
-
|
|
248
|
-
return {};
|
|
249
|
-
},
|
|
250
|
-
},
|
|
251
|
-
|
|
252
218
|
"experimental.session.compacting": async (
|
|
253
219
|
_input: { sessionID: string },
|
|
254
220
|
output: { context: string[] },
|
package/src/lsp/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ export function buildLspConfig(lsp: Record<string, LspServerConfig>): Record<str
|
|
|
4
4
|
const result: Record<string, unknown> = {};
|
|
5
5
|
|
|
6
6
|
for (const [name, server] of Object.entries(lsp)) {
|
|
7
|
+
if (server.enabled === false) continue;
|
|
7
8
|
result[name] = {
|
|
8
9
|
command: server.command,
|
|
9
10
|
extensions: server.extensions,
|
package/src/mcp/registry.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { join } from "node:path"
|
|
2
|
+
import { existsSync } from "node:fs"
|
|
2
3
|
import type { McpServerConfig } from "../config/types.js"
|
|
3
4
|
|
|
4
5
|
export type HiaiMcpName =
|
|
@@ -22,7 +23,11 @@ export interface HiaiMcpRegistryEntry {
|
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
function resolveAssetScript(...segments: string[]): string {
|
|
25
|
-
|
|
26
|
+
const candidates = [
|
|
27
|
+
join(import.meta.dirname, "..", "assets", ...segments),
|
|
28
|
+
join(import.meta.dirname, "..", "..", "assets", ...segments),
|
|
29
|
+
]
|
|
30
|
+
return candidates.find((candidate) => existsSync(candidate)) ?? candidates[0]
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
function createNpmPackageCommand(pkg: string, ...args: string[]): string[] {
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
AvailableCategory,
|
|
6
6
|
} from "../agents/dynamic-agent-prompt-builder"
|
|
7
7
|
import type { HiaiOpenCodeConfig } from "../config"
|
|
8
|
+
import type { McpServerConfig } from "../config/types"
|
|
8
9
|
import { isInteractiveBashEnabled } from "../create-runtime-tmux-config"
|
|
9
10
|
import type { PluginContext, ToolsRecord } from "./types"
|
|
10
11
|
|
|
@@ -146,6 +147,7 @@ export function createToolRegistry(args: {
|
|
|
146
147
|
managers: Pick<Managers, "backgroundManager" | "tmuxSessionManager" | "skillMcpManager">
|
|
147
148
|
skillContext: SkillContext
|
|
148
149
|
availableCategories: AvailableCategory[]
|
|
150
|
+
builtinMcp?: Record<string, McpServerConfig>
|
|
149
151
|
interactiveBashEnabled?: boolean
|
|
150
152
|
toolFactories?: Partial<ToolRegistryFactories>
|
|
151
153
|
}): ToolRegistryResult {
|
|
@@ -155,6 +157,7 @@ export function createToolRegistry(args: {
|
|
|
155
157
|
managers,
|
|
156
158
|
skillContext,
|
|
157
159
|
availableCategories,
|
|
160
|
+
builtinMcp,
|
|
158
161
|
interactiveBashEnabled = isInteractiveBashEnabled(),
|
|
159
162
|
toolFactories,
|
|
160
163
|
} = args
|
|
@@ -216,6 +219,7 @@ export function createToolRegistry(args: {
|
|
|
216
219
|
manager: managers.skillMcpManager,
|
|
217
220
|
getLoadedSkills: () => skillContext.mergedSkills,
|
|
218
221
|
getSessionID: getSessionIDForMcp,
|
|
222
|
+
builtinMcp,
|
|
219
223
|
})
|
|
220
224
|
|
|
221
225
|
const commands = factories.discoverCommandsSync(ctx.directory, {
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
|
|
2
|
+
import { dirname, join } from "node:path"
|
|
3
|
+
|
|
4
|
+
import type { HiaiOpencodeConfig, McpServerConfig } from "../config/types"
|
|
5
|
+
import { resolveEnvVars } from "../config/loader"
|
|
6
|
+
import { log } from "./logger"
|
|
7
|
+
|
|
8
|
+
type StaticMcpServer = {
|
|
9
|
+
type?: "http" | "stdio"
|
|
10
|
+
url?: string
|
|
11
|
+
command?: string
|
|
12
|
+
args?: string[]
|
|
13
|
+
env?: Record<string, string>
|
|
14
|
+
headers?: Record<string, string>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const MCP_EXPORT_MARKER = "hiai-opencode"
|
|
18
|
+
|
|
19
|
+
type StaticMcpJsonPayload = {
|
|
20
|
+
mcpServers: Record<string, StaticMcpServer>
|
|
21
|
+
_meta?: {
|
|
22
|
+
generatedBy: typeof MCP_EXPORT_MARKER
|
|
23
|
+
version: 1
|
|
24
|
+
generatedAt: string
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function toStaticMcpServer(config: McpServerConfig): StaticMcpServer | null {
|
|
29
|
+
if (config.enabled === false) return null
|
|
30
|
+
|
|
31
|
+
if (config.type === "remote") {
|
|
32
|
+
const headers = config.headers
|
|
33
|
+
? Object.fromEntries(Object.entries(config.headers).map(([key, value]) => [key, resolveEnvVars(value)]))
|
|
34
|
+
: undefined
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
type: "http",
|
|
38
|
+
url: config.url,
|
|
39
|
+
...(headers ? { headers } : {}),
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const [command, ...args] = config.command ?? []
|
|
44
|
+
if (!command) return null
|
|
45
|
+
|
|
46
|
+
const env = config.environment
|
|
47
|
+
? Object.fromEntries(Object.entries(config.environment).map(([key, value]) => [key, resolveEnvVars(value)]))
|
|
48
|
+
: undefined
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
command,
|
|
52
|
+
...(args.length > 0 ? { args } : {}),
|
|
53
|
+
...(env ? { env } : {}),
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function buildStaticMcpJson(config: HiaiOpencodeConfig): StaticMcpJsonPayload {
|
|
58
|
+
const mcpServers: Record<string, StaticMcpServer> = {}
|
|
59
|
+
|
|
60
|
+
for (const [name, serverConfig] of Object.entries(config.mcp ?? {})) {
|
|
61
|
+
const converted = toStaticMcpServer(serverConfig)
|
|
62
|
+
if (converted) {
|
|
63
|
+
mcpServers[name] = converted
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
_meta: {
|
|
69
|
+
generatedBy: MCP_EXPORT_MARKER,
|
|
70
|
+
version: 1,
|
|
71
|
+
generatedAt: new Date().toISOString(),
|
|
72
|
+
},
|
|
73
|
+
mcpServers,
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function isManagedStaticMcpFile(path: string): boolean {
|
|
78
|
+
if (!existsSync(path)) return false
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
const raw = readFileSync(path, "utf-8")
|
|
82
|
+
const parsed = JSON.parse(raw) as StaticMcpJsonPayload
|
|
83
|
+
return parsed?._meta?.generatedBy === MCP_EXPORT_MARKER
|
|
84
|
+
} catch {
|
|
85
|
+
return false
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function autoExportStaticMcpJson(directory: string, config: HiaiOpencodeConfig): void {
|
|
90
|
+
const mode = process.env.HIAI_OPENCODE_AUTO_EXPORT_MCP?.trim().toLowerCase() || "if-missing"
|
|
91
|
+
if (mode === "0" || mode === "false" || mode === "no" || mode === "off") {
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const outputPath = process.env.HIAI_OPENCODE_MCP_EXPORT_PATH?.trim() || join(directory, ".mcp.json")
|
|
96
|
+
if (mode === "if-missing" && existsSync(outputPath)) {
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const isForceMode = mode === "force"
|
|
101
|
+
if (mode === "always" && existsSync(outputPath) && !isManagedStaticMcpFile(outputPath) && !isForceMode) {
|
|
102
|
+
console.warn(
|
|
103
|
+
`[hiai-opencode] WARNING: refusing to overwrite non-managed static MCP config at ${outputPath}. `
|
|
104
|
+
+ "Set HIAI_OPENCODE_AUTO_EXPORT_MCP=force to override.",
|
|
105
|
+
)
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
mkdirSync(dirname(outputPath), { recursive: true })
|
|
111
|
+
const payload = buildStaticMcpJson(config)
|
|
112
|
+
writeFileSync(outputPath, `${JSON.stringify(payload, null, 2)}\n`)
|
|
113
|
+
log("[hiai-opencode] exported static MCP config", {
|
|
114
|
+
outputPath,
|
|
115
|
+
servers: Object.keys(payload.mcpServers),
|
|
116
|
+
})
|
|
117
|
+
} catch (error) {
|
|
118
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
119
|
+
console.warn(`[hiai-opencode] WARNING: failed to export static MCP config to ${outputPath}: ${message}`)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type { EnvironmentCheckResult } from "./environment-check"
|
|
2
2
|
export { checkEnvironment, formatEnvironmentCheck } from "./environment-check"
|
|
3
|
-
export { CLI_LANGUAGES,
|
|
3
|
+
export { CLI_LANGUAGES, LANG_EXTENSIONS } from "./language-support"
|
|
4
4
|
export { DEFAULT_TIMEOUT_MS, DEFAULT_MAX_OUTPUT_BYTES, DEFAULT_MAX_MATCHES } from "./language-support"
|
|
5
5
|
export { findSgCliPathSync, getSgCliPath, setSgCliPath } from "./sg-cli-path"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync } from "fs"
|
|
2
2
|
|
|
3
|
-
import { CLI_LANGUAGES
|
|
3
|
+
import { CLI_LANGUAGES } from "./language-support"
|
|
4
4
|
import { getSgCliPath } from "./sg-cli-path"
|
|
5
5
|
|
|
6
6
|
export interface EnvironmentCheckResult {
|
|
@@ -9,14 +9,10 @@ export interface EnvironmentCheckResult {
|
|
|
9
9
|
path: string
|
|
10
10
|
error?: string
|
|
11
11
|
}
|
|
12
|
-
napi: {
|
|
13
|
-
available: boolean
|
|
14
|
-
error?: string
|
|
15
|
-
}
|
|
16
12
|
}
|
|
17
13
|
|
|
18
14
|
/**
|
|
19
|
-
* Check if ast-grep CLI
|
|
15
|
+
* Check if ast-grep CLI is available.
|
|
20
16
|
* Call this at startup to provide early feedback about missing dependencies.
|
|
21
17
|
*/
|
|
22
18
|
export function checkEnvironment(): EnvironmentCheckResult {
|
|
@@ -26,9 +22,6 @@ export function checkEnvironment(): EnvironmentCheckResult {
|
|
|
26
22
|
available: false,
|
|
27
23
|
path: cliPath ?? "not found",
|
|
28
24
|
},
|
|
29
|
-
napi: {
|
|
30
|
-
available: false,
|
|
31
|
-
},
|
|
32
25
|
}
|
|
33
26
|
|
|
34
27
|
if (cliPath && existsSync(cliPath)) {
|
|
@@ -39,17 +32,6 @@ export function checkEnvironment(): EnvironmentCheckResult {
|
|
|
39
32
|
result.cli.error = `Binary not found: ${cliPath}`
|
|
40
33
|
}
|
|
41
34
|
|
|
42
|
-
// Check NAPI availability
|
|
43
|
-
try {
|
|
44
|
-
require("@ast-grep/napi")
|
|
45
|
-
result.napi.available = true
|
|
46
|
-
} catch (error) {
|
|
47
|
-
result.napi.available = false
|
|
48
|
-
result.napi.error = `@ast-grep/napi not installed: ${
|
|
49
|
-
error instanceof Error ? error.message : String(error)
|
|
50
|
-
}`
|
|
51
|
-
}
|
|
52
|
-
|
|
53
35
|
return result
|
|
54
36
|
}
|
|
55
37
|
|
|
@@ -70,20 +52,8 @@ export function formatEnvironmentCheck(result: EnvironmentCheckResult): string {
|
|
|
70
52
|
lines.push(" Install: bun add -D @ast-grep/cli")
|
|
71
53
|
}
|
|
72
54
|
|
|
73
|
-
// NAPI status
|
|
74
|
-
if (result.napi.available) {
|
|
75
|
-
lines.push("[OK] NAPI: Available")
|
|
76
|
-
} else {
|
|
77
|
-
lines.push("[X] NAPI: Not available")
|
|
78
|
-
if (result.napi.error) {
|
|
79
|
-
lines.push(` Error: ${result.napi.error}`)
|
|
80
|
-
}
|
|
81
|
-
lines.push(" Install: bun add -D @ast-grep/napi")
|
|
82
|
-
}
|
|
83
|
-
|
|
84
55
|
lines.push("")
|
|
85
56
|
lines.push(`CLI supports ${CLI_LANGUAGES.length} languages`)
|
|
86
|
-
lines.push(`NAPI supports ${NAPI_LANGUAGES.length} languages: ${NAPI_LANGUAGES.join(", ")}`)
|
|
87
57
|
|
|
88
58
|
return lines.join("\n")
|
|
89
59
|
}
|
|
@@ -27,9 +27,6 @@ export const CLI_LANGUAGES = [
|
|
|
27
27
|
"yaml",
|
|
28
28
|
] as const
|
|
29
29
|
|
|
30
|
-
// NAPI supported languages (5 total - native bindings)
|
|
31
|
-
export const NAPI_LANGUAGES = ["html", "javascript", "tsx", "css", "typescript"] as const
|
|
32
|
-
|
|
33
30
|
export const DEFAULT_TIMEOUT_MS = 300_000
|
|
34
31
|
export const DEFAULT_MAX_OUTPUT_BYTES = 1 * 1024 * 1024
|
|
35
32
|
export const DEFAULT_MAX_MATCHES = 500
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import type { CLI_LANGUAGES
|
|
1
|
+
import type { CLI_LANGUAGES } from "./constants"
|
|
2
2
|
|
|
3
3
|
export type CliLanguage = (typeof CLI_LANGUAGES)[number]
|
|
4
|
-
export type NapiLanguage = (typeof NAPI_LANGUAGES)[number]
|
|
5
4
|
|
|
6
5
|
export interface Position {
|
|
7
6
|
line: number
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import type { ToolContext } from "@opencode-ai/plugin/tool"
|
|
3
|
+
|
|
4
|
+
import { createSkillMcpTool } from "./tools"
|
|
5
|
+
|
|
6
|
+
test("skill_mcp falls back to enabled builtin MCP when skill MCP is absent", async () => {
|
|
7
|
+
let callCaptured: { serverName: string; toolName: string } | null = null
|
|
8
|
+
|
|
9
|
+
const toolDef = createSkillMcpTool({
|
|
10
|
+
manager: {
|
|
11
|
+
async callTool(info, _context, toolName) {
|
|
12
|
+
callCaptured = { serverName: info.serverName, toolName }
|
|
13
|
+
return { ok: true }
|
|
14
|
+
},
|
|
15
|
+
async readResource() {
|
|
16
|
+
return { ok: true }
|
|
17
|
+
},
|
|
18
|
+
async getPrompt() {
|
|
19
|
+
return { ok: true }
|
|
20
|
+
},
|
|
21
|
+
} as any,
|
|
22
|
+
getLoadedSkills: () => [],
|
|
23
|
+
getSessionID: () => "session-test",
|
|
24
|
+
builtinMcp: {
|
|
25
|
+
firecrawl: {
|
|
26
|
+
enabled: true,
|
|
27
|
+
type: "local",
|
|
28
|
+
command: ["node", "firecrawl.mjs"],
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const result = await toolDef.execute(
|
|
34
|
+
{
|
|
35
|
+
mcp_name: "firecrawl",
|
|
36
|
+
tool_name: "firecrawl_search",
|
|
37
|
+
arguments: { query: "test" },
|
|
38
|
+
} as any,
|
|
39
|
+
{ sessionID: "session-test" } as ToolContext,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
expect(callCaptured).toEqual({ serverName: "firecrawl", toolName: "firecrawl_search" })
|
|
43
|
+
expect(result).toContain("\"ok\": true")
|
|
44
|
+
})
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { tool, type ToolDefinition } from "@opencode-ai/plugin"
|
|
2
2
|
import type { ToolContext } from "@opencode-ai/plugin/tool"
|
|
3
3
|
import { BUILTIN_MCP_TOOL_HINTS, SKILL_MCP_DESCRIPTION } from "./constants"
|
|
4
|
+
import type { McpServerConfig } from "../../config/types"
|
|
4
5
|
import type { SkillMcpArgs } from "./types"
|
|
5
6
|
import type { SkillMcpManager, SkillMcpClientInfo, SkillMcpServerContext } from "../../features/skill-mcp-manager"
|
|
7
|
+
import type { ClaudeCodeMcpServer } from "../../features/claude-code-mcp-loader/types"
|
|
6
8
|
import type { LoadedSkill } from "../../features/opencode-skill-loader/types"
|
|
7
9
|
|
|
8
10
|
interface SkillMcpToolOptions {
|
|
9
11
|
manager: SkillMcpManager
|
|
10
12
|
getLoadedSkills: () => LoadedSkill[]
|
|
11
13
|
getSessionID?: () => string | undefined
|
|
14
|
+
builtinMcp?: Record<string, McpServerConfig>
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
type OperationType = { type: "tool" | "resource" | "prompt"; name: string }
|
|
@@ -60,6 +63,28 @@ function findMcpServer(
|
|
|
60
63
|
return null
|
|
61
64
|
}
|
|
62
65
|
|
|
66
|
+
function convertBuiltinMcpConfig(config: McpServerConfig): ClaudeCodeMcpServer | null {
|
|
67
|
+
if (config.enabled === false) return null
|
|
68
|
+
|
|
69
|
+
if (config.type === "remote") {
|
|
70
|
+
return {
|
|
71
|
+
type: "http",
|
|
72
|
+
url: config.url,
|
|
73
|
+
headers: config.headers,
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const [command, ...args] = config.command ?? []
|
|
78
|
+
if (!command) return null
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
type: "stdio",
|
|
82
|
+
command,
|
|
83
|
+
args,
|
|
84
|
+
env: config.environment,
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
63
88
|
function formatAvailableMcps(skills: LoadedSkill[]): string {
|
|
64
89
|
const mcps: string[] = []
|
|
65
90
|
for (const skill of skills) {
|
|
@@ -72,6 +97,14 @@ function formatAvailableMcps(skills: LoadedSkill[]): string {
|
|
|
72
97
|
return mcps.length > 0 ? mcps.join("\n") : " (none found)"
|
|
73
98
|
}
|
|
74
99
|
|
|
100
|
+
function formatAvailableBuiltinMcps(builtinMcp: Record<string, McpServerConfig> | undefined): string {
|
|
101
|
+
const names = Object.entries(builtinMcp ?? {})
|
|
102
|
+
.filter(([, config]) => config.enabled !== false)
|
|
103
|
+
.map(([name]) => ` - "${name}" from hiai-opencode config`)
|
|
104
|
+
|
|
105
|
+
return names.length > 0 ? names.join("\n") : " (none found)"
|
|
106
|
+
}
|
|
107
|
+
|
|
75
108
|
function formatBuiltinMcpHint(mcpName: string): string | null {
|
|
76
109
|
const nativeTools = BUILTIN_MCP_TOOL_HINTS[mcpName]
|
|
77
110
|
if (!nativeTools) return null
|
|
@@ -119,7 +152,7 @@ export function applyGrepFilter(output: string, pattern: string | undefined): st
|
|
|
119
152
|
}
|
|
120
153
|
|
|
121
154
|
export function createSkillMcpTool(options: SkillMcpToolOptions): ToolDefinition {
|
|
122
|
-
const { manager, getLoadedSkills, getSessionID } = options
|
|
155
|
+
const { manager, getLoadedSkills, getSessionID, builtinMcp } = options
|
|
123
156
|
|
|
124
157
|
return tool({
|
|
125
158
|
description: SKILL_MCP_DESCRIPTION,
|
|
@@ -141,8 +174,10 @@ export function createSkillMcpTool(options: SkillMcpToolOptions): ToolDefinition
|
|
|
141
174
|
const operation = validateOperationParams(args)
|
|
142
175
|
const skills = getLoadedSkills()
|
|
143
176
|
const found = findMcpServer(args.mcp_name, skills)
|
|
177
|
+
const builtinConfig = builtinMcp?.[args.mcp_name]
|
|
178
|
+
const convertedBuiltinConfig = builtinConfig ? convertBuiltinMcpConfig(builtinConfig) : null
|
|
144
179
|
|
|
145
|
-
if (!found) {
|
|
180
|
+
if (!found && !convertedBuiltinConfig) {
|
|
146
181
|
const builtinHint = formatBuiltinMcpHint(args.mcp_name)
|
|
147
182
|
if (builtinHint) {
|
|
148
183
|
throw new Error(builtinHint)
|
|
@@ -153,7 +188,10 @@ export function createSkillMcpTool(options: SkillMcpToolOptions): ToolDefinition
|
|
|
153
188
|
`Available MCP servers in loaded skills:\n` +
|
|
154
189
|
formatAvailableMcps(skills) +
|
|
155
190
|
`\n\n` +
|
|
156
|
-
`
|
|
191
|
+
`Available MCP servers in hiai-opencode config:\n` +
|
|
192
|
+
formatAvailableBuiltinMcps(builtinMcp) +
|
|
193
|
+
`\n\n` +
|
|
194
|
+
`Hint: Load the skill first for skill-embedded MCPs. Builtin hiai-opencode MCPs can be called directly when enabled in hiai-opencode.json.`,
|
|
157
195
|
)
|
|
158
196
|
}
|
|
159
197
|
|
|
@@ -164,14 +202,14 @@ export function createSkillMcpTool(options: SkillMcpToolOptions): ToolDefinition
|
|
|
164
202
|
|
|
165
203
|
const info: SkillMcpClientInfo = {
|
|
166
204
|
serverName: args.mcp_name,
|
|
167
|
-
skillName: found
|
|
205
|
+
skillName: found?.skill.name ?? "hiai-opencode",
|
|
168
206
|
sessionID,
|
|
169
|
-
scope: found
|
|
207
|
+
scope: found?.skill.scope ?? "user",
|
|
170
208
|
}
|
|
171
209
|
|
|
172
210
|
const context: SkillMcpServerContext = {
|
|
173
|
-
config: found
|
|
174
|
-
skillName: found
|
|
211
|
+
config: found?.config ?? convertedBuiltinConfig!,
|
|
212
|
+
skillName: found?.skill.name ?? "hiai-opencode",
|
|
175
213
|
}
|
|
176
214
|
|
|
177
215
|
const parsedArgs = parseArguments(args.arguments)
|
|
Binary file
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/config/models.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export declare const MODEL_ROLE_GUIDE: readonly ["fast: cheap/default for bounded helpers, researcher-style scans, platform chores", "mid: balanced default for steady execution/review work", "high: stronger general-purpose model for primary implementation and planning", "ultrahigh: highest-cost/high-accuracy slot for hard architecture or critical decisions", "vision: preferred for UI/media/multimodal interpretation and visual work", "reasoning: preferred for deeper multi-step reasoning when latency/cost are acceptable"];
|
|
2
|
-
export declare const PROVIDER_MODEL_RULES: readonly ["openai: use `openai/<model>` for direct OpenAI calls, e.g. `openai/gpt-5` or `openai/o1`", "anthropic: use `anthropic/<model>`, e.g. `anthropic/claude-3.5-sonnet`", "deepseek: use `deepseek/<model>` when connected directly", "glm: use `z-ai/<model>` or the provider id exposed by your gateway/client", "minimax: use `minimax/<model>`", "qwen: use `qwen/<model>`", "ollama: use the native local model id in Ollama config, e.g. `qwen3.5:4b`", "openrouter: use `openrouter/<vendor>/<model>`, e.g. `openrouter/anthropic/claude-3.5-sonnet`", "rule: store fully qualified model ids in config; avoid local aliases like `fast`, `sonnet`, or provider-less ids"];
|
|
3
|
-
export declare const MODEL_PRESETS: {
|
|
4
|
-
readonly fast: "openrouter/google/gemini-2.0-flash";
|
|
5
|
-
readonly mid: "openrouter/anthropic/claude-3.5-sonnet";
|
|
6
|
-
readonly high: "openrouter/anthropic/claude-3.5-opus";
|
|
7
|
-
readonly ultrahigh: "openrouter/openai/gpt-4o";
|
|
8
|
-
readonly vision: "openrouter/google/gemini-2.0-pro-exp-02-05";
|
|
9
|
-
readonly reasoning: "openrouter/openai/o1";
|
|
10
|
-
readonly strategist: "openrouter/z-ai/glm-5.1";
|
|
11
|
-
readonly critic: "openrouter/qwen/qwen2.5-72b-instruct";
|
|
12
|
-
readonly writing: "openrouter/kimi/kimi-latest";
|
|
13
|
-
};
|