@vxnus/siduri 0.1.8 → 0.1.10
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/builtin-manifests.d.ts +2 -0
- package/dist/builtin-manifests.js +492 -0
- package/dist/clean-machine-e2e.test.d.ts +1 -0
- package/dist/clean-machine-e2e.test.js +236 -0
- package/dist/configurators/behavior.d.ts +2 -0
- package/dist/configurators/behavior.js +36 -0
- package/dist/configurators/body.d.ts +2 -0
- package/dist/configurators/body.js +60 -0
- package/dist/configurators/brain.d.ts +6 -0
- package/dist/configurators/brain.js +80 -0
- package/dist/configurators/ear.d.ts +2 -0
- package/dist/configurators/ear.js +28 -0
- package/dist/configurators/hands.d.ts +2 -0
- package/dist/configurators/hands.js +27 -0
- package/dist/configurators/index.d.ts +24 -0
- package/dist/configurators/index.js +78 -0
- package/dist/configurators/knowledge.d.ts +6 -0
- package/dist/configurators/knowledge.js +100 -0
- package/dist/configurators/memory.d.ts +2 -0
- package/dist/configurators/memory.js +44 -0
- package/dist/configurators/mouth.d.ts +2 -0
- package/dist/configurators/mouth.js +15 -0
- package/dist/configurators/observation.d.ts +2 -0
- package/dist/configurators/observation.js +12 -0
- package/dist/configurators/types.d.ts +12 -0
- package/dist/configurators/types.js +2 -0
- package/dist/configurators/vision.d.ts +2 -0
- package/dist/configurators/vision.js +39 -0
- package/dist/configurators/voice.d.ts +2 -0
- package/dist/configurators/voice.js +477 -0
- package/dist/configurators.test.d.ts +1 -0
- package/dist/configurators.test.js +331 -0
- package/dist/db.d.ts +25 -0
- package/dist/db.js +152 -0
- package/dist/discovery.d.ts +13 -0
- package/dist/discovery.js +87 -0
- package/dist/discovery.test.d.ts +1 -0
- package/dist/discovery.test.js +69 -0
- package/dist/doctor-db.test.d.ts +1 -0
- package/dist/doctor-db.test.js +141 -0
- package/dist/doctor.d.ts +19 -0
- package/dist/doctor.js +225 -0
- package/dist/generator.d.ts +21 -0
- package/dist/generator.js +666 -0
- package/dist/generator.test.d.ts +1 -0
- package/dist/generator.test.js +197 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +401 -0
- package/dist/manifest.d.ts +33 -0
- package/dist/manifest.js +40 -0
- package/dist/providers/knowledge-hub.d.ts +37 -0
- package/dist/providers/knowledge-hub.js +141 -0
- package/dist/providers/openrouter.d.ts +32 -0
- package/dist/providers/openrouter.js +201 -0
- package/dist/release-check.d.ts +9 -0
- package/dist/release-check.js +133 -0
- package/dist/runtime-parity.test.d.ts +1 -0
- package/dist/runtime-parity.test.js +140 -0
- package/dist/web-template.d.ts +2 -0
- package/dist/web-template.js +557 -0
- package/package.json +1 -1
|
@@ -0,0 +1,666 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateInstanceFiles = generateInstanceFiles;
|
|
4
|
+
const web_template_1 = require("./web-template");
|
|
5
|
+
function getDefaultConfigForManifest(manifest) {
|
|
6
|
+
const schema = manifest.configSchema || {};
|
|
7
|
+
const props = schema.properties || {};
|
|
8
|
+
const config = {};
|
|
9
|
+
for (const [key, val] of Object.entries(props)) {
|
|
10
|
+
if (val.default !== undefined) {
|
|
11
|
+
config[key] = val.default;
|
|
12
|
+
}
|
|
13
|
+
else if (val.enum && val.enum.length > 0) {
|
|
14
|
+
config[key] = val.enum[0];
|
|
15
|
+
}
|
|
16
|
+
else if (val.type === 'string') {
|
|
17
|
+
config[key] = '';
|
|
18
|
+
}
|
|
19
|
+
else if (val.type === 'number') {
|
|
20
|
+
config[key] = 0;
|
|
21
|
+
}
|
|
22
|
+
else if (val.type === 'boolean') {
|
|
23
|
+
config[key] = false;
|
|
24
|
+
}
|
|
25
|
+
else if (val.type === 'array') {
|
|
26
|
+
config[key] = [];
|
|
27
|
+
}
|
|
28
|
+
else if (val.type === 'object') {
|
|
29
|
+
config[key] = {};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// Provide sensible defaults for known common keys if missing
|
|
33
|
+
if (manifest.organType === 'brain') {
|
|
34
|
+
config.provider = config.provider || 'openrouter';
|
|
35
|
+
config.model = config.model || 'anthropic/claude-3.5-sonnet';
|
|
36
|
+
config.apiKeyEnv = 'OPENROUTER_API_KEY';
|
|
37
|
+
}
|
|
38
|
+
else if (manifest.organType === 'memory') {
|
|
39
|
+
config.provider = config.provider || 'postgres';
|
|
40
|
+
}
|
|
41
|
+
else if (manifest.organType === 'voice') {
|
|
42
|
+
config.provider = config.provider || 'voicevox';
|
|
43
|
+
config.speakerId = config.speakerId || 1;
|
|
44
|
+
config.baseUrl = config.baseUrl || 'http://localhost:50021';
|
|
45
|
+
}
|
|
46
|
+
else if (manifest.organType === 'body') {
|
|
47
|
+
config.provider = config.provider || 'live2d';
|
|
48
|
+
config.initialExpression = config.initialExpression || 'neutral';
|
|
49
|
+
config.modelPath = config.modelPath || './assets/body/default/model.model3.json';
|
|
50
|
+
config.modelUrl = config.modelUrl || '/assets/body/default/model.model3.json';
|
|
51
|
+
}
|
|
52
|
+
else if (manifest.organType === 'hands') {
|
|
53
|
+
config.defaultTimeoutMs = config.defaultTimeoutMs || 10000;
|
|
54
|
+
config.providers = config.providers || [];
|
|
55
|
+
}
|
|
56
|
+
else if (manifest.organType === 'knowledge') {
|
|
57
|
+
config.provider = config.provider || 'none';
|
|
58
|
+
}
|
|
59
|
+
else if (manifest.organType === 'behavior') {
|
|
60
|
+
config.provider = config.provider || 'active_self';
|
|
61
|
+
}
|
|
62
|
+
else if (manifest.organType === 'vision') {
|
|
63
|
+
config.provider = config.provider || 'openrouter';
|
|
64
|
+
config.model = config.model || 'gpt-4-vision';
|
|
65
|
+
}
|
|
66
|
+
else if (manifest.organType === 'mouth') {
|
|
67
|
+
config.defaultMedium = 'web';
|
|
68
|
+
config.maxTextLength = config.maxTextLength || 8000;
|
|
69
|
+
}
|
|
70
|
+
return config;
|
|
71
|
+
}
|
|
72
|
+
function generateInstanceFiles(options) {
|
|
73
|
+
const instanceName = options.name || 'my-siduri';
|
|
74
|
+
const instanceId = options.id || 'default';
|
|
75
|
+
const coreVersion = options.coreVersion || '^1.0.8';
|
|
76
|
+
const manifests = options.selectedManifests;
|
|
77
|
+
const hasMemory = manifests.some((m) => m.organType === 'memory');
|
|
78
|
+
const hasVoice = manifests.some((m) => m.organType === 'voice');
|
|
79
|
+
const hasBody = manifests.some((m) => m.organType === 'body');
|
|
80
|
+
const hasMouth = manifests.some((m) => m.organType === 'mouth');
|
|
81
|
+
const voiceConfig = options.organConfigs?.voice || options.organConfigs?.['@siduri-x/voice'];
|
|
82
|
+
const isVoicevox = hasVoice && (!voiceConfig || voiceConfig.provider === 'voicevox');
|
|
83
|
+
const memoryConfig = options.organConfigs?.memory || options.organConfigs?.['@siduri-x/memory'];
|
|
84
|
+
const isPostgresLocal = hasMemory && (!memoryConfig || memoryConfig.deployment === 'local' || memoryConfig.provider === 'postgres');
|
|
85
|
+
// 1. Optional docker-compose.yml
|
|
86
|
+
let dockerComposeYaml;
|
|
87
|
+
const dockerServices = [];
|
|
88
|
+
const dockerVolumes = [];
|
|
89
|
+
if (hasMemory && isPostgresLocal) {
|
|
90
|
+
dockerServices.push([
|
|
91
|
+
' db:',
|
|
92
|
+
' image: postgres:15',
|
|
93
|
+
' environment:',
|
|
94
|
+
' POSTGRES_USER: postgres',
|
|
95
|
+
' POSTGRES_PASSWORD: password',
|
|
96
|
+
' POSTGRES_DB: siduri',
|
|
97
|
+
' ports:',
|
|
98
|
+
' - "5432:5432"',
|
|
99
|
+
' volumes:',
|
|
100
|
+
' - postgres_data:/var/lib/postgresql/data',
|
|
101
|
+
' healthcheck:',
|
|
102
|
+
' test: ["CMD-SHELL", "pg_isready -U postgres"]',
|
|
103
|
+
' interval: 2s',
|
|
104
|
+
' timeout: 5s',
|
|
105
|
+
' retries: 5',
|
|
106
|
+
].join('\n'));
|
|
107
|
+
dockerVolumes.push(' postgres_data:');
|
|
108
|
+
}
|
|
109
|
+
if (hasVoice && voiceConfig?.rvc?.enabled) {
|
|
110
|
+
dockerServices.push([
|
|
111
|
+
' rvc:',
|
|
112
|
+
' image: ghcr.io/vxnus-studio/rvc-headless:latest',
|
|
113
|
+
' ports:',
|
|
114
|
+
' - "50055:50055"',
|
|
115
|
+
' volumes:',
|
|
116
|
+
' - ./assets/voice:/app/models',
|
|
117
|
+
' environment:',
|
|
118
|
+
' - RVC_MODELS_DIR=/app/models',
|
|
119
|
+
].join('\n'));
|
|
120
|
+
}
|
|
121
|
+
if (dockerServices.length > 0) {
|
|
122
|
+
const composeLines = [
|
|
123
|
+
'version: "3.8"',
|
|
124
|
+
'',
|
|
125
|
+
'services:',
|
|
126
|
+
...dockerServices,
|
|
127
|
+
];
|
|
128
|
+
if (dockerVolumes.length > 0) {
|
|
129
|
+
composeLines.push('', 'volumes:', ...dockerVolumes);
|
|
130
|
+
}
|
|
131
|
+
composeLines.push('');
|
|
132
|
+
dockerComposeYaml = composeLines.join('\n');
|
|
133
|
+
}
|
|
134
|
+
// 2. package.json
|
|
135
|
+
const dependencies = {
|
|
136
|
+
'@siduri-x/core': coreVersion,
|
|
137
|
+
};
|
|
138
|
+
for (const m of manifests) {
|
|
139
|
+
dependencies[m.name] = `^${m.version || '1.0.1'}`;
|
|
140
|
+
}
|
|
141
|
+
const scripts = {
|
|
142
|
+
start: 'node src/index.js',
|
|
143
|
+
dev: 'node --watch src/index.js',
|
|
144
|
+
doctor: 'siduri doctor',
|
|
145
|
+
db: 'siduri db',
|
|
146
|
+
};
|
|
147
|
+
if (dockerComposeYaml) {
|
|
148
|
+
scripts['services:up'] = 'docker compose up -d';
|
|
149
|
+
scripts['services:down'] = 'docker compose down';
|
|
150
|
+
scripts['services:logs'] = 'docker compose logs -f';
|
|
151
|
+
}
|
|
152
|
+
const packageJsonObj = {
|
|
153
|
+
name: instanceName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'my-siduri',
|
|
154
|
+
private: true,
|
|
155
|
+
type: 'module',
|
|
156
|
+
scripts,
|
|
157
|
+
dependencies,
|
|
158
|
+
};
|
|
159
|
+
const packageJson = JSON.stringify(packageJsonObj, null, 2) + '\n';
|
|
160
|
+
// 3. siduri.config.json
|
|
161
|
+
const organsConfig = {};
|
|
162
|
+
for (const m of manifests) {
|
|
163
|
+
const customConfig = options.organConfigs?.[m.configKey] || options.organConfigs?.[m.organType];
|
|
164
|
+
organsConfig[m.configKey] = customConfig || getDefaultConfigForManifest(m);
|
|
165
|
+
}
|
|
166
|
+
const configObj = {
|
|
167
|
+
$schema: './siduri.schema.json',
|
|
168
|
+
id: instanceId,
|
|
169
|
+
name: instanceName,
|
|
170
|
+
organs: organsConfig,
|
|
171
|
+
};
|
|
172
|
+
const siduriConfigJson = JSON.stringify(configObj, null, 2) + '\n';
|
|
173
|
+
// 4. siduri.schema.json
|
|
174
|
+
const organPropertiesSchema = {};
|
|
175
|
+
for (const m of manifests) {
|
|
176
|
+
organPropertiesSchema[m.configKey] = m.configSchema || { type: 'object' };
|
|
177
|
+
}
|
|
178
|
+
const schemaObj = {
|
|
179
|
+
$schema: 'http://json-schema.org/draft-07/schema#',
|
|
180
|
+
title: `Siduri Configuration Schema (${instanceName})`,
|
|
181
|
+
type: 'object',
|
|
182
|
+
required: ['id', 'name', 'organs'],
|
|
183
|
+
additionalProperties: false,
|
|
184
|
+
properties: {
|
|
185
|
+
$schema: { type: 'string' },
|
|
186
|
+
id: { type: 'string', description: 'Unique companion isolation ID' },
|
|
187
|
+
name: { type: 'string', description: 'Display name of the companion' },
|
|
188
|
+
organs: {
|
|
189
|
+
type: 'object',
|
|
190
|
+
additionalProperties: false,
|
|
191
|
+
properties: organPropertiesSchema,
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
const siduriSchemaJson = JSON.stringify(schemaObj, null, 2) + '\n';
|
|
196
|
+
// 5. .env.example
|
|
197
|
+
const envLines = [];
|
|
198
|
+
for (const m of manifests) {
|
|
199
|
+
if (m.environment && m.environment.length > 0) {
|
|
200
|
+
envLines.push(`# ${m.displayName || m.name}`);
|
|
201
|
+
for (const envVar of m.environment) {
|
|
202
|
+
if (envVar.description) {
|
|
203
|
+
envLines.push(`# ${envVar.description}${envVar.required ? ' (required)' : ' (optional)'}`);
|
|
204
|
+
}
|
|
205
|
+
const defaultVal = envVar.default || '';
|
|
206
|
+
envLines.push(`${envVar.name}=${defaultVal}`);
|
|
207
|
+
}
|
|
208
|
+
envLines.push('');
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const envExample = envLines.length > 0 ? envLines.join('\n') : '# No external environment variables required\n';
|
|
212
|
+
// 6. src/index.js
|
|
213
|
+
const importLines = [
|
|
214
|
+
`import { createServer } from 'node:http';`,
|
|
215
|
+
`import { readFile, stat, readdir } from 'node:fs/promises';`,
|
|
216
|
+
`import path from 'node:path';`,
|
|
217
|
+
`import { fileURLToPath } from 'node:url';`,
|
|
218
|
+
`import { SiduriRuntime, dispatchCompanionChat } from '@siduri-x/core';`,
|
|
219
|
+
];
|
|
220
|
+
for (const m of manifests) {
|
|
221
|
+
importLines.push(`import { ${m.factory} } from '${m.name}';`);
|
|
222
|
+
}
|
|
223
|
+
const instantiationLines = [];
|
|
224
|
+
const organMapEntries = [];
|
|
225
|
+
for (const m of manifests) {
|
|
226
|
+
const varName = m.configKey;
|
|
227
|
+
instantiationLines.push(`const ${varName} = new ${m.factory}(config.organs.${m.configKey});`);
|
|
228
|
+
organMapEntries.push(` ${varName},`);
|
|
229
|
+
}
|
|
230
|
+
if (hasMouth && hasVoice) {
|
|
231
|
+
instantiationLines.push(`if (typeof mouth?.setVoiceOrgan === 'function') mouth.setVoiceOrgan(voice);`);
|
|
232
|
+
}
|
|
233
|
+
const selectedDisplayNames = manifests.map((m) => m.displayName.split(' ')[0] || m.organType).join(', ');
|
|
234
|
+
const srcIndexJs = [
|
|
235
|
+
...importLines,
|
|
236
|
+
'',
|
|
237
|
+
`const __filename = fileURLToPath(import.meta.url);`,
|
|
238
|
+
`const __dirname = path.dirname(__filename);`,
|
|
239
|
+
`const rootDir = path.resolve(__dirname, '..');`,
|
|
240
|
+
'',
|
|
241
|
+
`const config = JSON.parse(`,
|
|
242
|
+
` await readFile(path.join(rootDir, 'siduri.config.json'), 'utf8')`,
|
|
243
|
+
`);`,
|
|
244
|
+
'',
|
|
245
|
+
...instantiationLines,
|
|
246
|
+
'',
|
|
247
|
+
`const runtime = new SiduriRuntime(config.id, config, {`,
|
|
248
|
+
...organMapEntries,
|
|
249
|
+
`});`,
|
|
250
|
+
'',
|
|
251
|
+
`await runtime.initialize();`,
|
|
252
|
+
'',
|
|
253
|
+
`const audioCache = new Map();`,
|
|
254
|
+
'',
|
|
255
|
+
`const server = createServer(async (req, res) => {`,
|
|
256
|
+
` const parsedUrl = new URL(req.url, \`http://\${req.headers.host || 'localhost'}\`);`,
|
|
257
|
+
` const pathname = parsedUrl.pathname;`,
|
|
258
|
+
'',
|
|
259
|
+
` // API: Status & Health Probes (minimal safe operational state, no secrets/credentials/config.organs)`,
|
|
260
|
+
` if ((pathname === '/health' || pathname === '/api/status') && req.method === 'GET') {`,
|
|
261
|
+
` res.writeHead(200, { 'Content-Type': 'application/json' });`,
|
|
262
|
+
` res.end(JSON.stringify({`,
|
|
263
|
+
` status: 'ok',`,
|
|
264
|
+
` uptime: process.uptime(),`,
|
|
265
|
+
` }));`,
|
|
266
|
+
` return;`,
|
|
267
|
+
` }`,
|
|
268
|
+
'',
|
|
269
|
+
` // API: Audio buffer retrieval for voice playback`,
|
|
270
|
+
` if (pathname.startsWith('/api/audio/') && req.method === 'GET') {`,
|
|
271
|
+
` const audioId = pathname.slice('/api/audio/'.length);`,
|
|
272
|
+
` const buffer = audioCache.get(audioId);`,
|
|
273
|
+
` if (buffer) {`,
|
|
274
|
+
` res.writeHead(200, { 'Content-Type': 'audio/wav' });`,
|
|
275
|
+
` res.end(Buffer.from(buffer));`,
|
|
276
|
+
` return;`,
|
|
277
|
+
` }`,
|
|
278
|
+
` res.writeHead(404, { 'Content-Type': 'application/json' });`,
|
|
279
|
+
` res.end(JSON.stringify({ error: 'Audio not found' }));`,
|
|
280
|
+
` return;`,
|
|
281
|
+
` }`,
|
|
282
|
+
'',
|
|
283
|
+
` // API: Memory Claims & Items`,
|
|
284
|
+
` if ((pathname === '/memory' || pathname === '/memory/claims' || pathname === '/api/memory/claims') && req.method === 'GET') {`,
|
|
285
|
+
` res.writeHead(200, { 'Content-Type': 'application/json' });`,
|
|
286
|
+
` try {`,
|
|
287
|
+
` const claims = typeof memory?.getAllClaims === 'function' ? await memory.getAllClaims() : (typeof memory?.getClaims === 'function' ? await memory.getClaims() : []);`,
|
|
288
|
+
` res.end(JSON.stringify({ claims, items: claims }));`,
|
|
289
|
+
` } catch (e) {`,
|
|
290
|
+
` res.end(JSON.stringify({ claims: [], items: [] }));`,
|
|
291
|
+
` }`,
|
|
292
|
+
` return;`,
|
|
293
|
+
` }`,
|
|
294
|
+
'',
|
|
295
|
+
` // API: Memory Proposals`,
|
|
296
|
+
` if (pathname === '/memory/proposals' && req.method === 'GET') {`,
|
|
297
|
+
` res.writeHead(200, { 'Content-Type': 'application/json' });`,
|
|
298
|
+
` try {`,
|
|
299
|
+
` const proposals = typeof memory?.getPendingClaims === 'function' ? await memory.getPendingClaims() : [];`,
|
|
300
|
+
` res.end(JSON.stringify({ proposals }));`,
|
|
301
|
+
` } catch (e) {`,
|
|
302
|
+
` res.end(JSON.stringify({ proposals: [] }));`,
|
|
303
|
+
` }`,
|
|
304
|
+
` return;`,
|
|
305
|
+
` }`,
|
|
306
|
+
'',
|
|
307
|
+
` // API: Memory Directives`,
|
|
308
|
+
` if ((pathname === '/memory/behavioral' || pathname === '/api/memory/directives') && req.method === 'GET') {`,
|
|
309
|
+
` res.writeHead(200, { 'Content-Type': 'application/json' });`,
|
|
310
|
+
` try {`,
|
|
311
|
+
` const directives = typeof memory?.getDirectives === 'function' ? await memory.getDirectives() : [`,
|
|
312
|
+
` { domain: 'personality', name: 'Active Self Tone', content: 'Warm, empathetic, and thoughtful conversational style.' },`,
|
|
313
|
+
` { domain: 'cognition', name: 'Authoritative Memory', content: 'Ground responses in verified claims and personal history.' }`,
|
|
314
|
+
` ];`,
|
|
315
|
+
` res.end(JSON.stringify({ directives }));`,
|
|
316
|
+
` } catch (e) {`,
|
|
317
|
+
` res.end(JSON.stringify({ directives: [] }));`,
|
|
318
|
+
` }`,
|
|
319
|
+
` return;`,
|
|
320
|
+
` }`,
|
|
321
|
+
'',
|
|
322
|
+
` // API: Memory Proposal Approval / Rejection`,
|
|
323
|
+
` if ((pathname === '/memory/proposals/approve' || pathname === '/memory/proposals/reject') && req.method === 'POST') {`,
|
|
324
|
+
` let body = '';`,
|
|
325
|
+
` req.on('data', (chunk) => { body += chunk; });`,
|
|
326
|
+
` req.on('end', async () => {`,
|
|
327
|
+
` try {`,
|
|
328
|
+
` const payload = JSON.parse(body || '{}');`,
|
|
329
|
+
` const claimId = payload.id || payload.claimId;`,
|
|
330
|
+
` if (!claimId) {`,
|
|
331
|
+
` res.writeHead(400, { 'Content-Type': 'application/json' });`,
|
|
332
|
+
` res.end(JSON.stringify({ error: 'Missing required claim id' }));`,
|
|
333
|
+
` return;`,
|
|
334
|
+
` }`,
|
|
335
|
+
` if (pathname.endsWith('approve')) {`,
|
|
336
|
+
` if (typeof memory?.approveClaim === 'function') {`,
|
|
337
|
+
` await memory.approveClaim(claimId);`,
|
|
338
|
+
` }`,
|
|
339
|
+
` res.writeHead(200, { 'Content-Type': 'application/json' });`,
|
|
340
|
+
` res.end(JSON.stringify({ approved: true, id: claimId }));`,
|
|
341
|
+
` } else {`,
|
|
342
|
+
` if (typeof memory?.rejectClaim === 'function') {`,
|
|
343
|
+
` await memory.rejectClaim(claimId);`,
|
|
344
|
+
` }`,
|
|
345
|
+
` res.writeHead(200, { 'Content-Type': 'application/json' });`,
|
|
346
|
+
` res.end(JSON.stringify({ rejected: true, id: claimId }));`,
|
|
347
|
+
` }`,
|
|
348
|
+
` } catch (err) {`,
|
|
349
|
+
` res.writeHead(500, { 'Content-Type': 'application/json' });`,
|
|
350
|
+
` res.end(JSON.stringify({ error: err.message }));`,
|
|
351
|
+
` }`,
|
|
352
|
+
` });`,
|
|
353
|
+
` return;`,
|
|
354
|
+
` }`,
|
|
355
|
+
'',
|
|
356
|
+
` // API: Evidence packs`,
|
|
357
|
+
` if (pathname === '/evidence/packs' && req.method === 'GET') {`,
|
|
358
|
+
` res.writeHead(200, { 'Content-Type': 'application/json' });`,
|
|
359
|
+
` res.end(JSON.stringify({ packs: [] }));`,
|
|
360
|
+
` return;`,
|
|
361
|
+
` }`,
|
|
362
|
+
'',
|
|
363
|
+
` // API: Chat interaction (routed through canonical dispatchCompanionChat from @siduri-x/core)`,
|
|
364
|
+
` if ((pathname === '/chat' || pathname === '/api/chat') && req.method === 'POST') {`,
|
|
365
|
+
` let body = '';`,
|
|
366
|
+
` req.on('data', (chunk) => { body += chunk; });`,
|
|
367
|
+
` req.on('end', async () => {`,
|
|
368
|
+
` try {`,
|
|
369
|
+
` const payload = JSON.parse(body || '{}');`,
|
|
370
|
+
` const response = await dispatchCompanionChat(runtime, {`,
|
|
371
|
+
` id: config.id,`,
|
|
372
|
+
` companionId: config.id,`,
|
|
373
|
+
` message: payload.message || payload.text || '',`,
|
|
374
|
+
` context: payload.context,`,
|
|
375
|
+
` history: Array.isArray(payload.history) ? payload.history : [],`,
|
|
376
|
+
` });`,
|
|
377
|
+
'',
|
|
378
|
+
` res.writeHead(200, { 'Content-Type': 'application/json' });`,
|
|
379
|
+
` res.end(JSON.stringify(response));`,
|
|
380
|
+
` } catch (err) {`,
|
|
381
|
+
` res.writeHead(500, { 'Content-Type': 'application/json' });`,
|
|
382
|
+
` res.end(JSON.stringify({ error: err.message }));`,
|
|
383
|
+
` }`,
|
|
384
|
+
` });`,
|
|
385
|
+
` return;`,
|
|
386
|
+
` }`,
|
|
387
|
+
'',
|
|
388
|
+
` // API: Real-time SSE streaming (Mouth transport)`,
|
|
389
|
+
` if ((pathname === '/chat/stream' || pathname === '/api/chat/stream') && req.method === 'POST') {`,
|
|
390
|
+
` let body = '';`,
|
|
391
|
+
` req.on('data', (chunk) => { body += chunk; });`,
|
|
392
|
+
` req.on('end', async () => {`,
|
|
393
|
+
` res.writeHead(200, {`,
|
|
394
|
+
` 'Content-Type': 'text/event-stream',`,
|
|
395
|
+
` 'Cache-Control': 'no-cache, no-transform',`,
|
|
396
|
+
` 'Connection': 'keep-alive',`,
|
|
397
|
+
` });`,
|
|
398
|
+
` const abortController = new AbortController();`,
|
|
399
|
+
` const onClose = () => {`,
|
|
400
|
+
` abortController.abort('client_disconnect');`,
|
|
401
|
+
` if (typeof runtime.interruptMouth === 'function') {`,
|
|
402
|
+
` runtime.interruptMouth('client_disconnect');`,
|
|
403
|
+
` }`,
|
|
404
|
+
` };`,
|
|
405
|
+
` req.on('close', onClose);`,
|
|
406
|
+
'',
|
|
407
|
+
` try {`,
|
|
408
|
+
` const payload = JSON.parse(body || '{}');`,
|
|
409
|
+
` const response = await dispatchCompanionChat(runtime, {`,
|
|
410
|
+
` id: config.id,`,
|
|
411
|
+
` companionId: config.id,`,
|
|
412
|
+
` message: payload.message || payload.text || '',`,
|
|
413
|
+
` context: payload.context,`,
|
|
414
|
+
` history: Array.isArray(payload.history) ? payload.history : [],`,
|
|
415
|
+
` medium: 'web',`,
|
|
416
|
+
` signal: abortController.signal,`,
|
|
417
|
+
` });`,
|
|
418
|
+
'',
|
|
419
|
+
` res.write(\`event: staged\\ndata: \${JSON.stringify({ response_id: response.response_id, correlation_id: response.correlation_id, status: response.status })}\\n\\n\`);`,
|
|
420
|
+
'',
|
|
421
|
+
` const avatarEvent = response.metadata?.events?.find(`,
|
|
422
|
+
` (e) => (e.kind === 'avatar' || e.kind === 'body') && (e.approval === 'APPROVED' || !e.approval)`,
|
|
423
|
+
` );`,
|
|
424
|
+
` if (avatarEvent) {`,
|
|
425
|
+
` res.write(\`event: avatar\\ndata: \${JSON.stringify(avatarEvent)}\\n\\n\`);`,
|
|
426
|
+
` }`,
|
|
427
|
+
'',
|
|
428
|
+
` const speechText = response.delivery?.text || response.response?.subtitle_en || response.response?.spoken_ja || '';`,
|
|
429
|
+
` const utterance = {`,
|
|
430
|
+
` utteranceId: response.response_id || 'utt-stream',`,
|
|
431
|
+
` companionId: config.id,`,
|
|
432
|
+
` responseId: response.response_id,`,
|
|
433
|
+
` correlationId: response.correlation_id,`,
|
|
434
|
+
` text: speechText,`,
|
|
435
|
+
` medium: 'web',`,
|
|
436
|
+
` expression: avatarEvent?.expression,`,
|
|
437
|
+
` action: avatarEvent?.action,`,
|
|
438
|
+
` signal: abortController.signal,`,
|
|
439
|
+
` };`,
|
|
440
|
+
'',
|
|
441
|
+
` if (runtime.mouth && typeof runtime.mouth.stream === 'function') {`,
|
|
442
|
+
` for await (const chunk of runtime.mouth.stream(utterance)) {`,
|
|
443
|
+
` if (abortController.signal.aborted) {`,
|
|
444
|
+
` res.write(\`event: chunk\\ndata: \${JSON.stringify({ ...chunk, interrupted: true })}\\n\\n\`);`,
|
|
445
|
+
` break;`,
|
|
446
|
+
` }`,
|
|
447
|
+
` res.write(\`event: chunk\\ndata: \${JSON.stringify(chunk)}\\n\\n\`);`,
|
|
448
|
+
` }`,
|
|
449
|
+
` } else {`,
|
|
450
|
+
` res.write(\`event: chunk\\ndata: \${JSON.stringify({ utteranceId: utterance.utteranceId, index: 1, deltaText: speechText, isComplete: true, medium: 'web' })}\\n\\n\`);`,
|
|
451
|
+
` }`,
|
|
452
|
+
'',
|
|
453
|
+
` res.write(\`event: done\\ndata: \${JSON.stringify(response)}\\n\\n\`);`,
|
|
454
|
+
` res.end();`,
|
|
455
|
+
` } catch (err) {`,
|
|
456
|
+
` if (abortController.signal.aborted) {`,
|
|
457
|
+
` res.write(\`event: interrupted\\ndata: \${JSON.stringify({ reason: abortController.signal.reason })}\\n\\n\`);`,
|
|
458
|
+
` } else {`,
|
|
459
|
+
` res.write(\`event: error\\ndata: \${JSON.stringify({ error: err.message })}\\n\\n\`);`,
|
|
460
|
+
` }`,
|
|
461
|
+
` res.end();`,
|
|
462
|
+
` } finally {`,
|
|
463
|
+
` req.removeListener('close', onClose);`,
|
|
464
|
+
` }`,
|
|
465
|
+
` });`,
|
|
466
|
+
` return;`,
|
|
467
|
+
` }`,
|
|
468
|
+
'',
|
|
469
|
+
` // API: Barge-in interruption`,
|
|
470
|
+
` if ((pathname === '/chat/interrupt' || pathname === '/mouth/interrupt' || pathname === '/api/chat/interrupt') && req.method === 'POST') {`,
|
|
471
|
+
` let body = '';`,
|
|
472
|
+
` req.on('data', (chunk) => { body += chunk; });`,
|
|
473
|
+
` req.on('end', () => {`,
|
|
474
|
+
` try {`,
|
|
475
|
+
` const payload = JSON.parse(body || '{}');`,
|
|
476
|
+
` const reason = payload.reason || 'user_barge_in';`,
|
|
477
|
+
` if (typeof runtime.interruptMouth === 'function') {`,
|
|
478
|
+
` runtime.interruptMouth(reason);`,
|
|
479
|
+
` }`,
|
|
480
|
+
` res.writeHead(200, { 'Content-Type': 'application/json' });`,
|
|
481
|
+
` res.end(JSON.stringify({ success: true, interrupted: true, reason }));`,
|
|
482
|
+
` } catch (e) {`,
|
|
483
|
+
` res.writeHead(500, { 'Content-Type': 'application/json' });`,
|
|
484
|
+
` res.end(JSON.stringify({ error: e.message }));`,
|
|
485
|
+
` }`,
|
|
486
|
+
` });`,
|
|
487
|
+
` return;`,
|
|
488
|
+
` }`,
|
|
489
|
+
'',
|
|
490
|
+
` // API: Mouth channels & health`,
|
|
491
|
+
` if ((pathname === '/mouth/health' || pathname === '/api/mouth/health') && req.method === 'GET') {`,
|
|
492
|
+
` res.writeHead(200, { 'Content-Type': 'application/json' });`,
|
|
493
|
+
` res.end(JSON.stringify({ provider: 'siduri-mouth', configured: Boolean(runtime.mouth) }));`,
|
|
494
|
+
` return;`,
|
|
495
|
+
` }`,
|
|
496
|
+
'',
|
|
497
|
+
` if ((pathname === '/mouth/channels' || pathname === '/api/mouth/channels') && req.method === 'GET') {`,
|
|
498
|
+
` res.writeHead(200, { 'Content-Type': 'application/json' });`,
|
|
499
|
+
` const channels = typeof runtime.mouth?.getRegisteredChannels === 'function' ? runtime.mouth.getRegisteredChannels() : [];`,
|
|
500
|
+
` res.end(JSON.stringify({ channels }));`,
|
|
501
|
+
` return;`,
|
|
502
|
+
` }`,
|
|
503
|
+
'',
|
|
504
|
+
` // API: Model Catalog Discovery (Body & Voice)`,
|
|
505
|
+
` if (pathname === '/api/models/body' && req.method === 'GET') {`,
|
|
506
|
+
` res.writeHead(200, { 'Content-Type': 'application/json' });`,
|
|
507
|
+
` try {`,
|
|
508
|
+
` const bodyDir = path.join(rootDir, 'assets', 'body');`,
|
|
509
|
+
` const entries = await readdir(bodyDir, { withFileTypes: true }).catch(() => []);`,
|
|
510
|
+
` const models = entries.filter((e) => e.isDirectory()).map((e) => e.name);`,
|
|
511
|
+
` res.end(JSON.stringify({ models: models.length ? models : ['default'] }));`,
|
|
512
|
+
` } catch {`,
|
|
513
|
+
` res.end(JSON.stringify({ models: ['default'] }));`,
|
|
514
|
+
` }`,
|
|
515
|
+
` return;`,
|
|
516
|
+
` }`,
|
|
517
|
+
'',
|
|
518
|
+
` if (pathname === '/api/models/voice' && req.method === 'GET') {`,
|
|
519
|
+
` res.writeHead(200, { 'Content-Type': 'application/json' });`,
|
|
520
|
+
` try {`,
|
|
521
|
+
` const voiceDir = path.join(rootDir, 'assets', 'voice');`,
|
|
522
|
+
` const entries = await readdir(voiceDir, { withFileTypes: true }).catch(() => []);`,
|
|
523
|
+
` const models = entries.filter((e) => e.isDirectory()).map((e) => e.name);`,
|
|
524
|
+
` res.end(JSON.stringify({ models: models.length ? models : ['default'] }));`,
|
|
525
|
+
` } catch {`,
|
|
526
|
+
` res.end(JSON.stringify({ models: ['default'] }));`,
|
|
527
|
+
` }`,
|
|
528
|
+
` return;`,
|
|
529
|
+
` }`,
|
|
530
|
+
'',
|
|
531
|
+
` // Static files & Next.js apps/web export routing with strict boundary verification`,
|
|
532
|
+
` let decodedPathname = pathname;`,
|
|
533
|
+
` try {`,
|
|
534
|
+
` while (decodedPathname.includes('%')) {`,
|
|
535
|
+
` const next = decodeURIComponent(decodedPathname);`,
|
|
536
|
+
` if (next === decodedPathname) break;`,
|
|
537
|
+
` decodedPathname = next;`,
|
|
538
|
+
` }`,
|
|
539
|
+
` } catch {`,
|
|
540
|
+
` res.writeHead(400, { 'Content-Type': 'text/plain' });`,
|
|
541
|
+
` res.end('Bad Request');`,
|
|
542
|
+
` return;`,
|
|
543
|
+
` }`,
|
|
544
|
+
` const normalizedPath = path.normalize(decodedPathname).replace(/^[/\\\\]+/, '');`,
|
|
545
|
+
` const canonicalPublicRoot = path.resolve(rootDir, 'public');`,
|
|
546
|
+
` const canonicalAssetsRoot = path.resolve(rootDir, 'assets');`,
|
|
547
|
+
` const candidatePairs = [`,
|
|
548
|
+
` { root: canonicalPublicRoot, target: path.resolve(canonicalPublicRoot, normalizedPath, 'index.html') },`,
|
|
549
|
+
` { root: canonicalPublicRoot, target: path.resolve(canonicalPublicRoot, normalizedPath + '.html') },`,
|
|
550
|
+
` { root: canonicalPublicRoot, target: path.resolve(canonicalPublicRoot, normalizedPath) },`,
|
|
551
|
+
` ];`,
|
|
552
|
+
'',
|
|
553
|
+
` if (!normalizedPath || normalizedPath === '.' || normalizedPath === './') {`,
|
|
554
|
+
` candidatePairs.unshift({ root: canonicalPublicRoot, target: path.resolve(canonicalPublicRoot, 'index.html') });`,
|
|
555
|
+
` }`,
|
|
556
|
+
'',
|
|
557
|
+
` // Compatibility fallback for legacy /live2d/<modelName>/... -> ./assets/body/<modelName>/...`,
|
|
558
|
+
` if (decodedPathname.startsWith('/live2d/')) {`,
|
|
559
|
+
` const relativeAssetPath = path.normalize(decodedPathname.slice('/live2d/'.length)).replace(/^[/\\\\]+/, '');`,
|
|
560
|
+
` candidatePairs.unshift({ root: canonicalAssetsRoot, target: path.resolve(canonicalAssetsRoot, 'body', relativeAssetPath) });`,
|
|
561
|
+
` candidatePairs.unshift({ root: canonicalAssetsRoot, target: path.resolve(canonicalAssetsRoot, 'body', 'model', relativeAssetPath) });`,
|
|
562
|
+
` }`,
|
|
563
|
+
'',
|
|
564
|
+
` for (const { root, target } of candidatePairs) {`,
|
|
565
|
+
` try {`,
|
|
566
|
+
` // Strict path containment check: target must reside within configured root directory`,
|
|
567
|
+
` const relative = path.relative(root, target);`,
|
|
568
|
+
` if (relative.startsWith('..') || path.isAbsolute(relative)) {`,
|
|
569
|
+
` continue;`,
|
|
570
|
+
` }`,
|
|
571
|
+
` const fileStat = await stat(target);`,
|
|
572
|
+
` if (fileStat.isFile()) {`,
|
|
573
|
+
` const ext = path.extname(target).toLowerCase();`,
|
|
574
|
+
` const mimeTypes = {`,
|
|
575
|
+
` '.html': 'text/html; charset=utf-8',`,
|
|
576
|
+
` '.js': 'application/javascript; charset=utf-8',`,
|
|
577
|
+
` '.css': 'text/css; charset=utf-8',`,
|
|
578
|
+
` '.json': 'application/json',`,
|
|
579
|
+
` '.png': 'image/png',`,
|
|
580
|
+
` '.jpg': 'image/jpeg',`,
|
|
581
|
+
` '.svg': 'image/svg+xml',`,
|
|
582
|
+
` '.wav': 'audio/wav',`,
|
|
583
|
+
` '.ico': 'image/x-icon',`,
|
|
584
|
+
` '.txt': 'text/plain; charset=utf-8',`,
|
|
585
|
+
` '.moc3': 'application/octet-stream',`,
|
|
586
|
+
` };`,
|
|
587
|
+
` const contentType = mimeTypes[ext] || 'application/octet-stream';`,
|
|
588
|
+
` const content = await readFile(target);`,
|
|
589
|
+
` res.writeHead(200, { 'Content-Type': contentType });`,
|
|
590
|
+
` res.end(content);`,
|
|
591
|
+
` return;`,
|
|
592
|
+
` }`,
|
|
593
|
+
` } catch (e) {}`,
|
|
594
|
+
` }`,
|
|
595
|
+
'',
|
|
596
|
+
` res.writeHead(404, { 'Content-Type': 'text/plain' });`,
|
|
597
|
+
` res.end('Not Found');`,
|
|
598
|
+
`});`,
|
|
599
|
+
'',
|
|
600
|
+
`const PORT = process.env.PORT || 3000;`,
|
|
601
|
+
`server.listen(Number(PORT), '127.0.0.1', () => {`,
|
|
602
|
+
` console.log(\`✓ Siduri [\${config.name}] initialized with [${selectedDisplayNames}].\`);`,
|
|
603
|
+
` console.log(\`➜ Web Companion & Memory Console running at: http://127.0.0.1:\${PORT}\`);`,
|
|
604
|
+
` if (process.env.PORT === '0') {`,
|
|
605
|
+
` server.close();`,
|
|
606
|
+
` }`,
|
|
607
|
+
`});`,
|
|
608
|
+
'',
|
|
609
|
+
].join('\n');
|
|
610
|
+
// 7. README.md
|
|
611
|
+
const readmeLines = [
|
|
612
|
+
`# ${instanceName}`,
|
|
613
|
+
'',
|
|
614
|
+
`Standalone Siduri AI companion instance generated with explicitly composed organs:`,
|
|
615
|
+
'',
|
|
616
|
+
...manifests.map((m) => `- **${m.displayName}** (\`${m.name}\`)`),
|
|
617
|
+
'',
|
|
618
|
+
'## Prerequisites',
|
|
619
|
+
'',
|
|
620
|
+
'- **Node.js**: `v20.0.0` or higher',
|
|
621
|
+
'- **Environment**: Valid `.env` file (configured from `.env.example`)',
|
|
622
|
+
];
|
|
623
|
+
if (hasMemory) {
|
|
624
|
+
readmeLines.push('- **Local Services (Optional)**: Docker (or standalone alternatives):');
|
|
625
|
+
readmeLines.push(' - **PostgreSQL**: Required for memory claims & durable state (or use cloud Supabase/Neon)');
|
|
626
|
+
}
|
|
627
|
+
if (isVoicevox) {
|
|
628
|
+
readmeLines.push('- **VOICEVOX**: Note: The Voicevox engine executable will be securely auto-downloaded at runtime by Siduri if no local URL is provided.');
|
|
629
|
+
}
|
|
630
|
+
readmeLines.push('', '## Getting Started', '', '### 1. Install Dependencies', '```bash', 'npm install', '```', '', '### 2. Configure Environment', '```bash', 'cp .env.example .env', '```', 'Fill in your LLM API key (e.g. `OPENROUTER_API_KEY`) and any other service credentials in `.env`.');
|
|
631
|
+
if (dockerComposeYaml) {
|
|
632
|
+
readmeLines.push('', '### 3. Start Local Services (Docker)', '```bash', 'npm run services:up', '```', '*(To stop services later, run `npm run services:down`)*');
|
|
633
|
+
}
|
|
634
|
+
if (hasMemory) {
|
|
635
|
+
readmeLines.push('', `### ${dockerComposeYaml ? '4' : '3'}. Database Migrations`, 'Ensure `DATABASE_URL` in `.env` is reachable, then push the memory organ PostgreSQL schema:', '```bash', 'npx @vxnus/siduri db push', '```');
|
|
636
|
+
}
|
|
637
|
+
readmeLines.push('', `### ${hasMemory ? (dockerComposeYaml ? '5' : '4') : (dockerComposeYaml ? '4' : '3')}. Diagnostics & Health Probe`, 'Verify all environment variables, services, and organ connections:', '```bash', 'npm run doctor', '```', '', `### ${hasMemory ? (dockerComposeYaml ? '6' : '5') : (dockerComposeYaml ? '5' : '4')}. Start Companion & Web Console`, 'Launch your companion runtime and Web UI / Memory Control Panel:', '```bash', 'npm start', '```', 'Then open `http://localhost:3000` in your browser.');
|
|
638
|
+
const companionSlug = instanceName.toLowerCase().replace(/[^a-z0-9_-]/g, '') || 'default';
|
|
639
|
+
const createAssetsDirs = [];
|
|
640
|
+
if (hasBody) {
|
|
641
|
+
createAssetsDirs.push(`assets/body/${companionSlug}`);
|
|
642
|
+
readmeLines.push('', '### Body & Avatar Models', `Place your Live2D Cubism model assets into \`./assets/body/${companionSlug}/\`:`, '- `model.model3.json`', '- `model.moc3`', '- textures directory');
|
|
643
|
+
}
|
|
644
|
+
if (hasVoice) {
|
|
645
|
+
createAssetsDirs.push(`assets/voice/${companionSlug}`);
|
|
646
|
+
readmeLines.push('', '### Voice & RVC Models', `Place your character RVC voice models into \`./assets/voice/${companionSlug}/\`:`, `- \`${companionSlug}.pth\` (Target voice weights)`, `- \`${companionSlug}.index\` (Feature index file)`);
|
|
647
|
+
}
|
|
648
|
+
readmeLines.push('');
|
|
649
|
+
const readmeMd = readmeLines.join('\n');
|
|
650
|
+
const webHtml = (0, web_template_1.generateWebHtml)(instanceName, manifests);
|
|
651
|
+
const result = {
|
|
652
|
+
'package.json': packageJson,
|
|
653
|
+
'siduri.config.json': siduriConfigJson,
|
|
654
|
+
'siduri.schema.json': siduriSchemaJson,
|
|
655
|
+
'.env.example': envExample,
|
|
656
|
+
'README.md': readmeMd,
|
|
657
|
+
'src/index.js': srcIndexJs,
|
|
658
|
+
'public/index.html': webHtml,
|
|
659
|
+
createAssetsBodyDir: hasBody,
|
|
660
|
+
createAssetsDirs,
|
|
661
|
+
};
|
|
662
|
+
if (dockerComposeYaml) {
|
|
663
|
+
result['docker-compose.yml'] = dockerComposeYaml;
|
|
664
|
+
}
|
|
665
|
+
return result;
|
|
666
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|