@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.
@@ -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
- Ask the user to choose a primary model and optionally 1-3 fallback models.
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.0.0",
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
  }
@@ -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
- * Used by "start" subcommand to discover available models.
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
- * Extract available models from the OpenCode host config, grouped by family.
60
- * Returns a map of family -> model IDs.
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(config: Config | null): Map<string, string[]> {
63
- const modelsByFamily = new Map<string, string[]>();
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
- // Also include top-level model and small_model
87
- const topModel = getStringField(configRecord, "model");
88
- const smallModel = getStringField(configRecord, "small_model");
89
- for (const m of [topModel, smallModel]) {
90
- if (m && !seen.has(m)) {
91
- seen.add(m);
92
- const family = extractFamily(m);
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 modelsByFamily;
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 modelsByFamily = discoverAvailableModels(openCodeConfig);
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(modelsByFamily),
136
+ availableModels: Object.fromEntries(modelsByProvider),
145
137
  groups,
146
138
  currentConfig: currentConfig
147
139
  ? { configured: currentConfig.configured, groups: currentConfig.groups }