@hiai-gg/hiai-opencode 0.1.4 → 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/AGENTS.md +5 -3
- package/ARCHITECTURE.md +7 -6
- package/README.md +16 -4
- package/config/hiai-opencode.schema.json +71 -148
- package/dist/config/defaults.d.ts +1 -0
- package/dist/config/platform-schema.d.ts +273 -4
- package/dist/config/schema/categories.d.ts +2 -2
- package/dist/config/types.d.ts +21 -2
- package/dist/create-tools.d.ts +2 -0
- package/dist/index.js +577 -342
- package/dist/internals/plugins/websearch-cited/index.d.ts +7 -1
- package/dist/mcp/types.d.ts +1 -1
- package/dist/plugin/tool-registry.d.ts +2 -0
- package/dist/tools/skill-mcp/tools.d.ts +2 -0
- package/hiai-opencode.json +39 -173
- package/package.json +1 -1
- package/src/config/defaults.ts +158 -28
- package/src/config/loader.test.ts +16 -1
- package/src/config/loader.ts +4 -2
- package/src/config/platform-schema.ts +36 -2
- package/src/config/types.ts +33 -2
- package/src/create-tools.ts +4 -1
- package/src/index.ts +39 -1
- 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/tool-registry.ts +4 -0
- package/src/tools/skill-mcp/tools.ts +45 -7
|
@@ -3,9 +3,15 @@ import type { GetAuth } from "./types";
|
|
|
3
3
|
export declare const GOOGLE_PROVIDER_ID = "google";
|
|
4
4
|
export declare const OPENAI_PROVIDER_ID = "openai";
|
|
5
5
|
export declare const OPENROUTER_PROVIDER_ID = "openrouter";
|
|
6
|
+
type SelectedProviderID = typeof GOOGLE_PROVIDER_ID | typeof OPENAI_PROVIDER_ID | typeof OPENROUTER_PROVIDER_ID;
|
|
6
7
|
export declare function registerGetAuth(providerID: string, getAuth: GetAuth): void;
|
|
7
8
|
export declare function resolveGetAuth(providerID: string): GetAuth | undefined;
|
|
8
|
-
|
|
9
|
+
type SelectedWebsearchConfig = {
|
|
10
|
+
providerID: SelectedProviderID;
|
|
11
|
+
model: string;
|
|
12
|
+
};
|
|
13
|
+
export type WebsearchCitedFallback = SelectedWebsearchConfig;
|
|
14
|
+
declare const WebsearchCitedPlugin: (_ctx?: unknown, fallback?: WebsearchCitedFallback) => Promise<any>;
|
|
9
15
|
export declare const WebsearchCitedGooglePlugin: Plugin;
|
|
10
16
|
export declare const WebsearchCitedOpenAIPlugin: Plugin;
|
|
11
17
|
export default WebsearchCitedPlugin;
|
package/dist/mcp/types.d.ts
CHANGED
|
@@ -3,10 +3,10 @@ export declare const McpNameSchema: z.ZodEnum<{
|
|
|
3
3
|
websearch: "websearch";
|
|
4
4
|
stitch: "stitch";
|
|
5
5
|
firecrawl: "firecrawl";
|
|
6
|
+
context7: "context7";
|
|
6
7
|
playwright: "playwright";
|
|
7
8
|
"sequential-thinking": "sequential-thinking";
|
|
8
9
|
rag: "rag";
|
|
9
|
-
context7: "context7";
|
|
10
10
|
mempalace: "mempalace";
|
|
11
11
|
grep_app: "grep_app";
|
|
12
12
|
}>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AvailableCategory } from "../agents/dynamic-agent-prompt-builder";
|
|
2
2
|
import type { HiaiOpenCodeConfig } from "../config";
|
|
3
|
+
import type { McpServerConfig } from "../config/types";
|
|
3
4
|
import type { PluginContext, ToolsRecord } from "./types";
|
|
4
5
|
import { builtinTools, createBackgroundTools, createCallOmoAgent, createLookAt, createSkillMcpTool, createSkillTool, createGrepTools, createGlobTools, createAstGrepTools, createSessionManagerTools, createDelegateTask, discoverCommandsSync, interactive_bash, createTaskCreateTool, createTaskGetTool, createTaskList, createTaskUpdateTool, createHashlineEditTool } from "../tools";
|
|
5
6
|
import type { Managers } from "../create-managers";
|
|
@@ -35,6 +36,7 @@ export declare function createToolRegistry(args: {
|
|
|
35
36
|
managers: Pick<Managers, "backgroundManager" | "tmuxSessionManager" | "skillMcpManager">;
|
|
36
37
|
skillContext: SkillContext;
|
|
37
38
|
availableCategories: AvailableCategory[];
|
|
39
|
+
builtinMcp?: Record<string, McpServerConfig>;
|
|
38
40
|
interactiveBashEnabled?: boolean;
|
|
39
41
|
toolFactories?: Partial<ToolRegistryFactories>;
|
|
40
42
|
}): ToolRegistryResult;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { type ToolDefinition } from "@opencode-ai/plugin";
|
|
2
|
+
import type { McpServerConfig } from "../../config/types";
|
|
2
3
|
import type { SkillMcpManager } from "../../features/skill-mcp-manager";
|
|
3
4
|
import type { LoadedSkill } from "../../features/opencode-skill-loader/types";
|
|
4
5
|
interface SkillMcpToolOptions {
|
|
5
6
|
manager: SkillMcpManager;
|
|
6
7
|
getLoadedSkills: () => LoadedSkill[];
|
|
7
8
|
getSessionID?: () => string | undefined;
|
|
9
|
+
builtinMcp?: Record<string, McpServerConfig>;
|
|
8
10
|
}
|
|
9
11
|
export declare function applyGrepFilter(output: string, pattern: string | undefined): string;
|
|
10
12
|
export declare function createSkillMcpTool(options: SkillMcpToolOptions): ToolDefinition;
|
package/hiai-opencode.json
CHANGED
|
@@ -1,170 +1,69 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_description": "
|
|
2
|
+
"_description": "hiai-opencode user config. Edit only this file in your project as .opencode/hiai-opencode.json. Model provider credentials are configured in OpenCode Connect; this file stores model IDs only.",
|
|
3
3
|
"$schema": "./config/hiai-opencode.schema.json",
|
|
4
|
-
"
|
|
4
|
+
"models": {
|
|
5
5
|
"bob": {
|
|
6
|
-
"model": "openrouter/
|
|
7
|
-
"
|
|
6
|
+
"model": "openrouter/moonshotai/kimi-k2.6",
|
|
7
|
+
"recommended": "xhigh"
|
|
8
8
|
},
|
|
9
9
|
"coder": {
|
|
10
|
-
"model": "openrouter/
|
|
11
|
-
"
|
|
10
|
+
"model": "openrouter/minimax/minimax-m2.7",
|
|
11
|
+
"recommended": "high"
|
|
12
12
|
},
|
|
13
13
|
"strategist": {
|
|
14
|
-
"model": "openrouter/
|
|
15
|
-
"
|
|
14
|
+
"model": "openrouter/anthropic/claude-opus-latest",
|
|
15
|
+
"recommended": "high"
|
|
16
16
|
},
|
|
17
17
|
"guard": {
|
|
18
|
-
"model": "openrouter/
|
|
19
|
-
"
|
|
18
|
+
"model": "openrouter/qwen/qwen3.6-plus",
|
|
19
|
+
"recommended": "middle"
|
|
20
20
|
},
|
|
21
21
|
"critic": {
|
|
22
|
-
"model": "openrouter/
|
|
23
|
-
"
|
|
22
|
+
"model": "openrouter/xiaomi/mimo-v2.5-pro",
|
|
23
|
+
"recommended": "high"
|
|
24
24
|
},
|
|
25
25
|
"designer": {
|
|
26
|
-
"model": "openrouter/google/gemini-3.1-pro",
|
|
27
|
-
"
|
|
26
|
+
"model": "openrouter/google/gemini-3.1-pro-preview",
|
|
27
|
+
"recommended": "design"
|
|
28
28
|
},
|
|
29
29
|
"researcher": {
|
|
30
|
-
"model": "openrouter/
|
|
31
|
-
"
|
|
30
|
+
"model": "openrouter/deepseek/deepseek-v4-flash",
|
|
31
|
+
"recommended": "fast"
|
|
32
32
|
},
|
|
33
|
-
"
|
|
34
|
-
"model": "openrouter/
|
|
35
|
-
"
|
|
33
|
+
"manager": {
|
|
34
|
+
"model": "openrouter/qwen/qwen3.5-9b",
|
|
35
|
+
"recommended": "fast"
|
|
36
36
|
},
|
|
37
37
|
"brainstormer": {
|
|
38
|
-
"model": "openrouter/
|
|
39
|
-
"
|
|
38
|
+
"model": "openrouter/mistralai/mistral-small-2603",
|
|
39
|
+
"recommended": "writing"
|
|
40
40
|
},
|
|
41
|
-
"
|
|
42
|
-
"model": "openrouter/google/
|
|
43
|
-
"
|
|
44
|
-
},
|
|
45
|
-
"sub": {
|
|
46
|
-
"model": "openrouter/google/gemini-2.0-flash",
|
|
47
|
-
"description": "Hidden compatibility executor. Primary subtask execution is handled by Coder."
|
|
48
|
-
},
|
|
49
|
-
"quality-guardian": {
|
|
50
|
-
"model": "openrouter/anthropic/claude-3.5-sonnet",
|
|
51
|
-
"description": "Hidden compatibility review agent. Primary review work is handled by Critic."
|
|
52
|
-
},
|
|
53
|
-
"agent-skills": {
|
|
54
|
-
"model": "openrouter/google/gemini-2.0-flash",
|
|
55
|
-
"description": "Hidden system agent for skill registry, discovery, and capability orchestration."
|
|
41
|
+
"vision": {
|
|
42
|
+
"model": "openrouter/google/gemma-4-26b-a4b-it",
|
|
43
|
+
"recommended": "vision"
|
|
56
44
|
}
|
|
57
45
|
},
|
|
58
|
-
"agentRequirements": {},
|
|
59
|
-
"categories": {
|
|
60
|
-
"visual-engineering": {
|
|
61
|
-
"model": "openrouter/google/gemini-2.0-pro-exp-02-05",
|
|
62
|
-
"variant": "high",
|
|
63
|
-
"description": "Frontend and UI systems work with design-system discipline. Uses Coder deep execution contour."
|
|
64
|
-
},
|
|
65
|
-
"artistry": {
|
|
66
|
-
"model": "openrouter/google/gemini-2.0-pro-exp-02-05",
|
|
67
|
-
"variant": "high",
|
|
68
|
-
"description": "High-effort creative problem solving beyond standard patterns. Uses Coder deep execution contour."
|
|
69
|
-
},
|
|
70
|
-
"ultrabrain": {
|
|
71
|
-
"model": "openrouter/openai/gpt-4o",
|
|
72
|
-
"variant": "xhigh",
|
|
73
|
-
"description": "Hard logic and architecture tasks. Uses Coder deep execution contour."
|
|
74
|
-
},
|
|
75
|
-
"deep": {
|
|
76
|
-
"model": "openrouter/openai/o1",
|
|
77
|
-
"variant": "medium",
|
|
78
|
-
"description": "Deep autonomous implementation with full context buildup. Uses Coder deep execution contour."
|
|
79
|
-
},
|
|
80
|
-
"quick": {
|
|
81
|
-
"model": "openrouter/google/gemini-2.0-flash",
|
|
82
|
-
"description": "Fast bounded execution: single-file fixes, typos, and simple modifications. Uses Coder fast execution contour."
|
|
83
|
-
},
|
|
84
|
-
"writing": {
|
|
85
|
-
"model": "openrouter/kimi/kimi-latest",
|
|
86
|
-
"description": "Documentation and prose tasks with bounded scope. Uses Coder fast execution contour."
|
|
87
|
-
},
|
|
88
|
-
"git": {
|
|
89
|
-
"model": "openrouter/google/gemini-2.0-flash",
|
|
90
|
-
"description": "Git, commit, diff, and repository hygiene tasks."
|
|
91
|
-
},
|
|
92
|
-
"unspecified-low": {
|
|
93
|
-
"model": "openrouter/anthropic/claude-3.5-sonnet",
|
|
94
|
-
"description": "Unclassifiable moderate tasks with bounded scope. Uses Coder fast execution contour."
|
|
95
|
-
},
|
|
96
|
-
"unspecified-high": {
|
|
97
|
-
"model": "openrouter/anthropic/claude-3.5-opus",
|
|
98
|
-
"variant": "max",
|
|
99
|
-
"description": "Unclassifiable substantial tasks across modules. Uses Coder deep execution contour."
|
|
100
|
-
}
|
|
101
|
-
},
|
|
102
|
-
"categoryRequirements": {},
|
|
103
|
-
"modelFamilies": [],
|
|
104
46
|
"auth": {
|
|
105
47
|
"googleSearch": "{env:GOOGLE_SEARCH_API_KEY}",
|
|
106
48
|
"stitch": "{env:STITCH_AI_API_KEY}",
|
|
107
49
|
"firecrawl": "{env:FIRECRAWL_API_KEY}",
|
|
108
50
|
"context7": "{env:CONTEXT7_API_KEY}"
|
|
109
51
|
},
|
|
110
|
-
"ollama": {
|
|
111
|
-
"enabled": false,
|
|
112
|
-
"model": "{env:OLLAMA_MODEL:-qwen3.5:4b}",
|
|
113
|
-
"baseUrl": "{env:OLLAMA_BASE_URL:-http://localhost:11434}",
|
|
114
|
-
"purpose": "helper"
|
|
115
|
-
},
|
|
116
|
-
"fast_apply": {
|
|
117
|
-
"enabled": false,
|
|
118
|
-
"ollama_url": "{env:OLLAMA_BASE_URL:-http://localhost:11434}",
|
|
119
|
-
"model": "{env:OLLAMA_MODEL:-qwen3.5:4b}",
|
|
120
|
-
"timeout": 30000
|
|
121
|
-
},
|
|
122
52
|
"mcp": {
|
|
123
|
-
"playwright": {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
},
|
|
128
|
-
"
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
},
|
|
135
|
-
"
|
|
136
|
-
|
|
137
|
-
"command": ["node", "{pluginRoot}/assets/runtime/npm-package-runner.mjs", "@modelcontextprotocol/server-sequential-thinking"],
|
|
138
|
-
"timeout": 600000
|
|
139
|
-
},
|
|
140
|
-
"firecrawl": {
|
|
141
|
-
"enabled": true,
|
|
142
|
-
"command": ["node", "{pluginRoot}/assets/runtime/npm-package-runner.mjs", "firecrawl-mcp"],
|
|
143
|
-
"environment": { "FIRECRAWL_API_KEY": "{env:FIRECRAWL_API_KEY}" },
|
|
144
|
-
"timeout": 600000
|
|
145
|
-
},
|
|
146
|
-
"rag": {
|
|
147
|
-
"enabled": true,
|
|
148
|
-
"type": "local",
|
|
149
|
-
"command": ["node", "{pluginRoot}/assets/mcp/rag.mjs"],
|
|
150
|
-
"environment": {
|
|
151
|
-
"OPENCODE_RAG_URL": "{env:OPENCODE_RAG_URL:-http://localhost:9002/tools/search}"
|
|
152
|
-
},
|
|
153
|
-
"timeout": 600000
|
|
154
|
-
},
|
|
155
|
-
"mempalace": {
|
|
156
|
-
"enabled": true,
|
|
157
|
-
"type": "local",
|
|
158
|
-
"command": ["node", "{pluginRoot}/assets/mcp/mempalace.mjs", "--palace", "./.opencode/palace"],
|
|
159
|
-
"timeout": 600000
|
|
160
|
-
},
|
|
161
|
-
"context7": {
|
|
162
|
-
"enabled": true,
|
|
163
|
-
"type": "remote",
|
|
164
|
-
"url": "https://mcp.context7.com/mcp",
|
|
165
|
-
"headers": { "X-API-KEY": "{env:CONTEXT7_API_KEY}" },
|
|
166
|
-
"timeout": 600000
|
|
167
|
-
}
|
|
53
|
+
"playwright": { "enabled": true },
|
|
54
|
+
"stitch": { "enabled": true },
|
|
55
|
+
"sequential-thinking": { "enabled": true },
|
|
56
|
+
"firecrawl": { "enabled": true },
|
|
57
|
+
"rag": { "enabled": true },
|
|
58
|
+
"mempalace": { "enabled": true },
|
|
59
|
+
"context7": { "enabled": true }
|
|
60
|
+
},
|
|
61
|
+
"lsp": {
|
|
62
|
+
"typescript": { "enabled": true },
|
|
63
|
+
"svelte": { "enabled": true },
|
|
64
|
+
"eslint": { "enabled": true },
|
|
65
|
+
"bash": { "enabled": true },
|
|
66
|
+
"pyright": { "enabled": true }
|
|
168
67
|
},
|
|
169
68
|
"skill_discovery": {
|
|
170
69
|
"config_sources": true,
|
|
@@ -175,40 +74,7 @@
|
|
|
175
74
|
"project_agents": false,
|
|
176
75
|
"global_agents": false
|
|
177
76
|
},
|
|
178
|
-
"skills": {
|
|
179
|
-
"enabled": true,
|
|
180
|
-
"disabled": []
|
|
181
|
-
},
|
|
182
|
-
"lsp": {
|
|
183
|
-
"typescript": {
|
|
184
|
-
"command": ["typescript-language-server", "--stdio"],
|
|
185
|
-
"extensions": [".ts", ".tsx", ".mts", ".cts"]
|
|
186
|
-
},
|
|
187
|
-
"svelte": {
|
|
188
|
-
"command": ["svelteserver", "--stdio"],
|
|
189
|
-
"extensions": [".svelte"]
|
|
190
|
-
},
|
|
191
|
-
"eslint": {
|
|
192
|
-
"command": ["node", "{pluginRoot}/assets/runtime/npm-package-runner.mjs", "eslint-lsp", "--stdio"],
|
|
193
|
-
"extensions": [".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs", ".svelte"]
|
|
194
|
-
},
|
|
195
|
-
"bash": {
|
|
196
|
-
"command": ["node", "{pluginRoot}/assets/runtime/npm-package-runner.mjs", "bash-language-server", "start"],
|
|
197
|
-
"extensions": [".sh", ".bash"]
|
|
198
|
-
},
|
|
199
|
-
"pyright": {
|
|
200
|
-
"command": ["pyright-langserver", "--stdio"],
|
|
201
|
-
"extensions": [".py"]
|
|
202
|
-
}
|
|
203
|
-
},
|
|
204
77
|
"subtask2": {
|
|
205
|
-
"replace_generic": true
|
|
206
|
-
"generic_return": null
|
|
207
|
-
},
|
|
208
|
-
"permissions": {
|
|
209
|
-
"read": { "*": "allow", "*.env": "deny", "*.env.*": "deny", "*.env.example": "allow" },
|
|
210
|
-
"edit": { "*": "allow" },
|
|
211
|
-
"bash": { "*": "allow" },
|
|
212
|
-
"deny_paths": ["**/backup/**", "**/secrets.*", "**/.env", "**/.env.*"]
|
|
78
|
+
"replace_generic": true
|
|
213
79
|
}
|
|
214
80
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hiai-gg/hiai-opencode",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Unified OpenCode plugin — canonical 12-agent model with bundled skills, MCP integrations, LSP, and permissions in one install.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
package/src/config/defaults.ts
CHANGED
|
@@ -1,23 +1,61 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Runtime defaults for hiai-opencode.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* - agent models
|
|
7
|
-
* - category models
|
|
8
|
-
* - user-editable runtime defaults
|
|
4
|
+
* The user-facing config owns model choice through 10 primary agent slots.
|
|
5
|
+
* Internal routing derives hidden agents and task categories from those slots.
|
|
9
6
|
*/
|
|
10
7
|
import { existsSync, readFileSync } from "node:fs"
|
|
11
8
|
import { dirname, join, normalize } from "node:path"
|
|
12
|
-
import
|
|
9
|
+
import { createDefaultMcpConfig } from "../mcp/registry.js"
|
|
10
|
+
import type { HiaiOpencodeConfig, LspServerConfig } from "./types.js"
|
|
11
|
+
|
|
12
|
+
const REQUIRED_MODEL_SLOTS = [
|
|
13
|
+
"bob",
|
|
14
|
+
"coder",
|
|
15
|
+
"strategist",
|
|
16
|
+
"guard",
|
|
17
|
+
"critic",
|
|
18
|
+
"designer",
|
|
19
|
+
"researcher",
|
|
20
|
+
"manager",
|
|
21
|
+
"brainstormer",
|
|
22
|
+
"vision",
|
|
23
|
+
] as const
|
|
24
|
+
|
|
25
|
+
type ModelSlot = (typeof REQUIRED_MODEL_SLOTS)[number]
|
|
26
|
+
|
|
27
|
+
const DEFAULT_LSP: Record<string, LspServerConfig> = {
|
|
28
|
+
typescript: {
|
|
29
|
+
enabled: true,
|
|
30
|
+
command: ["typescript-language-server", "--stdio"],
|
|
31
|
+
extensions: [".ts", ".tsx", ".mts", ".cts"],
|
|
32
|
+
},
|
|
33
|
+
svelte: {
|
|
34
|
+
enabled: true,
|
|
35
|
+
command: ["svelteserver", "--stdio"],
|
|
36
|
+
extensions: [".svelte"],
|
|
37
|
+
},
|
|
38
|
+
eslint: {
|
|
39
|
+
enabled: true,
|
|
40
|
+
command: ["node", "{pluginRoot}/assets/runtime/npm-package-runner.mjs", "eslint-lsp", "--stdio"],
|
|
41
|
+
extensions: [".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs", ".svelte"],
|
|
42
|
+
},
|
|
43
|
+
bash: {
|
|
44
|
+
enabled: true,
|
|
45
|
+
command: ["node", "{pluginRoot}/assets/runtime/npm-package-runner.mjs", "bash-language-server", "start"],
|
|
46
|
+
extensions: [".sh", ".bash"],
|
|
47
|
+
},
|
|
48
|
+
pyright: {
|
|
49
|
+
enabled: true,
|
|
50
|
+
command: ["pyright-langserver", "--stdio"],
|
|
51
|
+
extensions: [".py"],
|
|
52
|
+
},
|
|
53
|
+
}
|
|
13
54
|
|
|
14
55
|
function findPluginRoot(): string {
|
|
15
56
|
const candidates = [
|
|
16
|
-
// Source tree: src/config/defaults.ts -> repo root
|
|
17
57
|
join(import.meta.dirname, "..", ".."),
|
|
18
|
-
// Built package: dist/index.js bundle -> package root
|
|
19
58
|
join(import.meta.dirname, ".."),
|
|
20
|
-
// Non-bundled compiled output: dist/config/defaults.js -> package root
|
|
21
59
|
join(import.meta.dirname, "..", ".."),
|
|
22
60
|
dirname(process.argv[1] ?? ""),
|
|
23
61
|
process.cwd(),
|
|
@@ -25,35 +63,122 @@ function findPluginRoot(): string {
|
|
|
25
63
|
|
|
26
64
|
for (const candidate of candidates) {
|
|
27
65
|
const root = normalize(candidate)
|
|
28
|
-
if (existsSync(join(root, "hiai-opencode.json")))
|
|
29
|
-
return root
|
|
30
|
-
}
|
|
66
|
+
if (existsSync(join(root, "hiai-opencode.json"))) return root
|
|
31
67
|
}
|
|
32
68
|
|
|
33
|
-
throw new Error(
|
|
34
|
-
"[hiai-opencode] Cannot find bundled hiai-opencode.json. The package is incomplete.",
|
|
35
|
-
)
|
|
69
|
+
throw new Error("[hiai-opencode] Cannot find bundled hiai-opencode.json. The package is incomplete.")
|
|
36
70
|
}
|
|
37
71
|
|
|
38
72
|
function expandPluginRootPlaceholders(value: unknown, pluginRoot: string): unknown {
|
|
39
|
-
if (typeof value === "string") {
|
|
40
|
-
|
|
73
|
+
if (typeof value === "string") return value.replaceAll("{pluginRoot}", pluginRoot)
|
|
74
|
+
if (Array.isArray(value)) return value.map((item) => expandPluginRootPlaceholders(item, pluginRoot))
|
|
75
|
+
if (value && typeof value === "object") {
|
|
76
|
+
return Object.fromEntries(
|
|
77
|
+
Object.entries(value).map(([key, entry]) => [key, expandPluginRootPlaceholders(entry, pluginRoot)]),
|
|
78
|
+
)
|
|
41
79
|
}
|
|
80
|
+
return value
|
|
81
|
+
}
|
|
42
82
|
|
|
43
|
-
|
|
44
|
-
|
|
83
|
+
function requireModelSlots(config: HiaiOpencodeConfig): Record<ModelSlot, string> {
|
|
84
|
+
const models = config.models ?? {}
|
|
85
|
+
const resolved = Object.fromEntries(
|
|
86
|
+
REQUIRED_MODEL_SLOTS.map((slot) => {
|
|
87
|
+
const value = models[slot]
|
|
88
|
+
const model = typeof value === "string" ? value : value?.model
|
|
89
|
+
return [slot, model?.trim() ?? ""]
|
|
90
|
+
}),
|
|
91
|
+
) as Record<ModelSlot, string>
|
|
92
|
+
|
|
93
|
+
const missing = REQUIRED_MODEL_SLOTS.filter((slot) => !resolved[slot])
|
|
94
|
+
if (missing.length > 0) {
|
|
95
|
+
throw new Error(`[hiai-opencode] Missing required model slot(s) in hiai-opencode.json: ${missing.join(", ")}`)
|
|
45
96
|
}
|
|
97
|
+
return resolved
|
|
98
|
+
}
|
|
46
99
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
100
|
+
function deriveAgents(models: Record<ModelSlot, string>): HiaiOpencodeConfig["agents"] {
|
|
101
|
+
return {
|
|
102
|
+
bob: { model: models.bob },
|
|
103
|
+
coder: { model: models.coder },
|
|
104
|
+
strategist: { model: models.strategist },
|
|
105
|
+
guard: { model: models.guard },
|
|
106
|
+
critic: { model: models.critic },
|
|
107
|
+
designer: { model: models.designer },
|
|
108
|
+
researcher: { model: models.researcher },
|
|
109
|
+
"platform-manager": { model: models.manager },
|
|
110
|
+
brainstormer: { model: models.brainstormer },
|
|
111
|
+
multimodal: { model: models.vision },
|
|
112
|
+
sub: { model: models.coder },
|
|
113
|
+
"quality-guardian": { model: models.critic },
|
|
114
|
+
"agent-skills": { model: models.manager },
|
|
54
115
|
}
|
|
116
|
+
}
|
|
55
117
|
|
|
56
|
-
|
|
118
|
+
function deriveCategories(models: Record<ModelSlot, string>): HiaiOpencodeConfig["categories"] {
|
|
119
|
+
return {
|
|
120
|
+
"visual-engineering": { model: models.designer, variant: "high" },
|
|
121
|
+
artistry: { model: models.designer, variant: "high" },
|
|
122
|
+
ultrabrain: { model: models.strategist, variant: "xhigh" },
|
|
123
|
+
deep: { model: models.coder, variant: "medium" },
|
|
124
|
+
quick: { model: models.researcher },
|
|
125
|
+
writing: { model: models.brainstormer },
|
|
126
|
+
git: { model: models.manager },
|
|
127
|
+
"unspecified-low": { model: models.coder },
|
|
128
|
+
"unspecified-high": { model: models.bob, variant: "max" },
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function deriveMcp(config: HiaiOpencodeConfig): HiaiOpencodeConfig["mcp"] {
|
|
133
|
+
const defaults = createDefaultMcpConfig()
|
|
134
|
+
const userMcp = config.mcp ?? {}
|
|
135
|
+
return Object.fromEntries(
|
|
136
|
+
Object.entries(defaults).map(([name, entry]) => {
|
|
137
|
+
const override = userMcp[name] ?? {}
|
|
138
|
+
return [name, { ...entry, ...override }]
|
|
139
|
+
}),
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function deriveLsp(config: HiaiOpencodeConfig): HiaiOpencodeConfig["lsp"] {
|
|
144
|
+
const userLsp = config.lsp ?? {}
|
|
145
|
+
return Object.fromEntries(
|
|
146
|
+
Object.entries(DEFAULT_LSP).map(([name, entry]) => {
|
|
147
|
+
const override = userLsp[name] ?? {}
|
|
148
|
+
return [name, { ...entry, ...override }]
|
|
149
|
+
}),
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function materializeConfig(rawConfig: HiaiOpencodeConfig): HiaiOpencodeConfig {
|
|
154
|
+
const models = requireModelSlots(rawConfig)
|
|
155
|
+
return {
|
|
156
|
+
...rawConfig,
|
|
157
|
+
agents: deriveAgents(models),
|
|
158
|
+
agentRequirements: {},
|
|
159
|
+
categories: deriveCategories(models),
|
|
160
|
+
categoryRequirements: {},
|
|
161
|
+
modelFamilies: [],
|
|
162
|
+
mcp: deriveMcp(rawConfig),
|
|
163
|
+
lsp: deriveLsp(rawConfig),
|
|
164
|
+
subtask2: {
|
|
165
|
+
replace_generic: true,
|
|
166
|
+
generic_return: null,
|
|
167
|
+
...(rawConfig.subtask2 ?? {}),
|
|
168
|
+
},
|
|
169
|
+
skills: {
|
|
170
|
+
enabled: true,
|
|
171
|
+
disabled: [],
|
|
172
|
+
...(rawConfig.skills ?? {}),
|
|
173
|
+
},
|
|
174
|
+
permissions: {
|
|
175
|
+
read: { "*": "allow", "*.env": "deny", "*.env.*": "deny", "*.env.example": "allow" },
|
|
176
|
+
edit: { "*": "allow" },
|
|
177
|
+
bash: { "*": "allow" },
|
|
178
|
+
deny_paths: ["**/backup/**", "**/secrets.*", "**/.env", "**/.env.*"],
|
|
179
|
+
...(rawConfig.permissions ?? {}),
|
|
180
|
+
},
|
|
181
|
+
}
|
|
57
182
|
}
|
|
58
183
|
|
|
59
184
|
function loadBundledDefaultConfig(): HiaiOpencodeConfig {
|
|
@@ -61,8 +186,13 @@ function loadBundledDefaultConfig(): HiaiOpencodeConfig {
|
|
|
61
186
|
const configPath = join(pluginRoot, "hiai-opencode.json")
|
|
62
187
|
const raw = readFileSync(configPath, "utf-8")
|
|
63
188
|
const parsed = JSON.parse(raw) as HiaiOpencodeConfig
|
|
189
|
+
const materialized = materializeConfig(parsed)
|
|
190
|
+
|
|
191
|
+
return expandPluginRootPlaceholders(materialized, pluginRoot) as HiaiOpencodeConfig
|
|
192
|
+
}
|
|
64
193
|
|
|
65
|
-
|
|
194
|
+
export function applyModelSlots(config: HiaiOpencodeConfig): HiaiOpencodeConfig {
|
|
195
|
+
return materializeConfig(config)
|
|
66
196
|
}
|
|
67
197
|
|
|
68
198
|
export const defaultConfig: HiaiOpencodeConfig = loadBundledDefaultConfig()
|
|
@@ -28,9 +28,23 @@ function withTempProject(
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
const requiredModels = {
|
|
32
|
+
bob: { model: "openrouter/test/bob", recommended: "xhigh" },
|
|
33
|
+
coder: { model: "openrouter/test/coder", recommended: "high" },
|
|
34
|
+
strategist: { model: "openrouter/test/strategist", recommended: "high" },
|
|
35
|
+
guard: "openrouter/test/guard",
|
|
36
|
+
critic: "openrouter/test/critic",
|
|
37
|
+
designer: "openrouter/test/designer",
|
|
38
|
+
researcher: "openrouter/test/researcher",
|
|
39
|
+
manager: "openrouter/test/manager",
|
|
40
|
+
brainstormer: "openrouter/test/brainstormer",
|
|
41
|
+
vision: "openrouter/test/vision",
|
|
42
|
+
};
|
|
43
|
+
|
|
31
44
|
test("loadConfig accepts compact enabled-only LSP entries for builtin servers", () => {
|
|
32
45
|
withTempProject(
|
|
33
46
|
{
|
|
47
|
+
models: requiredModels,
|
|
34
48
|
lsp: {
|
|
35
49
|
typescript: {
|
|
36
50
|
enabled: true,
|
|
@@ -42,7 +56,7 @@ test("loadConfig accepts compact enabled-only LSP entries for builtin servers",
|
|
|
42
56
|
const tsServer = loaded.lsp?.typescript;
|
|
43
57
|
|
|
44
58
|
expect(tsServer).toBeDefined();
|
|
45
|
-
expect(tsServer?.command
|
|
59
|
+
expect(tsServer?.command?.length).toBeGreaterThan(0);
|
|
46
60
|
expect(tsServer?.extensions).toContain(".ts");
|
|
47
61
|
},
|
|
48
62
|
);
|
|
@@ -51,6 +65,7 @@ test("loadConfig accepts compact enabled-only LSP entries for builtin servers",
|
|
|
51
65
|
test("loadConfig ignores unknown compact enabled-only LSP entries", () => {
|
|
52
66
|
withTempProject(
|
|
53
67
|
{
|
|
68
|
+
models: requiredModels,
|
|
54
69
|
lsp: {
|
|
55
70
|
"custom-server": {
|
|
56
71
|
enabled: true,
|
package/src/config/loader.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { parse } from "jsonc-parser";
|
|
4
4
|
import { HiaiOpencodeConfigSchema } from "./platform-schema.js";
|
|
5
|
-
import { defaultConfig } from "./defaults.js";
|
|
5
|
+
import { applyModelSlots, defaultConfig } from "./defaults.js";
|
|
6
6
|
import {
|
|
7
7
|
LEGACY_AGENT_ALIAS_TO_CANONICAL,
|
|
8
8
|
type CanonicalAgentName,
|
|
@@ -157,10 +157,12 @@ export function loadConfig(projectDir: string): HiaiOpencodeConfig {
|
|
|
157
157
|
const validated = HiaiOpencodeConfigSchema.parse(normalizedParsed);
|
|
158
158
|
const normalized = normalizeAgentAliases(validated);
|
|
159
159
|
|
|
160
|
-
|
|
160
|
+
const merged = deepMerge(
|
|
161
161
|
BASE_CONFIG as unknown as Record<string, unknown>,
|
|
162
162
|
normalized as unknown as Record<string, unknown>,
|
|
163
163
|
) as HiaiOpencodeConfig;
|
|
164
|
+
|
|
165
|
+
return applyModelSlots(merged);
|
|
164
166
|
}
|
|
165
167
|
|
|
166
168
|
export function resolveEnvVars(value: string): string {
|