@kodrunhq/opencode-autopilot 1.0.0 → 1.1.1
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 +25 -1
- package/src/installer.ts +31 -2
- package/src/tools/configure.ts +55 -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.1",
|
|
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,12 @@ 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 {
|
|
15
|
+
import {
|
|
16
|
+
ocConfigure,
|
|
17
|
+
setAvailableProviders,
|
|
18
|
+
setOpenCodeConfig,
|
|
19
|
+
setProviderDiscoveryPromise,
|
|
20
|
+
} from "./tools/configure";
|
|
16
21
|
import { ocCreateAgent } from "./tools/create-agent";
|
|
17
22
|
import { ocCreateCommand } from "./tools/create-command";
|
|
18
23
|
import { ocCreateSkill } from "./tools/create-skill";
|
|
@@ -34,6 +39,25 @@ const plugin: Plugin = async (input) => {
|
|
|
34
39
|
console.error("[opencode-autopilot] Asset installation errors:", installResult.errors);
|
|
35
40
|
}
|
|
36
41
|
|
|
42
|
+
// Discover available providers/models in the background (non-blocking).
|
|
43
|
+
// The promise is stored so oc_configure "start" can await it (with timeout).
|
|
44
|
+
try {
|
|
45
|
+
const discoveryPromise = client.provider
|
|
46
|
+
.list({ query: { directory: process.cwd() } })
|
|
47
|
+
.then((providerResponse) => {
|
|
48
|
+
const providerData = providerResponse.data;
|
|
49
|
+
if (providerData?.all) {
|
|
50
|
+
setAvailableProviders(providerData.all);
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
.catch(() => {
|
|
54
|
+
// Provider discovery is best-effort; configure will show empty models
|
|
55
|
+
});
|
|
56
|
+
setProviderDiscoveryPromise(discoveryPromise);
|
|
57
|
+
} catch {
|
|
58
|
+
// Guard against synchronous failures (e.g. client.provider undefined)
|
|
59
|
+
}
|
|
60
|
+
|
|
37
61
|
// Load config for first-load detection and fallback settings
|
|
38
62
|
const config = await loadConfig();
|
|
39
63
|
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,54 @@ 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
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Promise that resolves when provider discovery completes (or fails).
|
|
46
|
+
* handleStart awaits this (with timeout) so oc_configure doesn't race
|
|
47
|
+
* against background discovery.
|
|
48
|
+
*/
|
|
49
|
+
let providerDiscoveryPromise: Promise<void> = Promise.resolve();
|
|
50
|
+
|
|
34
51
|
// --- Exported helpers for test/plugin wiring ---
|
|
35
52
|
|
|
36
53
|
export function resetPendingAssignments(): void {
|
|
37
54
|
pendingAssignments = new Map();
|
|
55
|
+
availableProviders = [];
|
|
56
|
+
providerDiscoveryPromise = Promise.resolve();
|
|
38
57
|
}
|
|
39
58
|
|
|
40
59
|
export function setOpenCodeConfig(config: Config | null): void {
|
|
41
60
|
openCodeConfig = config;
|
|
42
61
|
}
|
|
43
62
|
|
|
63
|
+
export function setAvailableProviders(
|
|
64
|
+
providers: ReadonlyArray<{
|
|
65
|
+
readonly id: string;
|
|
66
|
+
readonly name: string;
|
|
67
|
+
readonly models: Readonly<Record<string, { readonly id: string; readonly name: string }>>;
|
|
68
|
+
}>,
|
|
69
|
+
): void {
|
|
70
|
+
availableProviders = providers;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function setProviderDiscoveryPromise(promise: Promise<void>): void {
|
|
74
|
+
providerDiscoveryPromise = promise;
|
|
75
|
+
}
|
|
76
|
+
|
|
44
77
|
// --- Core logic ---
|
|
45
78
|
|
|
46
79
|
interface ConfigureArgs {
|
|
@@ -50,53 +83,24 @@ interface ConfigureArgs {
|
|
|
50
83
|
readonly fallbacks?: string;
|
|
51
84
|
}
|
|
52
85
|
|
|
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
86
|
/**
|
|
59
|
-
*
|
|
60
|
-
* Returns a map of
|
|
87
|
+
* Discover available models from the stored provider data.
|
|
88
|
+
* Returns a map of provider ID -> list of "providerId/modelId" strings.
|
|
61
89
|
*/
|
|
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
|
-
}
|
|
90
|
+
function discoverAvailableModels(): Map<string, string[]> {
|
|
91
|
+
const modelsByProvider = new Map<string, string[]>();
|
|
85
92
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const list = modelsByFamily.get(family) ?? [];
|
|
94
|
-
list.push(m);
|
|
95
|
-
modelsByFamily.set(family, list);
|
|
93
|
+
for (const provider of availableProviders) {
|
|
94
|
+
const modelIds: string[] = [];
|
|
95
|
+
for (const modelKey of Object.keys(provider.models)) {
|
|
96
|
+
modelIds.push(`${provider.id}/${modelKey}`);
|
|
97
|
+
}
|
|
98
|
+
if (modelIds.length > 0) {
|
|
99
|
+
modelsByProvider.set(provider.id, modelIds);
|
|
96
100
|
}
|
|
97
101
|
}
|
|
98
102
|
|
|
99
|
-
return
|
|
103
|
+
return modelsByProvider;
|
|
100
104
|
}
|
|
101
105
|
|
|
102
106
|
function serializeDiversityWarnings(warnings: readonly DiversityWarning[]): readonly {
|
|
@@ -114,7 +118,13 @@ function serializeDiversityWarnings(warnings: readonly DiversityWarning[]): read
|
|
|
114
118
|
}
|
|
115
119
|
|
|
116
120
|
async function handleStart(configPath?: string): Promise<string> {
|
|
117
|
-
|
|
121
|
+
// Wait for background provider discovery (up to 5s) before building model list
|
|
122
|
+
await Promise.race([
|
|
123
|
+
providerDiscoveryPromise,
|
|
124
|
+
new Promise<void>((resolve) => setTimeout(resolve, 5000)),
|
|
125
|
+
]);
|
|
126
|
+
|
|
127
|
+
const modelsByProvider = discoverAvailableModels();
|
|
118
128
|
|
|
119
129
|
// Load current plugin config to show existing assignments
|
|
120
130
|
const currentConfig = await loadConfig(configPath);
|
|
@@ -141,7 +151,7 @@ async function handleStart(configPath?: string): Promise<string> {
|
|
|
141
151
|
return JSON.stringify({
|
|
142
152
|
action: "configure",
|
|
143
153
|
stage: "start",
|
|
144
|
-
availableModels: Object.fromEntries(
|
|
154
|
+
availableModels: Object.fromEntries(modelsByProvider),
|
|
145
155
|
groups,
|
|
146
156
|
currentConfig: currentConfig
|
|
147
157
|
? { configured: currentConfig.configured, groups: currentConfig.groups }
|