skyloom 1.13.6 → 1.13.8
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/.github/workflows/ci.yml +36 -36
- package/README.md +220 -159
- package/config/providers.yaml +39 -39
- package/config/skills/api_integrator/SKILL.md +15 -15
- package/config/skills/arch_designer/SKILL.md +13 -13
- package/config/skills/ci_cd_manager/SKILL.md +14 -14
- package/config/skills/code_analysis/SKILL.md +13 -13
- package/config/skills/code_generator/SKILL.md +12 -12
- package/config/skills/code_reviewer/SKILL.md +13 -13
- package/config/skills/content_writer/SKILL.md +14 -14
- package/config/skills/data_transformer/SKILL.md +15 -15
- package/config/skills/document_analysis/SKILL.md +13 -13
- package/config/skills/emotional_companion/SKILL.md +15 -15
- package/config/skills/performance_checker/SKILL.md +14 -14
- package/config/skills/security_auditor/SKILL.md +14 -14
- package/config/skills/self_evolve/SKILL.md +13 -13
- package/config/skills/sys_operator/SKILL.md +15 -15
- package/config/skills/task_planner/SKILL.md +14 -14
- package/config/skills/web_research/SKILL.md +14 -14
- package/config/skills/workflow_designer/SKILL.md +13 -13
- package/dist/agents/dew.js +52 -52
- package/dist/agents/fair.js +84 -84
- package/dist/agents/fog.js +30 -30
- package/dist/agents/frost.js +32 -32
- package/dist/agents/rain.js +32 -32
- package/dist/agents/snow.js +68 -68
- package/dist/cli/commands_md.d.ts +41 -0
- package/dist/cli/commands_md.d.ts.map +1 -0
- package/dist/cli/commands_md.js +140 -0
- package/dist/cli/commands_md.js.map +1 -0
- package/dist/cli/input_macros.d.ts +28 -0
- package/dist/cli/input_macros.d.ts.map +1 -0
- package/dist/cli/input_macros.js +120 -0
- package/dist/cli/input_macros.js.map +1 -0
- package/dist/cli/loom.d.ts +220 -0
- package/dist/cli/loom.d.ts.map +1 -0
- package/dist/cli/loom.js +1094 -0
- package/dist/cli/loom.js.map +1 -0
- package/dist/cli/loom_chat.d.ts +20 -0
- package/dist/cli/loom_chat.d.ts.map +1 -0
- package/dist/cli/loom_chat.js +685 -0
- package/dist/cli/loom_chat.js.map +1 -0
- package/dist/cli/main.js +310 -14
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/tui.d.ts.map +1 -1
- package/dist/cli/tui.js +7 -1
- package/dist/cli/tui.js.map +1 -1
- package/dist/core/agent.d.ts +20 -0
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/agent.js +199 -16
- package/dist/core/agent.js.map +1 -1
- package/dist/core/factory.d.ts.map +1 -1
- package/dist/core/factory.js +34 -2
- package/dist/core/factory.js.map +1 -1
- package/dist/core/file_checkpoint.d.ts +57 -0
- package/dist/core/file_checkpoint.d.ts.map +1 -0
- package/dist/core/file_checkpoint.js +162 -0
- package/dist/core/file_checkpoint.js.map +1 -0
- package/dist/core/hooks.d.ts +43 -0
- package/dist/core/hooks.d.ts.map +1 -0
- package/dist/core/hooks.js +110 -0
- package/dist/core/hooks.js.map +1 -0
- package/dist/core/llm.d.ts.map +1 -1
- package/dist/core/llm.js +15 -9
- package/dist/core/llm.js.map +1 -1
- package/dist/core/longdoc.js +5 -5
- package/dist/core/mcp.d.ts +16 -0
- package/dist/core/mcp.d.ts.map +1 -1
- package/dist/core/mcp.js +55 -0
- package/dist/core/mcp.js.map +1 -1
- package/dist/core/model_config.d.ts +40 -0
- package/dist/core/model_config.d.ts.map +1 -0
- package/dist/core/model_config.js +191 -0
- package/dist/core/model_config.js.map +1 -0
- package/dist/core/skill.d.ts +7 -0
- package/dist/core/skill.d.ts.map +1 -1
- package/dist/core/skill.js +47 -0
- package/dist/core/skill.js.map +1 -1
- package/dist/core/skymd.d.ts +39 -0
- package/dist/core/skymd.d.ts.map +1 -0
- package/dist/core/skymd.js +177 -0
- package/dist/core/skymd.js.map +1 -0
- package/dist/core/tool.d.ts +12 -0
- package/dist/core/tool.d.ts.map +1 -1
- package/dist/core/tool.js +30 -0
- package/dist/core/tool.js.map +1 -1
- package/dist/core/verify.d.ts +27 -0
- package/dist/core/verify.d.ts.map +1 -0
- package/dist/core/verify.js +62 -0
- package/dist/core/verify.js.map +1 -0
- package/dist/skills/loader.d.ts +22 -2
- package/dist/skills/loader.d.ts.map +1 -1
- package/dist/skills/loader.js +45 -15
- package/dist/skills/loader.js.map +1 -1
- package/dist/tools/builtin.d.ts.map +1 -1
- package/dist/tools/builtin.js +13 -3
- package/dist/tools/builtin.js.map +1 -1
- package/dist/tools/model_tool.d.ts +11 -0
- package/dist/tools/model_tool.d.ts.map +1 -0
- package/dist/tools/model_tool.js +71 -0
- package/dist/tools/model_tool.js.map +1 -0
- package/dist/tools/todo.d.ts +30 -0
- package/dist/tools/todo.d.ts.map +1 -0
- package/dist/tools/todo.js +78 -0
- package/dist/tools/todo.js.map +1 -0
- package/docs/AESTHETIC_DESIGN.md +152 -144
- package/docs/OPTIMIZATION_PLAN.md +178 -178
- package/package.json +68 -68
- package/scripts/install.js +48 -48
- package/scripts/link.js +10 -10
- package/setup.bat +79 -79
- package/skill-test-ty2fOA/test.md +10 -10
- package/src/agents/dew.ts +70 -70
- package/src/agents/fair.ts +102 -102
- package/src/agents/fog.ts +48 -48
- package/src/agents/frost.ts +50 -50
- package/src/agents/rain.ts +50 -50
- package/src/agents/snow.ts +239 -239
- package/src/cli/commands_md.ts +112 -0
- package/src/cli/input_macros.ts +83 -0
- package/src/cli/loom.ts +982 -0
- package/src/cli/loom_chat.ts +598 -0
- package/src/cli/main.ts +255 -9
- package/src/cli/mode.ts +58 -58
- package/src/cli/tui.ts +228 -222
- package/src/core/agent/guard.ts +134 -134
- package/src/core/agent/task.ts +100 -100
- package/src/core/agent.ts +195 -16
- package/src/core/arbitrate.ts +162 -162
- package/src/core/catalog.ts +178 -178
- package/src/core/checkpoint.ts +94 -94
- package/src/core/estimate.ts +104 -104
- package/src/core/evolve.ts +191 -191
- package/src/core/factory.ts +31 -2
- package/src/core/file_checkpoint.ts +136 -0
- package/src/core/filter.ts +103 -103
- package/src/core/graph.ts +156 -156
- package/src/core/hooks.ts +126 -0
- package/src/core/icons.ts +53 -53
- package/src/core/index.ts +37 -37
- package/src/core/learn.ts +146 -146
- package/src/core/llm.ts +15 -9
- package/src/core/longdoc.ts +155 -155
- package/src/core/mcp.ts +48 -0
- package/src/core/mcp_server.ts +176 -176
- package/src/core/model_config.ts +157 -0
- package/src/core/profile.ts +255 -255
- package/src/core/router.ts +124 -124
- package/src/core/sandbox.ts +142 -142
- package/src/core/security.ts +243 -243
- package/src/core/skill.ts +42 -0
- package/src/core/skymd.ts +143 -0
- package/src/core/theme.ts +65 -65
- package/src/core/tool.ts +30 -0
- package/src/core/tool_router.ts +193 -193
- package/src/core/vector.ts +152 -152
- package/src/core/verify.ts +71 -0
- package/src/core/workspace.ts +150 -150
- package/src/plugins/loader.ts +66 -66
- package/src/skills/loader.ts +45 -16
- package/src/sql.js.d.ts +29 -29
- package/src/tools/builtin.ts +13 -3
- package/src/tools/computer.ts +269 -269
- package/src/tools/delegate.ts +49 -49
- package/src/tools/model_tool.ts +74 -0
- package/src/tools/todo.ts +76 -0
- package/src/web/tts.ts +93 -93
- package/tests/agent.test.ts +159 -159
- package/tests/agent_helpers.test.ts +48 -48
- package/tests/bus.test.ts +121 -121
- package/tests/catalog.test.ts +86 -86
- package/tests/checkpoint_commands.test.ts +124 -0
- package/tests/claude_compat.test.ts +110 -0
- package/tests/config.test.ts +41 -41
- package/tests/guard.test.ts +75 -75
- package/tests/icons.test.ts +45 -45
- package/tests/loom.test.ts +248 -0
- package/tests/memory.test.ts +170 -170
- package/tests/model_config.test.ts +109 -0
- package/tests/router.test.ts +86 -86
- package/tests/schemas.test.ts +51 -51
- package/tests/semantic.test.ts +83 -83
- package/tests/setup.ts +10 -10
- package/tests/skill.test.ts +172 -172
- package/tests/skymd.test.ts +146 -0
- package/tests/task.test.ts +60 -60
- package/tests/todo_toolstats.test.ts +94 -0
- package/tests/tool.test.ts +108 -108
- package/tests/tool_router.test.ts +71 -71
- package/tests/tui.test.ts +67 -67
- package/vitest.config.ts +17 -17
- package/=12 +0 -0
- package/=8 +0 -0
package/src/core/catalog.ts
CHANGED
|
@@ -1,178 +1,178 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Model & Provider Catalog — single source of truth.
|
|
3
|
-
*
|
|
4
|
-
* Replaces the three drifting copies of model data (config/models.yaml,
|
|
5
|
-
* the hardcoded setup-wizard list in cli/main.ts, and the README table)
|
|
6
|
-
* with one typed, validated catalog loaded from config/models.yaml.
|
|
7
|
-
*
|
|
8
|
-
* Every model exposed here is intended to be directly callable. Fictional
|
|
9
|
-
* or unreleased models must not appear in models.yaml.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import * as fs from "fs";
|
|
13
|
-
import * as path from "path";
|
|
14
|
-
import * as yaml from "yaml";
|
|
15
|
-
import { CONFIG_DIR } from "./config";
|
|
16
|
-
import { getLogger } from "./logger";
|
|
17
|
-
|
|
18
|
-
const log = getLogger("catalog");
|
|
19
|
-
|
|
20
|
-
/** A single callable model. Costs are USD per 1M tokens. */
|
|
21
|
-
export interface ModelInfo {
|
|
22
|
-
/** Model id as passed to the provider (e.g. "gpt-4o", "openai/gpt-4.1"). */
|
|
23
|
-
id: string;
|
|
24
|
-
/** Catalog provider key (e.g. "openai", "deepseek"). */
|
|
25
|
-
provider: string;
|
|
26
|
-
/** Context window in tokens. */
|
|
27
|
-
context: number;
|
|
28
|
-
/** Input cost, USD per 1M tokens. */
|
|
29
|
-
costIn: number;
|
|
30
|
-
/** Output cost, USD per 1M tokens. */
|
|
31
|
-
costOut: number;
|
|
32
|
-
/** Short human description. */
|
|
33
|
-
desc: string;
|
|
34
|
-
/** True for local/free providers (ollama, zero-cost). */
|
|
35
|
-
local: boolean;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** Display + ordering metadata for a provider in setup/UI. */
|
|
39
|
-
export interface ProviderMeta {
|
|
40
|
-
id: string;
|
|
41
|
-
/** Human label shown in the setup wizard. */
|
|
42
|
-
name: string;
|
|
43
|
-
/** Env var that supplies the API key (when applicable). */
|
|
44
|
-
envVar?: string;
|
|
45
|
-
/** Sort order in the wizard. */
|
|
46
|
-
order: number;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Provider display metadata. Ordering matches the setup wizard.
|
|
51
|
-
* Provider *ids* match the keys used in config/models.yaml.
|
|
52
|
-
*/
|
|
53
|
-
export const PROVIDER_META: Record<string, ProviderMeta> = {
|
|
54
|
-
deepseek: { id: "deepseek", name: "DeepSeek", envVar: "DEEPSEEK_API_KEY", order: 1 },
|
|
55
|
-
openai: { id: "openai", name: "OpenAI", envVar: "OPENAI_API_KEY", order: 2 },
|
|
56
|
-
anthropic: { id: "anthropic", name: "Anthropic", envVar: "ANTHROPIC_API_KEY", order: 3 },
|
|
57
|
-
google: { id: "google", name: "Google Gemini", envVar: "GEMINI_API_KEY", order: 4 },
|
|
58
|
-
groq: { id: "groq", name: "Groq", envVar: "GROQ_API_KEY", order: 5 },
|
|
59
|
-
openrouter: { id: "openrouter", name: "OpenRouter (多模型)", envVar: "OPENROUTER_API_KEY", order: 6 },
|
|
60
|
-
mistral: { id: "mistral", name: "Mistral", envVar: "MISTRAL_API_KEY", order: 7 },
|
|
61
|
-
xai: { id: "xai", name: "xAI (Grok)", envVar: "XAI_API_KEY", order: 8 },
|
|
62
|
-
ollama: { id: "ollama", name: "Ollama 本地", order: 9 },
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
/** Raw shape of an entry in config/models.yaml. */
|
|
66
|
-
interface RawModelEntry {
|
|
67
|
-
name: string;
|
|
68
|
-
context?: number;
|
|
69
|
-
cost_in?: number;
|
|
70
|
-
cost_out?: number;
|
|
71
|
-
desc?: string;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
let catalogCache: Map<string, ModelInfo[]> | null = null;
|
|
75
|
-
|
|
76
|
-
function readModelsYaml(): Record<string, RawModelEntry[]> {
|
|
77
|
-
const modelPath = path.join(CONFIG_DIR, "models.yaml");
|
|
78
|
-
try {
|
|
79
|
-
if (!fs.existsSync(modelPath)) {
|
|
80
|
-
log.warn("models_yaml_missing", { path: modelPath });
|
|
81
|
-
return {};
|
|
82
|
-
}
|
|
83
|
-
const data = yaml.parse(fs.readFileSync(modelPath, "utf-8"));
|
|
84
|
-
return (data && typeof data === "object" ? data : {}) as Record<string, RawModelEntry[]>;
|
|
85
|
-
} catch (e) {
|
|
86
|
-
log.error("models_yaml_parse_failed", { error: String(e) });
|
|
87
|
-
return {};
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/** Load (and cache) the full catalog as provider -> models. */
|
|
92
|
-
export function loadCatalog(): Map<string, ModelInfo[]> {
|
|
93
|
-
if (catalogCache) return catalogCache;
|
|
94
|
-
|
|
95
|
-
const raw = readModelsYaml();
|
|
96
|
-
const catalog = new Map<string, ModelInfo[]>();
|
|
97
|
-
|
|
98
|
-
for (const [provider, entries] of Object.entries(raw)) {
|
|
99
|
-
if (!Array.isArray(entries)) continue;
|
|
100
|
-
const models: ModelInfo[] = [];
|
|
101
|
-
for (const e of entries) {
|
|
102
|
-
if (!e || typeof e.name !== "string") continue;
|
|
103
|
-
const costIn = e.cost_in ?? 0;
|
|
104
|
-
const costOut = e.cost_out ?? 0;
|
|
105
|
-
models.push({
|
|
106
|
-
id: e.name,
|
|
107
|
-
provider,
|
|
108
|
-
context: e.context ?? 0,
|
|
109
|
-
costIn,
|
|
110
|
-
costOut,
|
|
111
|
-
desc: e.desc ?? "",
|
|
112
|
-
local: provider === "ollama" || (costIn === 0 && costOut === 0),
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
if (models.length > 0) catalog.set(provider, models);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
catalogCache = catalog;
|
|
119
|
-
return catalog;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/** Clear the cache (used by tests). */
|
|
123
|
-
export function resetCatalogCache(): void {
|
|
124
|
-
catalogCache = null;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/** All provider ids present in the catalog, in wizard order. */
|
|
128
|
-
export function listProviders(): string[] {
|
|
129
|
-
const present = [...loadCatalog().keys()];
|
|
130
|
-
return present.sort((a, b) => (PROVIDER_META[a]?.order ?? 99) - (PROVIDER_META[b]?.order ?? 99));
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/** Models for a provider (empty array if unknown). */
|
|
134
|
-
export function modelsFor(provider: string): ModelInfo[] {
|
|
135
|
-
return loadCatalog().get(provider) ?? [];
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/** Flat list of every callable model across providers. */
|
|
139
|
-
export function allModels(): ModelInfo[] {
|
|
140
|
-
return [...loadCatalog().values()].flat();
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Look up a model by id. Matching is tolerant of a "provider/" prefix
|
|
145
|
-
* (e.g. "openai/gpt-4o" resolves to the "gpt-4o" entry under openai).
|
|
146
|
-
*/
|
|
147
|
-
export function getModelInfo(modelId: string): ModelInfo | null {
|
|
148
|
-
const all = allModels();
|
|
149
|
-
const exact = all.find((m) => m.id === modelId);
|
|
150
|
-
if (exact) return exact;
|
|
151
|
-
// tolerate provider/ prefix on either side
|
|
152
|
-
const stripped = modelId.includes("/") ? modelId.split("/").slice(1).join("/") : modelId;
|
|
153
|
-
return all.find((m) => m.id === stripped || m.id.split("/").slice(1).join("/") === modelId) ?? null;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/** Whether a model id is a known, callable model in the catalog. */
|
|
157
|
-
export function isKnownModel(modelId: string): boolean {
|
|
158
|
-
return getModelInfo(modelId) !== null;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/** Provider display label (falls back to the raw id). */
|
|
162
|
-
export function providerLabel(provider: string): string {
|
|
163
|
-
return PROVIDER_META[provider]?.name ?? provider;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Validate that a configured default model is callable. Returns a short
|
|
168
|
-
* list of suggested model ids when it is not, so callers can fail loudly
|
|
169
|
-
* instead of 404-ing at request time.
|
|
170
|
-
*/
|
|
171
|
-
export function validateModel(modelId: string | undefined): { ok: boolean; suggestions: string[] } {
|
|
172
|
-
if (modelId && isKnownModel(modelId)) return { ok: true, suggestions: [] };
|
|
173
|
-
const suggestions = allModels()
|
|
174
|
-
.filter((m) => !m.local)
|
|
175
|
-
.slice(0, 6)
|
|
176
|
-
.map((m) => m.id);
|
|
177
|
-
return { ok: false, suggestions };
|
|
178
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Model & Provider Catalog — single source of truth.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the three drifting copies of model data (config/models.yaml,
|
|
5
|
+
* the hardcoded setup-wizard list in cli/main.ts, and the README table)
|
|
6
|
+
* with one typed, validated catalog loaded from config/models.yaml.
|
|
7
|
+
*
|
|
8
|
+
* Every model exposed here is intended to be directly callable. Fictional
|
|
9
|
+
* or unreleased models must not appear in models.yaml.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import * as fs from "fs";
|
|
13
|
+
import * as path from "path";
|
|
14
|
+
import * as yaml from "yaml";
|
|
15
|
+
import { CONFIG_DIR } from "./config";
|
|
16
|
+
import { getLogger } from "./logger";
|
|
17
|
+
|
|
18
|
+
const log = getLogger("catalog");
|
|
19
|
+
|
|
20
|
+
/** A single callable model. Costs are USD per 1M tokens. */
|
|
21
|
+
export interface ModelInfo {
|
|
22
|
+
/** Model id as passed to the provider (e.g. "gpt-4o", "openai/gpt-4.1"). */
|
|
23
|
+
id: string;
|
|
24
|
+
/** Catalog provider key (e.g. "openai", "deepseek"). */
|
|
25
|
+
provider: string;
|
|
26
|
+
/** Context window in tokens. */
|
|
27
|
+
context: number;
|
|
28
|
+
/** Input cost, USD per 1M tokens. */
|
|
29
|
+
costIn: number;
|
|
30
|
+
/** Output cost, USD per 1M tokens. */
|
|
31
|
+
costOut: number;
|
|
32
|
+
/** Short human description. */
|
|
33
|
+
desc: string;
|
|
34
|
+
/** True for local/free providers (ollama, zero-cost). */
|
|
35
|
+
local: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Display + ordering metadata for a provider in setup/UI. */
|
|
39
|
+
export interface ProviderMeta {
|
|
40
|
+
id: string;
|
|
41
|
+
/** Human label shown in the setup wizard. */
|
|
42
|
+
name: string;
|
|
43
|
+
/** Env var that supplies the API key (when applicable). */
|
|
44
|
+
envVar?: string;
|
|
45
|
+
/** Sort order in the wizard. */
|
|
46
|
+
order: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Provider display metadata. Ordering matches the setup wizard.
|
|
51
|
+
* Provider *ids* match the keys used in config/models.yaml.
|
|
52
|
+
*/
|
|
53
|
+
export const PROVIDER_META: Record<string, ProviderMeta> = {
|
|
54
|
+
deepseek: { id: "deepseek", name: "DeepSeek", envVar: "DEEPSEEK_API_KEY", order: 1 },
|
|
55
|
+
openai: { id: "openai", name: "OpenAI", envVar: "OPENAI_API_KEY", order: 2 },
|
|
56
|
+
anthropic: { id: "anthropic", name: "Anthropic", envVar: "ANTHROPIC_API_KEY", order: 3 },
|
|
57
|
+
google: { id: "google", name: "Google Gemini", envVar: "GEMINI_API_KEY", order: 4 },
|
|
58
|
+
groq: { id: "groq", name: "Groq", envVar: "GROQ_API_KEY", order: 5 },
|
|
59
|
+
openrouter: { id: "openrouter", name: "OpenRouter (多模型)", envVar: "OPENROUTER_API_KEY", order: 6 },
|
|
60
|
+
mistral: { id: "mistral", name: "Mistral", envVar: "MISTRAL_API_KEY", order: 7 },
|
|
61
|
+
xai: { id: "xai", name: "xAI (Grok)", envVar: "XAI_API_KEY", order: 8 },
|
|
62
|
+
ollama: { id: "ollama", name: "Ollama 本地", order: 9 },
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/** Raw shape of an entry in config/models.yaml. */
|
|
66
|
+
interface RawModelEntry {
|
|
67
|
+
name: string;
|
|
68
|
+
context?: number;
|
|
69
|
+
cost_in?: number;
|
|
70
|
+
cost_out?: number;
|
|
71
|
+
desc?: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let catalogCache: Map<string, ModelInfo[]> | null = null;
|
|
75
|
+
|
|
76
|
+
function readModelsYaml(): Record<string, RawModelEntry[]> {
|
|
77
|
+
const modelPath = path.join(CONFIG_DIR, "models.yaml");
|
|
78
|
+
try {
|
|
79
|
+
if (!fs.existsSync(modelPath)) {
|
|
80
|
+
log.warn("models_yaml_missing", { path: modelPath });
|
|
81
|
+
return {};
|
|
82
|
+
}
|
|
83
|
+
const data = yaml.parse(fs.readFileSync(modelPath, "utf-8"));
|
|
84
|
+
return (data && typeof data === "object" ? data : {}) as Record<string, RawModelEntry[]>;
|
|
85
|
+
} catch (e) {
|
|
86
|
+
log.error("models_yaml_parse_failed", { error: String(e) });
|
|
87
|
+
return {};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Load (and cache) the full catalog as provider -> models. */
|
|
92
|
+
export function loadCatalog(): Map<string, ModelInfo[]> {
|
|
93
|
+
if (catalogCache) return catalogCache;
|
|
94
|
+
|
|
95
|
+
const raw = readModelsYaml();
|
|
96
|
+
const catalog = new Map<string, ModelInfo[]>();
|
|
97
|
+
|
|
98
|
+
for (const [provider, entries] of Object.entries(raw)) {
|
|
99
|
+
if (!Array.isArray(entries)) continue;
|
|
100
|
+
const models: ModelInfo[] = [];
|
|
101
|
+
for (const e of entries) {
|
|
102
|
+
if (!e || typeof e.name !== "string") continue;
|
|
103
|
+
const costIn = e.cost_in ?? 0;
|
|
104
|
+
const costOut = e.cost_out ?? 0;
|
|
105
|
+
models.push({
|
|
106
|
+
id: e.name,
|
|
107
|
+
provider,
|
|
108
|
+
context: e.context ?? 0,
|
|
109
|
+
costIn,
|
|
110
|
+
costOut,
|
|
111
|
+
desc: e.desc ?? "",
|
|
112
|
+
local: provider === "ollama" || (costIn === 0 && costOut === 0),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
if (models.length > 0) catalog.set(provider, models);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
catalogCache = catalog;
|
|
119
|
+
return catalog;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Clear the cache (used by tests). */
|
|
123
|
+
export function resetCatalogCache(): void {
|
|
124
|
+
catalogCache = null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** All provider ids present in the catalog, in wizard order. */
|
|
128
|
+
export function listProviders(): string[] {
|
|
129
|
+
const present = [...loadCatalog().keys()];
|
|
130
|
+
return present.sort((a, b) => (PROVIDER_META[a]?.order ?? 99) - (PROVIDER_META[b]?.order ?? 99));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Models for a provider (empty array if unknown). */
|
|
134
|
+
export function modelsFor(provider: string): ModelInfo[] {
|
|
135
|
+
return loadCatalog().get(provider) ?? [];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Flat list of every callable model across providers. */
|
|
139
|
+
export function allModels(): ModelInfo[] {
|
|
140
|
+
return [...loadCatalog().values()].flat();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Look up a model by id. Matching is tolerant of a "provider/" prefix
|
|
145
|
+
* (e.g. "openai/gpt-4o" resolves to the "gpt-4o" entry under openai).
|
|
146
|
+
*/
|
|
147
|
+
export function getModelInfo(modelId: string): ModelInfo | null {
|
|
148
|
+
const all = allModels();
|
|
149
|
+
const exact = all.find((m) => m.id === modelId);
|
|
150
|
+
if (exact) return exact;
|
|
151
|
+
// tolerate provider/ prefix on either side
|
|
152
|
+
const stripped = modelId.includes("/") ? modelId.split("/").slice(1).join("/") : modelId;
|
|
153
|
+
return all.find((m) => m.id === stripped || m.id.split("/").slice(1).join("/") === modelId) ?? null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Whether a model id is a known, callable model in the catalog. */
|
|
157
|
+
export function isKnownModel(modelId: string): boolean {
|
|
158
|
+
return getModelInfo(modelId) !== null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Provider display label (falls back to the raw id). */
|
|
162
|
+
export function providerLabel(provider: string): string {
|
|
163
|
+
return PROVIDER_META[provider]?.name ?? provider;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Validate that a configured default model is callable. Returns a short
|
|
168
|
+
* list of suggested model ids when it is not, so callers can fail loudly
|
|
169
|
+
* instead of 404-ing at request time.
|
|
170
|
+
*/
|
|
171
|
+
export function validateModel(modelId: string | undefined): { ok: boolean; suggestions: string[] } {
|
|
172
|
+
if (modelId && isKnownModel(modelId)) return { ok: true, suggestions: [] };
|
|
173
|
+
const suggestions = allModels()
|
|
174
|
+
.filter((m) => !m.local)
|
|
175
|
+
.slice(0, 6)
|
|
176
|
+
.map((m) => m.id);
|
|
177
|
+
return { ok: false, suggestions };
|
|
178
|
+
}
|
package/src/core/checkpoint.ts
CHANGED
|
@@ -1,94 +1,94 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Orchestration checkpoint — save/restore task state.
|
|
3
|
-
*
|
|
4
|
-
* Writes ~/.skyloom/task_checkpoint.json so a long-running orchestration
|
|
5
|
-
* interrupted by Ctrl-C can be resumed.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import * as fs from 'fs';
|
|
9
|
-
import * as path from 'path';
|
|
10
|
-
import { USER_CONFIG_DIR } from './config';
|
|
11
|
-
|
|
12
|
-
function checkpointPath(): string {
|
|
13
|
-
return path.join(USER_CONFIG_DIR, 'task_checkpoint.json');
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Save current orchestration state so it can be resumed later.
|
|
18
|
-
*/
|
|
19
|
-
export function save(
|
|
20
|
-
goal: string,
|
|
21
|
-
tasks: any[],
|
|
22
|
-
results: any[],
|
|
23
|
-
completedIds?: Set<string>
|
|
24
|
-
): void {
|
|
25
|
-
const cids = completedIds || new Set(results.map((r: any) => r.id));
|
|
26
|
-
const payload = {
|
|
27
|
-
goal,
|
|
28
|
-
tasks: tasks.map(serializeTask),
|
|
29
|
-
results: results.map(serializeResult),
|
|
30
|
-
completed_ids: Array.from(cids).sort(),
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const p = checkpointPath();
|
|
34
|
-
const dir = path.dirname(p);
|
|
35
|
-
if (!fs.existsSync(dir)) {
|
|
36
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const tmp = p + '.tmp';
|
|
40
|
-
fs.writeFileSync(tmp, JSON.stringify(payload, null, 2), 'utf-8');
|
|
41
|
-
fs.renameSync(tmp, p);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Return the last saved checkpoint dict, or null if none / unreadable.
|
|
46
|
-
*/
|
|
47
|
-
export function load(): Record<string, any> | null {
|
|
48
|
-
const p = checkpointPath();
|
|
49
|
-
if (!fs.existsSync(p)) {
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
try {
|
|
53
|
-
const data = JSON.parse(fs.readFileSync(p, 'utf-8'));
|
|
54
|
-
return typeof data === 'object' && data !== null ? data : null;
|
|
55
|
-
} catch {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Delete the checkpoint file.
|
|
62
|
-
*/
|
|
63
|
-
export function clear(): void {
|
|
64
|
-
try {
|
|
65
|
-
const p = checkpointPath();
|
|
66
|
-
if (fs.existsSync(p)) {
|
|
67
|
-
fs.unlinkSync(p);
|
|
68
|
-
}
|
|
69
|
-
} catch {
|
|
70
|
-
// Ignore cleanup errors
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// ── Serialization helpers ──
|
|
75
|
-
|
|
76
|
-
function serializeTask(t: any): Record<string, any> {
|
|
77
|
-
return {
|
|
78
|
-
id: t.id,
|
|
79
|
-
description: t.description,
|
|
80
|
-
assigned_to: t.assignedTo ?? t.assigned_to,
|
|
81
|
-
all_deps: t.allDeps ?? t.all_deps ?? [],
|
|
82
|
-
status: t.status?.value ?? t.status ?? 'unknown',
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function serializeResult(r: any): Record<string, any> {
|
|
87
|
-
return {
|
|
88
|
-
id: r.id,
|
|
89
|
-
agent: r.agent,
|
|
90
|
-
description: r.description,
|
|
91
|
-
success: r.success,
|
|
92
|
-
content: r.content,
|
|
93
|
-
};
|
|
94
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Orchestration checkpoint — save/restore task state.
|
|
3
|
+
*
|
|
4
|
+
* Writes ~/.skyloom/task_checkpoint.json so a long-running orchestration
|
|
5
|
+
* interrupted by Ctrl-C can be resumed.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import { USER_CONFIG_DIR } from './config';
|
|
11
|
+
|
|
12
|
+
function checkpointPath(): string {
|
|
13
|
+
return path.join(USER_CONFIG_DIR, 'task_checkpoint.json');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Save current orchestration state so it can be resumed later.
|
|
18
|
+
*/
|
|
19
|
+
export function save(
|
|
20
|
+
goal: string,
|
|
21
|
+
tasks: any[],
|
|
22
|
+
results: any[],
|
|
23
|
+
completedIds?: Set<string>
|
|
24
|
+
): void {
|
|
25
|
+
const cids = completedIds || new Set(results.map((r: any) => r.id));
|
|
26
|
+
const payload = {
|
|
27
|
+
goal,
|
|
28
|
+
tasks: tasks.map(serializeTask),
|
|
29
|
+
results: results.map(serializeResult),
|
|
30
|
+
completed_ids: Array.from(cids).sort(),
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const p = checkpointPath();
|
|
34
|
+
const dir = path.dirname(p);
|
|
35
|
+
if (!fs.existsSync(dir)) {
|
|
36
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const tmp = p + '.tmp';
|
|
40
|
+
fs.writeFileSync(tmp, JSON.stringify(payload, null, 2), 'utf-8');
|
|
41
|
+
fs.renameSync(tmp, p);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Return the last saved checkpoint dict, or null if none / unreadable.
|
|
46
|
+
*/
|
|
47
|
+
export function load(): Record<string, any> | null {
|
|
48
|
+
const p = checkpointPath();
|
|
49
|
+
if (!fs.existsSync(p)) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const data = JSON.parse(fs.readFileSync(p, 'utf-8'));
|
|
54
|
+
return typeof data === 'object' && data !== null ? data : null;
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Delete the checkpoint file.
|
|
62
|
+
*/
|
|
63
|
+
export function clear(): void {
|
|
64
|
+
try {
|
|
65
|
+
const p = checkpointPath();
|
|
66
|
+
if (fs.existsSync(p)) {
|
|
67
|
+
fs.unlinkSync(p);
|
|
68
|
+
}
|
|
69
|
+
} catch {
|
|
70
|
+
// Ignore cleanup errors
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ── Serialization helpers ──
|
|
75
|
+
|
|
76
|
+
function serializeTask(t: any): Record<string, any> {
|
|
77
|
+
return {
|
|
78
|
+
id: t.id,
|
|
79
|
+
description: t.description,
|
|
80
|
+
assigned_to: t.assignedTo ?? t.assigned_to,
|
|
81
|
+
all_deps: t.allDeps ?? t.all_deps ?? [],
|
|
82
|
+
status: t.status?.value ?? t.status ?? 'unknown',
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function serializeResult(r: any): Record<string, any> {
|
|
87
|
+
return {
|
|
88
|
+
id: r.id,
|
|
89
|
+
agent: r.agent,
|
|
90
|
+
description: r.description,
|
|
91
|
+
success: r.success,
|
|
92
|
+
content: r.content,
|
|
93
|
+
};
|
|
94
|
+
}
|