@openpalm/lib 0.9.5 → 0.9.7
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/package.json +1 -1
- package/src/control-plane/channels.ts +3 -0
- package/src/control-plane/connection-mapping.ts +2 -2
- package/src/control-plane/core-asset-provider.ts +1 -0
- package/src/control-plane/core-assets.ts +28 -0
- package/src/control-plane/docker.ts +2 -1
- package/src/control-plane/env.test.ts +109 -0
- package/src/control-plane/env.ts +2 -2
- package/src/control-plane/fs-asset-provider.ts +4 -0
- package/src/control-plane/install-edge-cases.test.ts +1214 -0
- package/src/control-plane/lifecycle.ts +11 -2
- package/src/control-plane/model-runner.ts +27 -2
- package/src/control-plane/setup-status.ts +1 -1
- package/src/control-plane/setup.test.ts +720 -1
- package/src/control-plane/setup.ts +597 -115
- package/src/control-plane/stack-spec.ts +64 -0
- package/src/control-plane/staging.ts +29 -6
- package/src/control-plane/types.ts +2 -3
- package/src/index.ts +30 -0
- package/src/provider-constants.ts +21 -8
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stack specification file (openpalm.yaml) management.
|
|
3
|
+
*
|
|
4
|
+
* The stack spec is a YAML document that captures the high-level
|
|
5
|
+
* configuration of an OpenPalm installation: connections, capability
|
|
6
|
+
* assignments, and feature flags. It lives in CONFIG_HOME.
|
|
7
|
+
*/
|
|
8
|
+
import { mkdirSync, writeFileSync, readFileSync, existsSync } from "node:fs";
|
|
9
|
+
import { stringify as yamlStringify, parse as yamlParse } from "yaml";
|
|
10
|
+
|
|
11
|
+
// ── Types ──────────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
export type StackSpecConnection = {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
provider: string;
|
|
17
|
+
baseUrl: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type StackSpecAssignments = {
|
|
21
|
+
llm: { connectionId: string; model: string; smallModel?: string };
|
|
22
|
+
embeddings: { connectionId: string; model: string; embeddingDims?: number };
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type StackSpec = {
|
|
26
|
+
version: 3;
|
|
27
|
+
connections: StackSpecConnection[];
|
|
28
|
+
assignments: StackSpecAssignments;
|
|
29
|
+
ollamaEnabled: boolean;
|
|
30
|
+
voice?: { tts?: string; stt?: string };
|
|
31
|
+
channels?: string[];
|
|
32
|
+
services?: Record<string, boolean>;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// ── Constants ──────────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
export const STACK_SPEC_FILENAME = "openpalm.yaml";
|
|
38
|
+
|
|
39
|
+
// ── Read / Write ────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
export function stackSpecPath(configDir: string): string {
|
|
42
|
+
return `${configDir}/${STACK_SPEC_FILENAME}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function writeStackSpec(configDir: string, spec: StackSpec): void {
|
|
46
|
+
mkdirSync(configDir, { recursive: true });
|
|
47
|
+
const content = yamlStringify(spec, { indent: 2 });
|
|
48
|
+
writeFileSync(stackSpecPath(configDir), content);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function readStackSpec(configDir: string): StackSpec | null {
|
|
52
|
+
const path = stackSpecPath(configDir);
|
|
53
|
+
if (!existsSync(path)) return null;
|
|
54
|
+
let raw: unknown;
|
|
55
|
+
try {
|
|
56
|
+
raw = yamlParse(readFileSync(path, "utf-8"));
|
|
57
|
+
} catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
if (typeof raw !== "object" || raw === null) return null;
|
|
61
|
+
const obj = raw as Record<string, unknown>;
|
|
62
|
+
if (obj.version !== 3) return null;
|
|
63
|
+
return obj as unknown as StackSpec;
|
|
64
|
+
}
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
readCoreCaddyfile,
|
|
21
21
|
readCoreCompose,
|
|
22
22
|
readOllamaCompose,
|
|
23
|
+
readAdminCompose,
|
|
23
24
|
ensureSecretsSchema,
|
|
24
25
|
ensureStackSchema,
|
|
25
26
|
PUBLIC_ACCESS_IMPORT,
|
|
@@ -53,9 +54,23 @@ export function isOllamaEnabled(state: ControlPlaneState): boolean {
|
|
|
53
54
|
return match?.[1]?.trim().toLowerCase() === "true";
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
// ── Admin State ──────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Check whether admin is enabled in the stack by reading the
|
|
61
|
+
* OPENPALM_ADMIN_ENABLED flag from DATA_HOME/stack.env.
|
|
62
|
+
*/
|
|
63
|
+
export function isAdminEnabled(state: ControlPlaneState): boolean {
|
|
64
|
+
const stackEnvPath = `${state.dataDir}/stack.env`;
|
|
65
|
+
if (!existsSync(stackEnvPath)) return false;
|
|
66
|
+
const content = readFileSync(stackEnvPath, "utf-8");
|
|
67
|
+
const match = content.match(/^OPENPALM_ADMIN_ENABLED=(.+)$/m);
|
|
68
|
+
return match?.[1]?.trim().toLowerCase() === "true";
|
|
69
|
+
}
|
|
70
|
+
|
|
56
71
|
// ── Caddyfile Staging ─────────────────────────────────────────────────
|
|
57
72
|
|
|
58
|
-
function withDefaultLanOnly(rawCaddy: string): string | null {
|
|
73
|
+
export function withDefaultLanOnly(rawCaddy: string): string | null {
|
|
59
74
|
if (rawCaddy.includes(PUBLIC_ACCESS_IMPORT) || rawCaddy.includes(LAN_ONLY_IMPORT)) {
|
|
60
75
|
return rawCaddy;
|
|
61
76
|
}
|
|
@@ -75,7 +90,7 @@ function withDefaultLanOnly(rawCaddy: string): string | null {
|
|
|
75
90
|
return null;
|
|
76
91
|
}
|
|
77
92
|
|
|
78
|
-
function stageChannelCaddyfiles(state: ControlPlaneState): void {
|
|
93
|
+
export function stageChannelCaddyfiles(state: ControlPlaneState): void {
|
|
79
94
|
const stagedChannelsDir = `${state.stateDir}/artifacts/channels`;
|
|
80
95
|
const stagedPublicDir = `${stagedChannelsDir}/public`;
|
|
81
96
|
const stagedLanDir = `${stagedChannelsDir}/lan`;
|
|
@@ -126,7 +141,7 @@ function stageCompose(_state: ControlPlaneState, assets: CoreAssetProvider): str
|
|
|
126
141
|
|
|
127
142
|
// ── Env Staging ───────────────────────────────────────────────────────
|
|
128
143
|
|
|
129
|
-
function stageSecretsEnv(state: ControlPlaneState): void {
|
|
144
|
+
export function stageSecretsEnv(state: ControlPlaneState): void {
|
|
130
145
|
const artifactDir = `${state.stateDir}/artifacts`;
|
|
131
146
|
mkdirSync(artifactDir, { recursive: true });
|
|
132
147
|
|
|
@@ -168,8 +183,12 @@ function stageStackEnv(state: ControlPlaneState): void {
|
|
|
168
183
|
writeFileSync(dataStackEnv, base);
|
|
169
184
|
}
|
|
170
185
|
|
|
186
|
+
// Preserve existing OPENPALM_SETUP_COMPLETE=true from stack.env;
|
|
187
|
+
// only mark complete if it was already true (not inferred from token presence).
|
|
188
|
+
const alreadyComplete = /^OPENPALM_SETUP_COMPLETE=true$/mi.test(base);
|
|
189
|
+
|
|
171
190
|
const adminManaged: Record<string, string> = {
|
|
172
|
-
OPENPALM_SETUP_COMPLETE:
|
|
191
|
+
OPENPALM_SETUP_COMPLETE: alreadyComplete ? "true" : "false"
|
|
173
192
|
};
|
|
174
193
|
for (const [ch, secret] of Object.entries(state.channelSecrets)) {
|
|
175
194
|
adminManaged[`CHANNEL_${ch.toUpperCase()}_SECRET`] = secret;
|
|
@@ -223,7 +242,7 @@ function generateFallbackStackEnv(state: ControlPlaneState): string {
|
|
|
223
242
|
|
|
224
243
|
// ── Channel YML Staging ───────────────────────────────────────────────
|
|
225
244
|
|
|
226
|
-
function stageChannelYmlFiles(state: ControlPlaneState): void {
|
|
245
|
+
export function stageChannelYmlFiles(state: ControlPlaneState): void {
|
|
227
246
|
const stagedChannelsDir = `${state.stateDir}/artifacts/channels`;
|
|
228
247
|
mkdirSync(stagedChannelsDir, { recursive: true });
|
|
229
248
|
|
|
@@ -268,7 +287,7 @@ function validateAutomationContent(content: string, fileName: string): boolean {
|
|
|
268
287
|
return parseAutomationYaml(content, fileName) !== null;
|
|
269
288
|
}
|
|
270
289
|
|
|
271
|
-
function stageAutomationFiles(state: ControlPlaneState): void {
|
|
290
|
+
export function stageAutomationFiles(state: ControlPlaneState): void {
|
|
272
291
|
const stagedDir = `${state.stateDir}/automations`;
|
|
273
292
|
mkdirSync(stagedDir, { recursive: true });
|
|
274
293
|
|
|
@@ -355,6 +374,10 @@ export function persistArtifacts(
|
|
|
355
374
|
writeFileSync(`${artifactDir}/ollama.yml`, readOllamaCompose(assets));
|
|
356
375
|
}
|
|
357
376
|
|
|
377
|
+
if (isAdminEnabled(state)) {
|
|
378
|
+
writeFileSync(`${artifactDir}/admin.yml`, readAdminCompose(assets));
|
|
379
|
+
}
|
|
380
|
+
|
|
358
381
|
const allChannels = discoverChannels(state.configDir);
|
|
359
382
|
for (const ch of allChannels) {
|
|
360
383
|
if (!state.channelSecrets[ch.name]) {
|
|
@@ -8,10 +8,9 @@ export type CoreServiceName =
|
|
|
8
8
|
| "assistant"
|
|
9
9
|
| "guardian"
|
|
10
10
|
| "memory"
|
|
11
|
-
| "caddy"
|
|
12
11
|
| "scheduler";
|
|
13
12
|
|
|
14
|
-
export type OptionalServiceName = "admin" | "docker-socket-proxy";
|
|
13
|
+
export type OptionalServiceName = "admin" | "caddy" | "docker-socket-proxy";
|
|
15
14
|
|
|
16
15
|
export type AccessScope = "host" | "lan";
|
|
17
16
|
export type CallerType = "assistant" | "cli" | "ui" | "system" | "test" | "unknown";
|
|
@@ -133,7 +132,6 @@ export type ControlPlaneState = {
|
|
|
133
132
|
// ── Constants ──────────────────────────────────────────────────────────
|
|
134
133
|
|
|
135
134
|
export const CORE_SERVICES: CoreServiceName[] = [
|
|
136
|
-
"caddy",
|
|
137
135
|
"memory",
|
|
138
136
|
"assistant",
|
|
139
137
|
"guardian",
|
|
@@ -141,6 +139,7 @@ export const CORE_SERVICES: CoreServiceName[] = [
|
|
|
141
139
|
];
|
|
142
140
|
|
|
143
141
|
export const OPTIONAL_SERVICES: OptionalServiceName[] = [
|
|
142
|
+
"caddy",
|
|
144
143
|
"admin",
|
|
145
144
|
"docker-socket-proxy",
|
|
146
145
|
];
|
package/src/index.ts
CHANGED
|
@@ -196,6 +196,8 @@ export {
|
|
|
196
196
|
readCoreCompose,
|
|
197
197
|
ensureOllamaCompose,
|
|
198
198
|
readOllamaCompose,
|
|
199
|
+
ensureAdminCompose,
|
|
200
|
+
readAdminCompose,
|
|
199
201
|
ensureOpenCodeSystemConfig,
|
|
200
202
|
ensureAdminOpenCodeConfig,
|
|
201
203
|
ensureCoreAutomations,
|
|
@@ -207,6 +209,7 @@ export {
|
|
|
207
209
|
sha256,
|
|
208
210
|
randomHex,
|
|
209
211
|
isOllamaEnabled,
|
|
212
|
+
isAdminEnabled,
|
|
210
213
|
stagedEnvFile,
|
|
211
214
|
stagedStackEnvFile,
|
|
212
215
|
buildEnvFiles,
|
|
@@ -214,6 +217,11 @@ export {
|
|
|
214
217
|
stageArtifacts,
|
|
215
218
|
buildArtifactMeta,
|
|
216
219
|
persistArtifacts,
|
|
220
|
+
withDefaultLanOnly,
|
|
221
|
+
stageChannelCaddyfiles,
|
|
222
|
+
stageChannelYmlFiles,
|
|
223
|
+
stageSecretsEnv,
|
|
224
|
+
stageAutomationFiles,
|
|
217
225
|
} from "./control-plane/staging.js";
|
|
218
226
|
|
|
219
227
|
// ── Lifecycle ───────────────────────────────────────────────────────────
|
|
@@ -278,6 +286,19 @@ export {
|
|
|
278
286
|
export type { LocalProviderDetection } from "./control-plane/model-runner.js";
|
|
279
287
|
export { detectLocalProviders } from "./control-plane/model-runner.js";
|
|
280
288
|
|
|
289
|
+
// ── Stack Spec ───────────────────────────────────────────────────────────
|
|
290
|
+
export type {
|
|
291
|
+
StackSpec,
|
|
292
|
+
StackSpecConnection,
|
|
293
|
+
StackSpecAssignments,
|
|
294
|
+
} from "./control-plane/stack-spec.js";
|
|
295
|
+
export {
|
|
296
|
+
STACK_SPEC_FILENAME,
|
|
297
|
+
stackSpecPath,
|
|
298
|
+
writeStackSpec,
|
|
299
|
+
readStackSpec,
|
|
300
|
+
} from "./control-plane/stack-spec.js";
|
|
301
|
+
|
|
281
302
|
// ── Setup ────────────────────────────────────────────────────────────────
|
|
282
303
|
export type {
|
|
283
304
|
SetupConnection,
|
|
@@ -285,6 +306,10 @@ export type {
|
|
|
285
306
|
SetupInput,
|
|
286
307
|
SetupResult,
|
|
287
308
|
DetectedProvider,
|
|
309
|
+
SetupConfig,
|
|
310
|
+
SetupConfigAssignments,
|
|
311
|
+
ChannelCredentials,
|
|
312
|
+
ServiceConfig,
|
|
288
313
|
} from "./control-plane/setup.js";
|
|
289
314
|
export {
|
|
290
315
|
validateSetupInput,
|
|
@@ -292,4 +317,9 @@ export {
|
|
|
292
317
|
buildConnectionEnvVarMap,
|
|
293
318
|
performSetup,
|
|
294
319
|
detectProviders,
|
|
320
|
+
CHANNEL_CREDENTIAL_ENV_MAP,
|
|
321
|
+
validateSetupConfig,
|
|
322
|
+
normalizeToSetupInput,
|
|
323
|
+
performSetupFromConfig,
|
|
324
|
+
buildChannelCredentialEnvVars,
|
|
295
325
|
} from "./control-plane/setup.js";
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
/** Supported LLM providers. */
|
|
9
9
|
export const LLM_PROVIDERS = [
|
|
10
10
|
"openai", "anthropic", "ollama", "groq", "together",
|
|
11
|
-
"mistral", "deepseek", "xai", "lmstudio", "model-runner"
|
|
11
|
+
"mistral", "deepseek", "xai", "lmstudio", "model-runner",
|
|
12
|
+
"google", "huggingface"
|
|
12
13
|
] as const;
|
|
13
14
|
|
|
14
15
|
/** Default base URLs per provider. */
|
|
@@ -22,6 +23,8 @@ export const PROVIDER_DEFAULT_URLS: Record<string, string> = {
|
|
|
22
23
|
lmstudio: "http://host.docker.internal:1234",
|
|
23
24
|
ollama: "http://host.docker.internal:11434",
|
|
24
25
|
"model-runner": "http://model-runner.docker.internal/engines",
|
|
26
|
+
google: "https://generativelanguage.googleapis.com",
|
|
27
|
+
huggingface: "https://router.huggingface.co/v1",
|
|
25
28
|
};
|
|
26
29
|
|
|
27
30
|
/** Map provider name → env var for the API key. */
|
|
@@ -31,6 +34,10 @@ export const PROVIDER_KEY_MAP: Record<string, string> = {
|
|
|
31
34
|
groq: "GROQ_API_KEY",
|
|
32
35
|
mistral: "MISTRAL_API_KEY",
|
|
33
36
|
google: "GOOGLE_API_KEY",
|
|
37
|
+
deepseek: "DEEPSEEK_API_KEY",
|
|
38
|
+
together: "TOGETHER_API_KEY",
|
|
39
|
+
xai: "XAI_API_KEY",
|
|
40
|
+
huggingface: "HF_TOKEN",
|
|
34
41
|
};
|
|
35
42
|
|
|
36
43
|
/** Known embedding model dimensions (cloud providers). */
|
|
@@ -42,6 +49,8 @@ export const EMBEDDING_DIMS: Record<string, number> = {
|
|
|
42
49
|
"ollama/mxbai-embed-large": 1024,
|
|
43
50
|
"ollama/all-minilm": 384,
|
|
44
51
|
"ollama/snowflake-arctic-embed": 1024,
|
|
52
|
+
"google/text-embedding-004": 768,
|
|
53
|
+
"huggingface/sentence-transformers/all-MiniLM-L6-v2": 384,
|
|
45
54
|
};
|
|
46
55
|
|
|
47
56
|
/** Provider display labels for UI. */
|
|
@@ -56,17 +65,21 @@ export const PROVIDER_LABELS: Record<string, string> = {
|
|
|
56
65
|
xai: "xAI (Grok)",
|
|
57
66
|
lmstudio: "LM Studio",
|
|
58
67
|
"model-runner": "Docker Model Runner",
|
|
68
|
+
google: "Google AI",
|
|
69
|
+
huggingface: "Hugging Face",
|
|
59
70
|
};
|
|
60
71
|
|
|
61
72
|
/**
|
|
62
|
-
* Map provider name →
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
73
|
+
* Map provider name → memory-package-compatible provider name.
|
|
74
|
+
* The memory package (@openpalm/memory) has native adapters for openai,
|
|
75
|
+
* ollama, and lmstudio. model-runner speaks OpenAI protocol so it maps
|
|
76
|
+
* to "openai". Ollama has its own adapter. LM Studio has its own adapter
|
|
77
|
+
* that avoids response_format (which LM Studio doesn't reliably support).
|
|
67
78
|
*/
|
|
68
79
|
export function mem0ProviderName(provider: string): string {
|
|
69
|
-
if (provider === "model-runner"
|
|
80
|
+
if (provider === "model-runner") return "openai";
|
|
81
|
+
if (provider === "ollama") return "ollama";
|
|
82
|
+
if (provider === "lmstudio") return "lmstudio";
|
|
70
83
|
return provider;
|
|
71
84
|
}
|
|
72
85
|
|
|
@@ -82,7 +95,7 @@ export function mem0BaseUrlConfig(
|
|
|
82
95
|
const trimmed = baseUrl.trim();
|
|
83
96
|
if (!trimmed) return null;
|
|
84
97
|
|
|
85
|
-
const normalized = trimmed.replace(/\/+$/, "");
|
|
98
|
+
const normalized = trimmed.replace(/\/+$/, "").replace(/\/v1$/, "");
|
|
86
99
|
return { key: "openai_base_url", value: `${normalized}/v1` };
|
|
87
100
|
}
|
|
88
101
|
|