oh-my-parallel-agent-opencode 0.1.0

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/src/index.ts ADDED
@@ -0,0 +1,116 @@
1
+ import type { Plugin, PluginContext } from "@opencode-ai/plugin"
2
+ import { ParallelAgentConfigSchema } from "./config/schema"
3
+ import { createDynamicAgent, type SupportedAgentName } from "./agents"
4
+ import { resolveBaseAgent } from "./utils/dynamic-agent"
5
+ import * as fs from "fs"
6
+ import * as path from "path"
7
+
8
+ const CONFIG_FILE_NAME = "oh-my-parallel-agent.json"
9
+
10
+ function loadConfig(directory: string) {
11
+ const configPath_var = path.join(directory, CONFIG_FILE_NAME)
12
+
13
+ if (!fs.existsSync(configPath_var)) {
14
+ return null
15
+ }
16
+
17
+ try {
18
+ const content_var = fs.readFileSync(configPath_var, "utf-8")
19
+ const parsed_var = JSON.parse(content_var)
20
+ const result_var = ParallelAgentConfigSchema.safeParse(parsed_var)
21
+
22
+ if (!result_var.success) {
23
+ console.error(`[oh-my-parallel-agent] 설정 파일 파싱 오류:`, result_var.error.message)
24
+ return null
25
+ }
26
+
27
+ return result_var.data
28
+ } catch (error) {
29
+ console.error(`[oh-my-parallel-agent] 설정 파일 읽기 오류:`, error)
30
+ return null
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Oh My Parallel Agent 플러그인
36
+ *
37
+ * 설정 파일(oh-my-parallel-agent.json)에 정의된 동적 에이전트들을
38
+ * OpenCode에 등록합니다.
39
+ *
40
+ * @example
41
+ * {
42
+ * "agents": {
43
+ * "momus-1": { "model": "openai/gpt-5.2" },
44
+ * "momus-2": { "model": "anthropic/claude-sonnet-4-20250514" }
45
+ * }
46
+ * }
47
+ */
48
+ const OhMyParallelAgentPlugin: Plugin = async (ctx: PluginContext) => {
49
+ const pluginConfig_var = loadConfig(ctx.directory)
50
+
51
+ return {
52
+ name: "oh-my-parallel-agent-opencode",
53
+
54
+ config: async (config: Record<string, unknown>) => {
55
+ if (!pluginConfig_var) {
56
+ return config
57
+ }
58
+
59
+ const agentEntries_var = pluginConfig_var.agents
60
+ if (!agentEntries_var || Object.keys(agentEntries_var).length === 0) {
61
+ return config
62
+ }
63
+
64
+ const existingAgents_var = (config.agent as Record<string, unknown>) ?? {}
65
+ const newAgents_var: Record<string, unknown> = {}
66
+
67
+ for (const [agentName_var, agentConfig_var] of Object.entries(agentEntries_var)) {
68
+ const resolved_var = resolveBaseAgent(agentName_var)
69
+
70
+ if (!resolved_var) {
71
+ console.warn(`[oh-my-parallel-agent] 지원하지 않는 에이전트 이름: ${agentName_var}`)
72
+ continue
73
+ }
74
+
75
+ const { baseAgent, variant } = resolved_var
76
+ const modelToUse_var = agentConfig_var.model ?? "anthropic/claude-sonnet-4-20250514"
77
+
78
+ try {
79
+ const dynamicAgent_var = createDynamicAgent(
80
+ baseAgent as SupportedAgentName,
81
+ modelToUse_var,
82
+ {
83
+ model: agentConfig_var.model,
84
+ variant: agentConfig_var.variant,
85
+ prompt_append: agentConfig_var.prompt_append,
86
+ temperature: agentConfig_var.temperature,
87
+ }
88
+ )
89
+
90
+ if (variant && dynamicAgent_var.description) {
91
+ dynamicAgent_var.description = `${dynamicAgent_var.description} (variant ${variant})`
92
+ }
93
+
94
+ dynamicAgent_var.mode = "subagent"
95
+
96
+ newAgents_var[agentName_var] = dynamicAgent_var
97
+ } catch (error) {
98
+ console.error(`[oh-my-parallel-agent] 에이전트 생성 실패 (${agentName_var}):`, error)
99
+ }
100
+ }
101
+
102
+ config.agent = {
103
+ ...existingAgents_var,
104
+ ...newAgents_var,
105
+ }
106
+
107
+ return config
108
+ },
109
+ }
110
+ }
111
+
112
+ export default OhMyParallelAgentPlugin
113
+
114
+ export type { ParallelAgentConfig, AgentOverrideConfig } from "./config/schema"
115
+ export type { SupportedAgentName } from "./agents"
116
+ export { resolveBaseAgent, isDynamicAgentName } from "./utils/dynamic-agent"
@@ -0,0 +1,129 @@
1
+ import { existsSync, readFileSync } from "fs"
2
+ import { join } from "path"
3
+ import type { AgentConfig } from "@opencode-ai/sdk"
4
+ import { ParallelAgentConfigSchema, type ParallelAgentConfig } from "../config/schema"
5
+ import { resolveBaseAgent } from "../utils/dynamic-agent"
6
+ import { createDynamicAgent, type SupportedAgentName } from "../agents"
7
+
8
+ /**
9
+ * 설정 파일 경로를 찾는 함수
10
+ *
11
+ * 우선순위:
12
+ * 1. 프로젝트 루트의 .opencode/ 디렉토리
13
+ * 2. 사용자 홈의 .config/opencode/ 디렉토리
14
+ *
15
+ * @returns 설정 파일 전체 경로 또는 null
16
+ */
17
+ export function findConfigPath(): string | null {
18
+ const configFileName = "oh-my-parallel-agent-opencode.json"
19
+
20
+ // 1. 프로젝트 레벨 설정 (.opencode/)
21
+ const projectConfigPath = join(process.cwd(), ".opencode", configFileName)
22
+ if (existsSync(projectConfigPath)) {
23
+ return projectConfigPath
24
+ }
25
+
26
+ // 2. 글로벌 레벨 설정 (~/.config/opencode/)
27
+ const homeDir = process.env.HOME || process.env.USERPROFILE
28
+ if (homeDir) {
29
+ const globalConfigPath = join(homeDir, ".config", "opencode", configFileName)
30
+ if (existsSync(globalConfigPath)) {
31
+ return globalConfigPath
32
+ }
33
+ }
34
+
35
+ return null
36
+ }
37
+
38
+ /**
39
+ * 설정 파일을 로드하고 파싱하는 함수
40
+ *
41
+ * @param configPath - 설정 파일 경로 (optional, 없으면 자동 탐색)
42
+ * @returns 파싱된 ParallelAgentConfig 또는 null
43
+ * @throws Zod validation error if config is invalid
44
+ */
45
+ export function loadParallelAgentConfig(configPath?: string): ParallelAgentConfig | null {
46
+ const resolvedPath = configPath || findConfigPath()
47
+
48
+ if (!resolvedPath) {
49
+ return null
50
+ }
51
+
52
+ if (!existsSync(resolvedPath)) {
53
+ return null
54
+ }
55
+
56
+ try {
57
+ const fileContent = readFileSync(resolvedPath, "utf-8")
58
+ const jsonData = JSON.parse(fileContent)
59
+
60
+ const validatedConfig = ParallelAgentConfigSchema.parse(jsonData)
61
+ return validatedConfig
62
+ } catch (error) {
63
+ throw new Error(`Failed to load config from ${resolvedPath}: ${error}`)
64
+ }
65
+ }
66
+
67
+ /**
68
+ * 설정으로부터 동적 에이전트들을 생성하는 함수
69
+ *
70
+ * @param config - ParallelAgentConfig 객체
71
+ * @returns 에이전트 이름 -> AgentConfig 매핑
72
+ *
73
+ * @example
74
+ * const config = { agents: { "momus-2": { model: "openai/gpt-5.2" } } }
75
+ * const agents = createParallelAgents(config)
76
+ * // agents["momus-2"] = AgentConfig for momus with gpt-5.2
77
+ */
78
+ export function createParallelAgents(config: ParallelAgentConfig): Record<string, AgentConfig> {
79
+ const result: Record<string, AgentConfig> = {}
80
+
81
+ for (const [agentName, override] of Object.entries(config.agents)) {
82
+ // 1. 에이전트 이름 파싱 (예: "momus-2" -> { baseAgent: "momus", variant: "2" })
83
+ const resolved = resolveBaseAgent(agentName)
84
+
85
+ if (!resolved) {
86
+ // 지원하지 않는 에이전트 이름은 스킵
87
+ console.warn(`[oh-my-parallel-agent-opencode] Skipping unsupported agent name: ${agentName}`)
88
+ continue
89
+ }
90
+
91
+ // 2. 모델 결정 (override.model이 없으면 "default" 사용)
92
+ const modelToUse = override?.model ?? "default"
93
+
94
+ // 3. 동적 에이전트 생성
95
+ try {
96
+ const agentConfig = createDynamicAgent(
97
+ resolved.baseAgent as SupportedAgentName,
98
+ modelToUse,
99
+ override ?? undefined
100
+ )
101
+
102
+ result[agentName] = agentConfig
103
+ } catch (error) {
104
+ console.error(`[oh-my-parallel-agent-opencode] Failed to create agent ${agentName}:`, error)
105
+ }
106
+ }
107
+
108
+ return result
109
+ }
110
+
111
+ /**
112
+ * 메인 설정 핸들러 함수
113
+ *
114
+ * 설정 파일을 로드하고 동적 에이전트들을 생성합니다.
115
+ * oh-my-opencode의 createBuiltinAgents와 유사한 역할을 합니다.
116
+ *
117
+ * @param configPath - 선택적 설정 파일 경로
118
+ * @returns 생성된 에이전트 레코드
119
+ */
120
+ export function createParallelAgentsFromConfig(configPath?: string): Record<string, AgentConfig> {
121
+ const config = loadParallelAgentConfig(configPath)
122
+
123
+ if (!config) {
124
+ // 설정 파일이 없으면 빈 객체 반환
125
+ return {}
126
+ }
127
+
128
+ return createParallelAgents(config)
129
+ }
@@ -0,0 +1,37 @@
1
+ const SUPPORTED_BASE_AGENTS = ["librarian", "explore", "metis", "momus"] as const;
2
+
3
+ export interface ResolvedAgent {
4
+ baseAgent: string;
5
+ variant?: string;
6
+ }
7
+
8
+ export function resolveBaseAgent(agentName: string): ResolvedAgent | null {
9
+ if (!agentName || agentName.trim() === "") {
10
+ return null;
11
+ }
12
+
13
+ const pattern = /^(librarian|explore|metis|momus)(-(\d+))?$/;
14
+ const match = agentName.match(pattern);
15
+
16
+ if (!match) {
17
+ return null;
18
+ }
19
+
20
+ const baseAgent = match[1];
21
+ const variant = match[3];
22
+
23
+ if (variant) {
24
+ return {
25
+ baseAgent,
26
+ variant,
27
+ };
28
+ }
29
+
30
+ return {
31
+ baseAgent,
32
+ };
33
+ }
34
+
35
+ export function isDynamicAgentName(name: string): boolean {
36
+ return resolveBaseAgent(name) !== null;
37
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "declaration": true,
7
+ "outDir": "dist",
8
+ "rootDir": "src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "resolveJsonModule": true,
14
+ "lib": ["ESNext"],
15
+ "types": ["bun-types"]
16
+ },
17
+ "include": ["src/**/*", "__tests__/**/*"],
18
+ "exclude": ["node_modules", "dist"]
19
+ }