@mcoda/core 0.1.36 → 0.1.38
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/dist/api/MswarmApi.d.ts +94 -0
- package/dist/api/MswarmApi.d.ts.map +1 -0
- package/dist/api/MswarmApi.js +306 -0
- package/dist/api/MswarmConfigStore.d.ts +25 -0
- package/dist/api/MswarmConfigStore.d.ts.map +1 -0
- package/dist/api/MswarmConfigStore.js +63 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/services/estimate/VelocityService.d.ts.map +1 -1
- package/dist/services/estimate/VelocityService.js +1 -2
- package/dist/services/planning/CreateTasksService.d.ts +132 -1
- package/dist/services/planning/CreateTasksService.d.ts.map +1 -1
- package/dist/services/planning/CreateTasksService.js +5121 -938
- package/dist/services/planning/RefineTasksService.d.ts +10 -1
- package/dist/services/planning/RefineTasksService.d.ts.map +1 -1
- package/dist/services/planning/RefineTasksService.js +927 -216
- package/dist/services/planning/TaskSufficiencyService.d.ts.map +1 -1
- package/dist/services/planning/TaskSufficiencyService.js +147 -9
- package/package.json +6 -6
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { GlobalRepository } from "@mcoda/db";
|
|
2
|
+
export interface MswarmCloudAgent {
|
|
3
|
+
slug: string;
|
|
4
|
+
provider: string;
|
|
5
|
+
default_model: string;
|
|
6
|
+
cost_per_million?: number;
|
|
7
|
+
rating?: number;
|
|
8
|
+
reasoning_rating?: number;
|
|
9
|
+
max_complexity?: number;
|
|
10
|
+
capabilities: string[];
|
|
11
|
+
health_status?: string;
|
|
12
|
+
context_window?: number;
|
|
13
|
+
supports_tools: boolean;
|
|
14
|
+
model_id?: string;
|
|
15
|
+
display_name?: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
supports_reasoning?: boolean;
|
|
18
|
+
pricing_snapshot_id?: string;
|
|
19
|
+
pricing_version?: string;
|
|
20
|
+
sync?: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
export interface MswarmCloudAgentDetail extends MswarmCloudAgent {
|
|
23
|
+
pricing?: Record<string, unknown>;
|
|
24
|
+
supported_parameters?: string[];
|
|
25
|
+
status?: string;
|
|
26
|
+
moderation_status?: string;
|
|
27
|
+
mcoda_shape?: Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
export interface ListMswarmCloudAgentsOptions {
|
|
30
|
+
provider?: string;
|
|
31
|
+
limit?: number;
|
|
32
|
+
}
|
|
33
|
+
export interface MswarmApiOptions {
|
|
34
|
+
baseUrl?: string;
|
|
35
|
+
openAiBaseUrl?: string;
|
|
36
|
+
apiKey?: string;
|
|
37
|
+
timeoutMs?: number;
|
|
38
|
+
agentSlugPrefix?: string;
|
|
39
|
+
}
|
|
40
|
+
interface ResolvedMswarmApiOptions {
|
|
41
|
+
baseUrl: string;
|
|
42
|
+
openAiBaseUrl?: string;
|
|
43
|
+
apiKey: string;
|
|
44
|
+
timeoutMs: number;
|
|
45
|
+
agentSlugPrefix: string;
|
|
46
|
+
}
|
|
47
|
+
export interface ManagedMswarmCloudConfig {
|
|
48
|
+
managed: true;
|
|
49
|
+
remoteSlug: string;
|
|
50
|
+
provider: string;
|
|
51
|
+
modelId?: string;
|
|
52
|
+
displayName?: string;
|
|
53
|
+
description?: string;
|
|
54
|
+
supportsReasoning?: boolean;
|
|
55
|
+
pricingSnapshotId?: string;
|
|
56
|
+
pricingVersion?: string;
|
|
57
|
+
catalogBaseUrl: string;
|
|
58
|
+
openAiBaseUrl: string;
|
|
59
|
+
sync?: Record<string, unknown>;
|
|
60
|
+
syncedAt: string;
|
|
61
|
+
}
|
|
62
|
+
export interface ManagedMswarmAgentConfig extends Record<string, unknown> {
|
|
63
|
+
baseUrl: string;
|
|
64
|
+
apiBaseUrl: string;
|
|
65
|
+
mswarmCloud: ManagedMswarmCloudConfig;
|
|
66
|
+
}
|
|
67
|
+
export interface MswarmSyncRecord {
|
|
68
|
+
remoteSlug: string;
|
|
69
|
+
localSlug: string;
|
|
70
|
+
action: "created" | "updated";
|
|
71
|
+
provider: string;
|
|
72
|
+
defaultModel: string;
|
|
73
|
+
pricingVersion?: string;
|
|
74
|
+
}
|
|
75
|
+
export interface MswarmSyncSummary {
|
|
76
|
+
created: number;
|
|
77
|
+
updated: number;
|
|
78
|
+
agents: MswarmSyncRecord[];
|
|
79
|
+
}
|
|
80
|
+
export declare class MswarmApi {
|
|
81
|
+
private readonly repo;
|
|
82
|
+
private readonly options;
|
|
83
|
+
readonly baseUrl: string;
|
|
84
|
+
readonly agentSlugPrefix: string;
|
|
85
|
+
constructor(repo: GlobalRepository, options: ResolvedMswarmApiOptions);
|
|
86
|
+
static create(options?: MswarmApiOptions): Promise<MswarmApi>;
|
|
87
|
+
close(): Promise<void>;
|
|
88
|
+
private requestJson;
|
|
89
|
+
listCloudAgents(options?: ListMswarmCloudAgentsOptions): Promise<MswarmCloudAgent[]>;
|
|
90
|
+
getCloudAgent(slug: string): Promise<MswarmCloudAgentDetail>;
|
|
91
|
+
syncCloudAgents(options?: ListMswarmCloudAgentsOptions): Promise<MswarmSyncSummary>;
|
|
92
|
+
}
|
|
93
|
+
export {};
|
|
94
|
+
//# sourceMappingURL=MswarmApi.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MswarmApi.d.ts","sourceRoot":"","sources":["../../src/api/MswarmApi.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { GlobalRepository } from "@mcoda/db";
|
|
2
|
+
import { CryptoHelper, } from "@mcoda/shared";
|
|
3
|
+
import { MswarmConfigStore } from "./MswarmConfigStore.js";
|
|
4
|
+
const DEFAULT_BASE_URL = "https://api.mswarm.org/";
|
|
5
|
+
const DEFAULT_TIMEOUT_MS = 15000;
|
|
6
|
+
const DEFAULT_AGENT_SLUG_PREFIX = "mswarm-cloud";
|
|
7
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8
|
+
const resolveString = (value) => typeof value === "string" && value.trim() ? value : undefined;
|
|
9
|
+
const resolveNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
10
|
+
const resolveBoolean = (value) => typeof value === "boolean" ? value : undefined;
|
|
11
|
+
const resolveStringArray = (value) => {
|
|
12
|
+
if (!Array.isArray(value))
|
|
13
|
+
return [];
|
|
14
|
+
return value.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
|
|
15
|
+
};
|
|
16
|
+
const normalizeBaseUrl = (value, label) => {
|
|
17
|
+
const trimmed = value?.trim();
|
|
18
|
+
if (!trimmed) {
|
|
19
|
+
throw new Error(`${label} is required`);
|
|
20
|
+
}
|
|
21
|
+
let parsed;
|
|
22
|
+
try {
|
|
23
|
+
parsed = new URL(trimmed);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
throw new Error(`${label} must be an absolute URL`);
|
|
27
|
+
}
|
|
28
|
+
return parsed.toString();
|
|
29
|
+
};
|
|
30
|
+
const normalizePositiveInt = (value, label, fallback) => {
|
|
31
|
+
if (value === undefined)
|
|
32
|
+
return fallback;
|
|
33
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
34
|
+
throw new Error(`${label} must be a positive integer`);
|
|
35
|
+
}
|
|
36
|
+
return Math.trunc(value);
|
|
37
|
+
};
|
|
38
|
+
const resolveOptions = async (options = {}) => {
|
|
39
|
+
const envTimeoutRaw = process.env.MCODA_MSWARM_TIMEOUT_MS;
|
|
40
|
+
const envTimeout = envTimeoutRaw ? Number.parseInt(envTimeoutRaw, 10) : undefined;
|
|
41
|
+
const directBaseUrl = options.baseUrl ?? process.env.MCODA_MSWARM_BASE_URL;
|
|
42
|
+
const directOpenAiBaseUrl = options.openAiBaseUrl ?? process.env.MCODA_MSWARM_OPENAI_BASE_URL;
|
|
43
|
+
const directApiKey = options.apiKey ?? process.env.MCODA_MSWARM_API_KEY;
|
|
44
|
+
const directTimeout = options.timeoutMs ?? envTimeout;
|
|
45
|
+
const directAgentSlugPrefix = options.agentSlugPrefix ?? process.env.MCODA_MSWARM_AGENT_SLUG_PREFIX;
|
|
46
|
+
const needsStoredFallback = directBaseUrl === undefined ||
|
|
47
|
+
directApiKey === undefined ||
|
|
48
|
+
directTimeout === undefined ||
|
|
49
|
+
directAgentSlugPrefix === undefined;
|
|
50
|
+
const stored = needsStoredFallback ? await new MswarmConfigStore().readState() : {};
|
|
51
|
+
return {
|
|
52
|
+
baseUrl: normalizeBaseUrl(directBaseUrl ?? stored.baseUrl ?? DEFAULT_BASE_URL, "MCODA_MSWARM_BASE_URL"),
|
|
53
|
+
openAiBaseUrl: directOpenAiBaseUrl
|
|
54
|
+
? normalizeBaseUrl(directOpenAiBaseUrl, "MCODA_MSWARM_OPENAI_BASE_URL")
|
|
55
|
+
: undefined,
|
|
56
|
+
apiKey: resolveString(directApiKey ?? stored.apiKey) ?? (() => {
|
|
57
|
+
throw new Error("MCODA_MSWARM_API_KEY is required");
|
|
58
|
+
})(),
|
|
59
|
+
timeoutMs: normalizePositiveInt(directTimeout ?? stored.timeoutMs, "MCODA_MSWARM_TIMEOUT_MS", DEFAULT_TIMEOUT_MS),
|
|
60
|
+
agentSlugPrefix: resolveString(directAgentSlugPrefix ?? stored.agentSlugPrefix) ?? DEFAULT_AGENT_SLUG_PREFIX,
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
const uniqueStrings = (values) => Array.from(new Set(values.filter((value) => value.trim().length > 0)));
|
|
64
|
+
const toManagedLocalSlug = (prefix, remoteSlug) => {
|
|
65
|
+
const normalized = remoteSlug
|
|
66
|
+
.trim()
|
|
67
|
+
.toLowerCase()
|
|
68
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
69
|
+
.replace(/^-+|-+$/g, "");
|
|
70
|
+
return `${prefix}-${normalized || "agent"}`;
|
|
71
|
+
};
|
|
72
|
+
const toHealthStatus = (value) => {
|
|
73
|
+
const normalized = value?.trim().toLowerCase();
|
|
74
|
+
if (!normalized)
|
|
75
|
+
return undefined;
|
|
76
|
+
if (normalized === "healthy")
|
|
77
|
+
return "healthy";
|
|
78
|
+
if (normalized === "degraded" || normalized === "unknown" || normalized === "limited")
|
|
79
|
+
return "degraded";
|
|
80
|
+
if (normalized === "unreachable" || normalized === "offline")
|
|
81
|
+
return "unreachable";
|
|
82
|
+
return undefined;
|
|
83
|
+
};
|
|
84
|
+
const isManagedMswarmConfig = (config) => {
|
|
85
|
+
if (!isRecord(config))
|
|
86
|
+
return false;
|
|
87
|
+
if (!isRecord(config.mswarmCloud))
|
|
88
|
+
return false;
|
|
89
|
+
return config.mswarmCloud.managed === true;
|
|
90
|
+
};
|
|
91
|
+
const toManagedConfig = (existingConfig, catalogBaseUrl, openAiBaseUrl, agent, syncedAt) => {
|
|
92
|
+
const nextConfig = {
|
|
93
|
+
...(existingConfig ?? {}),
|
|
94
|
+
baseUrl: openAiBaseUrl,
|
|
95
|
+
apiBaseUrl: openAiBaseUrl,
|
|
96
|
+
mswarmCloud: {
|
|
97
|
+
managed: true,
|
|
98
|
+
remoteSlug: agent.slug,
|
|
99
|
+
provider: agent.provider,
|
|
100
|
+
modelId: agent.model_id,
|
|
101
|
+
displayName: agent.display_name,
|
|
102
|
+
description: agent.description,
|
|
103
|
+
supportsReasoning: agent.supports_reasoning,
|
|
104
|
+
pricingSnapshotId: agent.pricing_snapshot_id,
|
|
105
|
+
pricingVersion: agent.pricing_version,
|
|
106
|
+
catalogBaseUrl,
|
|
107
|
+
openAiBaseUrl,
|
|
108
|
+
sync: isRecord(agent.sync) ? agent.sync : undefined,
|
|
109
|
+
syncedAt,
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
return nextConfig;
|
|
113
|
+
};
|
|
114
|
+
const toCloudAgent = (value) => {
|
|
115
|
+
if (!isRecord(value)) {
|
|
116
|
+
throw new Error("mswarm returned an invalid cloud-agent payload");
|
|
117
|
+
}
|
|
118
|
+
const slug = resolveString(value.slug);
|
|
119
|
+
const provider = resolveString(value.provider);
|
|
120
|
+
const defaultModel = resolveString(value.default_model);
|
|
121
|
+
const supportsTools = resolveBoolean(value.supports_tools);
|
|
122
|
+
if (!slug || !provider || !defaultModel || supportsTools === undefined) {
|
|
123
|
+
throw new Error("mswarm cloud-agent payload is missing required fields");
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
slug,
|
|
127
|
+
provider,
|
|
128
|
+
default_model: defaultModel,
|
|
129
|
+
cost_per_million: resolveNumber(value.cost_per_million),
|
|
130
|
+
rating: resolveNumber(value.rating),
|
|
131
|
+
reasoning_rating: resolveNumber(value.reasoning_rating),
|
|
132
|
+
max_complexity: resolveNumber(value.max_complexity),
|
|
133
|
+
capabilities: resolveStringArray(value.capabilities),
|
|
134
|
+
health_status: resolveString(value.health_status),
|
|
135
|
+
context_window: resolveNumber(value.context_window),
|
|
136
|
+
supports_tools: supportsTools,
|
|
137
|
+
model_id: resolveString(value.model_id),
|
|
138
|
+
display_name: resolveString(value.display_name),
|
|
139
|
+
description: resolveString(value.description),
|
|
140
|
+
supports_reasoning: resolveBoolean(value.supports_reasoning),
|
|
141
|
+
pricing_snapshot_id: resolveString(value.pricing_snapshot_id),
|
|
142
|
+
pricing_version: resolveString(value.pricing_version),
|
|
143
|
+
sync: isRecord(value.sync) ? value.sync : undefined,
|
|
144
|
+
};
|
|
145
|
+
};
|
|
146
|
+
const toCloudAgentDetail = (value) => {
|
|
147
|
+
const agent = toCloudAgent(value);
|
|
148
|
+
const record = isRecord(value) ? value : {};
|
|
149
|
+
return {
|
|
150
|
+
...agent,
|
|
151
|
+
pricing: isRecord(record.pricing) ? record.pricing : undefined,
|
|
152
|
+
supported_parameters: resolveStringArray(record.supported_parameters),
|
|
153
|
+
status: resolveString(record.status),
|
|
154
|
+
moderation_status: resolveString(record.moderation_status),
|
|
155
|
+
mcoda_shape: isRecord(record.mcoda_shape) ? record.mcoda_shape : undefined,
|
|
156
|
+
};
|
|
157
|
+
};
|
|
158
|
+
const toAgentModels = (agentId, entry) => [
|
|
159
|
+
{
|
|
160
|
+
agentId,
|
|
161
|
+
modelName: entry.default_model,
|
|
162
|
+
isDefault: true,
|
|
163
|
+
config: {
|
|
164
|
+
provider: entry.provider,
|
|
165
|
+
remoteSlug: entry.slug,
|
|
166
|
+
modelId: entry.model_id,
|
|
167
|
+
pricingVersion: entry.pricing_version,
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
];
|
|
171
|
+
export class MswarmApi {
|
|
172
|
+
constructor(repo, options) {
|
|
173
|
+
this.repo = repo;
|
|
174
|
+
this.options = options;
|
|
175
|
+
this.baseUrl = options.baseUrl;
|
|
176
|
+
this.agentSlugPrefix = options.agentSlugPrefix;
|
|
177
|
+
}
|
|
178
|
+
static async create(options = {}) {
|
|
179
|
+
const repo = await GlobalRepository.create();
|
|
180
|
+
return new MswarmApi(repo, await resolveOptions(options));
|
|
181
|
+
}
|
|
182
|
+
async close() {
|
|
183
|
+
await this.repo.close();
|
|
184
|
+
}
|
|
185
|
+
async requestJson(pathname, query) {
|
|
186
|
+
const url = new URL(pathname, this.options.baseUrl);
|
|
187
|
+
if (query) {
|
|
188
|
+
for (const [key, value] of Object.entries(query)) {
|
|
189
|
+
if (value === undefined)
|
|
190
|
+
continue;
|
|
191
|
+
url.searchParams.set(key, String(value));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const controller = new AbortController();
|
|
195
|
+
const timeout = setTimeout(() => controller.abort(), this.options.timeoutMs);
|
|
196
|
+
try {
|
|
197
|
+
const response = await fetch(url.toString(), {
|
|
198
|
+
headers: {
|
|
199
|
+
accept: "application/json",
|
|
200
|
+
"x-api-key": this.options.apiKey,
|
|
201
|
+
},
|
|
202
|
+
signal: controller.signal,
|
|
203
|
+
});
|
|
204
|
+
if (!response.ok) {
|
|
205
|
+
const body = await response.text().catch(() => "");
|
|
206
|
+
throw new Error(`mswarm request failed (${response.status}): ${body || response.statusText}`);
|
|
207
|
+
}
|
|
208
|
+
try {
|
|
209
|
+
return (await response.json());
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
throw new Error(`mswarm response was not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
217
|
+
throw new Error(`mswarm request timed out after ${this.options.timeoutMs}ms`);
|
|
218
|
+
}
|
|
219
|
+
throw error;
|
|
220
|
+
}
|
|
221
|
+
finally {
|
|
222
|
+
clearTimeout(timeout);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
async listCloudAgents(options = {}) {
|
|
226
|
+
const payload = await this.requestJson("/v1/swarm/cloud/agents", {
|
|
227
|
+
shape: "mcoda",
|
|
228
|
+
provider: options.provider,
|
|
229
|
+
limit: options.limit,
|
|
230
|
+
});
|
|
231
|
+
const agents = Array.isArray(payload.agents) ? payload.agents : [];
|
|
232
|
+
return agents.map(toCloudAgent);
|
|
233
|
+
}
|
|
234
|
+
async getCloudAgent(slug) {
|
|
235
|
+
if (!slug.trim()) {
|
|
236
|
+
throw new Error("Cloud-agent slug is required");
|
|
237
|
+
}
|
|
238
|
+
const payload = await this.requestJson(`/v1/swarm/cloud/agents/${encodeURIComponent(slug)}`);
|
|
239
|
+
return toCloudAgentDetail(payload);
|
|
240
|
+
}
|
|
241
|
+
async syncCloudAgents(options = {}) {
|
|
242
|
+
const agents = await this.listCloudAgents(options);
|
|
243
|
+
const openAiBaseUrl = this.options.openAiBaseUrl ?? new URL("/v1/swarm/openai/", this.options.baseUrl).toString();
|
|
244
|
+
const syncedAt = new Date().toISOString();
|
|
245
|
+
const encryptedApiKey = await CryptoHelper.encryptSecret(this.options.apiKey);
|
|
246
|
+
const records = [];
|
|
247
|
+
for (const agent of agents) {
|
|
248
|
+
const localSlug = toManagedLocalSlug(this.options.agentSlugPrefix, agent.slug);
|
|
249
|
+
const existing = await this.repo.getAgentBySlug(localSlug);
|
|
250
|
+
if (existing && (!isManagedMswarmConfig(existing.config) || existing.config.mswarmCloud.remoteSlug !== agent.slug)) {
|
|
251
|
+
throw new Error(`Refusing to overwrite non-mswarm agent ${localSlug}`);
|
|
252
|
+
}
|
|
253
|
+
const existingConfig = existing && isRecord(existing.config) ? existing.config : undefined;
|
|
254
|
+
const nextConfig = toManagedConfig(existingConfig, this.options.baseUrl, openAiBaseUrl, agent, syncedAt);
|
|
255
|
+
const baseInput = {
|
|
256
|
+
slug: localSlug,
|
|
257
|
+
adapter: "openai-api",
|
|
258
|
+
defaultModel: agent.default_model,
|
|
259
|
+
openaiCompatible: true,
|
|
260
|
+
contextWindow: agent.context_window,
|
|
261
|
+
supportsTools: agent.supports_tools,
|
|
262
|
+
rating: agent.rating,
|
|
263
|
+
reasoningRating: agent.reasoning_rating,
|
|
264
|
+
costPerMillion: agent.cost_per_million,
|
|
265
|
+
maxComplexity: agent.max_complexity,
|
|
266
|
+
config: nextConfig,
|
|
267
|
+
capabilities: uniqueStrings(agent.capabilities),
|
|
268
|
+
};
|
|
269
|
+
const stored = existing
|
|
270
|
+
? await this.repo.updateAgent(existing.id, baseInput)
|
|
271
|
+
: await this.repo.createAgent(baseInput);
|
|
272
|
+
if (!stored) {
|
|
273
|
+
throw new Error(`Failed to persist synced agent ${localSlug}`);
|
|
274
|
+
}
|
|
275
|
+
await this.repo.setAgentModels(stored.id, toAgentModels(stored.id, agent));
|
|
276
|
+
await this.repo.setAgentAuth(stored.id, encryptedApiKey);
|
|
277
|
+
const mappedHealth = toHealthStatus(agent.health_status);
|
|
278
|
+
if (mappedHealth) {
|
|
279
|
+
const health = {
|
|
280
|
+
agentId: stored.id,
|
|
281
|
+
status: mappedHealth,
|
|
282
|
+
lastCheckedAt: syncedAt,
|
|
283
|
+
details: {
|
|
284
|
+
source: "mswarm",
|
|
285
|
+
remoteSlug: agent.slug,
|
|
286
|
+
remoteHealthStatus: agent.health_status,
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
await this.repo.setAgentHealth(health);
|
|
290
|
+
}
|
|
291
|
+
records.push({
|
|
292
|
+
remoteSlug: agent.slug,
|
|
293
|
+
localSlug,
|
|
294
|
+
action: existing ? "updated" : "created",
|
|
295
|
+
provider: agent.provider,
|
|
296
|
+
defaultModel: agent.default_model,
|
|
297
|
+
pricingVersion: agent.pricing_version,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
return {
|
|
301
|
+
created: records.filter((record) => record.action === "created").length,
|
|
302
|
+
updated: records.filter((record) => record.action === "updated").length,
|
|
303
|
+
agents: records,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface StoredMswarmConfigState {
|
|
2
|
+
baseUrl?: string;
|
|
3
|
+
encryptedApiKey?: string;
|
|
4
|
+
timeoutMs?: number;
|
|
5
|
+
agentSlugPrefix?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface MswarmConfigFileState extends Record<string, unknown> {
|
|
8
|
+
mswarm?: StoredMswarmConfigState;
|
|
9
|
+
}
|
|
10
|
+
export interface MswarmConfigState {
|
|
11
|
+
baseUrl?: string;
|
|
12
|
+
apiKey?: string;
|
|
13
|
+
timeoutMs?: number;
|
|
14
|
+
agentSlugPrefix?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare class MswarmConfigStore {
|
|
17
|
+
private readonly configFilePath;
|
|
18
|
+
constructor(configFilePath?: string);
|
|
19
|
+
configPath(): string;
|
|
20
|
+
readState(): Promise<MswarmConfigState>;
|
|
21
|
+
saveApiKey(apiKey: string): Promise<MswarmConfigState>;
|
|
22
|
+
private readConfigFile;
|
|
23
|
+
private writeConfigFile;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=MswarmConfigStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MswarmConfigStore.d.ts","sourceRoot":"","sources":["../../src/api/MswarmConfigStore.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,uBAAuB;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,qBAAsB,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACpE,MAAM,CAAC,EAAE,uBAAuB,CAAC;CAClC;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,qBAAa,iBAAiB;IAChB,OAAO,CAAC,QAAQ,CAAC,cAAc;gBAAd,cAAc,GAAE,MAAyC;IAEtF,UAAU,IAAI,MAAM;IAId,SAAS,IAAI,OAAO,CAAC,iBAAiB,CAAC;IAqBvC,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;YAiB9C,cAAc;YAad,eAAe;CAI9B"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { CryptoHelper, PathHelper } from "@mcoda/shared";
|
|
4
|
+
export class MswarmConfigStore {
|
|
5
|
+
constructor(configFilePath = PathHelper.getGlobalConfigPath()) {
|
|
6
|
+
this.configFilePath = configFilePath;
|
|
7
|
+
}
|
|
8
|
+
configPath() {
|
|
9
|
+
return this.configFilePath;
|
|
10
|
+
}
|
|
11
|
+
async readState() {
|
|
12
|
+
const fileState = await this.readConfigFile();
|
|
13
|
+
const stored = fileState.mswarm ?? {};
|
|
14
|
+
let apiKey;
|
|
15
|
+
if (stored.encryptedApiKey) {
|
|
16
|
+
try {
|
|
17
|
+
apiKey = await CryptoHelper.decryptSecret(stored.encryptedApiKey);
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
throw new Error(`Stored mswarm API key at ${this.configPath()} could not be decrypted: ${error.message ?? String(error)}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
baseUrl: stored.baseUrl,
|
|
25
|
+
apiKey,
|
|
26
|
+
timeoutMs: stored.timeoutMs,
|
|
27
|
+
agentSlugPrefix: stored.agentSlugPrefix,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
async saveApiKey(apiKey) {
|
|
31
|
+
const trimmed = apiKey.trim();
|
|
32
|
+
if (!trimmed) {
|
|
33
|
+
throw new Error("mswarm api key is required");
|
|
34
|
+
}
|
|
35
|
+
const fileState = await this.readConfigFile();
|
|
36
|
+
const encryptedApiKey = await CryptoHelper.encryptSecret(trimmed);
|
|
37
|
+
await this.writeConfigFile({
|
|
38
|
+
...fileState,
|
|
39
|
+
mswarm: {
|
|
40
|
+
...(fileState.mswarm ?? {}),
|
|
41
|
+
encryptedApiKey,
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
return this.readState();
|
|
45
|
+
}
|
|
46
|
+
async readConfigFile() {
|
|
47
|
+
try {
|
|
48
|
+
const raw = await fs.readFile(this.configPath(), "utf8");
|
|
49
|
+
const parsed = JSON.parse(raw);
|
|
50
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
51
|
+
return parsed;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return {};
|
|
56
|
+
}
|
|
57
|
+
return {};
|
|
58
|
+
}
|
|
59
|
+
async writeConfigFile(config) {
|
|
60
|
+
await PathHelper.ensureDir(path.dirname(this.configPath()));
|
|
61
|
+
await fs.writeFile(this.configPath(), JSON.stringify(config, null, 2), "utf8");
|
|
62
|
+
}
|
|
63
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export * from "./api/AgentsApi.js";
|
|
2
|
+
export * from "./api/MswarmApi.js";
|
|
3
|
+
export * from "./api/MswarmConfigStore.js";
|
|
2
4
|
export * from "./services/docs/DocsService.js";
|
|
3
5
|
export * from "./services/openapi/OpenApiService.js";
|
|
4
6
|
export * from "./services/jobs/JobService.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,sCAAsC,CAAC;AACrD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,uCAAuC,CAAC;AACtD,cAAc,qCAAqC,CAAC;AACpD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,4CAA4C,CAAC;AAC3D,cAAc,2CAA2C,CAAC;AAC1D,cAAc,+CAA+C,CAAC;AAC9D,cAAc,mCAAmC,CAAC;AAClD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,0CAA0C,CAAC;AACzD,cAAc,yCAAyC,CAAC;AACxD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,wCAAwC,CAAC;AACvD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,wCAAwC,CAAC;AACvD,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sCAAsC,CAAC;AACrD,cAAc,wCAAwC,CAAC;AACvD,cAAc,wCAAwC,CAAC;AACvD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,0CAA0C,CAAC;AACzD,cAAc,uCAAuC,CAAC;AACtD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,qCAAqC,CAAC;AACpD,cAAc,0CAA0C,CAAC;AACzD,cAAc,qCAAqC,CAAC;AACpD,cAAc,yCAAyC,CAAC;AACxD,cAAc,yCAAyC,CAAC;AACxD,cAAc,sCAAsC,CAAC;AACrD,cAAc,iCAAiC,CAAC;AAChD,cAAc,0CAA0C,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,sCAAsC,CAAC;AACrD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,uCAAuC,CAAC;AACtD,cAAc,qCAAqC,CAAC;AACpD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,4CAA4C,CAAC;AAC3D,cAAc,2CAA2C,CAAC;AAC1D,cAAc,+CAA+C,CAAC;AAC9D,cAAc,mCAAmC,CAAC;AAClD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,0CAA0C,CAAC;AACzD,cAAc,yCAAyC,CAAC;AACxD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,wCAAwC,CAAC;AACvD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,wCAAwC,CAAC;AACvD,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sCAAsC,CAAC;AACrD,cAAc,wCAAwC,CAAC;AACvD,cAAc,wCAAwC,CAAC;AACvD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,0CAA0C,CAAC;AACzD,cAAc,uCAAuC,CAAC;AACtD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,qCAAqC,CAAC;AACpD,cAAc,0CAA0C,CAAC;AACzD,cAAc,qCAAqC,CAAC;AACpD,cAAc,yCAAyC,CAAC;AACxD,cAAc,yCAAyC,CAAC;AACxD,cAAc,sCAAsC,CAAC;AACrD,cAAc,iCAAiC,CAAC;AAChD,cAAc,0CAA0C,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export * from "./api/AgentsApi.js";
|
|
2
|
+
export * from "./api/MswarmApi.js";
|
|
3
|
+
export * from "./api/MswarmConfigStore.js";
|
|
2
4
|
export * from "./services/docs/DocsService.js";
|
|
3
5
|
export * from "./services/openapi/OpenApiService.js";
|
|
4
6
|
export * from "./services/jobs/JobService.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VelocityService.d.ts","sourceRoot":"","sources":["../../../src/services/estimate/VelocityService.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"VelocityService.d.ts","sourceRoot":"","sources":["../../../src/services/estimate/VelocityService.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,KAAK,EAAE,iBAAiB,EAAkB,eAAe,EAAoB,MAAM,YAAY,CAAC;AAKvG,qBAAa,eAAe;IAExB,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,cAAc,CAAC;IAJzB,OAAO;WAOM,MAAM,CAAC,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,eAAe,CAAC;IAOvE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;mBAIP,wBAAwB;IAiB7C,OAAO,CAAC,aAAa;YAuCP,eAAe;IAgC7B,OAAO,CAAC,gBAAgB;YAuBV,iBAAiB;YA2BjB,yBAAyB;YAsHzB,+BAA+B;YAmD/B,mCAAmC;YAoEnC,mBAAmB;IAqBjC,OAAO,CAAC,qBAAqB;IAQvB,oBAAoB,CAAC,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,iBAAiB,CAAC;CAmFtF"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
2
|
import { randomUUID } from "node:crypto";
|
|
4
3
|
import { Connection } from "@mcoda/db";
|
|
5
4
|
import { PathHelper, READY_TO_CODE_REVIEW } from "@mcoda/shared";
|
|
@@ -22,7 +21,7 @@ export class VelocityService {
|
|
|
22
21
|
await this.connection.close();
|
|
23
22
|
}
|
|
24
23
|
static async readGlobalVelocityConfig() {
|
|
25
|
-
const configPath =
|
|
24
|
+
const configPath = PathHelper.getGlobalConfigPath();
|
|
26
25
|
try {
|
|
27
26
|
const raw = await fs.readFile(configPath, "utf8");
|
|
28
27
|
const parsed = JSON.parse(raw);
|