@litfamily/litopencode 1.0.19 → 1.0.21
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/CHANGELOG.md +14 -0
- package/README-Ko-KR.md +6 -6
- package/README.md +6 -6
- package/dist/agents/registry.js +2 -2
- package/dist/cli/doctor.d.ts +5 -0
- package/dist/cli/doctor.js +27 -6
- package/dist/cli/install-config.d.ts +6 -0
- package/dist/cli/install-config.js +32 -2
- package/dist/cli/install.js +4 -1
- package/dist/cli/model-catalog.d.ts +6 -1
- package/dist/cli/model-catalog.js +32 -15
- package/dist/cli/model-routing.js +14 -9
- package/dist/cli/types.d.ts +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.js +10 -2
- package/dist/model-route-policy.d.ts +9 -1
- package/dist/model-route-policy.js +46 -47
- package/dist/tui/mission-band.d.ts +28 -8
- package/dist/tui/mission-band.js +223 -39
- package/dist/tui/mission-skills.d.ts +38 -0
- package/dist/tui/mission-skills.js +81 -0
- package/dist/tui/mission-source.d.ts +17 -0
- package/dist/tui/mission-source.js +30 -0
- package/dist/tui/mission-text.d.ts +4 -1
- package/dist/tui/mission-text.js +2 -2
- package/dist/tui.js +47 -21
- package/docs/assets/readme/badge-version.svg +1 -1
- package/docs/migration.md +1 -1
- package/docs/reference-Ko-KR.md +36 -13
- package/docs/reference.md +37 -14
- package/package.json +1 -1
- package/skills/lit-pptx/package-lock.json +167 -119
- package/skills/lit-pptx/package.json +1 -1
- package/skills/managed-skill-manifest.json +4 -4
- package/skills/readme-studio/templates/hyperframes/package-lock.json +169 -121
- package/skills/readme-studio/templates/remotion/package-lock.json +3 -3
- package/tools/check-pack-payload.mjs +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@ are tracked separately from this product history.
|
|
|
5
5
|
|
|
6
6
|
## Unreleased
|
|
7
7
|
|
|
8
|
+
## 1.0.21 - 2026-10-12
|
|
9
|
+
|
|
10
|
+
- The README demo on GitHub is a new film of one real OpenCode turn. It plays without music, and a numbered label names each part LitOpenCode adds as it appears: the `lit` word, the status card switching to WORK, the light mode for a small fix, the reply that reads before editing, the fix with its test, the real test run and the closing summary.
|
|
11
|
+
- The README now shows the LIT status card as it looks in OpenCode: quiet before the first lit prompt, bright skill chips while a request runs, and dimmed recent chips after the turn. It also walks through automatic handoff with pictures of the `lit-handoff auto` messages, the message and toast when the handoff runs, and the doctor block, in English and Korean.
|
|
12
|
+
- The quick start has a short prompt to paste into a coding agent so it installs LitOpenCode for you. The agent checks the requirements, shows a dry run and asks before installing, runs `litopencode doctor`, and reminds you to restart OpenCode.
|
|
13
|
+
- The LIT card along the bottom of the screen no longer stays on WORK after the agent has finished. When the session's turn ends it settles to `○ IDLE`, no step stays lit and the skill chips dim to the recent list; a subagent finishing does not change the main card, and your next prompt brings WORK back.
|
|
14
|
+
- Install now asks for a reasoning effort after each model, for the lead and the helpers, and lists every effort OpenCode offers for that model with the recommended one marked as the default. GPT-6 Astra and GPT-6.1 Sol offer low to max; GPT-6 Luna, GPT-6 Sol and the GPT-5.6 models offer none to max, so GPT-5.6 Luna can now use xhigh. OpenCode offers `ultra` for no model, so it is no longer offered either. A route you saved with `ultra` keeps working as `max`, `litopencode doctor` warns about it, and the next install changes it to `max` with a one-line notice after backing up `litopencode.json`.
|
|
15
|
+
- `litopencode doctor` now reads the automatic handoff setting from your project, the same place LitOpenCode itself uses, so a `lit-handoff auto on 70` sent in chat shows up in doctor. Run it from the project folder.
|
|
16
|
+
- lit-pptx now installs sharp 0.35.5, and the readme-studio motion templates use sharp 0.35.5 and source-map-js 1.2.2.
|
|
17
|
+
|
|
18
|
+
## 1.0.20 - 2026-10-12
|
|
19
|
+
|
|
20
|
+
- The band along the bottom of OpenCode's main screen is now a small card that stays on screen for the whole session. The top edge shows LIT, the current workflow and its phase. The first row shows PLAN ━ WORK ━ REVIEW with the goal and the next task. The second row shows a chip for each LitOpenCode skill that ran: skills used by the current request are bright and get a short shine as they appear, then dim into a recent list when the answer is done, and extra chips fold into `+N`. The bottom edge shows context use, elapsed time and the git branch. Before you use lit in a session, the card shows the project's last goal. `band: false` still hides it and `band: { card: true }` still adds the detail card.
|
|
21
|
+
|
|
8
22
|
## 1.0.19 - 2026-10-11
|
|
9
23
|
|
|
10
24
|
- The README is shorter for first-time readers. It says what LitOpenCode does, how to install it and what to type first, and the detail now lives in docs pages (guide, skills, screens and brand) in English and Korean. The GitHub page also plays a short film of a real session.
|
package/README-Ko-KR.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
<p align="center"><picture><source media="(prefers-reduced-motion: reduce)" srcset="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
1
|
+
<p align="center"><picture><source media="(prefers-reduced-motion: reduce)" srcset="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.21/docs/assets/cover-motion-still.webp" /><img src="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.21/docs/assets/cover-motion.webp" width="100%" alt="LitFamily 모션 커버: 다섯 로봇 패널이 차례로 켜지고, LitOpenCode 로봇의 눈과 테두리가 빛난 뒤 LITFAMILY와 KEEP THE WORK LIT. 문구가 밝아지는 영상" /></picture></p>
|
|
2
2
|
|
|
3
3
|
# LitOpenCode
|
|
4
4
|
|
|
5
5
|
**LitOpenCode는 OpenCode용 워크플로 플러그인입니다. 프롬프트에 `lit`만 붙이면 계획하고, 만들고, 확인하고, 기록까지 남겨서 다음 세션이 어디서 멈췄는지 바로 압니다.**
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<a href="https://www.npmjs.com/package/@litfamily/litopencode"><img src="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
8
|
+
<a href="https://www.npmjs.com/package/@litfamily/litopencode"><img src="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.21/docs/assets/readme/badge-version.svg" alt="1.0.21" /></a>
|
|
9
9
|
<a href="https://www.npmjs.com/package/@litfamily/litopencode"><img src="https://img.shields.io/npm/dm/@litfamily/litopencode?label=downloads&color=D7F75B" alt="월간 npm 다운로드 수" /></a>
|
|
10
|
-
<a href="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
10
|
+
<a href="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.21/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.21/docs/assets/readme/badge-license.svg" alt="MIT 라이선스" /></a>
|
|
11
11
|
<a href="https://github.com/wjgoarxiv/litopencode"><img src="https://img.shields.io/github/stars/wjgoarxiv/litopencode?style=social" alt="GitHub 스타" /></a>
|
|
12
12
|
</p>
|
|
13
13
|
|
|
@@ -57,9 +57,9 @@ npm uninstall -g @litfamily/litopencode
|
|
|
57
57
|
## 더 보기
|
|
58
58
|
|
|
59
59
|
- [GitHub의 사용 안내](https://github.com/wjgoarxiv/litopencode/blob/main/docs/guide-Ko-KR.md): 설치 옵션, 자동 인수인계, 문제 해결, 제거
|
|
60
|
-
- [워크플로 상세 안내](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
61
|
-
- [마이그레이션 안내](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
62
|
-
- [LitFamily 허브](https://github.com/wjgoarxiv/litfamily) · [MIT 라이선스](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
60
|
+
- [워크플로 상세 안내](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.21/docs/reference-Ko-KR.md) · [English reference](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.21/docs/reference.md)
|
|
61
|
+
- [마이그레이션 안내](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.21/docs/migration.md) · [Changelog](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.21/CHANGELOG.md) · [개인정보와 네트워크 동작](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.21/docs/privacy.md)
|
|
62
|
+
- [LitFamily 허브](https://github.com/wjgoarxiv/litfamily) · [MIT 라이선스](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.21/LICENSE) · [ASCII 글꼴 라이선스](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.21/docs/assets/readme/JetBrainsMono-OFL.txt)
|
|
63
63
|
|
|
64
64
|
<details>
|
|
65
65
|
<summary>ASCII 로고 복사</summary>
|
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
<p align="center"><picture><source media="(prefers-reduced-motion: reduce)" srcset="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
1
|
+
<p align="center"><picture><source media="(prefers-reduced-motion: reduce)" srcset="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.21/docs/assets/cover-motion-still.webp" /><img src="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.21/docs/assets/cover-motion.webp" width="100%" alt="LitFamily motion cover: five armored robots power on one by one, the LitOpenCode robot wakes with glowing eyes and a lit frame, then LITFAMILY and KEEP THE WORK LIT. light up." /></picture></p>
|
|
2
2
|
|
|
3
3
|
# LitOpenCode
|
|
4
4
|
|
|
5
5
|
**LitOpenCode is a workflow plugin for OpenCode. Add `lit` to a prompt and the work gets planned, built, checked and written down, so the next session knows where you stopped.**
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<a href="https://www.npmjs.com/package/@litfamily/litopencode"><img src="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
8
|
+
<a href="https://www.npmjs.com/package/@litfamily/litopencode"><img src="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.21/docs/assets/readme/badge-version.svg" alt="1.0.21" /></a>
|
|
9
9
|
<a href="https://www.npmjs.com/package/@litfamily/litopencode"><img src="https://img.shields.io/npm/dm/@litfamily/litopencode?label=downloads&color=D7F75B" alt="npm downloads per month" /></a>
|
|
10
|
-
<a href="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
10
|
+
<a href="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.21/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.21/docs/assets/readme/badge-license.svg" alt="MIT license" /></a>
|
|
11
11
|
<a href="https://github.com/wjgoarxiv/litopencode"><img src="https://img.shields.io/github/stars/wjgoarxiv/litopencode?style=social" alt="GitHub stars" /></a>
|
|
12
12
|
</p>
|
|
13
13
|
|
|
@@ -57,9 +57,9 @@ npm uninstall -g @litfamily/litopencode
|
|
|
57
57
|
## More
|
|
58
58
|
|
|
59
59
|
- [Guide on GitHub](https://github.com/wjgoarxiv/litopencode/blob/main/docs/guide.md): install options, automatic handoff, troubleshooting and removal.
|
|
60
|
-
- [Workflow reference](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
61
|
-
- [Migration notes](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
62
|
-
- [LitFamily hub](https://github.com/wjgoarxiv/litfamily) · [MIT license](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
60
|
+
- [Workflow reference](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.21/docs/reference.md)
|
|
61
|
+
- [Migration notes](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.21/docs/migration.md) · [Changelog](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.21/CHANGELOG.md) · [Privacy and network behavior](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.21/docs/privacy.md)
|
|
62
|
+
- [LitFamily hub](https://github.com/wjgoarxiv/litfamily) · [MIT license](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.21/LICENSE) · [ASCII font license](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.21/docs/assets/readme/JetBrainsMono-OFL.txt)
|
|
63
63
|
|
|
64
64
|
<details>
|
|
65
65
|
<summary>Copy ASCII logo</summary>
|
package/dist/agents/registry.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { litOpenCodeDefaultAgents } from "./defaults.js";
|
|
2
2
|
import { litOpenCodeSpecialistAgents } from "./specialists.js";
|
|
3
3
|
import { withReaderFacingCommunicationContract } from "../reader-facing-output.js";
|
|
4
|
-
import { defaultGlobalConfigFile, configSectionWasAuthored, effectiveAgentConfig } from "../config.js";
|
|
4
|
+
import { defaultGlobalConfigFile, configSectionWasAuthored, effectiveAgentConfig, withRetiredEffortsReplaced } from "../config.js";
|
|
5
5
|
import { defaultOpenAiRoutes, newGenerationOpenAiModels, openAiModelEfforts, openaiProviderModels } from "../cli/model-catalog.js";
|
|
6
6
|
import { assertSafeEffectiveAgentRoutes } from "../model-route-policy.js";
|
|
7
7
|
export const litOpenCodeAgents = Object.freeze([
|
|
@@ -190,7 +190,7 @@ function routeLeadAgent(config, authoredOverride) {
|
|
|
190
190
|
if (hasAuthoredRouteFields(config.agents["lit-loop"]))
|
|
191
191
|
return authoredOverride;
|
|
192
192
|
const lead = leadCategoryRoute(config);
|
|
193
|
-
return lead === undefined ? authoredOverride : { ...authoredOverride, ...lead };
|
|
193
|
+
return lead === undefined ? authoredOverride : withRetiredEffortsReplaced({ ...authoredOverride, ...lead });
|
|
194
194
|
}
|
|
195
195
|
function applySpecialLeadRoute(agentTarget, agentId) {
|
|
196
196
|
const existing = agentTarget[agentId];
|
package/dist/cli/doctor.d.ts
CHANGED
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
import type { CliResult } from "./types.ts";
|
|
2
|
+
/**
|
|
3
|
+
* The project the plugin would use from here: OpenCode's worktree is the git root above the working
|
|
4
|
+
* directory, and without one the directory itself.
|
|
5
|
+
*/
|
|
6
|
+
export declare function doctorProjectRoot(cwd?: string): string;
|
|
2
7
|
export declare function doctor(root: string): Promise<CliResult>;
|
package/dist/cli/doctor.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import fsSync from "node:fs";
|
|
1
2
|
import fs from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { litOpenCodeAgents, registerLitOpenCodeAgents } from "../agents.js";
|
|
4
5
|
import { defaultGlobalConfigFile, loadConfig, mergeConfigs, readLitOpenCodeConfigFile } from "../config.js";
|
|
5
|
-
import { diagnoseModelRoutes, effectiveAuthoredAgentRoutes, projectModelRoutesForDiagnostics } from "../model-route-policy.js";
|
|
6
|
-
import { litOpenCodeConfigFileForOpenCodeRoot } from "../state.js";
|
|
6
|
+
import { diagnoseModelRoutes, effectiveAuthoredAgentRoutes, retiredEffortRoutes, retiredEffortWarning, projectModelRoutesForDiagnostics } from "../model-route-policy.js";
|
|
7
|
+
import { createRuntimePaths, litOpenCodeConfigFileForOpenCodeRoot } from "../state.js";
|
|
7
8
|
import { isRecord, readJsonObjectIfPresent, readPackageMetadata } from "./json.js";
|
|
8
9
|
import { parseJsonc } from "./jsonc.js";
|
|
9
10
|
import { inspectCommandAliases } from "./command-aliases.js";
|
|
@@ -83,6 +84,19 @@ async function inspectPlugin(root, metadata) {
|
|
|
83
84
|
...(openCodeConfig.error === undefined ? {} : { error: openCodeConfig.error })
|
|
84
85
|
};
|
|
85
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* The project the plugin would use from here: OpenCode's worktree is the git root above the working
|
|
89
|
+
* directory, and without one the directory itself.
|
|
90
|
+
*/
|
|
91
|
+
export function doctorProjectRoot(cwd = process.cwd()) {
|
|
92
|
+
const start = path.resolve(cwd);
|
|
93
|
+
for (let dir = start;; dir = path.dirname(dir)) {
|
|
94
|
+
if (fsSync.existsSync(path.join(dir, ".git")))
|
|
95
|
+
return dir;
|
|
96
|
+
if (path.dirname(dir) === dir)
|
|
97
|
+
return start;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
86
100
|
export async function doctor(root) {
|
|
87
101
|
const metadata = await readPackageMetadata();
|
|
88
102
|
const loaded = await loadConfig(root);
|
|
@@ -117,9 +131,14 @@ export async function doctor(root) {
|
|
|
117
131
|
const tui = await inspectTuiPlugin(root, installPlugin.present && installPlugin.configured !== null ? installPlugin.configured : installPlugin.expected);
|
|
118
132
|
const hostConfig = await readOpenCodeConfig(root);
|
|
119
133
|
const motion = probeMotionRuntime(metadata.packageRoot);
|
|
134
|
+
// The plugin keeps the auto-handoff setting per project, so doctor reads it from the project too.
|
|
135
|
+
const project = doctorProjectRoot();
|
|
136
|
+
const projectPaths = createRuntimePaths(project);
|
|
137
|
+
const projectLitConfig = await readLitOpenCodeConfigFile(projectPaths.configFile);
|
|
138
|
+
const autoHandoffConfig = projectLitConfig === undefined ? effectiveOpenCodeConfig : mergeConfigs(effectiveOpenCodeConfig, projectLitConfig);
|
|
120
139
|
const autoHandoff = await createAutoHandoffSettings({
|
|
121
|
-
stateFile:
|
|
122
|
-
config:
|
|
140
|
+
stateFile: projectPaths.autoHandoffFile,
|
|
141
|
+
config: autoHandoffConfig.autoHandoff
|
|
123
142
|
}).resolve();
|
|
124
143
|
const hostCompaction = inspectHostCompactionPoint(hostConfig.config);
|
|
125
144
|
const autoHandoffWarnings = [...autoHandoff.warnings];
|
|
@@ -137,7 +156,8 @@ export async function doctor(root) {
|
|
|
137
156
|
enabled: autoHandoff.enabled,
|
|
138
157
|
percent: autoHandoff.percent,
|
|
139
158
|
sources: autoHandoff.sources,
|
|
140
|
-
|
|
159
|
+
project,
|
|
160
|
+
stateFile: projectPaths.autoHandoffFile,
|
|
141
161
|
env: { enabled: autoHandoffEnvEnabled, percent: autoHandoffEnvPercent },
|
|
142
162
|
host: { autoCompaction: hostCompaction.autoCompaction, compactionPercent: hostCompaction.percent },
|
|
143
163
|
compaction: "The plugin starts compaction itself after the handoff is saved; the user does not need to run /compact.",
|
|
@@ -160,7 +180,8 @@ export async function doctor(root) {
|
|
|
160
180
|
agents: Object.keys(effectiveOpenCodeConfig.agents),
|
|
161
181
|
categories: Object.keys(effectiveOpenCodeConfig.categories),
|
|
162
182
|
effective: projectModelRoutesForDiagnostics(reportedRoutes, routeDiagnostics),
|
|
163
|
-
routeDiagnostics
|
|
183
|
+
routeDiagnostics,
|
|
184
|
+
retiredEfforts: retiredEffortRoutes(await readJsonObjectIfPresent(openCodeLitConfigPath)).map(retiredEffortWarning)
|
|
164
185
|
},
|
|
165
186
|
state: {
|
|
166
187
|
runtimeDir: loaded.paths.runtimeDir,
|
|
@@ -9,6 +9,12 @@ export declare function renderOpenCodeConfig(filePath: string, currentText: stri
|
|
|
9
9
|
export declare function writeOpenCodeConfig(filePath: string, next: Record<string, unknown>): Promise<void>;
|
|
10
10
|
export declare function readInstallModelClassification(root: string): Promise<ModelConfigClassification>;
|
|
11
11
|
export declare function readInstallModelSelection(root: string): Promise<InstallModelSelection | undefined>;
|
|
12
|
+
/**
|
|
13
|
+
* Rewrites saved routes that name an effort OpenCode no longer offers (ultra) to its replacement
|
|
14
|
+
* (max), after copying the file to `<file>.litopencode-backup`. Returns the one-line notice, or
|
|
15
|
+
* undefined when nothing changed. A dry run writes nothing and says nothing.
|
|
16
|
+
*/
|
|
17
|
+
export declare function migrateRetiredEfforts(root: string, dryRun: boolean): Promise<string | undefined>;
|
|
12
18
|
export declare function assertExistingModelRoutesSafe(root: string): Promise<void>;
|
|
13
19
|
export declare function describeInstallRoutes(root: string, hostConfig: Record<string, unknown> | null, selection: InstallModelSelection | undefined, permissionMode: InstallPermissionMode, permissionModeExplicit: boolean, outputStyle: string): Promise<NonNullable<InstallReport["routes"]>>;
|
|
14
20
|
export declare function readExistingOutputStyle(root: string): Promise<string>;
|
|
@@ -3,11 +3,11 @@ import fs from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { litOpenCodeAgents, registerLitOpenCodeAgents } from "../agents.js";
|
|
5
5
|
import { defaultGlobalConfigFile, defaultGlobalConfigJson, mergeConfigs, readLitOpenCodeConfigFile } from "../config.js";
|
|
6
|
-
import { diagnoseModelRoutes, effectiveAuthoredAgentRoutes, UnsafeModelRouteError } from "../model-route-policy.js";
|
|
6
|
+
import { diagnoseModelRoutes, effectiveAuthoredAgentRoutes, retiredEffortRoutes, UnsafeModelRouteError } from "../model-route-policy.js";
|
|
7
7
|
import { litOpenCodeConfigFileForOpenCodeRoot } from "../state.js";
|
|
8
8
|
import { isRecord, readJsonObjectIfPresent, readTextIfPresent } from "./json.js";
|
|
9
9
|
import { editJsoncObject, formatJsoncMember, hasJsoncComments, lineEnding } from "./jsonc.js";
|
|
10
|
-
import { installModelEfforts, isModelProvider, modelMenuRows, stripFastSuffix } from "./model-catalog.js";
|
|
10
|
+
import { installModelEfforts, isModelProvider, modelMenuRows, replacedRetiredEffort, stripFastSuffix } from "./model-catalog.js";
|
|
11
11
|
import { classifyModelConfig, selectedLitConfig } from "./model-policy.js";
|
|
12
12
|
export async function writeTextAtomically(filePath, text, overrides = {}) {
|
|
13
13
|
const tempPath = path.join(path.dirname(filePath), "." + path.basename(filePath) + ".tmp-" + randomUUID());
|
|
@@ -101,6 +101,36 @@ export async function readInstallModelSelection(root) {
|
|
|
101
101
|
return undefined;
|
|
102
102
|
return { ...planning, helper: execution };
|
|
103
103
|
}
|
|
104
|
+
// The same suffix the TUI config backups use.
|
|
105
|
+
const litConfigBackupSuffix = ".litopencode-backup";
|
|
106
|
+
/**
|
|
107
|
+
* Rewrites saved routes that name an effort OpenCode no longer offers (ultra) to its replacement
|
|
108
|
+
* (max), after copying the file to `<file>.litopencode-backup`. Returns the one-line notice, or
|
|
109
|
+
* undefined when nothing changed. A dry run writes nothing and says nothing.
|
|
110
|
+
*/
|
|
111
|
+
export async function migrateRetiredEfforts(root, dryRun) {
|
|
112
|
+
const filePath = litOpenCodeConfigFileForOpenCodeRoot(root);
|
|
113
|
+
const existing = await readValidatedConfig(root);
|
|
114
|
+
const retired = retiredEffortRoutes(existing);
|
|
115
|
+
if (existing === null || retired.length === 0 || dryRun)
|
|
116
|
+
return undefined;
|
|
117
|
+
const next = structuredClone(existing);
|
|
118
|
+
for (const { section, id } of retired) {
|
|
119
|
+
const route = next[section][id];
|
|
120
|
+
const provider = typeof route.provider === "string" ? route.provider : undefined;
|
|
121
|
+
const model = route.model;
|
|
122
|
+
for (const field of ["variant", "reasoningEffort"]) {
|
|
123
|
+
const replacement = replacedRetiredEffort(provider, model, typeof route[field] === "string" ? route[field] : undefined);
|
|
124
|
+
if (replacement !== undefined)
|
|
125
|
+
route[field] = replacement;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const backup = filePath + litConfigBackupSuffix;
|
|
129
|
+
await writeTextAtomically(backup, await fs.readFile(filePath, "utf8"));
|
|
130
|
+
await writeTextAtomically(filePath, JSON.stringify(next, null, 2) + "\n");
|
|
131
|
+
const count = retired.length === 1 ? "1 route" : `${retired.length} routes`;
|
|
132
|
+
return `Moved ${count} from ultra to max, which OpenCode no longer offers; the old file is at ${backup}.`;
|
|
133
|
+
}
|
|
104
134
|
export async function assertExistingModelRoutesSafe(root) {
|
|
105
135
|
const existing = await readValidatedConfig(root);
|
|
106
136
|
if (existing === null)
|
package/dist/cli/install.js
CHANGED
|
@@ -7,7 +7,7 @@ import { createRuntimePaths } from "../state.js";
|
|
|
7
7
|
import { describeCommandAliasMutation, ensureCommandAliases, inspectCommandAliases } from "./command-aliases.js";
|
|
8
8
|
import { openCodeHostCapabilities } from "./host-capabilities.js";
|
|
9
9
|
import { applyHostLimits, describeHostLimitMutation, ensureHostLimits } from "./host-limits.js";
|
|
10
|
-
import { assertExistingModelRoutesSafe, describeInstallRoutes, describeLitConfigMutation, ensureLitConfig, OpenCodeConfigCommentError, readExistingOutputStyle, readInstallModelClassification, readInstallModelSelection, renderOpenCodeConfig, writeOpenCodeConfig } from "./install-config.js";
|
|
10
|
+
import { assertExistingModelRoutesSafe, migrateRetiredEfforts, describeInstallRoutes, describeLitConfigMutation, ensureLitConfig, OpenCodeConfigCommentError, readExistingOutputStyle, readInstallModelClassification, readInstallModelSelection, renderOpenCodeConfig, writeOpenCodeConfig } from "./install-config.js";
|
|
11
11
|
import { readJsonObjectIfPresent, readPackageMetadata, readTextIfPresent } from "./json.js";
|
|
12
12
|
import { renderInstallReport, wrapReceiptLine } from "./install-report.js";
|
|
13
13
|
import { renderLitOpenCodeWordmark } from "./install-tui.js";
|
|
@@ -215,6 +215,9 @@ async function verifyInstalledSurfaces(root, configFile, target, metadata, selec
|
|
|
215
215
|
throw new Error("Install verification failed: " + failures.join("; "));
|
|
216
216
|
}
|
|
217
217
|
export async function install(root, dryRun, modelSelection = undefined, modelPrompt = "auto", permissionMode = "safe", permissionModeExplicit = false, permissionPrompt = "auto", progress, outputStylePrompt = "auto") {
|
|
218
|
+
const retiredNotice = await migrateRetiredEfforts(root, dryRun);
|
|
219
|
+
if (retiredNotice !== undefined)
|
|
220
|
+
process.stdout.write(retiredNotice + "\n");
|
|
218
221
|
await assertExistingModelRoutesSafe(root);
|
|
219
222
|
const classification = await readInstallModelClassification(root);
|
|
220
223
|
const currentModel = await readInstallModelSelection(root);
|
|
@@ -13,7 +13,7 @@ export type ProviderMenuRow = {
|
|
|
13
13
|
readonly credentialEnv: string;
|
|
14
14
|
};
|
|
15
15
|
export declare const providerMenu: readonly ProviderMenuRow[];
|
|
16
|
-
export declare const installModelEfforts: readonly ["low", "medium", "high", "xhigh", "max"
|
|
16
|
+
export declare const installModelEfforts: readonly ["none", "low", "medium", "high", "xhigh", "max"];
|
|
17
17
|
export declare const nativeReasoningEfforts: readonly ["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"];
|
|
18
18
|
export declare const defaultOpenAiRoutes: Readonly<{
|
|
19
19
|
lead: Readonly<{
|
|
@@ -73,6 +73,11 @@ export declare const openaiProviderModels: Readonly<{
|
|
|
73
73
|
}>;
|
|
74
74
|
export declare function openAiModelRow(model: string): ModelMenuRow | undefined;
|
|
75
75
|
export declare function openAiModelEfforts(model: string): readonly InstallModelEffort[];
|
|
76
|
+
/**
|
|
77
|
+
* OpenCode no longer offers `ultra` for any OpenAI row. A saved route that names it on a catalog
|
|
78
|
+
* model runs as `max`, and the next install rewrites it; this returns that replacement, or undefined.
|
|
79
|
+
*/
|
|
80
|
+
export declare function replacedRetiredEffort(provider: string | undefined, model: string | undefined, effort: string | undefined): "max" | undefined;
|
|
76
81
|
export declare function isInstallModelEffort(value: string): value is InstallModelEffort;
|
|
77
82
|
export declare function openaiModelHelpLine(): string;
|
|
78
83
|
export declare function shippedDefaultsSummary(): string;
|
|
@@ -6,8 +6,13 @@ export const providerMenu = [
|
|
|
6
6
|
{ provider: "openai", label: "OpenAI GPT-6 family / GPT-5.6 previous generation", credentialEnv: "OPENAI_API_KEY" },
|
|
7
7
|
{ provider: "xai", label: "xAI Grok", credentialEnv: "XAI_API_KEY" }
|
|
8
8
|
];
|
|
9
|
-
export const installModelEfforts = Object.freeze(["low", "medium", "high", "xhigh", "max"
|
|
10
|
-
|
|
9
|
+
export const installModelEfforts = Object.freeze(["none", "low", "medium", "high", "xhigh", "max"]);
|
|
10
|
+
// What a config file may name. Custom models are user-owned, so this stays wider than any catalog row.
|
|
11
|
+
export const nativeReasoningEfforts = Object.freeze(["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"]);
|
|
12
|
+
// The efforts OpenCode itself offers for each OpenAI row: the model's `variants` in
|
|
13
|
+
// `opencode models openai --verbose`, checked on OpenCode 1.18.35.
|
|
14
|
+
const hostEffortsWithNone = installModelEfforts;
|
|
15
|
+
const hostEffortsFromLow = installModelEfforts.slice(1);
|
|
11
16
|
export const defaultOpenAiRoutes = Object.freeze({
|
|
12
17
|
lead: Object.freeze({ provider: "openai", model: "gpt-6-astra", effort: "xhigh" }),
|
|
13
18
|
helper: Object.freeze({ provider: "openai", model: "gpt-6-luna", effort: "max" })
|
|
@@ -39,18 +44,18 @@ export const hostLimitModelIds = Object.freeze([
|
|
|
39
44
|
defaultOpenAiRoutes.helper.model,
|
|
40
45
|
previousGenerationOpenAiModels.luna
|
|
41
46
|
]);
|
|
42
|
-
// The OpenAI rows
|
|
43
|
-
//
|
|
44
|
-
// all consume one catalog rather than
|
|
47
|
+
// The OpenAI rows choose the models and their recommended efforts; each row's
|
|
48
|
+
// efforts are the host list above. The picker, flags, config reader, and route
|
|
49
|
+
// guard all consume this one catalog rather than independent literal lists.
|
|
45
50
|
export const openaiRows = [
|
|
46
|
-
{ provider: "openai", model: defaultOpenAiRoutes.lead.model, effort: defaultOpenAiRoutes.lead.effort, efforts:
|
|
47
|
-
{ provider: "openai", model: newGenerationOpenAiModels.sol, effort: "xhigh", efforts:
|
|
48
|
-
{ provider: "openai", model: defaultOpenAiRoutes.helper.model, effort: defaultOpenAiRoutes.helper.effort, efforts:
|
|
49
|
-
{ provider: "openai", model: previousGpt6OpenAiModels.sol, effort: "xhigh", efforts:
|
|
50
|
-
{ provider: "openai", model: previousGenerationOpenAiModels.sol, effort: "xhigh", efforts:
|
|
51
|
-
{ provider: "openai", model: previousGenerationOpenAiModels.luna, effort: "max", efforts:
|
|
52
|
-
{ provider: "openai", model: previousGenerationOpenAiModels.family, effort: "high", efforts:
|
|
53
|
-
{ provider: "openai", model: previousGenerationOpenAiModels.terra, effort: "xhigh", efforts:
|
|
51
|
+
{ provider: "openai", model: defaultOpenAiRoutes.lead.model, effort: defaultOpenAiRoutes.lead.effort, efforts: hostEffortsFromLow, hint: "frontier reasoning (recommended lead)" },
|
|
52
|
+
{ provider: "openai", model: newGenerationOpenAiModels.sol, effort: "xhigh", efforts: hostEffortsFromLow, hint: "coding lead (recommended alternative)" },
|
|
53
|
+
{ provider: "openai", model: defaultOpenAiRoutes.helper.model, effort: defaultOpenAiRoutes.helper.effort, efforts: hostEffortsWithNone, hint: "balanced (recommended helper)" },
|
|
54
|
+
{ provider: "openai", model: previousGpt6OpenAiModels.sol, effort: "xhigh", efforts: hostEffortsWithNone, hint: "coding lead, previous generation" },
|
|
55
|
+
{ provider: "openai", model: previousGenerationOpenAiModels.sol, effort: "xhigh", efforts: hostEffortsWithNone, hint: "deepest reasoning, previous generation" },
|
|
56
|
+
{ provider: "openai", model: previousGenerationOpenAiModels.luna, effort: "max", efforts: hostEffortsWithNone, hint: "balanced, previous generation" },
|
|
57
|
+
{ provider: "openai", model: previousGenerationOpenAiModels.family, effort: "high", efforts: hostEffortsWithNone, hint: "previous generation" },
|
|
58
|
+
{ provider: "openai", model: previousGenerationOpenAiModels.terra, effort: "xhigh", efforts: hostEffortsWithNone, hint: "previous generation" }
|
|
54
59
|
];
|
|
55
60
|
export const openaiProviderModels = Object.freeze({
|
|
56
61
|
[newGenerationOpenAiModels.astra]: Object.freeze({ name: "GPT-6 Astra", reasoning: true, temperature: false, tool_call: true }),
|
|
@@ -64,6 +69,18 @@ export function openAiModelRow(model) {
|
|
|
64
69
|
export function openAiModelEfforts(model) {
|
|
65
70
|
return openAiModelRow(model)?.efforts ?? [];
|
|
66
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* OpenCode no longer offers `ultra` for any OpenAI row. A saved route that names it on a catalog
|
|
74
|
+
* model runs as `max`, and the next install rewrites it; this returns that replacement, or undefined.
|
|
75
|
+
*/
|
|
76
|
+
export function replacedRetiredEffort(provider, model, effort) {
|
|
77
|
+
if (effort !== "ultra" || model === undefined)
|
|
78
|
+
return undefined;
|
|
79
|
+
const slash = model.indexOf("/");
|
|
80
|
+
const owner = slash > 0 ? model.slice(0, slash) : provider;
|
|
81
|
+
const id = slash > 0 ? model.slice(slash + 1) : model;
|
|
82
|
+
return owner === "openai" && openAiModelRow(stripFastSuffix(id)) !== undefined ? "max" : undefined;
|
|
83
|
+
}
|
|
67
84
|
export function isInstallModelEffort(value) {
|
|
68
85
|
return installModelEfforts.includes(value);
|
|
69
86
|
}
|
|
@@ -79,13 +96,13 @@ export function legacyFastAliasHelp() {
|
|
|
79
96
|
return "The previous-generation GPT-5.6 rows retain their <id>-fast aliases; GPT-6 rows have no inferred -fast alias.";
|
|
80
97
|
}
|
|
81
98
|
export function modelEffortHelp() {
|
|
82
|
-
return `GPT-6 Astra and Sol accept ${
|
|
99
|
+
return `GPT-6 Astra and GPT-6.1 Sol accept ${openAiModelEfforts(newGenerationOpenAiModels.astra).join(", ")}; GPT-6 Luna, GPT-6 Sol and the GPT-5.6 rows accept ${openAiModelEfforts(newGenerationOpenAiModels.luna).join(", ")}.`;
|
|
83
100
|
}
|
|
84
101
|
// Used only when the host catalog (~/.cache/opencode/models.json) is absent or unreadable.
|
|
85
102
|
const fallbackXaiRows = [
|
|
86
103
|
{ provider: "xai", model: "grok-4.6", effort: "xhigh", efforts: ["low", "medium", "high", "xhigh"], hint: "frontier reasoning (catalog unavailable)" },
|
|
87
104
|
{ provider: "xai", model: "grok-4.5", effort: "high", efforts: ["low", "medium", "high"], hint: "catalog unavailable" },
|
|
88
|
-
{ provider: "xai", model: "grok-4.3", effort: "high", efforts: ["low", "medium", "high"], hint: "catalog unavailable" }
|
|
105
|
+
{ provider: "xai", model: "grok-4.3", effort: "high", efforts: ["none", "low", "medium", "high"], hint: "catalog unavailable" }
|
|
89
106
|
];
|
|
90
107
|
const xaiRowLimit = 3;
|
|
91
108
|
export function hostCatalogPath() {
|
|
@@ -120,19 +120,24 @@ async function chooseProvider(reader, title, defaultProvider) {
|
|
|
120
120
|
const index = await chooseIndex(reader, title, providerMenu.map((row) => ({ label: row.label.padEnd(35, " ") + " (" + row.provider + ")", hint: "" })), defaultIndex);
|
|
121
121
|
return providerMenu[index].provider;
|
|
122
122
|
}
|
|
123
|
-
|
|
123
|
+
// After a model, the role's effort: every effort the host offers for that model, the
|
|
124
|
+
// recommended one marked, and the current route's effort as the default when it is still offered.
|
|
125
|
+
async function chooseEffort(reader, role, row, current) {
|
|
126
|
+
const preferred = current !== undefined && row.efforts.includes(current) ? current : row.effort;
|
|
127
|
+
const index = await chooseIndex(reader, "Choose the " + role + " reasoning effort for " + row.model + ".", row.efforts.map((effort) => ({ label: effort, hint: effort === row.effort ? "recommended" : "" })), Math.max(0, row.efforts.indexOf(preferred)));
|
|
128
|
+
return row.efforts[index];
|
|
129
|
+
}
|
|
130
|
+
async function chooseRoute(reader, title, role, provider, defaultIndex, preferred) {
|
|
124
131
|
const rows = modelMenuRows(provider);
|
|
125
132
|
const preferredModel = preferred === undefined ? undefined : provider === "openai" ? stripFastSuffix(preferred.model) : preferred.model;
|
|
126
133
|
const preferredIndex = preferredModel === undefined ? -1 : rows.findIndex((row) => row.model === preferredModel);
|
|
127
134
|
const selectedDefault = preferredIndex >= 0 ? preferredIndex : Math.min(defaultIndex, rows.length - 1);
|
|
128
135
|
const index = await chooseIndex(reader, title, rows.map((row) => ({ label: menuLabel(row), hint: row.hint })), selectedDefault);
|
|
129
136
|
const row = rows[index];
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
return { provider: row.provider, model: row.model, effort: row.effort };
|
|
137
|
+
const same = preferred !== undefined && row.provider === preferred.provider && row.model === preferredModel;
|
|
138
|
+
const effort = await chooseEffort(reader, role, row, same ? preferred.effort : undefined);
|
|
139
|
+
// An existing route keeps its exact model id (a -fast alias included).
|
|
140
|
+
return same ? { ...preferred, effort } : { provider: row.provider, model: row.model, effort };
|
|
136
141
|
}
|
|
137
142
|
function recommendedHelperIndex(provider) {
|
|
138
143
|
return Math.max(0, modelMenuRows(provider).findIndex((row) => row.hint.includes("recommended helper")));
|
|
@@ -193,10 +198,10 @@ export async function promptModelSelection(selection, mode, dryRun, classificati
|
|
|
193
198
|
process.stdout.write("\nExisting route (" + classification.class + ") will be rewritten in managed keys only.\n");
|
|
194
199
|
}
|
|
195
200
|
const leadProvider = await chooseProvider(reader, "Choose the provider for LitOpenCode agents.", preferredSelection !== undefined && isModelProvider(preferredSelection.provider) ? preferredSelection.provider : "openai");
|
|
196
|
-
const lead = await chooseRoute(reader, "Choose the LEAD model (plans and reviews).", leadProvider, 0, preferredSelection);
|
|
201
|
+
const lead = await chooseRoute(reader, "Choose the LEAD model (plans and reviews).", "LEAD", leadProvider, 0, preferredSelection);
|
|
197
202
|
const preferredHelper = preferredSelection?.helper;
|
|
198
203
|
const helperProvider = await chooseProvider(reader, "Choose the provider for HELPER agents (workers, explorers, librarians).", preferredHelper !== undefined && isModelProvider(preferredHelper.provider) ? preferredHelper.provider : leadProvider);
|
|
199
|
-
const helper = await chooseRoute(reader, "Choose the HELPER model (spawned and delegated agents).", helperProvider, recommendedHelperIndex(helperProvider), preferredHelper);
|
|
204
|
+
const helper = await chooseRoute(reader, "Choose the HELPER model (spawned and delegated agents).", "HELPER", helperProvider, recommendedHelperIndex(helperProvider), preferredHelper);
|
|
200
205
|
const chosen = { ...lead, helper };
|
|
201
206
|
process.stdout.write(renderModelRouteCard(chosen, configPath));
|
|
202
207
|
await askLine(reader, "");
|
package/dist/cli/types.d.ts
CHANGED
|
@@ -54,7 +54,7 @@ export type InstallModelRoute = {
|
|
|
54
54
|
export type InstallModelSelection = InstallModelRoute & {
|
|
55
55
|
readonly helper?: InstallModelRoute;
|
|
56
56
|
};
|
|
57
|
-
export type InstallModelEffort = "low" | "medium" | "high" | "xhigh" | "max"
|
|
57
|
+
export type InstallModelEffort = "none" | "low" | "medium" | "high" | "xhigh" | "max";
|
|
58
58
|
export type ModelConfigClass = "fresh" | "managed_legacy" | "gpt56_managed" | "astra_managed" | "gpt56_other" | "custom" | "mixed";
|
|
59
59
|
export type ModelConfigClassification = {
|
|
60
60
|
readonly class: ModelConfigClass;
|
package/dist/config.d.ts
CHANGED
|
@@ -74,4 +74,6 @@ export declare function mergeConfigs(base: LitOpenCodeConfig, override: Partial<
|
|
|
74
74
|
export declare function defaultGlobalConfigFile(): LitOpenCodeConfig;
|
|
75
75
|
export declare function defaultGlobalConfigJson(): Record<string, unknown>;
|
|
76
76
|
export declare function effectiveAgentConfig(config: LitOpenCodeConfig, agentId: string): LitOpenCodeAgentModelConfig;
|
|
77
|
+
/** A saved route on an effort OpenCode no longer offers runs as its replacement until install rewrites it. */
|
|
78
|
+
export declare function withRetiredEffortsReplaced(route: LitOpenCodeAgentModelConfig): LitOpenCodeAgentModelConfig;
|
|
77
79
|
export declare function loadConfig(projectRoot: string): Promise<LoadedConfig>;
|
package/dist/config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRuntimePaths } from "./state.js";
|
|
2
2
|
import { readLitOpenCodeConfigFile } from "./config-parser.js";
|
|
3
|
-
import { defaultOpenAiRoutes } from "./cli/model-catalog.js";
|
|
3
|
+
import { defaultOpenAiRoutes, replacedRetiredEffort } from "./cli/model-catalog.js";
|
|
4
4
|
export { LitOpenCodeConfigError, readLitOpenCodeConfigFile } from "./config-parser.js";
|
|
5
5
|
// The runtime receives a merged config, so keep source ownership alongside
|
|
6
6
|
// (rather than inside) the JSON-shaped config. This never serializes into a
|
|
@@ -140,7 +140,15 @@ export function defaultGlobalConfigJson() {
|
|
|
140
140
|
export function effectiveAgentConfig(config, agentId) {
|
|
141
141
|
const agentConfig = config.agents[agentId] ?? {};
|
|
142
142
|
const categoryConfig = agentConfig.category ? config.categories[agentConfig.category] : undefined;
|
|
143
|
-
return mergeAgentModelConfig(categoryConfig, agentConfig);
|
|
143
|
+
return withRetiredEffortsReplaced(mergeAgentModelConfig(categoryConfig, agentConfig));
|
|
144
|
+
}
|
|
145
|
+
/** A saved route on an effort OpenCode no longer offers runs as its replacement until install rewrites it. */
|
|
146
|
+
export function withRetiredEffortsReplaced(route) {
|
|
147
|
+
const variant = replacedRetiredEffort(route.provider, route.model, route.variant);
|
|
148
|
+
const reasoningEffort = replacedRetiredEffort(route.provider, route.model, route.reasoningEffort);
|
|
149
|
+
if (variant === undefined && reasoningEffort === undefined)
|
|
150
|
+
return route;
|
|
151
|
+
return { ...route, ...(variant === undefined ? {} : { variant }), ...(reasoningEffort === undefined ? {} : { reasoningEffort }) };
|
|
144
152
|
}
|
|
145
153
|
export async function loadConfig(projectRoot) {
|
|
146
154
|
const paths = createRuntimePaths(projectRoot);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type LitOpenCodeAgentModelConfig, type LitOpenCodeConfig } from "./config.ts";
|
|
2
2
|
export type ModelRouteDiagnostic = {
|
|
3
3
|
readonly agentId: string;
|
|
4
|
-
readonly code: "
|
|
4
|
+
readonly code: "astra_effort_unsupported" | "astra_effort_conflict" | "model_effort_unsupported" | "model_effort_conflict";
|
|
5
5
|
readonly model: string;
|
|
6
6
|
readonly effort: string;
|
|
7
7
|
};
|
|
@@ -23,3 +23,11 @@ export declare class UnsafeModelRouteError extends Error {
|
|
|
23
23
|
constructor(diagnostics: readonly ModelRouteDiagnostic[]);
|
|
24
24
|
}
|
|
25
25
|
export declare function assertSafeEffectiveAgentRoutes(config: LitOpenCodeConfig, agentIds: readonly string[]): void;
|
|
26
|
+
export type RetiredEffortRoute = {
|
|
27
|
+
readonly section: "categories" | "agents";
|
|
28
|
+
readonly id: string;
|
|
29
|
+
readonly model: string;
|
|
30
|
+
};
|
|
31
|
+
/** The saved routes in a litopencode.json object that still name an effort OpenCode no longer offers. */
|
|
32
|
+
export declare function retiredEffortRoutes(config: unknown): readonly RetiredEffortRoute[];
|
|
33
|
+
export declare function retiredEffortWarning(route: RetiredEffortRoute): string;
|