@hiai-gg/hiai-opencode 0.1.3 → 0.1.5
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 -18
- package/AGENTS.md +77 -23
- package/ARCHITECTURE.md +15 -17
- package/LICENSE.md +1 -0
- package/README.md +189 -94
- package/assets/cli/hiai-opencode.mjs +276 -0
- package/assets/mcp/playwright.mjs +7 -0
- package/config/hiai-opencode.schema.json +85 -50
- package/dist/config/defaults.d.ts +1 -3
- package/dist/config/index.d.ts +0 -1
- package/dist/config/platform-schema.d.ts +343 -4
- package/dist/config/schema/agent-overrides.d.ts +256 -0
- package/dist/config/schema/categories.d.ts +1 -1
- package/dist/config/schema/commands.d.ts +1 -0
- package/dist/config/schema/index.d.ts +2 -0
- package/dist/config/schema/oh-my-opencode-config.d.ts +267 -0
- package/dist/config/schema/skill-discovery.d.ts +11 -0
- package/dist/config/types.d.ts +33 -3
- package/dist/create-tools.d.ts +2 -0
- package/dist/features/builtin-commands/templates/mcp-status.d.ts +1 -0
- package/dist/features/builtin-commands/types.d.ts +1 -1
- package/dist/features/opencode-skill-loader/loader.d.ts +2 -0
- package/dist/index.js +744 -358
- package/dist/internals/plugins/websearch-cited/index.d.ts +7 -1
- package/dist/mcp/types.d.ts +1 -1
- package/dist/plugin/skill-discovery-config.d.ts +4 -0
- package/dist/plugin/tool-registry.d.ts +2 -0
- package/dist/shared/startup-diagnostics.d.ts +6 -0
- package/dist/tools/skill-mcp/tools.d.ts +2 -0
- package/hiai-opencode.json +55 -33
- package/package.json +4 -1
- package/src/agents/AGENTS.md +3 -4
- package/src/config/defaults.ts +186 -77
- package/src/config/index.ts +0 -1
- package/src/config/loader.test.ts +16 -1
- package/src/config/loader.ts +4 -2
- package/src/config/platform-schema.ts +53 -4
- package/src/config/schema/agent-overrides.ts +2 -0
- package/src/config/schema/commands.ts +1 -0
- package/src/config/schema/fast-apply.ts +4 -4
- package/src/config/schema/index.ts +2 -0
- package/src/config/schema/oh-my-opencode-config.ts +3 -0
- package/src/config/schema/skill-discovery.ts +25 -0
- package/src/config/types.ts +49 -2
- package/src/create-tools.ts +4 -1
- package/src/features/builtin-commands/commands.ts +7 -0
- package/src/features/builtin-commands/templates/mcp-status.ts +36 -0
- package/src/features/builtin-commands/types.ts +1 -1
- package/src/features/builtin-skills/skills/playwright.ts +24 -2
- package/src/features/opencode-skill-loader/loader.ts +11 -0
- package/src/index.ts +53 -14
- package/src/internals/plugins/websearch-cited/index.ts +10 -5
- package/src/lsp/index.ts +1 -0
- package/src/mcp/registry.ts +6 -1
- package/src/plugin/hooks/create-tool-guard-hooks.ts +1 -1
- package/src/plugin/skill-context.ts +31 -13
- package/src/plugin/skill-discovery-config.ts +32 -0
- package/src/plugin/tool-registry.ts +4 -0
- package/src/plugin-handlers/agent-config-handler.ts +20 -13
- package/src/plugin-handlers/command-config-handler.ts +22 -12
- package/src/shared/migration/agent-names.ts +5 -5
- package/src/shared/startup-diagnostics.ts +77 -0
- package/src/tools/skill-mcp/tools.ts +45 -7
- package/src/config/models.ts +0 -32
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[] {
|
|
@@ -136,7 +136,7 @@ export function createToolGuardHooks(args: {
|
|
|
136
136
|
: null
|
|
137
137
|
|
|
138
138
|
const fastApply = isHookEnabled("fast-apply")
|
|
139
|
-
? safeHook("fast-apply", () => createFastApplyHook(pluginConfig.fast_apply ?? { enabled: false, ollama_url: "
|
|
139
|
+
? safeHook("fast-apply", () => createFastApplyHook(pluginConfig.fast_apply ?? { enabled: false, ollama_url: "", model: "", timeout: 30000 }))
|
|
140
140
|
: null
|
|
141
141
|
|
|
142
142
|
return {
|
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
10
|
discoverConfigSourceSkills,
|
|
11
|
+
discoverManagedPluginSkills,
|
|
11
12
|
discoverUserClaudeSkills,
|
|
12
13
|
discoverProjectClaudeSkills,
|
|
13
14
|
discoverOpencodeGlobalSkills,
|
|
@@ -18,6 +19,7 @@ import {
|
|
|
18
19
|
} from "../features/opencode-skill-loader"
|
|
19
20
|
import { createBuiltinSkills } from "../features/builtin-skills"
|
|
20
21
|
import { getSystemMcpServerNames } from "../features/claude-code-mcp-loader"
|
|
22
|
+
import { resolveSkillDiscoveryConfig } from "./skill-discovery-config"
|
|
21
23
|
|
|
22
24
|
export type SkillContext = {
|
|
23
25
|
mergedSkills: LoadedSkill[]
|
|
@@ -71,21 +73,37 @@ export async function createSkillContext(args: {
|
|
|
71
73
|
return true
|
|
72
74
|
})
|
|
73
75
|
|
|
74
|
-
const
|
|
75
|
-
const [
|
|
76
|
+
const discovery = resolveSkillDiscoveryConfig(pluginConfig)
|
|
77
|
+
const [
|
|
78
|
+
managedPluginSkills,
|
|
79
|
+
configSourceSkills,
|
|
80
|
+
userSkills,
|
|
81
|
+
globalSkills,
|
|
82
|
+
projectSkills,
|
|
83
|
+
opencodeProjectSkills,
|
|
84
|
+
agentsProjectSkills,
|
|
85
|
+
agentsGlobalSkills,
|
|
86
|
+
] =
|
|
76
87
|
await Promise.all([
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
88
|
+
discoverManagedPluginSkills(),
|
|
89
|
+
discovery.config_sources
|
|
90
|
+
? discoverConfigSourceSkills({
|
|
91
|
+
config: pluginConfig.skills,
|
|
92
|
+
configDir: directory,
|
|
93
|
+
})
|
|
94
|
+
: Promise.resolve([]),
|
|
95
|
+
discovery.global_claude ? discoverUserClaudeSkills() : Promise.resolve([]),
|
|
96
|
+
discovery.global_opencode ? discoverOpencodeGlobalSkills() : Promise.resolve([]),
|
|
97
|
+
discovery.project_claude ? discoverProjectClaudeSkills(directory) : Promise.resolve([]),
|
|
98
|
+
discovery.project_opencode ? discoverOpencodeProjectSkills(directory) : Promise.resolve([]),
|
|
99
|
+
discovery.project_agents ? discoverProjectAgentsSkills(directory) : Promise.resolve([]),
|
|
100
|
+
discovery.global_agents ? discoverGlobalAgentsSkills() : Promise.resolve([]),
|
|
87
101
|
])
|
|
88
102
|
|
|
103
|
+
const filteredManagedPluginSkills = filterProviderGatedSkills(
|
|
104
|
+
managedPluginSkills,
|
|
105
|
+
browserProvider,
|
|
106
|
+
)
|
|
89
107
|
const filteredConfigSourceSkills = filterProviderGatedSkills(
|
|
90
108
|
configSourceSkills,
|
|
91
109
|
browserProvider,
|
|
@@ -109,7 +127,7 @@ export async function createSkillContext(args: {
|
|
|
109
127
|
const mergedSkills = mergeSkills(
|
|
110
128
|
builtinSkills,
|
|
111
129
|
pluginConfig.skills,
|
|
112
|
-
filteredConfigSourceSkills,
|
|
130
|
+
[...filteredManagedPluginSkills, ...filteredConfigSourceSkills],
|
|
113
131
|
[...filteredUserSkills, ...filteredAgentsGlobalSkills],
|
|
114
132
|
filteredGlobalSkills,
|
|
115
133
|
[...filteredProjectSkills, ...filteredAgentsProjectSkills],
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { HiaiOpenCodeConfig } from "../config"
|
|
2
|
+
import type { SkillDiscoveryConfig } from "../config/schema"
|
|
3
|
+
|
|
4
|
+
export type ResolvedSkillDiscoveryConfig = Required<SkillDiscoveryConfig>
|
|
5
|
+
|
|
6
|
+
const DEFAULT_SKILL_DISCOVERY: ResolvedSkillDiscoveryConfig = {
|
|
7
|
+
config_sources: true,
|
|
8
|
+
project_opencode: true,
|
|
9
|
+
global_opencode: false,
|
|
10
|
+
project_claude: false,
|
|
11
|
+
global_claude: false,
|
|
12
|
+
project_agents: false,
|
|
13
|
+
global_agents: false,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function resolveSkillDiscoveryConfig(
|
|
17
|
+
pluginConfig: HiaiOpenCodeConfig,
|
|
18
|
+
): ResolvedSkillDiscoveryConfig {
|
|
19
|
+
const resolved: ResolvedSkillDiscoveryConfig = {
|
|
20
|
+
...DEFAULT_SKILL_DISCOVERY,
|
|
21
|
+
...(pluginConfig.skill_discovery ?? {}),
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Compatibility switch: historically this only controlled Claude-style skills.
|
|
25
|
+
// Keep that meaning, but make it stronger for those two sources.
|
|
26
|
+
if (pluginConfig.claude_code?.skills === false) {
|
|
27
|
+
resolved.project_claude = false
|
|
28
|
+
resolved.global_claude = false
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return resolved
|
|
32
|
+
}
|
|
@@ -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, {
|
|
@@ -7,6 +7,7 @@ import { AGENT_NAME_MAP } from "../shared/migration";
|
|
|
7
7
|
import { registerAgentName } from "../features/claude-code-session-state";
|
|
8
8
|
import {
|
|
9
9
|
discoverConfigSourceSkills,
|
|
10
|
+
discoverManagedPluginSkills,
|
|
10
11
|
deduplicateSkillsByName,
|
|
11
12
|
discoverGlobalAgentsSkills,
|
|
12
13
|
discoverOpencodeGlobalSkills,
|
|
@@ -31,6 +32,7 @@ import {
|
|
|
31
32
|
filterProtectedAgentOverrides,
|
|
32
33
|
} from "./agent-override-protection";
|
|
33
34
|
import { buildStrategistAgentConfig } from "./strategist-agent-config-builder";
|
|
35
|
+
import { resolveSkillDiscoveryConfig } from "../plugin/skill-discovery-config";
|
|
34
36
|
|
|
35
37
|
type AgentConfigRecord = Record<string, Record<string, unknown> | undefined> & {
|
|
36
38
|
build?: Record<string, unknown>;
|
|
@@ -130,8 +132,9 @@ export async function applyAgentConfig(params: {
|
|
|
130
132
|
},
|
|
131
133
|
) as typeof params.pluginConfig.disabled_agents;
|
|
132
134
|
|
|
133
|
-
const
|
|
135
|
+
const discovery = resolveSkillDiscoveryConfig(params.pluginConfig);
|
|
134
136
|
const [
|
|
137
|
+
discoveredManagedPluginSkills,
|
|
135
138
|
discoveredConfigSourceSkills,
|
|
136
139
|
discoveredUserSkills,
|
|
137
140
|
discoveredProjectSkills,
|
|
@@ -140,23 +143,27 @@ export async function applyAgentConfig(params: {
|
|
|
140
143
|
discoveredOpencodeProjectSkills,
|
|
141
144
|
discoveredGlobalAgentsSkills,
|
|
142
145
|
] = await Promise.all([
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
146
|
+
discoverManagedPluginSkills(),
|
|
147
|
+
discovery.config_sources
|
|
148
|
+
? discoverConfigSourceSkills({
|
|
149
|
+
config: params.pluginConfig.skills,
|
|
150
|
+
configDir: params.ctx.directory,
|
|
151
|
+
})
|
|
152
|
+
: Promise.resolve([]),
|
|
153
|
+
discovery.global_claude ? discoverUserClaudeSkills() : Promise.resolve([]),
|
|
154
|
+
discovery.project_claude
|
|
155
|
+
? discoverProjectClaudeSkills(params.ctx.directory)
|
|
156
|
+
: Promise.resolve([]),
|
|
157
|
+
discovery.project_agents
|
|
152
158
|
? discoverProjectAgentsSkills(params.ctx.directory)
|
|
153
159
|
: Promise.resolve([]),
|
|
154
|
-
discoverOpencodeGlobalSkills(),
|
|
155
|
-
discoverOpencodeProjectSkills(params.ctx.directory),
|
|
156
|
-
|
|
160
|
+
discovery.global_opencode ? discoverOpencodeGlobalSkills() : Promise.resolve([]),
|
|
161
|
+
discovery.project_opencode ? discoverOpencodeProjectSkills(params.ctx.directory) : Promise.resolve([]),
|
|
162
|
+
discovery.global_agents ? discoverGlobalAgentsSkills() : Promise.resolve([]),
|
|
157
163
|
]);
|
|
158
164
|
|
|
159
165
|
const allDiscoveredSkills = [
|
|
166
|
+
...discoveredManagedPluginSkills,
|
|
160
167
|
...discoveredConfigSourceSkills,
|
|
161
168
|
...discoveredOpencodeProjectSkills,
|
|
162
169
|
...discoveredProjectSkills,
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import { loadBuiltinCommands } from "../features/builtin-commands";
|
|
13
13
|
import {
|
|
14
14
|
discoverConfigSourceSkills,
|
|
15
|
+
loadManagedPluginSkills,
|
|
15
16
|
loadGlobalAgentsSkills,
|
|
16
17
|
loadProjectAgentsSkills,
|
|
17
18
|
loadUserSkills,
|
|
@@ -26,6 +27,7 @@ import {
|
|
|
26
27
|
log,
|
|
27
28
|
} from "../shared";
|
|
28
29
|
import type { PluginComponents } from "./plugin-components-loader";
|
|
30
|
+
import { resolveSkillDiscoveryConfig } from "../plugin/skill-discovery-config";
|
|
29
31
|
|
|
30
32
|
export async function applyCommandConfig(params: {
|
|
31
33
|
config: Record<string, unknown>;
|
|
@@ -39,10 +41,13 @@ export async function applyCommandConfig(params: {
|
|
|
39
41
|
const systemCommands = (params.config.command as Record<string, unknown>) ?? {};
|
|
40
42
|
|
|
41
43
|
const includeClaudeCommands = params.pluginConfig.claude_code?.commands ?? true;
|
|
42
|
-
const
|
|
44
|
+
const discovery = resolveSkillDiscoveryConfig(params.pluginConfig);
|
|
43
45
|
|
|
44
46
|
const externalSkillPlugin = detectExternalSkillPlugin(params.ctx.directory);
|
|
45
|
-
if (
|
|
47
|
+
if (
|
|
48
|
+
(discovery.project_claude || discovery.global_claude || discovery.global_opencode) &&
|
|
49
|
+
externalSkillPlugin.detected
|
|
50
|
+
) {
|
|
46
51
|
log(getSkillPluginConflictWarning(externalSkillPlugin.pluginName!));
|
|
47
52
|
}
|
|
48
53
|
|
|
@@ -52,6 +57,7 @@ export async function applyCommandConfig(params: {
|
|
|
52
57
|
projectCommands,
|
|
53
58
|
opencodeGlobalCommands,
|
|
54
59
|
opencodeProjectCommands,
|
|
60
|
+
managedPluginSkills,
|
|
55
61
|
userSkills,
|
|
56
62
|
globalAgentsSkills,
|
|
57
63
|
projectSkills,
|
|
@@ -59,24 +65,28 @@ export async function applyCommandConfig(params: {
|
|
|
59
65
|
opencodeGlobalSkills,
|
|
60
66
|
opencodeProjectSkills,
|
|
61
67
|
] = await Promise.all([
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
68
|
+
discovery.config_sources
|
|
69
|
+
? discoverConfigSourceSkills({
|
|
70
|
+
config: params.pluginConfig.skills,
|
|
71
|
+
configDir: params.ctx.directory,
|
|
72
|
+
})
|
|
73
|
+
: Promise.resolve([]),
|
|
66
74
|
includeClaudeCommands ? loadUserCommands() : Promise.resolve({}),
|
|
67
75
|
includeClaudeCommands ? loadProjectCommands(params.ctx.directory) : Promise.resolve({}),
|
|
68
76
|
loadOpencodeGlobalCommands(),
|
|
69
77
|
loadOpencodeProjectCommands(params.ctx.directory),
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
78
|
+
loadManagedPluginSkills(),
|
|
79
|
+
discovery.global_claude ? loadUserSkills() : Promise.resolve({}),
|
|
80
|
+
discovery.global_agents ? loadGlobalAgentsSkills() : Promise.resolve({}),
|
|
81
|
+
discovery.project_claude ? loadProjectSkills(params.ctx.directory) : Promise.resolve({}),
|
|
82
|
+
discovery.project_agents ? loadProjectAgentsSkills(params.ctx.directory) : Promise.resolve({}),
|
|
83
|
+
discovery.global_opencode ? loadOpencodeGlobalSkills() : Promise.resolve({}),
|
|
84
|
+
discovery.project_opencode ? loadOpencodeProjectSkills(params.ctx.directory) : Promise.resolve({}),
|
|
76
85
|
]);
|
|
77
86
|
|
|
78
87
|
params.config.command = {
|
|
79
88
|
...builtinCommands,
|
|
89
|
+
...managedPluginSkills,
|
|
80
90
|
...skillsToCommandDefinitionRecord(configSourceSkills),
|
|
81
91
|
...userCommands,
|
|
82
92
|
...userSkills,
|
|
@@ -64,10 +64,10 @@ export const AGENT_NAME_MAP: Record<string, string> = {
|
|
|
64
64
|
// Designer
|
|
65
65
|
designer: "designer",
|
|
66
66
|
|
|
67
|
-
// Multimodal
|
|
68
|
-
ui: "
|
|
69
|
-
vision: "
|
|
70
|
-
multimodal: "
|
|
67
|
+
// Multimodal / Vision
|
|
68
|
+
ui: "multimodal",
|
|
69
|
+
vision: "multimodal",
|
|
70
|
+
multimodal: "multimodal",
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
export const BUILTIN_AGENT_NAMES = new Set([
|
|
@@ -77,7 +77,7 @@ export const BUILTIN_AGENT_NAMES = new Set([
|
|
|
77
77
|
"critic",
|
|
78
78
|
"designer",
|
|
79
79
|
"researcher",
|
|
80
|
-
"
|
|
80
|
+
"multimodal",
|
|
81
81
|
"platform-manager",
|
|
82
82
|
"guard",
|
|
83
83
|
])
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs"
|
|
2
|
+
import { join } from "node:path"
|
|
3
|
+
|
|
4
|
+
import type { HiaiOpenCodeConfig, HiaiOpencodeConfig } from "../config"
|
|
5
|
+
import { HIAI_MCP_REGISTRY } from "../mcp/registry"
|
|
6
|
+
import { parseJsoncSafe } from "./jsonc-parser"
|
|
7
|
+
import { getOpenCodeConfigPaths } from "./opencode-config-dir"
|
|
8
|
+
import { PLUGIN_NAME } from "./plugin-identity"
|
|
9
|
+
|
|
10
|
+
interface OpenCodeConfig {
|
|
11
|
+
plugin?: Array<string | [string, ...unknown[]]>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function readPlugins(configPath: string): string[] {
|
|
15
|
+
if (!existsSync(configPath)) return []
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const content = readFileSync(configPath, "utf-8")
|
|
19
|
+
const parsed = parseJsoncSafe<OpenCodeConfig>(content)
|
|
20
|
+
return (parsed.data?.plugin ?? [])
|
|
21
|
+
.map((entry) => typeof entry === "string" ? entry : Array.isArray(entry) ? entry[0] : "")
|
|
22
|
+
.filter((entry): entry is string => typeof entry === "string" && entry.trim().length > 0)
|
|
23
|
+
} catch {
|
|
24
|
+
return []
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function warnIfListPluginEntry(directory: string): void {
|
|
29
|
+
const globalPaths = getOpenCodeConfigPaths({ binary: "opencode", version: null })
|
|
30
|
+
const candidates = [
|
|
31
|
+
join(directory, ".opencode", "opencode.json"),
|
|
32
|
+
join(directory, ".opencode", "opencode.jsonc"),
|
|
33
|
+
globalPaths.configJson,
|
|
34
|
+
globalPaths.configJsonc,
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
for (const configPath of candidates) {
|
|
38
|
+
const plugins = readPlugins(configPath)
|
|
39
|
+
if (!plugins.includes("list")) continue
|
|
40
|
+
|
|
41
|
+
console.warn(`[hiai-opencode] WARNING: ${configPath} contains plugin: ["list"].`)
|
|
42
|
+
console.warn("[hiai-opencode] This can prevent hiai-opencode MCP servers from loading from that config scope.")
|
|
43
|
+
console.warn(`[hiai-opencode] Update it to: plugin: ["${PLUGIN_NAME}"]`)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function hasConfigAuthFallback(pluginConfig: HiaiOpenCodeConfig, envName: string): boolean {
|
|
48
|
+
if (envName === "FIRECRAWL_API_KEY") return !!pluginConfig.auth?.firecrawl?.trim()
|
|
49
|
+
if (envName === "STITCH_AI_API_KEY") return !!pluginConfig.auth?.stitch?.trim()
|
|
50
|
+
if (envName === "CONTEXT7_API_KEY") return !!pluginConfig.auth?.context7?.trim()
|
|
51
|
+
return false
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function warnMissingRequiredMcpEnv(args: {
|
|
55
|
+
pluginConfig: HiaiOpenCodeConfig
|
|
56
|
+
platformConfig: HiaiOpencodeConfig
|
|
57
|
+
}): void {
|
|
58
|
+
const disabled = new Set(args.pluginConfig.disabled_mcps ?? [])
|
|
59
|
+
const mcpConfig = args.platformConfig.mcp ?? {}
|
|
60
|
+
|
|
61
|
+
for (const [name, entry] of Object.entries(HIAI_MCP_REGISTRY)) {
|
|
62
|
+
if (disabled.has(name)) continue
|
|
63
|
+
if (mcpConfig[name]?.enabled === false) continue
|
|
64
|
+
if (!entry.requiredEnv || entry.requiredEnv.length === 0) continue
|
|
65
|
+
|
|
66
|
+
const missing = entry.requiredEnv.filter((envName) =>
|
|
67
|
+
!process.env[envName]?.trim() && !hasConfigAuthFallback(args.pluginConfig, envName)
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
if (missing.length === 0) continue
|
|
71
|
+
|
|
72
|
+
console.warn(
|
|
73
|
+
`[hiai-opencode] MCP "${name}" is enabled but missing required env: ${missing.join(", ")}.`
|
|
74
|
+
+ " The plugin will continue to load; set the key or disable this MCP in hiai-opencode.json.",
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -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)
|
package/src/config/models.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
export const MODEL_ROLE_GUIDE = [
|
|
2
|
-
"fast: cheap/default for bounded helpers, researcher-style scans, platform chores",
|
|
3
|
-
"mid: balanced default for steady execution/review work",
|
|
4
|
-
"high: stronger general-purpose model for primary implementation and planning",
|
|
5
|
-
"ultrahigh: highest-cost/high-accuracy slot for hard architecture or critical decisions",
|
|
6
|
-
"vision: preferred for UI/media/multimodal interpretation and visual work",
|
|
7
|
-
"reasoning: preferred for deeper multi-step reasoning when latency/cost are acceptable",
|
|
8
|
-
] as const;
|
|
9
|
-
|
|
10
|
-
export const PROVIDER_MODEL_RULES = [
|
|
11
|
-
"openai: use `openai/<model>` for direct OpenAI calls, e.g. `openai/gpt-5` or `openai/o1`",
|
|
12
|
-
"anthropic: use `anthropic/<model>`, e.g. `anthropic/claude-3.5-sonnet`",
|
|
13
|
-
"deepseek: use `deepseek/<model>` when connected directly",
|
|
14
|
-
"glm: use `z-ai/<model>` or the provider id exposed by your gateway/client",
|
|
15
|
-
"minimax: use `minimax/<model>`",
|
|
16
|
-
"qwen: use `qwen/<model>`",
|
|
17
|
-
"ollama: use the native local model id in Ollama config, e.g. `qwen3.5:4b`",
|
|
18
|
-
"openrouter: use `openrouter/<vendor>/<model>`, e.g. `openrouter/anthropic/claude-3.5-sonnet`",
|
|
19
|
-
"rule: store fully qualified model ids in config; avoid local aliases like `fast`, `sonnet`, or provider-less ids",
|
|
20
|
-
] as const;
|
|
21
|
-
|
|
22
|
-
export const MODEL_PRESETS = {
|
|
23
|
-
fast: "openrouter/google/gemini-2.0-flash",
|
|
24
|
-
mid: "openrouter/anthropic/claude-3.5-sonnet",
|
|
25
|
-
high: "openrouter/anthropic/claude-3.5-opus",
|
|
26
|
-
ultrahigh: "openrouter/openai/gpt-4o",
|
|
27
|
-
vision: "openrouter/google/gemini-2.0-pro-exp-02-05",
|
|
28
|
-
reasoning: "openrouter/openai/o1",
|
|
29
|
-
strategist: "openrouter/z-ai/glm-5.1",
|
|
30
|
-
critic: "openrouter/qwen/qwen2.5-72b-instruct",
|
|
31
|
-
writing: "openrouter/kimi/kimi-latest",
|
|
32
|
-
} as const;
|