@openpalm/lib 0.9.4
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/README.md +83 -0
- package/package.json +30 -0
- package/src/control-plane/audit.ts +40 -0
- package/src/control-plane/channels.ts +196 -0
- package/src/control-plane/connection-mapping.ts +191 -0
- package/src/control-plane/connection-migration-flags.ts +40 -0
- package/src/control-plane/connection-profiles.ts +317 -0
- package/src/control-plane/core-asset-provider.ts +20 -0
- package/src/control-plane/core-assets.ts +292 -0
- package/src/control-plane/docker.ts +448 -0
- package/src/control-plane/env.ts +70 -0
- package/src/control-plane/fs-asset-provider.ts +61 -0
- package/src/control-plane/fs-registry-provider.ts +46 -0
- package/src/control-plane/lifecycle.ts +373 -0
- package/src/control-plane/memory-config.ts +424 -0
- package/src/control-plane/model-runner.ts +101 -0
- package/src/control-plane/paths.ts +77 -0
- package/src/control-plane/registry-provider.ts +19 -0
- package/src/control-plane/scheduler.ts +498 -0
- package/src/control-plane/secrets.ts +177 -0
- package/src/control-plane/setup-status.ts +31 -0
- package/src/control-plane/setup.test.ts +476 -0
- package/src/control-plane/setup.ts +474 -0
- package/src/control-plane/staging.ts +376 -0
- package/src/control-plane/types.ts +165 -0
- package/src/index.ts +295 -0
- package/src/logger.ts +14 -0
- package/src/provider-constants.ts +106 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Artifact staging pipeline for the OpenPalm control plane.
|
|
3
|
+
*
|
|
4
|
+
* Stages artifacts from CONFIG_HOME/DATA_HOME into STATE_HOME:
|
|
5
|
+
* Caddyfile/compose staging, env staging, channel/automation file staging,
|
|
6
|
+
* and artifact persistence.
|
|
7
|
+
*
|
|
8
|
+
* All asset content is provided by a CoreAssetProvider (injected).
|
|
9
|
+
*/
|
|
10
|
+
import { mkdirSync, writeFileSync, readFileSync, existsSync, readdirSync, rmSync, copyFileSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
13
|
+
import { mergeEnvContent } from './env.js';
|
|
14
|
+
import type { ControlPlaneState, ArtifactMeta } from "./types.js";
|
|
15
|
+
import { discoverChannels } from "./channels.js";
|
|
16
|
+
import { appendAudit } from "./audit.js";
|
|
17
|
+
import { parseAutomationYaml } from "./scheduler.js";
|
|
18
|
+
import type { CoreAssetProvider } from "./core-asset-provider.js";
|
|
19
|
+
import {
|
|
20
|
+
readCoreCaddyfile,
|
|
21
|
+
readCoreCompose,
|
|
22
|
+
readOllamaCompose,
|
|
23
|
+
ensureSecretsSchema,
|
|
24
|
+
ensureStackSchema,
|
|
25
|
+
PUBLIC_ACCESS_IMPORT,
|
|
26
|
+
LAN_ONLY_IMPORT
|
|
27
|
+
} from "./core-assets.js";
|
|
28
|
+
|
|
29
|
+
const DEFAULT_IMAGE_TAG = process.env.OPENPALM_IMAGE_TAG ?? "latest";
|
|
30
|
+
|
|
31
|
+
// ── Crypto Utilities ──────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
export function sha256(content: string): string {
|
|
34
|
+
return createHash("sha256").update(content).digest("hex");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Generate a hex string using Node's crypto.randomBytes (CSPRNG). */
|
|
38
|
+
export function randomHex(bytes: number): string {
|
|
39
|
+
return randomBytes(bytes).toString("hex");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ── Ollama State ─────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Check whether Ollama is enabled in the stack by reading the
|
|
46
|
+
* OPENPALM_OLLAMA_ENABLED flag from DATA_HOME/stack.env.
|
|
47
|
+
*/
|
|
48
|
+
export function isOllamaEnabled(state: ControlPlaneState): boolean {
|
|
49
|
+
const stackEnvPath = `${state.dataDir}/stack.env`;
|
|
50
|
+
if (!existsSync(stackEnvPath)) return false;
|
|
51
|
+
const content = readFileSync(stackEnvPath, "utf-8");
|
|
52
|
+
const match = content.match(/^OPENPALM_OLLAMA_ENABLED=(.+)$/m);
|
|
53
|
+
return match?.[1]?.trim().toLowerCase() === "true";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ── Caddyfile Staging ─────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
function withDefaultLanOnly(rawCaddy: string): string | null {
|
|
59
|
+
if (rawCaddy.includes(PUBLIC_ACCESS_IMPORT) || rawCaddy.includes(LAN_ONLY_IMPORT)) {
|
|
60
|
+
return rawCaddy;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const blockStarts = [
|
|
64
|
+
/(handle_path\s+[^\n{]+\{\s*\n?)/,
|
|
65
|
+
/(handle\s+[^\n{]+\{\s*\n?)/,
|
|
66
|
+
/(route\s+[^\n{]+\{\s*\n?)/
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
for (const pattern of blockStarts) {
|
|
70
|
+
if (pattern.test(rawCaddy)) {
|
|
71
|
+
return rawCaddy.replace(pattern, "$1\timport lan_only\n");
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function stageChannelCaddyfiles(state: ControlPlaneState): void {
|
|
79
|
+
const stagedChannelsDir = `${state.stateDir}/artifacts/channels`;
|
|
80
|
+
const stagedPublicDir = `${stagedChannelsDir}/public`;
|
|
81
|
+
const stagedLanDir = `${stagedChannelsDir}/lan`;
|
|
82
|
+
rmSync(stagedPublicDir, { recursive: true, force: true });
|
|
83
|
+
rmSync(stagedLanDir, { recursive: true, force: true });
|
|
84
|
+
mkdirSync(stagedPublicDir, { recursive: true });
|
|
85
|
+
mkdirSync(stagedLanDir, { recursive: true });
|
|
86
|
+
|
|
87
|
+
const channels = discoverChannels(state.configDir);
|
|
88
|
+
for (const ch of channels) {
|
|
89
|
+
if (!ch.caddyPath) continue;
|
|
90
|
+
|
|
91
|
+
const raw = readFileSync(ch.caddyPath, "utf-8");
|
|
92
|
+
if (raw.includes(PUBLIC_ACCESS_IMPORT)) {
|
|
93
|
+
writeFileSync(`${stagedPublicDir}/${ch.name}.caddy`, raw);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const lanScoped = withDefaultLanOnly(raw);
|
|
98
|
+
if (!lanScoped) {
|
|
99
|
+
appendAudit(
|
|
100
|
+
state,
|
|
101
|
+
"system",
|
|
102
|
+
"channels.route.skip",
|
|
103
|
+
{
|
|
104
|
+
channel: ch.name,
|
|
105
|
+
reason: "Unable to infer route block for default LAN scoping"
|
|
106
|
+
},
|
|
107
|
+
false,
|
|
108
|
+
"",
|
|
109
|
+
"system"
|
|
110
|
+
);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
writeFileSync(`${stagedLanDir}/${ch.name}.caddy`, lanScoped);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function stageCaddyfile(_state: ControlPlaneState, assets: CoreAssetProvider): string {
|
|
118
|
+
return readCoreCaddyfile(assets);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ── Compose Staging ───────────────────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
function stageCompose(_state: ControlPlaneState, assets: CoreAssetProvider): string {
|
|
124
|
+
return readCoreCompose(assets);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ── Env Staging ───────────────────────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
function stageSecretsEnv(state: ControlPlaneState): void {
|
|
130
|
+
const artifactDir = `${state.stateDir}/artifacts`;
|
|
131
|
+
mkdirSync(artifactDir, { recursive: true });
|
|
132
|
+
|
|
133
|
+
const source = `${state.configDir}/secrets.env`;
|
|
134
|
+
const content = existsSync(source) ? readFileSync(source, "utf-8") : "";
|
|
135
|
+
writeFileSync(`${artifactDir}/secrets.env`, content);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Return the path to the staged secrets.env in STATE_HOME. */
|
|
139
|
+
export function stagedEnvFile(state: ControlPlaneState): string {
|
|
140
|
+
return `${state.stateDir}/artifacts/secrets.env`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Return the path to the staged stack.env in STATE_HOME. */
|
|
144
|
+
export function stagedStackEnvFile(state: ControlPlaneState): string {
|
|
145
|
+
return `${state.stateDir}/artifacts/stack.env`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Return both staged env files in load order: [stack.env, secrets.env].
|
|
150
|
+
* Non-existent files are omitted so docker compose does not error.
|
|
151
|
+
*/
|
|
152
|
+
export function buildEnvFiles(state: ControlPlaneState): string[] {
|
|
153
|
+
return [stagedStackEnvFile(state), stagedEnvFile(state)].filter(existsSync);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function stageStackEnv(state: ControlPlaneState): void {
|
|
157
|
+
const artifactDir = `${state.stateDir}/artifacts`;
|
|
158
|
+
mkdirSync(artifactDir, { recursive: true });
|
|
159
|
+
|
|
160
|
+
const dataStackEnv = `${state.dataDir}/stack.env`;
|
|
161
|
+
|
|
162
|
+
let base = "";
|
|
163
|
+
if (existsSync(dataStackEnv)) {
|
|
164
|
+
base = readFileSync(dataStackEnv, "utf-8");
|
|
165
|
+
} else {
|
|
166
|
+
base = generateFallbackStackEnv(state);
|
|
167
|
+
mkdirSync(state.dataDir, { recursive: true });
|
|
168
|
+
writeFileSync(dataStackEnv, base);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const adminManaged: Record<string, string> = {
|
|
172
|
+
OPENPALM_SETUP_COMPLETE: state.adminToken ? "true" : "false"
|
|
173
|
+
};
|
|
174
|
+
for (const [ch, secret] of Object.entries(state.channelSecrets)) {
|
|
175
|
+
adminManaged[`CHANNEL_${ch.toUpperCase()}_SECRET`] = secret;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const content = mergeEnvContent(base, adminManaged, {
|
|
179
|
+
sectionHeader: "# ── Admin-managed ──────────────────────────────────────────────────"
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
writeFileSync(dataStackEnv, content);
|
|
183
|
+
writeFileSync(`${artifactDir}/stack.env`, content);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function generateFallbackStackEnv(state: ControlPlaneState): string {
|
|
187
|
+
const uid = typeof process.getuid === "function" ? (process.getuid() ?? 1000) : 1000;
|
|
188
|
+
const gid = typeof process.getgid === "function" ? (process.getgid() ?? 1000) : 1000;
|
|
189
|
+
|
|
190
|
+
const home = process.env.HOME ?? "/home/node";
|
|
191
|
+
const workDir = process.env.OPENPALM_WORK_DIR ?? `${home}/openpalm`;
|
|
192
|
+
|
|
193
|
+
return [
|
|
194
|
+
"# OpenPalm Stack Configuration — system-managed, do not edit",
|
|
195
|
+
"# Auto-generated fallback (setup.sh has not run yet).",
|
|
196
|
+
"",
|
|
197
|
+
"# ── XDG Paths ──────────────────────────────────────────────────────",
|
|
198
|
+
`OPENPALM_CONFIG_HOME=${state.configDir}`,
|
|
199
|
+
`OPENPALM_DATA_HOME=${state.dataDir}`,
|
|
200
|
+
`OPENPALM_STATE_HOME=${state.stateDir}`,
|
|
201
|
+
`OPENPALM_WORK_DIR=${workDir}`,
|
|
202
|
+
"",
|
|
203
|
+
"# ── User/Group ──────────────────────────────────────────────────────",
|
|
204
|
+
`OPENPALM_UID=${uid}`,
|
|
205
|
+
`OPENPALM_GID=${gid}`,
|
|
206
|
+
"",
|
|
207
|
+
"# ── Docker Socket ───────────────────────────────────────────────────",
|
|
208
|
+
`OPENPALM_DOCKER_SOCK=${process.env.OPENPALM_DOCKER_SOCK ?? "/var/run/docker.sock"}`,
|
|
209
|
+
"",
|
|
210
|
+
"# ── Images ──────────────────────────────────────────────────────────",
|
|
211
|
+
`OPENPALM_IMAGE_NAMESPACE=${process.env.OPENPALM_IMAGE_NAMESPACE ?? "openpalm"}`,
|
|
212
|
+
`OPENPALM_IMAGE_TAG=${DEFAULT_IMAGE_TAG}`,
|
|
213
|
+
"",
|
|
214
|
+
"# ── Networking ──────────────────────────────────────────────────────",
|
|
215
|
+
"# SECURITY: Bind addresses default to 127.0.0.1. Changing to 0.0.0.0 exposes services publicly.",
|
|
216
|
+
`OPENPALM_INGRESS_BIND_ADDRESS=${process.env.OPENPALM_INGRESS_BIND_ADDRESS ?? "127.0.0.1"}`,
|
|
217
|
+
`OPENPALM_INGRESS_PORT=${process.env.OPENPALM_INGRESS_PORT ?? "8080"}`,
|
|
218
|
+
"",
|
|
219
|
+
"# ── Channel HMAC Secrets ────────────────────────────────────────────",
|
|
220
|
+
""
|
|
221
|
+
].join("\n");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ── Channel YML Staging ───────────────────────────────────────────────
|
|
225
|
+
|
|
226
|
+
function stageChannelYmlFiles(state: ControlPlaneState): void {
|
|
227
|
+
const stagedChannelsDir = `${state.stateDir}/artifacts/channels`;
|
|
228
|
+
mkdirSync(stagedChannelsDir, { recursive: true });
|
|
229
|
+
|
|
230
|
+
for (const f of readdirSync(stagedChannelsDir)) {
|
|
231
|
+
if (f.endsWith(".yml")) {
|
|
232
|
+
rmSync(`${stagedChannelsDir}/${f}`, { force: true });
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const channels = discoverChannels(state.configDir);
|
|
237
|
+
for (const ch of channels) {
|
|
238
|
+
const content = readFileSync(ch.ymlPath, "utf-8");
|
|
239
|
+
writeFileSync(`${stagedChannelsDir}/${ch.name}.yml`, content);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Discover staged channel .yml overlays from STATE_HOME/artifacts/channels/.
|
|
245
|
+
*/
|
|
246
|
+
export function discoverStagedChannelYmls(stateDir: string): string[] {
|
|
247
|
+
const channelsDir = `${stateDir}/artifacts/channels`;
|
|
248
|
+
if (!existsSync(channelsDir)) return [];
|
|
249
|
+
|
|
250
|
+
return readdirSync(channelsDir, { withFileTypes: true })
|
|
251
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".yml"))
|
|
252
|
+
.map((entry) => `${channelsDir}/${entry.name}`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ── Automation Staging ───────────────────────────────────────────────
|
|
256
|
+
|
|
257
|
+
const AUTOMATION_FILE_NAME_RE = /^[a-z0-9][a-z0-9-]{0,62}\.yml$/;
|
|
258
|
+
|
|
259
|
+
function discoverAutomationFiles(dir: string): { name: string; path: string }[] {
|
|
260
|
+
if (!existsSync(dir)) return [];
|
|
261
|
+
return readdirSync(dir, { withFileTypes: true })
|
|
262
|
+
.filter((entry) => entry.isFile() && !entry.name.startsWith("."))
|
|
263
|
+
.map((entry) => ({ name: entry.name, path: join(dir, entry.name) }))
|
|
264
|
+
.filter((entry) => AUTOMATION_FILE_NAME_RE.test(entry.name));
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function validateAutomationContent(content: string, fileName: string): boolean {
|
|
268
|
+
return parseAutomationYaml(content, fileName) !== null;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function stageAutomationFiles(state: ControlPlaneState): void {
|
|
272
|
+
const stagedDir = `${state.stateDir}/automations`;
|
|
273
|
+
mkdirSync(stagedDir, { recursive: true });
|
|
274
|
+
|
|
275
|
+
for (const f of readdirSync(stagedDir)) {
|
|
276
|
+
const fullPath = `${stagedDir}/${f}`;
|
|
277
|
+
if (!f.startsWith(".")) {
|
|
278
|
+
rmSync(fullPath, { force: true });
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const systemDir = `${state.dataDir}/automations`;
|
|
283
|
+
for (const entry of discoverAutomationFiles(systemDir)) {
|
|
284
|
+
const content = readFileSync(entry.path, "utf-8");
|
|
285
|
+
if (!validateAutomationContent(content, entry.name)) continue;
|
|
286
|
+
writeFileSync(`${stagedDir}/${entry.name}`, content);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const userDir = `${state.configDir}/automations`;
|
|
290
|
+
for (const entry of discoverAutomationFiles(userDir)) {
|
|
291
|
+
const content = readFileSync(entry.path, "utf-8");
|
|
292
|
+
if (!validateAutomationContent(content, entry.name)) continue;
|
|
293
|
+
writeFileSync(`${stagedDir}/${entry.name}`, content);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ── Env Schema Staging ────────────────────────────────────────────────
|
|
298
|
+
|
|
299
|
+
function stageEnvSchemas(state: ControlPlaneState, assets: CoreAssetProvider): void {
|
|
300
|
+
const destDir = `${state.dataDir}/assistant/env-schema`;
|
|
301
|
+
mkdirSync(destDir, { recursive: true });
|
|
302
|
+
|
|
303
|
+
const secretsSchemaPath = ensureSecretsSchema(assets);
|
|
304
|
+
const stackSchemaPath = ensureStackSchema(assets);
|
|
305
|
+
|
|
306
|
+
copyFileSync(secretsSchemaPath, `${destDir}/secrets.env.schema`);
|
|
307
|
+
copyFileSync(stackSchemaPath, `${destDir}/stack.env.schema`);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// ── Top-Level Staging ─────────────────────────────────────────────────
|
|
311
|
+
|
|
312
|
+
export function stageArtifacts(
|
|
313
|
+
state: ControlPlaneState,
|
|
314
|
+
assets: CoreAssetProvider
|
|
315
|
+
): {
|
|
316
|
+
compose: string;
|
|
317
|
+
caddyfile: string;
|
|
318
|
+
} {
|
|
319
|
+
return {
|
|
320
|
+
compose: stageCompose(state, assets),
|
|
321
|
+
caddyfile: stageCaddyfile(state, assets)
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// ── Artifact Metadata ──────────────────────────────────────────────────
|
|
326
|
+
|
|
327
|
+
export function buildArtifactMeta(artifacts: {
|
|
328
|
+
compose: string;
|
|
329
|
+
caddyfile: string;
|
|
330
|
+
}): ArtifactMeta[] {
|
|
331
|
+
const now = new Date().toISOString();
|
|
332
|
+
return (["compose", "caddyfile"] as const).map((name) => ({
|
|
333
|
+
name,
|
|
334
|
+
sha256: sha256(artifacts[name]),
|
|
335
|
+
generatedAt: now,
|
|
336
|
+
bytes: Buffer.byteLength(artifacts[name])
|
|
337
|
+
}));
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// ── Persistence ────────────────────────────────────────────────────────
|
|
341
|
+
|
|
342
|
+
export function persistArtifacts(
|
|
343
|
+
state: ControlPlaneState,
|
|
344
|
+
assets: CoreAssetProvider
|
|
345
|
+
): void {
|
|
346
|
+
const artifactDir = `${state.stateDir}/artifacts`;
|
|
347
|
+
const channelsDir = `${state.configDir}/channels`;
|
|
348
|
+
mkdirSync(artifactDir, { recursive: true });
|
|
349
|
+
mkdirSync(channelsDir, { recursive: true });
|
|
350
|
+
|
|
351
|
+
writeFileSync(`${artifactDir}/docker-compose.yml`, state.artifacts.compose);
|
|
352
|
+
writeFileSync(`${artifactDir}/Caddyfile`, state.artifacts.caddyfile);
|
|
353
|
+
|
|
354
|
+
if (isOllamaEnabled(state)) {
|
|
355
|
+
writeFileSync(`${artifactDir}/ollama.yml`, readOllamaCompose(assets));
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const allChannels = discoverChannels(state.configDir);
|
|
359
|
+
for (const ch of allChannels) {
|
|
360
|
+
if (!state.channelSecrets[ch.name]) {
|
|
361
|
+
state.channelSecrets[ch.name] = randomHex(16);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
stageStackEnv(state);
|
|
365
|
+
stageSecretsEnv(state);
|
|
366
|
+
stageChannelYmlFiles(state);
|
|
367
|
+
stageChannelCaddyfiles(state);
|
|
368
|
+
stageAutomationFiles(state);
|
|
369
|
+
stageEnvSchemas(state, assets);
|
|
370
|
+
|
|
371
|
+
state.artifactMeta = buildArtifactMeta(state.artifacts);
|
|
372
|
+
writeFileSync(
|
|
373
|
+
`${artifactDir}/manifest.json`,
|
|
374
|
+
JSON.stringify(state.artifactMeta, null, 2)
|
|
375
|
+
);
|
|
376
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types and constants for the OpenPalm control plane.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// ── Types ──────────────────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
export type CoreServiceName =
|
|
8
|
+
| "assistant"
|
|
9
|
+
| "guardian"
|
|
10
|
+
| "memory"
|
|
11
|
+
| "caddy"
|
|
12
|
+
| "scheduler";
|
|
13
|
+
|
|
14
|
+
export type OptionalServiceName = "admin" | "docker-socket-proxy";
|
|
15
|
+
|
|
16
|
+
export type AccessScope = "host" | "lan";
|
|
17
|
+
export type CallerType = "assistant" | "cli" | "ui" | "system" | "test" | "unknown";
|
|
18
|
+
|
|
19
|
+
export type ConnectionKind =
|
|
20
|
+
| "openai_compatible_remote"
|
|
21
|
+
| "openai_compatible_local"
|
|
22
|
+
| "ollama_local";
|
|
23
|
+
|
|
24
|
+
export type ConnectionAuthMode = "api_key" | "none";
|
|
25
|
+
|
|
26
|
+
export type CanonicalConnectionProfile = {
|
|
27
|
+
id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
kind: ConnectionKind;
|
|
30
|
+
provider: string;
|
|
31
|
+
baseUrl: string;
|
|
32
|
+
auth: {
|
|
33
|
+
mode: ConnectionAuthMode;
|
|
34
|
+
apiKeySecretRef?: string;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type RequiredCapability = "llm" | "embeddings";
|
|
39
|
+
export type OptionalCapability = "reranking" | "tts" | "stt";
|
|
40
|
+
export type Capability = RequiredCapability | OptionalCapability;
|
|
41
|
+
|
|
42
|
+
export type LlmAssignment = {
|
|
43
|
+
connectionId: string;
|
|
44
|
+
model: string;
|
|
45
|
+
smallModel?: string;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export type EmbeddingsAssignment = {
|
|
49
|
+
connectionId: string;
|
|
50
|
+
model: string;
|
|
51
|
+
embeddingDims?: number;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export type RerankerAssignment = {
|
|
55
|
+
enabled: boolean;
|
|
56
|
+
connectionId?: string;
|
|
57
|
+
mode?: "llm" | "dedicated";
|
|
58
|
+
model?: string;
|
|
59
|
+
topK?: number;
|
|
60
|
+
topN?: number;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export type TtsAssignment = {
|
|
64
|
+
enabled: boolean;
|
|
65
|
+
connectionId?: string;
|
|
66
|
+
model?: string;
|
|
67
|
+
voice?: string;
|
|
68
|
+
format?: string;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export type SttAssignment = {
|
|
72
|
+
enabled: boolean;
|
|
73
|
+
connectionId?: string;
|
|
74
|
+
model?: string;
|
|
75
|
+
language?: string;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export type CapabilityAssignments = {
|
|
79
|
+
llm: LlmAssignment;
|
|
80
|
+
embeddings: EmbeddingsAssignment;
|
|
81
|
+
reranking?: RerankerAssignment;
|
|
82
|
+
tts?: TtsAssignment;
|
|
83
|
+
stt?: SttAssignment;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export type CanonicalConnectionsDocument = {
|
|
87
|
+
version: 1;
|
|
88
|
+
profiles: CanonicalConnectionProfile[];
|
|
89
|
+
assignments: CapabilityAssignments;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/** Info about a discovered channel */
|
|
93
|
+
export type ChannelInfo = {
|
|
94
|
+
name: string;
|
|
95
|
+
hasRoute: boolean;
|
|
96
|
+
ymlPath: string;
|
|
97
|
+
caddyPath: string | null;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export type AuditEntry = {
|
|
101
|
+
at: string;
|
|
102
|
+
requestId: string;
|
|
103
|
+
actor: string;
|
|
104
|
+
callerType: CallerType;
|
|
105
|
+
action: string;
|
|
106
|
+
args: Record<string, unknown>;
|
|
107
|
+
ok: boolean;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export type ArtifactMeta = {
|
|
111
|
+
name: string;
|
|
112
|
+
sha256: string;
|
|
113
|
+
generatedAt: string;
|
|
114
|
+
bytes: number;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export type ControlPlaneState = {
|
|
118
|
+
adminToken: string;
|
|
119
|
+
setupToken: string;
|
|
120
|
+
stateDir: string;
|
|
121
|
+
configDir: string;
|
|
122
|
+
dataDir: string;
|
|
123
|
+
services: Record<string, "running" | "stopped">;
|
|
124
|
+
artifacts: {
|
|
125
|
+
compose: string;
|
|
126
|
+
caddyfile: string;
|
|
127
|
+
};
|
|
128
|
+
artifactMeta: ArtifactMeta[];
|
|
129
|
+
audit: AuditEntry[];
|
|
130
|
+
channelSecrets: Record<string, string>;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// ── Constants ──────────────────────────────────────────────────────────
|
|
134
|
+
|
|
135
|
+
export const CORE_SERVICES: CoreServiceName[] = [
|
|
136
|
+
"caddy",
|
|
137
|
+
"memory",
|
|
138
|
+
"assistant",
|
|
139
|
+
"guardian",
|
|
140
|
+
"scheduler",
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
export const OPTIONAL_SERVICES: OptionalServiceName[] = [
|
|
144
|
+
"admin",
|
|
145
|
+
"docker-socket-proxy",
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
export const CONNECTION_KINDS: ConnectionKind[] = [
|
|
149
|
+
"openai_compatible_remote",
|
|
150
|
+
"openai_compatible_local",
|
|
151
|
+
"ollama_local",
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
export const REQUIRED_CAPABILITIES: RequiredCapability[] = [
|
|
155
|
+
"llm",
|
|
156
|
+
"embeddings",
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
export const OPTIONAL_CAPABILITIES: OptionalCapability[] = [
|
|
160
|
+
"reranking",
|
|
161
|
+
"tts",
|
|
162
|
+
"stt",
|
|
163
|
+
];
|
|
164
|
+
|
|
165
|
+
export const MAX_AUDIT_MEMORY = 1000;
|