@tudeorangbiasa/sdd-multiagent-opencode 0.1.2 → 0.1.4
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.
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
const pluginDir = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const LEVELS = new Set(["low", "medium", "high"]);
|
|
7
|
+
const pendingOverrides = new Map();
|
|
8
|
+
const recentCommands = new Map();
|
|
9
|
+
|
|
10
|
+
function readJson(filePath) {
|
|
11
|
+
if (!fs.existsSync(filePath)) return null;
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
15
|
+
} catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function readProfile() {
|
|
21
|
+
const candidates = [
|
|
22
|
+
path.join(process.cwd(), ".sdd", "reasoning-profile.json"),
|
|
23
|
+
path.join(pluginDir, "..", "..", ".sdd", "reasoning-profile.json"),
|
|
24
|
+
path.join(pluginDir, "..", ".sdd", "reasoning-profile.json"),
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
for (const candidate of candidates) {
|
|
28
|
+
const profile = readJson(candidate);
|
|
29
|
+
if (profile) return profile;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
default: "low",
|
|
34
|
+
applyProviderOptions: true,
|
|
35
|
+
resetToolOverrideAfterNextRequest: true,
|
|
36
|
+
agents: {},
|
|
37
|
+
commands: {},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function normalizeLevel(level, fallback = "low") {
|
|
42
|
+
return LEVELS.has(level) ? level : fallback;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function applyReasoningOptions(output, level) {
|
|
46
|
+
if (!output.options) output.options = {};
|
|
47
|
+
|
|
48
|
+
output.options.reasoningEffort = level;
|
|
49
|
+
output.options.reasoning_effort = level;
|
|
50
|
+
output.options.reasoning = {
|
|
51
|
+
...(output.options.reasoning ?? {}),
|
|
52
|
+
effort: level,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
output.options.providerOptions = {
|
|
56
|
+
...(output.options.providerOptions ?? {}),
|
|
57
|
+
openai: {
|
|
58
|
+
...(output.options.providerOptions?.openai ?? {}),
|
|
59
|
+
reasoningEffort: level,
|
|
60
|
+
reasoning: {
|
|
61
|
+
...(output.options.providerOptions?.openai?.reasoning ?? {}),
|
|
62
|
+
effort: level,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function resolveLevel(profile, input) {
|
|
69
|
+
const override = pendingOverrides.get(input.sessionID);
|
|
70
|
+
if (override) return override;
|
|
71
|
+
|
|
72
|
+
const command = recentCommands.get(input.sessionID);
|
|
73
|
+
if (command && profile.commands?.[command]) {
|
|
74
|
+
return normalizeLevel(profile.commands[command], profile.default);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (profile.agents?.[input.agent]) {
|
|
78
|
+
return normalizeLevel(profile.agents[input.agent], profile.default);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return normalizeLevel(profile.default, "low");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export default async () => {
|
|
85
|
+
let toolHooks = {};
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const { tool } = await import("@opencode-ai/plugin");
|
|
89
|
+
toolHooks = {
|
|
90
|
+
tool: {
|
|
91
|
+
change_reasoning: tool({
|
|
92
|
+
description: "Set reasoning effort for the next model request in this session.",
|
|
93
|
+
args: {
|
|
94
|
+
level: tool.schema.enum(["low", "medium", "high"]).describe("Reasoning level to use: low, medium, or high."),
|
|
95
|
+
},
|
|
96
|
+
async execute(args, context) {
|
|
97
|
+
const level = normalizeLevel(args.level, "low");
|
|
98
|
+
pendingOverrides.set(context.sessionID, level);
|
|
99
|
+
context.metadata({ title: `Reasoning: ${level}`, metadata: { level } });
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
output: `Reasoning level set to ${level} for the next model request in this session.`,
|
|
103
|
+
metadata: { level },
|
|
104
|
+
};
|
|
105
|
+
},
|
|
106
|
+
}),
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
} catch {
|
|
110
|
+
toolHooks = {};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
...toolHooks,
|
|
115
|
+
|
|
116
|
+
"command.execute.before": async (input) => {
|
|
117
|
+
recentCommands.set(input.sessionID, input.command);
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
"chat.params": async (input, output) => {
|
|
121
|
+
const profile = readProfile();
|
|
122
|
+
const level = resolveLevel(profile, input);
|
|
123
|
+
|
|
124
|
+
if (profile.applyProviderOptions !== false) {
|
|
125
|
+
applyReasoningOptions(output, level);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
output.options.sddReasoningLevel = level;
|
|
129
|
+
|
|
130
|
+
if (profile.resetToolOverrideAfterNextRequest !== false) {
|
|
131
|
+
pendingOverrides.delete(input.sessionID);
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
"experimental.chat.system.transform": async (_input, output) => {
|
|
136
|
+
output.system.push(
|
|
137
|
+
"SDD auto reasoning is enabled. Use change_reasoning sparingly when the current task needs a different reasoning level. Use low for simple edits, medium for implementation/review, and high for architecture, planning, multi-agent orchestration, and hard debugging."
|
|
138
|
+
);
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
};
|
|
@@ -1,19 +1,36 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
const pluginDir = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
|
|
7
|
+
function readProfile() {
|
|
8
|
+
const candidates = [
|
|
9
|
+
path.join(process.cwd(), ".sdd", "model-profile.json"),
|
|
10
|
+
path.join(pluginDir, "..", "..", ".sdd", "model-profile.json"),
|
|
11
|
+
path.join(pluginDir, "..", ".sdd", "model-profile.json"),
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
for (const profilePath of candidates) {
|
|
15
|
+
if (!fs.existsSync(profilePath)) {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(fs.readFileSync(profilePath, "utf-8"));
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
3
28
|
|
|
4
29
|
export default async () => {
|
|
5
30
|
return {
|
|
6
31
|
config: (cfg) => {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
if (!fs.existsSync(profilePath)) {
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
let profile;
|
|
14
|
-
try {
|
|
15
|
-
profile = JSON.parse(fs.readFileSync(profilePath, "utf-8"));
|
|
16
|
-
} catch {
|
|
32
|
+
const profile = readProfile();
|
|
33
|
+
if (!profile) {
|
|
17
34
|
return;
|
|
18
35
|
}
|
|
19
36
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"default": "low",
|
|
3
|
+
"applyProviderOptions": true,
|
|
4
|
+
"resetToolOverrideAfterNextRequest": true,
|
|
5
|
+
"agents": {
|
|
6
|
+
"general": "medium",
|
|
7
|
+
"plan": "high",
|
|
8
|
+
"sdd-orchestrator": "high",
|
|
9
|
+
"sdd-planner": "high",
|
|
10
|
+
"sdd-explorer": "low",
|
|
11
|
+
"sdd-implementer": "medium",
|
|
12
|
+
"sdd-verifier": "medium",
|
|
13
|
+
"sdd-reviewer": "medium"
|
|
14
|
+
},
|
|
15
|
+
"commands": {
|
|
16
|
+
"brief": "medium",
|
|
17
|
+
"research": "medium",
|
|
18
|
+
"specify": "high",
|
|
19
|
+
"plan": "high",
|
|
20
|
+
"tasks": "high",
|
|
21
|
+
"implement": "medium",
|
|
22
|
+
"execute-parallel": "high",
|
|
23
|
+
"audit": "medium"
|
|
24
|
+
}
|
|
25
|
+
}
|
package/README.md
CHANGED
|
@@ -84,6 +84,31 @@ Default profile avoids paid GPT models:
|
|
|
84
84
|
|
|
85
85
|
`.opencode/plugins/sdd-model-router.js` reads this file at OpenCode startup and applies the model settings. Restart OpenCode after editing it.
|
|
86
86
|
|
|
87
|
+
### Auto Reasoning
|
|
88
|
+
|
|
89
|
+
The installer also creates `.sdd/reasoning-profile.json` and installs `.opencode/plugins/sdd-auto-reasoning.js`.
|
|
90
|
+
|
|
91
|
+
The plugin reverse-engineers the behavior of `@howaboua/pi-auto-reasoning-tool` for OpenCode:
|
|
92
|
+
- sets reasoning effort automatically per agent and command
|
|
93
|
+
- exposes `change_reasoning` when `@opencode-ai/plugin` is available
|
|
94
|
+
- resets tool overrides after the next model request by default
|
|
95
|
+
|
|
96
|
+
Default routing:
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"default": "low",
|
|
101
|
+
"agents": {
|
|
102
|
+
"sdd-orchestrator": "high",
|
|
103
|
+
"sdd-planner": "high",
|
|
104
|
+
"sdd-implementer": "medium",
|
|
105
|
+
"sdd-reviewer": "medium"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Restart OpenCode after editing `.sdd/reasoning-profile.json`.
|
|
111
|
+
|
|
87
112
|
### 1. Install (Manual)
|
|
88
113
|
|
|
89
114
|
#### Option A: Using Commands (Recommended)
|
package/bin/sdd-opencode.js
CHANGED
|
@@ -260,6 +260,20 @@ function installSdd(ctx) {
|
|
|
260
260
|
count++;
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
+
const reasoningProfileDest = path.join(sddDir, "reasoning-profile.json");
|
|
264
|
+
const reasoningProfileTemplateSrc = path.join(
|
|
265
|
+
sddDir,
|
|
266
|
+
"templates",
|
|
267
|
+
"reasoning-profile-template.json"
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
if (fs.existsSync(reasoningProfileDest) && !ctx.force) {
|
|
271
|
+
ctx.skipped.push(".sdd/reasoning-profile.json (exists)");
|
|
272
|
+
} else if (fs.existsSync(reasoningProfileTemplateSrc)) {
|
|
273
|
+
copyFileSafe(reasoningProfileTemplateSrc, reasoningProfileDest, ctx);
|
|
274
|
+
count++;
|
|
275
|
+
}
|
|
276
|
+
|
|
263
277
|
const specsDir = path.join(ctx.targetRoot, "specs");
|
|
264
278
|
ensureDir(path.join(specsDir, "active"));
|
|
265
279
|
ensureDir(path.join(specsDir, "backlog"));
|
|
@@ -303,6 +317,21 @@ function patchOpencodeJson(ctx) {
|
|
|
303
317
|
if (fs.existsSync(pkgJsonPath)) {
|
|
304
318
|
const pkgConfig = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
|
|
305
319
|
|
|
320
|
+
if (Array.isArray(pkgConfig.plugin)) {
|
|
321
|
+
if (!Array.isArray(targetConfig.plugin)) {
|
|
322
|
+
targetConfig.plugin = [];
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
for (const pluginEntry of pkgConfig.plugin) {
|
|
326
|
+
const key = JSON.stringify(pluginEntry);
|
|
327
|
+
const exists = targetConfig.plugin.some((existing) => JSON.stringify(existing) === key);
|
|
328
|
+
if (!exists) {
|
|
329
|
+
targetConfig.plugin.push(pluginEntry);
|
|
330
|
+
ctx.installed.push(`opencode.json (plugin: ${Array.isArray(pluginEntry) ? pluginEntry[0] : pluginEntry})`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
306
335
|
if (pkgConfig.model) {
|
|
307
336
|
targetConfig.model = pkgConfig.model;
|
|
308
337
|
ctx.installed.push("opencode.json (model patch)");
|
package/opencode.json
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
"$schema": "https://opencode.ai/config.json",
|
|
3
3
|
"model": "opencode/minimax-m2.5-free",
|
|
4
4
|
"small_model": "opencode/deepseek-v4-flash-free",
|
|
5
|
+
"plugin": [
|
|
6
|
+
"./plugins/sdd-model-router.js",
|
|
7
|
+
"./plugins/sdd-auto-reasoning.js"
|
|
8
|
+
],
|
|
5
9
|
"agent": {
|
|
6
10
|
"sdd-orchestrator": {
|
|
7
11
|
"description": "Parallel task coordination and DAG-based execution for SDD workflows. Use for /execute-parallel, --until-finish automation, and coordinating multiple subagents across complex projects.",
|