@kodrunhq/opencode-autopilot 1.0.0 → 1.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/assets/commands/oc-configure.md +13 -1
- package/package.json +1 -1
- package/src/index.ts +14 -1
- package/src/installer.ts +31 -2
- package/src/tools/configure.ts +37 -45
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Configure opencode-autopilot model assignments for each agent group
|
|
3
|
+
agent: autopilot
|
|
3
4
|
---
|
|
4
5
|
Help the user configure opencode-autopilot by walking through the model
|
|
5
6
|
assignment process interactively.
|
|
@@ -19,7 +20,18 @@ utilities last), explaining for each:
|
|
|
19
20
|
model diversity matters and which group they're adversarial to
|
|
20
21
|
4. List available models from the user's providers
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
For each group, present the available models as a numbered list:
|
|
24
|
+
|
|
25
|
+
Example format:
|
|
26
|
+
Primary model for Architects:
|
|
27
|
+
1. anthropic/claude-opus-4-6
|
|
28
|
+
2. openai/gpt-5.4
|
|
29
|
+
3. google/gemini-3.1-pro
|
|
30
|
+
|
|
31
|
+
Pick a number (or type a model ID):
|
|
32
|
+
|
|
33
|
+
If the user sends just a number, map it to the corresponding model.
|
|
34
|
+
Then ask for optional fallbacks the same way (1-3 fallback models).
|
|
23
35
|
Call oc_configure with subcommand "assign" for each group.
|
|
24
36
|
|
|
25
37
|
If the assign response contains diversityWarnings, explain them
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kodrunhq/opencode-autopilot",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Curated agents, skills, and commands for the OpenCode AI coding CLI — autonomous orchestrator, multi-agent code review, model fallback, and in-session asset creation tools.",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"keywords": [
|
package/src/index.ts
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
import { fallbackDefaults } from "./orchestrator/fallback/fallback-config";
|
|
13
13
|
import { resolveChain } from "./orchestrator/fallback/resolve-chain";
|
|
14
14
|
import { ocConfidence } from "./tools/confidence";
|
|
15
|
-
import { ocConfigure, setOpenCodeConfig } from "./tools/configure";
|
|
15
|
+
import { ocConfigure, setAvailableProviders, setOpenCodeConfig } from "./tools/configure";
|
|
16
16
|
import { ocCreateAgent } from "./tools/create-agent";
|
|
17
17
|
import { ocCreateCommand } from "./tools/create-command";
|
|
18
18
|
import { ocCreateSkill } from "./tools/create-skill";
|
|
@@ -34,6 +34,19 @@ const plugin: Plugin = async (input) => {
|
|
|
34
34
|
console.error("[opencode-autopilot] Asset installation errors:", installResult.errors);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
// Discover available providers/models from OpenCode SDK
|
|
38
|
+
try {
|
|
39
|
+
const providerResponse = await client.provider.list({
|
|
40
|
+
query: { directory: process.cwd() },
|
|
41
|
+
});
|
|
42
|
+
const providerData = providerResponse.data;
|
|
43
|
+
if (providerData?.all) {
|
|
44
|
+
setAvailableProviders(providerData.all);
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
// Provider discovery is best-effort; configure will show empty models
|
|
48
|
+
}
|
|
49
|
+
|
|
37
50
|
// Load config for first-load detection and fallback settings
|
|
38
51
|
const config = await loadConfig();
|
|
39
52
|
const fallbackConfig = config?.fallback ?? fallbackDefaults;
|
package/src/installer.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
import { readdir } from "node:fs/promises";
|
|
1
|
+
import { readdir, unlink } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { copyIfMissing, isEnoentError } from "./utils/fs-helpers";
|
|
4
4
|
import { getAssetsDir, getGlobalConfigDir } from "./utils/paths";
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Assets that were previously shipped but have since been removed from the source repo.
|
|
8
|
+
* These are cleaned up from the target directory on every install to avoid stale files.
|
|
9
|
+
*/
|
|
10
|
+
const DEPRECATED_ASSETS = ["agents/placeholder-agent.md", "commands/configure.md"] as const;
|
|
11
|
+
|
|
6
12
|
export interface InstallResult {
|
|
7
13
|
readonly copied: readonly string[];
|
|
8
14
|
readonly skipped: readonly string[];
|
|
@@ -112,10 +118,33 @@ async function processSkills(sourceDir: string, targetDir: string): Promise<Inst
|
|
|
112
118
|
return { copied, skipped, errors };
|
|
113
119
|
}
|
|
114
120
|
|
|
121
|
+
async function cleanupDeprecatedAssets(
|
|
122
|
+
targetDir: string,
|
|
123
|
+
): Promise<{ readonly removed: readonly string[]; readonly errors: readonly string[] }> {
|
|
124
|
+
const removed: string[] = [];
|
|
125
|
+
const errors: string[] = [];
|
|
126
|
+
for (const asset of DEPRECATED_ASSETS) {
|
|
127
|
+
try {
|
|
128
|
+
await unlink(join(targetDir, asset));
|
|
129
|
+
removed.push(asset);
|
|
130
|
+
} catch (error: unknown) {
|
|
131
|
+
if (!isEnoentError(error)) {
|
|
132
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
133
|
+
errors.push(`cleanup ${asset}: ${message}`);
|
|
134
|
+
}
|
|
135
|
+
// ENOENT is expected — asset already gone
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return { removed, errors };
|
|
139
|
+
}
|
|
140
|
+
|
|
115
141
|
export async function installAssets(
|
|
116
142
|
assetsDir: string = getAssetsDir(),
|
|
117
143
|
targetDir: string = getGlobalConfigDir(),
|
|
118
144
|
): Promise<InstallResult> {
|
|
145
|
+
// Remove deprecated assets before copying new ones
|
|
146
|
+
const cleanup = await cleanupDeprecatedAssets(targetDir);
|
|
147
|
+
|
|
119
148
|
const [agents, commands, skills] = await Promise.all([
|
|
120
149
|
processFiles(assetsDir, targetDir, "agents"),
|
|
121
150
|
processFiles(assetsDir, targetDir, "commands"),
|
|
@@ -125,6 +154,6 @@ export async function installAssets(
|
|
|
125
154
|
return {
|
|
126
155
|
copied: [...agents.copied, ...commands.copied, ...skills.copied],
|
|
127
156
|
skipped: [...agents.skipped, ...commands.skipped, ...skills.skipped],
|
|
128
|
-
errors: [...agents.errors, ...commands.errors, ...skills.errors],
|
|
157
|
+
errors: [...cleanup.errors, ...agents.errors, ...commands.errors, ...skills.errors],
|
|
129
158
|
};
|
|
130
159
|
}
|
package/src/tools/configure.ts
CHANGED
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
DIVERSITY_RULES,
|
|
10
10
|
GROUP_DEFINITIONS,
|
|
11
11
|
} from "../registry/model-groups";
|
|
12
|
-
import { extractFamily } from "../registry/resolver";
|
|
13
12
|
import type { DiversityWarning, GroupModelAssignment } from "../registry/types";
|
|
14
13
|
|
|
15
14
|
// --- Module-level state ---
|
|
@@ -27,20 +26,42 @@ let pendingAssignments: Map<string, GroupModelAssignment> = new Map();
|
|
|
27
26
|
|
|
28
27
|
/**
|
|
29
28
|
* Reference to the OpenCode host config, set by the plugin's config hook.
|
|
30
|
-
*
|
|
29
|
+
* Retained for potential future use by subcommands.
|
|
31
30
|
*/
|
|
31
|
+
// biome-ignore lint/correctness/noUnusedVariables: retained for API compatibility
|
|
32
32
|
let openCodeConfig: Config | null = null;
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Provider data from the OpenCode SDK, set during plugin initialization.
|
|
36
|
+
* Each entry maps a provider ID to its available models.
|
|
37
|
+
*/
|
|
38
|
+
let availableProviders: ReadonlyArray<{
|
|
39
|
+
readonly id: string;
|
|
40
|
+
readonly name: string;
|
|
41
|
+
readonly models: Readonly<Record<string, { readonly id: string; readonly name: string }>>;
|
|
42
|
+
}> = [];
|
|
43
|
+
|
|
34
44
|
// --- Exported helpers for test/plugin wiring ---
|
|
35
45
|
|
|
36
46
|
export function resetPendingAssignments(): void {
|
|
37
47
|
pendingAssignments = new Map();
|
|
48
|
+
availableProviders = [];
|
|
38
49
|
}
|
|
39
50
|
|
|
40
51
|
export function setOpenCodeConfig(config: Config | null): void {
|
|
41
52
|
openCodeConfig = config;
|
|
42
53
|
}
|
|
43
54
|
|
|
55
|
+
export function setAvailableProviders(
|
|
56
|
+
providers: ReadonlyArray<{
|
|
57
|
+
readonly id: string;
|
|
58
|
+
readonly name: string;
|
|
59
|
+
readonly models: Readonly<Record<string, { readonly id: string; readonly name: string }>>;
|
|
60
|
+
}>,
|
|
61
|
+
): void {
|
|
62
|
+
availableProviders = providers;
|
|
63
|
+
}
|
|
64
|
+
|
|
44
65
|
// --- Core logic ---
|
|
45
66
|
|
|
46
67
|
interface ConfigureArgs {
|
|
@@ -50,53 +71,24 @@ interface ConfigureArgs {
|
|
|
50
71
|
readonly fallbacks?: string;
|
|
51
72
|
}
|
|
52
73
|
|
|
53
|
-
function getStringField(obj: Record<string, unknown>, key: string): string | undefined {
|
|
54
|
-
const val = obj[key];
|
|
55
|
-
return typeof val === "string" ? val : undefined;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
74
|
/**
|
|
59
|
-
*
|
|
60
|
-
* Returns a map of
|
|
75
|
+
* Discover available models from the stored provider data.
|
|
76
|
+
* Returns a map of provider ID -> list of "providerId/modelId" strings.
|
|
61
77
|
*/
|
|
62
|
-
function discoverAvailableModels(
|
|
63
|
-
const
|
|
64
|
-
if (!config) return modelsByFamily;
|
|
65
|
-
|
|
66
|
-
const configRecord = config as Record<string, unknown>;
|
|
67
|
-
const seen = new Set<string>();
|
|
68
|
-
|
|
69
|
-
// Collect from agent configs
|
|
70
|
-
const agentConfigs = configRecord.agent;
|
|
71
|
-
if (agentConfigs && typeof agentConfigs === "object" && agentConfigs !== null) {
|
|
72
|
-
for (const agentCfg of Object.values(agentConfigs as Record<string, unknown>)) {
|
|
73
|
-
if (agentCfg && typeof agentCfg === "object" && agentCfg !== null) {
|
|
74
|
-
const model = getStringField(agentCfg as Record<string, unknown>, "model");
|
|
75
|
-
if (model && !seen.has(model)) {
|
|
76
|
-
seen.add(model);
|
|
77
|
-
const family = extractFamily(model);
|
|
78
|
-
const list = modelsByFamily.get(family) ?? [];
|
|
79
|
-
list.push(model);
|
|
80
|
-
modelsByFamily.set(family, list);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
78
|
+
function discoverAvailableModels(): Map<string, string[]> {
|
|
79
|
+
const modelsByProvider = new Map<string, string[]>();
|
|
85
80
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const list = modelsByFamily.get(family) ?? [];
|
|
94
|
-
list.push(m);
|
|
95
|
-
modelsByFamily.set(family, list);
|
|
81
|
+
for (const provider of availableProviders) {
|
|
82
|
+
const modelIds: string[] = [];
|
|
83
|
+
for (const modelKey of Object.keys(provider.models)) {
|
|
84
|
+
modelIds.push(`${provider.id}/${modelKey}`);
|
|
85
|
+
}
|
|
86
|
+
if (modelIds.length > 0) {
|
|
87
|
+
modelsByProvider.set(provider.id, modelIds);
|
|
96
88
|
}
|
|
97
89
|
}
|
|
98
90
|
|
|
99
|
-
return
|
|
91
|
+
return modelsByProvider;
|
|
100
92
|
}
|
|
101
93
|
|
|
102
94
|
function serializeDiversityWarnings(warnings: readonly DiversityWarning[]): readonly {
|
|
@@ -114,7 +106,7 @@ function serializeDiversityWarnings(warnings: readonly DiversityWarning[]): read
|
|
|
114
106
|
}
|
|
115
107
|
|
|
116
108
|
async function handleStart(configPath?: string): Promise<string> {
|
|
117
|
-
const
|
|
109
|
+
const modelsByProvider = discoverAvailableModels();
|
|
118
110
|
|
|
119
111
|
// Load current plugin config to show existing assignments
|
|
120
112
|
const currentConfig = await loadConfig(configPath);
|
|
@@ -141,7 +133,7 @@ async function handleStart(configPath?: string): Promise<string> {
|
|
|
141
133
|
return JSON.stringify({
|
|
142
134
|
action: "configure",
|
|
143
135
|
stage: "start",
|
|
144
|
-
availableModels: Object.fromEntries(
|
|
136
|
+
availableModels: Object.fromEntries(modelsByProvider),
|
|
145
137
|
groups,
|
|
146
138
|
currentConfig: currentConfig
|
|
147
139
|
? { configured: currentConfig.configured, groups: currentConfig.groups }
|