@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.
@@ -8,6 +8,37 @@ export const AgentConfigSchema = z.object({
8
8
  description: z.string().optional(),
9
9
  });
10
10
 
11
+ export const ModelRecommendationSchema = z.enum([
12
+ "xhigh",
13
+ "high",
14
+ "middle",
15
+ "fast",
16
+ "vision",
17
+ "writing",
18
+ "design",
19
+ ]);
20
+
21
+ export const ModelSlotConfigSchema = z.union([
22
+ z.string(),
23
+ z.object({
24
+ model: z.string(),
25
+ recommended: ModelRecommendationSchema.optional(),
26
+ }),
27
+ ]);
28
+
29
+ export const ModelSlotsConfigSchema = z.object({
30
+ bob: ModelSlotConfigSchema.optional(),
31
+ coder: ModelSlotConfigSchema.optional(),
32
+ strategist: ModelSlotConfigSchema.optional(),
33
+ guard: ModelSlotConfigSchema.optional(),
34
+ critic: ModelSlotConfigSchema.optional(),
35
+ designer: ModelSlotConfigSchema.optional(),
36
+ researcher: ModelSlotConfigSchema.optional(),
37
+ manager: ModelSlotConfigSchema.optional(),
38
+ brainstormer: ModelSlotConfigSchema.optional(),
39
+ vision: ModelSlotConfigSchema.optional(),
40
+ });
41
+
11
42
  export const FallbackEntrySchema = z.object({
12
43
  providers: z.array(z.string()),
13
44
  model: z.string(),
@@ -48,8 +79,9 @@ export const McpServerConfigSchema = z.object({
48
79
  });
49
80
 
50
81
  export const LspServerConfigSchema = z.object({
51
- command: z.array(z.string()),
52
- extensions: z.array(z.string()),
82
+ enabled: z.boolean().optional(),
83
+ command: z.array(z.string()).optional(),
84
+ extensions: z.array(z.string()).optional(),
53
85
  initialization: z.record(z.string(), z.unknown()).optional(),
54
86
  });
55
87
 
@@ -102,6 +134,7 @@ export const AuthKeysSchema = z.object({
102
134
  openrouter: z.string().optional(),
103
135
  stitch: z.string().optional(),
104
136
  firecrawl: z.string().optional(),
137
+ context7: z.string().optional(),
105
138
  });
106
139
 
107
140
  export const OllamaConfigSchema = z.object({
@@ -190,6 +223,7 @@ const AgentRequirementsConfigSchema = z.object({
190
223
 
191
224
  export const HiaiOpencodeConfigSchema = z.object({
192
225
  $schema: z.string().optional(),
226
+ models: ModelSlotsConfigSchema.optional(),
193
227
  agents: AgentsConfigSchema.optional(),
194
228
  agentRequirements: AgentRequirementsConfigSchema.optional(),
195
229
  categories: z.record(z.string(), CategoryConfigSchema).optional(),
@@ -7,6 +7,35 @@ export interface AgentConfig {
7
7
  description?: string;
8
8
  }
9
9
 
10
+ export type ModelRecommendation =
11
+ | "xhigh"
12
+ | "high"
13
+ | "middle"
14
+ | "fast"
15
+ | "vision"
16
+ | "writing"
17
+ | "design";
18
+
19
+ export type ModelSlotConfig =
20
+ | string
21
+ | {
22
+ model: string;
23
+ recommended?: ModelRecommendation;
24
+ };
25
+
26
+ export interface ModelSlotsConfig {
27
+ bob?: ModelSlotConfig;
28
+ coder?: ModelSlotConfig;
29
+ strategist?: ModelSlotConfig;
30
+ guard?: ModelSlotConfig;
31
+ critic?: ModelSlotConfig;
32
+ designer?: ModelSlotConfig;
33
+ researcher?: ModelSlotConfig;
34
+ manager?: ModelSlotConfig;
35
+ brainstormer?: ModelSlotConfig;
36
+ vision?: ModelSlotConfig;
37
+ }
38
+
10
39
  // Canonical 12-agent model exposed by schema/default config.
11
40
  export const CANONICAL_AGENT_NAMES = [
12
41
  "bob",
@@ -124,8 +153,9 @@ export interface McpServerConfig {
124
153
  }
125
154
 
126
155
  export interface LspServerConfig {
127
- command: string[];
128
- extensions: string[];
156
+ enabled?: boolean;
157
+ command?: string[];
158
+ extensions?: string[];
129
159
  initialization?: Record<string, unknown>;
130
160
  }
131
161
 
@@ -174,6 +204,7 @@ export interface OllamaConfig {
174
204
 
175
205
  export interface HiaiOpencodeConfig {
176
206
  $schema?: string;
207
+ models?: ModelSlotsConfig;
177
208
  agents?: AgentConfigMap;
178
209
  agentRequirements?: AgentRequirementMap;
179
210
  categories?: Record<string, CategoryConfig>;
@@ -1,5 +1,6 @@
1
1
  import type { AvailableCategory, AvailableSkill } from "./agents/dynamic-agent-prompt-builder"
2
2
  import type { HiaiOpenCodeConfig } from "./config"
3
+ import type { HiaiOpencodeConfig } from "./config/types"
3
4
  import type { BrowserAutomationProvider } from "./config/schema/browser-automation"
4
5
  import type { LoadedSkill } from "./features/opencode-skill-loader/types"
5
6
  import type { PluginContext, ToolsRecord } from "./plugin/types"
@@ -22,9 +23,10 @@ type CreateToolsResult = {
22
23
  export async function createTools(args: {
23
24
  ctx: PluginContext
24
25
  pluginConfig: HiaiOpenCodeConfig
26
+ platformConfig?: HiaiOpencodeConfig
25
27
  managers: Pick<Managers, "backgroundManager" | "tmuxSessionManager" | "skillMcpManager">
26
28
  }): Promise<CreateToolsResult> {
27
- const { ctx, pluginConfig, managers } = args
29
+ const { ctx, pluginConfig, platformConfig, managers } = args
28
30
 
29
31
  const skillContext = await createSkillContext({
30
32
  directory: ctx.directory,
@@ -39,6 +41,7 @@ export async function createTools(args: {
39
41
  managers,
40
42
  skillContext,
41
43
  availableCategories,
44
+ builtinMcp: platformConfig?.mcp,
42
45
  })
43
46
 
44
47
  return {
package/src/index.ts CHANGED
@@ -27,9 +27,13 @@ import type { HiaiOpencodeConfig } from "./config/types"
27
27
 
28
28
  import { createPlugin as createSubtask2Plugin } from "./internals/plugins/subtask2/core/plugin"
29
29
  import WebsearchCitedPlugin, {
30
+ GOOGLE_PROVIDER_ID,
31
+ OPENAI_PROVIDER_ID,
32
+ OPENROUTER_PROVIDER_ID,
30
33
  WebsearchCitedGooglePlugin,
31
34
  WebsearchCitedOpenAIPlugin
32
35
  } from "./internals/plugins/websearch-cited/index"
36
+ import type { WebsearchCitedFallback } from "./internals/plugins/websearch-cited/index"
33
37
  import { createBuiltinSkills } from "./features/builtin-skills"
34
38
  import {
35
39
  materializeBuiltinSkills,
@@ -38,6 +42,39 @@ import {
38
42
 
39
43
  let activePluginDispose: PluginDispose | null = null
40
44
 
45
+ function createWebsearchFallback(config: HiaiOpencodeConfig): WebsearchCitedFallback | undefined {
46
+ const model = config.agents?.researcher?.model?.trim()
47
+ if (!model) {
48
+ return undefined
49
+ }
50
+
51
+ if (model.startsWith("openrouter/")) {
52
+ return {
53
+ providerID: OPENROUTER_PROVIDER_ID,
54
+ model: model.slice("openrouter/".length),
55
+ }
56
+ }
57
+
58
+ if (model.startsWith("openai/")) {
59
+ return {
60
+ providerID: OPENAI_PROVIDER_ID,
61
+ model: model.slice("openai/".length),
62
+ }
63
+ }
64
+
65
+ if (model.startsWith("google/")) {
66
+ return {
67
+ providerID: GOOGLE_PROVIDER_ID,
68
+ model: model.slice("google/".length),
69
+ }
70
+ }
71
+
72
+ return {
73
+ providerID: OPENROUTER_PROVIDER_ID,
74
+ model,
75
+ }
76
+ }
77
+
41
78
  function configureBundledBunPtyLibrary(): void {
42
79
  if (process.env.BUN_PTY_LIB?.trim()) {
43
80
  return
@@ -129,6 +166,7 @@ const HiaiOpenCodePlugin: Plugin = async (ctx) => {
129
166
  const toolsResult = await createTools({
130
167
  ctx,
131
168
  pluginConfig,
169
+ platformConfig: internalConfig,
132
170
  managers,
133
171
  })
134
172
 
@@ -169,7 +207,7 @@ const HiaiOpenCodePlugin: Plugin = async (ctx) => {
169
207
  } catch (err) {
170
208
  console.error("[hiai-opencode] PTYPlugin failed to load:", err);
171
209
  }
172
- const websearchResult = await WebsearchCitedPlugin(ctx)
210
+ const websearchResult = await WebsearchCitedPlugin(ctx, createWebsearchFallback(internalConfig))
173
211
  const websearchGoogleResult = await WebsearchCitedGooglePlugin(ctx)
174
212
  const websearchOpenAIResult = await WebsearchCitedOpenAIPlugin(ctx)
175
213
 
@@ -44,6 +44,8 @@ type SelectedWebsearchConfig = {
44
44
  model: string;
45
45
  };
46
46
 
47
+ export type WebsearchCitedFallback = SelectedWebsearchConfig;
48
+
47
49
  type WebsearchCitedSelection = {
48
50
  selected?: SelectedWebsearchConfig;
49
51
  error?: string;
@@ -164,16 +166,16 @@ function parseOpenAIOptions(providerConfig: unknown, model: string | undefined):
164
166
  return result;
165
167
  }
166
168
 
167
- const WebsearchCitedPlugin: Plugin = () => {
168
- let selectedProvider: SelectedProviderID | undefined;
169
- let selectedModel: string | undefined;
169
+ const WebsearchCitedPlugin = (_ctx?: unknown, fallback?: WebsearchCitedFallback): Promise<any> => {
170
+ let selectedProvider: SelectedProviderID | undefined = fallback?.providerID;
171
+ let selectedModel: string | undefined = fallback?.model;
170
172
  let openaiConfig: OpenAIWebsearchConfig = {};
171
173
  let configError: string | undefined;
172
174
 
173
175
  return Promise.resolve({
174
176
  auth: {
175
177
  provider: OPENROUTER_PROVIDER_ID,
176
- loader(getAuth) {
178
+ loader(getAuth: GetAuth) {
177
179
  registerGetAuth(OPENROUTER_PROVIDER_ID, getAuth);
178
180
  return Promise.resolve({});
179
181
  },
@@ -184,7 +186,7 @@ const WebsearchCitedPlugin: Plugin = () => {
184
186
  },
185
187
  ],
186
188
  },
187
- config: (config) => {
189
+ config: (config: Config) => {
188
190
  const { selected, error } = findFirstWebsearchCitedConfig(config as any);
189
191
 
190
192
  selectedProvider = undefined;
@@ -199,6 +201,9 @@ const WebsearchCitedPlugin: Plugin = () => {
199
201
  const openaiProvider = config.provider?.openai;
200
202
  openaiConfig = parseOpenAIOptions(openaiProvider, selectedModel);
201
203
  }
204
+ } else if (!error && fallback) {
205
+ selectedProvider = fallback.providerID;
206
+ selectedModel = fallback.model;
202
207
  }
203
208
 
204
209
  return Promise.resolve();
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,
@@ -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
- return join(import.meta.dirname, "..", "assets", ...segments)
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, {
@@ -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
- `Hint: Load the skill first using the 'skill' tool, then call skill_mcp.`,
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.skill.name,
205
+ skillName: found?.skill.name ?? "hiai-opencode",
168
206
  sessionID,
169
- scope: found.skill.scope,
207
+ scope: found?.skill.scope ?? "user",
170
208
  }
171
209
 
172
210
  const context: SkillMcpServerContext = {
173
- config: found.config,
174
- skillName: found.skill.name,
211
+ config: found?.config ?? convertedBuiltinConfig!,
212
+ skillName: found?.skill.name ?? "hiai-opencode",
175
213
  }
176
214
 
177
215
  const parsedArgs = parseArguments(args.arguments)