@siduri-x/api 1.0.1 → 1.0.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/dist/index.js +18 -9
- package/package.json +16 -12
- package/src/app.ts +294 -95
- package/src/auth.test.ts +56 -23
- package/src/auth.ts +74 -22
- package/src/context-mapper.test.ts +42 -147
- package/src/context-mapper.ts +44 -179
- package/src/index.test.ts +61 -32
- package/src/index.ts +30 -21
- package/src/runtime.test.ts +57 -5
- package/src/t5-experience.test.ts +0 -1
- package/src/t6-security.test.ts +0 -1
- package/dist/app.d.ts +0 -9
- package/dist/app.js +0 -480
- package/dist/auth.d.ts +0 -8
- package/dist/auth.js +0 -40
- package/dist/auth.test.d.ts +0 -1
- package/dist/auth.test.js +0 -48
- package/dist/b0-b6.test.d.ts +0 -1
- package/dist/b0-b6.test.js +0 -121
- package/dist/context-mapper.d.ts +0 -15
- package/dist/context-mapper.js +0 -287
- package/dist/context-mapper.test.d.ts +0 -1
- package/dist/context-mapper.test.js +0 -233
- package/dist/cors.d.ts +0 -3
- package/dist/cors.js +0 -42
- package/dist/index.d.ts +0 -6
- package/dist/index.test.d.ts +0 -1
- package/dist/index.test.js +0 -115
- package/dist/runtime.d.ts +0 -1
- package/dist/runtime.js +0 -17
- package/dist/runtime.test.d.ts +0 -1
- package/dist/runtime.test.js +0 -240
- package/dist/smoke.test.d.ts +0 -0
- package/dist/smoke.test.js +0 -6
- package/dist/t4-gating.test.d.ts +0 -1
- package/dist/t4-gating.test.js +0 -193
- package/dist/t5-experience.test.d.ts +0 -1
- package/dist/t5-experience.test.js +0 -156
- package/dist/t6-security.test.d.ts +0 -1
- package/dist/t6-security.test.js +0 -424
- package/dist/t7-release.test.d.ts +0 -1
- package/dist/t7-release.test.js +0 -119
package/src/app.ts
CHANGED
|
@@ -4,17 +4,48 @@ import { createCorsOptions } from './cors';
|
|
|
4
4
|
import { SiduriRuntime, dispatchCompanionChat } from './runtime';
|
|
5
5
|
import { OpenAICompatibleBrain, OpenRouterBrain } from '@siduri-x/brain';
|
|
6
6
|
import { PostgresMemoryOrgan } from '@siduri-x/memory';
|
|
7
|
-
import { VoiceAdapter } from '@siduri-x/voice';
|
|
8
|
-
import { EKnowledgeAdapter } from '@siduri-x/knowledge';
|
|
9
|
-
import { OpenRouterVisionAdapter } from '@siduri-x/vision';
|
|
7
|
+
import { VoiceAdapter, VoiceConfig } from '@siduri-x/voice';
|
|
8
|
+
import { EKnowledgeAdapter, EKnowledgeConfig } from '@siduri-x/knowledge';
|
|
9
|
+
import { OpenRouterVisionAdapter, OpenRouterVisionConfig } from '@siduri-x/vision';
|
|
10
10
|
import { ActiveSelfCompiler } from '@siduri-x/behavior';
|
|
11
|
-
import { Live2DAdapter } from '@siduri-x/body';
|
|
11
|
+
import { Live2DAdapter, Live2DAdapterConfig } from '@siduri-x/body';
|
|
12
12
|
import { FixtureObservationOrgan } from '@siduri-x/observation';
|
|
13
|
-
import { DefaultHandsOrgan } from '@siduri-x/hands';
|
|
14
|
-
import { DefaultEarOrgan } from '@siduri-x/ear';
|
|
15
|
-
import {
|
|
13
|
+
import { DefaultHandsOrgan, DefaultHandsOrganConfig } from '@siduri-x/hands';
|
|
14
|
+
import { DefaultEarOrgan, EarOrganConfig } from '@siduri-x/ear';
|
|
15
|
+
import { DefaultMouthOrgan, DefaultMouthOrganConfig } from '@siduri-x/mouth';
|
|
16
|
+
import { attachIdentity, requireAuth, Identity } from './auth';
|
|
16
17
|
import { mapRequestContext } from './context-mapper';
|
|
17
18
|
|
|
19
|
+
export interface AppBrainConfig {
|
|
20
|
+
provider?: 'openrouter' | 'openai-compatible' | string;
|
|
21
|
+
model?: string;
|
|
22
|
+
apiKey?: string;
|
|
23
|
+
apiKeyEnv?: string;
|
|
24
|
+
baseUrl?: string;
|
|
25
|
+
timeoutMs?: number;
|
|
26
|
+
[key: string]: unknown;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface AppBehaviorConfig {
|
|
30
|
+
provider?: 'active_self' | 'none' | string;
|
|
31
|
+
preset?: string;
|
|
32
|
+
[key: string]: unknown;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface AppBootCompanionConfig {
|
|
36
|
+
name: string;
|
|
37
|
+
brain?: AppBrainConfig;
|
|
38
|
+
voice?: VoiceConfig;
|
|
39
|
+
knowledge?: EKnowledgeConfig;
|
|
40
|
+
vision?: OpenRouterVisionConfig;
|
|
41
|
+
behavior?: AppBehaviorConfig;
|
|
42
|
+
body?: Live2DAdapterConfig;
|
|
43
|
+
hands?: DefaultHandsOrganConfig;
|
|
44
|
+
ear?: EarOrganConfig;
|
|
45
|
+
mouth?: DefaultMouthOrganConfig;
|
|
46
|
+
[key: string]: unknown;
|
|
47
|
+
}
|
|
48
|
+
|
|
18
49
|
export interface AppInstance {
|
|
19
50
|
app: Express;
|
|
20
51
|
runtimes: Map<string, SiduriRuntime>;
|
|
@@ -28,63 +59,78 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
28
59
|
|
|
29
60
|
let observationOrgan: FixtureObservationOrgan | undefined;
|
|
30
61
|
|
|
31
|
-
function createBrain(config
|
|
32
|
-
const provider = config
|
|
62
|
+
function createBrain(config?: AppBrainConfig) {
|
|
63
|
+
const provider = config?.provider || 'openrouter';
|
|
33
64
|
const defaultKeyEnv = provider === 'openai-compatible' ? 'OPENAI_COMPATIBLE_API_KEY' : 'OPENROUTER_API_KEY';
|
|
34
|
-
const apiKey = config
|
|
65
|
+
const apiKey = config?.apiKey || process.env[config?.apiKeyEnv || defaultKeyEnv] || '';
|
|
35
66
|
if (provider === 'openai-compatible') {
|
|
36
67
|
return new OpenAICompatibleBrain({
|
|
37
68
|
apiKey,
|
|
38
|
-
model: config
|
|
39
|
-
baseUrl: config
|
|
69
|
+
model: config?.model || 'local-model',
|
|
70
|
+
baseUrl: config?.baseUrl || 'http://127.0.0.1:1234/v1',
|
|
40
71
|
});
|
|
41
72
|
}
|
|
42
|
-
return new OpenRouterBrain({ apiKey, model: config
|
|
73
|
+
return new OpenRouterBrain({ apiKey, model: config?.model || 'gpt-4o-mini' });
|
|
43
74
|
}
|
|
44
75
|
|
|
45
|
-
function isDisabled(config
|
|
76
|
+
function isDisabled(config?: { provider?: string }): boolean {
|
|
46
77
|
return !config || config.provider === 'none';
|
|
47
78
|
}
|
|
48
79
|
|
|
49
|
-
function createVoice(config
|
|
80
|
+
function createVoice(config?: VoiceConfig) {
|
|
50
81
|
return isDisabled(config)
|
|
51
82
|
? undefined
|
|
52
|
-
: new VoiceAdapter({
|
|
83
|
+
: new VoiceAdapter({
|
|
84
|
+
provider: (config?.provider as any) || 'voicevox',
|
|
85
|
+
baseUrl: config?.baseUrl || process.env.VOICEVOX_URL || 'http://localhost:50021',
|
|
86
|
+
speakerId: config?.speakerId || 1,
|
|
87
|
+
...config,
|
|
88
|
+
});
|
|
53
89
|
}
|
|
54
90
|
|
|
55
|
-
function createKnowledge(config
|
|
56
|
-
return isDisabled(config) ? undefined : new EKnowledgeAdapter(config);
|
|
91
|
+
function createKnowledge(config?: EKnowledgeConfig) {
|
|
92
|
+
return isDisabled(config) ? undefined : new EKnowledgeAdapter(config || {});
|
|
57
93
|
}
|
|
58
94
|
|
|
59
|
-
function createVision(config
|
|
95
|
+
function createVision(config?: OpenRouterVisionConfig & { provider?: string }) {
|
|
60
96
|
return isDisabled(config)
|
|
61
97
|
? undefined
|
|
62
|
-
: new OpenRouterVisionAdapter({
|
|
98
|
+
: new OpenRouterVisionAdapter({
|
|
99
|
+
apiKey: config?.apiKey || process.env.OPENROUTER_API_KEY || '',
|
|
100
|
+
model: config?.model || 'gpt-4-vision',
|
|
101
|
+
...config,
|
|
102
|
+
});
|
|
63
103
|
}
|
|
64
104
|
|
|
65
|
-
function createBehavior(config
|
|
105
|
+
function createBehavior(config?: AppBehaviorConfig) {
|
|
66
106
|
return isDisabled(config) ? undefined : new ActiveSelfCompiler();
|
|
67
107
|
}
|
|
68
108
|
|
|
69
|
-
function createBody(config
|
|
109
|
+
function createBody(config?: Live2DAdapterConfig & { provider?: string }) {
|
|
70
110
|
return isDisabled(config)
|
|
71
111
|
? undefined
|
|
72
112
|
: new Live2DAdapter(config);
|
|
73
113
|
}
|
|
74
114
|
|
|
75
|
-
function createHands(config
|
|
115
|
+
function createHands(config?: DefaultHandsOrganConfig & { provider?: string }) {
|
|
76
116
|
return isDisabled(config)
|
|
77
117
|
? new DefaultHandsOrgan()
|
|
78
118
|
: new DefaultHandsOrgan(config);
|
|
79
119
|
}
|
|
80
120
|
|
|
81
|
-
function createEar(config
|
|
121
|
+
function createEar(config?: EarOrganConfig & { provider?: string }) {
|
|
82
122
|
return isDisabled(config)
|
|
83
123
|
? new DefaultEarOrgan()
|
|
84
124
|
: new DefaultEarOrgan(config);
|
|
85
125
|
}
|
|
86
126
|
|
|
87
|
-
|
|
127
|
+
function createMouth(config?: DefaultMouthOrganConfig & { provider?: string }, voice?: any) {
|
|
128
|
+
return isDisabled(config)
|
|
129
|
+
? new DefaultMouthOrgan({ voice })
|
|
130
|
+
: new DefaultMouthOrgan({ ...config, voice });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
app.post('/boot', requireAuth, async (req, res) => {
|
|
88
134
|
try {
|
|
89
135
|
const { id, config } = req.body;
|
|
90
136
|
if (runtimes.has(id)) {
|
|
@@ -100,8 +146,21 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
100
146
|
const body = createBody(config.body);
|
|
101
147
|
const hands = createHands(config.hands);
|
|
102
148
|
const ear = createEar(config.ear);
|
|
103
|
-
|
|
104
|
-
|
|
149
|
+
const mouth = createMouth(config.mouth, voice);
|
|
150
|
+
|
|
151
|
+
const runtime = new SiduriRuntime(id, config, {
|
|
152
|
+
brain,
|
|
153
|
+
memory,
|
|
154
|
+
voice,
|
|
155
|
+
knowledge,
|
|
156
|
+
vision,
|
|
157
|
+
behavior,
|
|
158
|
+
body,
|
|
159
|
+
hands,
|
|
160
|
+
ear,
|
|
161
|
+
mouth,
|
|
162
|
+
observation: observationOrgan,
|
|
163
|
+
});
|
|
105
164
|
await runtime.initialize();
|
|
106
165
|
|
|
107
166
|
runtimes.set(id, runtime);
|
|
@@ -126,43 +185,45 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
126
185
|
const hasVoice = Array.from(runtimes.values()).some((r) => Boolean(r.voice));
|
|
127
186
|
res.json({ provider: "siduri-voice", configured: hasVoice });
|
|
128
187
|
});
|
|
129
|
-
app.get('/obs/health', (req, res) =>
|
|
188
|
+
app.get('/obs/health', (req, res) => {
|
|
189
|
+
const connected = Boolean(observationOrgan) || Array.from(runtimes.values()).some((r) => Boolean(r.observation));
|
|
190
|
+
res.json({ connected });
|
|
191
|
+
});
|
|
192
|
+
app.get('/mouth/health', (req, res) => {
|
|
193
|
+
const hasMouth = Array.from(runtimes.values()).some((r) => Boolean(r.mouth));
|
|
194
|
+
res.json({ provider: "siduri-mouth", configured: hasMouth });
|
|
195
|
+
});
|
|
196
|
+
app.get('/mouth/channels', (req, res) => {
|
|
197
|
+
const id = (req.query.id as string) || Array.from(runtimes.keys())[0];
|
|
198
|
+
const runtime = runtimes.get(id);
|
|
199
|
+
if (!runtime || !runtime.mouth || typeof runtime.mouth.getRegisteredChannels !== 'function') {
|
|
200
|
+
return res.json({ channels: [] });
|
|
201
|
+
}
|
|
202
|
+
res.json({ channels: runtime.mouth.getRegisteredChannels() });
|
|
203
|
+
});
|
|
130
204
|
app.get('/me', attachIdentity, (req, res) => {
|
|
131
205
|
const identity = (req as any).identity as Identity;
|
|
132
206
|
res.json({
|
|
133
|
-
actorId: identity.
|
|
134
|
-
role: identity.role,
|
|
135
|
-
authenticated: identity.
|
|
207
|
+
actorId: identity.actorId || (identity.authenticated ? 'owner-user' : 'anonymous-session'),
|
|
208
|
+
role: identity.role || (identity.authenticated ? 'OWNER' : 'VIEWER'),
|
|
209
|
+
authenticated: Boolean(identity.authenticated),
|
|
136
210
|
});
|
|
137
211
|
});
|
|
138
|
-
app.put('/me',
|
|
212
|
+
app.put('/me', requireAuth, (req, res) => res.json({ success: true }));
|
|
139
213
|
|
|
140
214
|
// CHAT (API context boundary validation)
|
|
141
215
|
app.post('/chat', attachIdentity, async (req, res) => {
|
|
142
216
|
const { id, message, history } = req.body;
|
|
143
217
|
const identity = (req as any).identity as Identity;
|
|
144
218
|
|
|
145
|
-
// Single-owner companion model:
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
:
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const mappingResult = mapRequestContext(
|
|
154
|
-
{
|
|
155
|
-
...req.body,
|
|
156
|
-
id: id || req.body.companionId,
|
|
157
|
-
role: effectiveRole,
|
|
158
|
-
authenticated: isOwner,
|
|
159
|
-
generateCorrelationId: true,
|
|
160
|
-
},
|
|
161
|
-
{
|
|
162
|
-
endpointPolicy: 'public',
|
|
163
|
-
defaultPublicAudience: 'audience-public',
|
|
164
|
-
}
|
|
165
|
-
);
|
|
219
|
+
// Single-owner companion model: map request context directly
|
|
220
|
+
const mappingResult = mapRequestContext({
|
|
221
|
+
...req.body,
|
|
222
|
+
id: id || req.body.companionId,
|
|
223
|
+
authenticated: identity?.authenticated ?? true,
|
|
224
|
+
source: identity?.source ?? 'local',
|
|
225
|
+
generateCorrelationId: true,
|
|
226
|
+
});
|
|
166
227
|
|
|
167
228
|
if (!mappingResult.accepted) {
|
|
168
229
|
return res.status(400).json({
|
|
@@ -182,6 +243,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
182
243
|
message,
|
|
183
244
|
context: mappingResult.context,
|
|
184
245
|
history,
|
|
246
|
+
...(req.body?.medium ? { medium: req.body.medium } : {}),
|
|
185
247
|
});
|
|
186
248
|
res.json(response);
|
|
187
249
|
} catch (e: any) {
|
|
@@ -189,53 +251,183 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
189
251
|
}
|
|
190
252
|
});
|
|
191
253
|
|
|
254
|
+
// REAL-TIME SSE STREAMING (Mouth transport)
|
|
255
|
+
app.post('/chat/stream', attachIdentity, async (req, res) => {
|
|
256
|
+
const { id, message, history } = req.body;
|
|
257
|
+
const identity = (req as any).identity as Identity;
|
|
258
|
+
|
|
259
|
+
const mappingResult = mapRequestContext({
|
|
260
|
+
...req.body,
|
|
261
|
+
id: id || req.body.companionId,
|
|
262
|
+
authenticated: identity?.authenticated ?? true,
|
|
263
|
+
source: identity?.source ?? 'local',
|
|
264
|
+
generateCorrelationId: true,
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
if (!mappingResult.accepted) {
|
|
268
|
+
return res.status(400).json({
|
|
269
|
+
accepted: false,
|
|
270
|
+
error: mappingResult.error,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const companionId = mappingResult.context!.companionId;
|
|
275
|
+
const runtime = runtimes.get(companionId);
|
|
276
|
+
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
277
|
+
|
|
278
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
279
|
+
res.setHeader('Cache-Control', 'no-cache, no-transform');
|
|
280
|
+
res.setHeader('Connection', 'keep-alive');
|
|
281
|
+
res.flushHeaders?.();
|
|
282
|
+
|
|
283
|
+
const abortController = new AbortController();
|
|
284
|
+
const onClose = () => {
|
|
285
|
+
abortController.abort('client_disconnect');
|
|
286
|
+
if (typeof runtime.interruptMouth === 'function') {
|
|
287
|
+
runtime.interruptMouth('client_disconnect');
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
req.on('close', onClose);
|
|
291
|
+
|
|
292
|
+
try {
|
|
293
|
+
const response = await dispatchCompanionChat(runtime, {
|
|
294
|
+
id: companionId,
|
|
295
|
+
companionId,
|
|
296
|
+
message,
|
|
297
|
+
context: mappingResult.context,
|
|
298
|
+
history,
|
|
299
|
+
medium: 'web',
|
|
300
|
+
signal: abortController.signal,
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
res.write(`event: staged\ndata: ${JSON.stringify({ response_id: response.response_id, correlation_id: response.correlation_id, status: response.status })}\n\n`);
|
|
304
|
+
|
|
305
|
+
const avatarEvent = response.metadata?.events?.find(
|
|
306
|
+
(e: any) => (e.kind === 'avatar' || e.kind === 'body') && (e.approval === 'APPROVED' || !e.approval)
|
|
307
|
+
);
|
|
308
|
+
if (avatarEvent) {
|
|
309
|
+
res.write(`event: avatar\ndata: ${JSON.stringify(avatarEvent)}\n\n`);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const speechText = response.delivery?.text || response.response?.subtitle_en || response.response?.spoken_ja || '';
|
|
313
|
+
const utterance = {
|
|
314
|
+
utteranceId: response.response_id || 'utt-stream',
|
|
315
|
+
companionId,
|
|
316
|
+
responseId: response.response_id,
|
|
317
|
+
correlationId: response.correlation_id,
|
|
318
|
+
text: speechText,
|
|
319
|
+
medium: 'web' as const,
|
|
320
|
+
expression: avatarEvent?.expression,
|
|
321
|
+
action: avatarEvent?.action,
|
|
322
|
+
signal: abortController.signal,
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
if (runtime.mouth && typeof runtime.mouth.stream === 'function') {
|
|
326
|
+
for await (const chunk of runtime.mouth.stream(utterance)) {
|
|
327
|
+
if (abortController.signal.aborted) {
|
|
328
|
+
res.write(`event: chunk\ndata: ${JSON.stringify({ ...chunk, interrupted: true })}\n\n`);
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
res.write(`event: chunk\ndata: ${JSON.stringify(chunk)}\n\n`);
|
|
332
|
+
}
|
|
333
|
+
} else {
|
|
334
|
+
res.write(`event: chunk\ndata: ${JSON.stringify({ utteranceId: utterance.utteranceId, index: 1, deltaText: speechText, isComplete: true, medium: 'web' })}\n\n`);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
res.write(`event: done\ndata: ${JSON.stringify(response)}\n\n`);
|
|
338
|
+
res.end();
|
|
339
|
+
} catch (e: any) {
|
|
340
|
+
if (abortController.signal.aborted) {
|
|
341
|
+
res.write(`event: interrupted\ndata: ${JSON.stringify({ reason: abortController.signal.reason })}\n\n`);
|
|
342
|
+
} else {
|
|
343
|
+
res.write(`event: error\ndata: ${JSON.stringify({ error: e.message })}\n\n`);
|
|
344
|
+
}
|
|
345
|
+
res.end();
|
|
346
|
+
} finally {
|
|
347
|
+
req.removeListener('close', onClose);
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
// INTERRUPTION / BARGE-IN
|
|
352
|
+
app.post('/chat/interrupt', attachIdentity, (req, res) => {
|
|
353
|
+
const companionId = req.body?.companionId || req.body?.id || Array.from(runtimes.keys())[0];
|
|
354
|
+
const runtime = companionId ? runtimes.get(companionId) : undefined;
|
|
355
|
+
const reason = req.body?.reason || 'user_barge_in';
|
|
356
|
+
|
|
357
|
+
if (runtime) {
|
|
358
|
+
runtime.interruptMouth(reason);
|
|
359
|
+
return res.json({ success: true, interrupted: true, companionId, reason });
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
for (const r of runtimes.values()) {
|
|
363
|
+
r.interruptMouth(reason);
|
|
364
|
+
}
|
|
365
|
+
return res.json({ success: true, interrupted: true, reason });
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
app.post('/mouth/interrupt', attachIdentity, (req, res) => {
|
|
369
|
+
const companionId = req.body?.companionId || req.body?.id || Array.from(runtimes.keys())[0];
|
|
370
|
+
const runtime = companionId ? runtimes.get(companionId) : undefined;
|
|
371
|
+
const reason = req.body?.reason || 'user_barge_in';
|
|
372
|
+
|
|
373
|
+
if (runtime) {
|
|
374
|
+
runtime.interruptMouth(reason);
|
|
375
|
+
return res.json({ success: true, interrupted: true, companionId, reason });
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
for (const r of runtimes.values()) {
|
|
379
|
+
r.interruptMouth(reason);
|
|
380
|
+
}
|
|
381
|
+
return res.json({ success: true, interrupted: true, reason });
|
|
382
|
+
});
|
|
383
|
+
|
|
192
384
|
// MEMORY GETTERS
|
|
193
|
-
app.get('/memory/proposals',
|
|
385
|
+
app.get('/memory/proposals', requireAuth, async (req, res) => {
|
|
194
386
|
const id = req.query.id as string || Array.from(runtimes.keys())[0];
|
|
195
387
|
const runtime = runtimes.get(id);
|
|
196
388
|
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
197
389
|
if (!runtime.memory) return res.json({ proposals: [] });
|
|
198
390
|
try {
|
|
199
|
-
const proposals = await runtime.
|
|
391
|
+
const proposals = await runtime.getPendingClaims();
|
|
200
392
|
res.json({ proposals });
|
|
201
393
|
} catch (e: any) {
|
|
202
394
|
res.status(500).json({ error: e.message });
|
|
203
395
|
}
|
|
204
396
|
});
|
|
205
397
|
|
|
206
|
-
app.get('/memory',
|
|
398
|
+
app.get('/memory', requireAuth, async (req, res) => {
|
|
207
399
|
const id = req.query.id as string || Array.from(runtimes.keys())[0];
|
|
208
400
|
const runtime = runtimes.get(id);
|
|
209
401
|
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
210
402
|
if (!runtime.memory) return res.json({ items: [] });
|
|
211
403
|
try {
|
|
212
|
-
const items = await runtime.
|
|
404
|
+
const items = await runtime.getClaims();
|
|
213
405
|
res.json({ items });
|
|
214
406
|
} catch (e: any) {
|
|
215
407
|
res.status(500).json({ error: e.message });
|
|
216
408
|
}
|
|
217
409
|
});
|
|
218
410
|
|
|
219
|
-
app.get('/memory/claims',
|
|
411
|
+
app.get('/memory/claims', requireAuth, async (req, res) => {
|
|
220
412
|
const id = req.query.id as string || Array.from(runtimes.keys())[0];
|
|
221
413
|
const runtime = runtimes.get(id);
|
|
222
414
|
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
223
415
|
if (!runtime.memory) return res.json({ claims: [] });
|
|
224
416
|
try {
|
|
225
|
-
const claims = await runtime.
|
|
417
|
+
const claims = await runtime.getClaims();
|
|
226
418
|
res.json({ claims });
|
|
227
419
|
} catch (e: any) {
|
|
228
420
|
res.status(500).json({ error: e.message });
|
|
229
421
|
}
|
|
230
422
|
});
|
|
231
423
|
|
|
232
|
-
app.get('/memory/behavioral',
|
|
424
|
+
app.get('/memory/behavioral', requireAuth, async (req, res) => {
|
|
233
425
|
const id = req.query.id as string || Array.from(runtimes.keys())[0];
|
|
234
426
|
const runtime = runtimes.get(id);
|
|
235
427
|
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
236
428
|
if (!runtime.memory) return res.json({ directives: [] });
|
|
237
429
|
try {
|
|
238
|
-
const directives = await runtime.
|
|
430
|
+
const directives = await runtime.getDirectives();
|
|
239
431
|
res.json({ directives });
|
|
240
432
|
} catch (e: any) {
|
|
241
433
|
res.status(500).json({ error: e.message });
|
|
@@ -243,7 +435,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
243
435
|
});
|
|
244
436
|
|
|
245
437
|
// MEMORY MUTATIONS - PROPOSALS
|
|
246
|
-
app.post('/memory/proposals/update',
|
|
438
|
+
app.post('/memory/proposals/update', requireAuth, async (req, res) => {
|
|
247
439
|
const id = req.body.companionId as string || Array.from(runtimes.keys())[0];
|
|
248
440
|
const runtime = runtimes.get(id);
|
|
249
441
|
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
@@ -255,33 +447,33 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
255
447
|
if (!claimId) {
|
|
256
448
|
return res.status(400).json({ error: "Missing required claim id" });
|
|
257
449
|
}
|
|
258
|
-
const updated = await runtime.
|
|
450
|
+
const updated = await runtime.updateClaim(claimId, req.body.updates || req.body);
|
|
259
451
|
res.json({ success: true, claim: updated });
|
|
260
452
|
} catch (e: any) {
|
|
261
453
|
res.status(500).json({ error: e.message });
|
|
262
454
|
}
|
|
263
455
|
});
|
|
264
456
|
|
|
265
|
-
app.post('/memory/proposals/approve',
|
|
457
|
+
app.post('/memory/proposals/approve', requireAuth, async (req, res) => {
|
|
266
458
|
const id = req.body.companionId as string || Array.from(runtimes.keys())[0];
|
|
267
459
|
const runtime = runtimes.get(id);
|
|
268
460
|
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
269
461
|
if (!runtime.memory) return res.status(400).json({ error: "Memory organ not configured" });
|
|
270
462
|
try {
|
|
271
|
-
await runtime.
|
|
463
|
+
await runtime.approveClaim(req.body.id);
|
|
272
464
|
res.json({ approved: true });
|
|
273
465
|
} catch (e: any) {
|
|
274
466
|
res.status(500).json({ error: e.message });
|
|
275
467
|
}
|
|
276
468
|
});
|
|
277
469
|
|
|
278
|
-
app.post('/memory/proposals/reject',
|
|
470
|
+
app.post('/memory/proposals/reject', requireAuth, async (req, res) => {
|
|
279
471
|
const id = req.body.companionId as string || Array.from(runtimes.keys())[0];
|
|
280
472
|
const runtime = runtimes.get(id);
|
|
281
473
|
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
282
474
|
if (!runtime.memory) return res.status(400).json({ error: "Memory organ not configured" });
|
|
283
475
|
try {
|
|
284
|
-
await runtime.
|
|
476
|
+
await runtime.rejectClaim(req.body.id);
|
|
285
477
|
res.json({ rejected: true });
|
|
286
478
|
} catch (e: any) {
|
|
287
479
|
res.status(500).json({ error: e.message });
|
|
@@ -289,52 +481,52 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
289
481
|
});
|
|
290
482
|
|
|
291
483
|
// MEMORY MUTATIONS - BEHAVIORAL
|
|
292
|
-
app.post('/memory/behavioral/approve',
|
|
484
|
+
app.post('/memory/behavioral/approve', requireAuth, async (req, res) => {
|
|
293
485
|
const id = req.body.companionId as string || Array.from(runtimes.keys())[0];
|
|
294
486
|
const runtime = runtimes.get(id);
|
|
295
487
|
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
296
488
|
if (!runtime.memory) return res.status(400).json({ error: "Memory organ not configured" });
|
|
297
489
|
try {
|
|
298
|
-
await runtime.
|
|
490
|
+
await runtime.approveDirective(req.body.id);
|
|
299
491
|
res.json({ approved: true });
|
|
300
492
|
} catch (e: any) {
|
|
301
493
|
res.status(500).json({ error: e.message });
|
|
302
494
|
}
|
|
303
495
|
});
|
|
304
496
|
|
|
305
|
-
app.post('/memory/behavioral/reject',
|
|
497
|
+
app.post('/memory/behavioral/reject', requireAuth, async (req, res) => {
|
|
306
498
|
const id = req.body.companionId as string || Array.from(runtimes.keys())[0];
|
|
307
499
|
const runtime = runtimes.get(id);
|
|
308
500
|
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
309
501
|
if (!runtime.memory) return res.status(400).json({ error: "Memory organ not configured" });
|
|
310
502
|
try {
|
|
311
|
-
await runtime.
|
|
503
|
+
await runtime.rejectDirective(req.body.id);
|
|
312
504
|
res.json({ rejected: true });
|
|
313
505
|
} catch (e: any) {
|
|
314
506
|
res.status(500).json({ error: e.message });
|
|
315
507
|
}
|
|
316
508
|
});
|
|
317
509
|
|
|
318
|
-
app.post('/memory/behavioral/revoke',
|
|
510
|
+
app.post('/memory/behavioral/revoke', requireAuth, async (req, res) => {
|
|
319
511
|
const id = req.body.companionId as string || Array.from(runtimes.keys())[0];
|
|
320
512
|
const runtime = runtimes.get(id);
|
|
321
513
|
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
322
514
|
if (!runtime.memory) return res.status(400).json({ error: "Memory organ not configured" });
|
|
323
515
|
try {
|
|
324
|
-
await runtime.
|
|
516
|
+
await runtime.revokeDirective(req.body.id);
|
|
325
517
|
res.json({ revoked: true });
|
|
326
518
|
} catch (e: any) {
|
|
327
519
|
res.status(500).json({ error: e.message });
|
|
328
520
|
}
|
|
329
521
|
});
|
|
330
522
|
|
|
331
|
-
app.post('/memory/behavioral/disable',
|
|
523
|
+
app.post('/memory/behavioral/disable', requireAuth, async (req, res) => {
|
|
332
524
|
const id = req.body.companionId as string || Array.from(runtimes.keys())[0];
|
|
333
525
|
const runtime = runtimes.get(id);
|
|
334
526
|
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
335
527
|
if (!runtime.memory) return res.status(400).json({ error: "Memory organ not configured" });
|
|
336
528
|
try {
|
|
337
|
-
await runtime.
|
|
529
|
+
await runtime.disableDirective(req.body.id);
|
|
338
530
|
res.json({ disabled: true });
|
|
339
531
|
} catch (e: any) {
|
|
340
532
|
res.status(500).json({ error: e.message });
|
|
@@ -343,7 +535,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
343
535
|
|
|
344
536
|
const isDevMode = process.env.NODE_ENV !== 'production' || process.env.SIDURI_DEV_MODE === 'true';
|
|
345
537
|
if (isDevMode) {
|
|
346
|
-
app.post('/dev/memory/reset',
|
|
538
|
+
app.post('/dev/memory/reset', requireAuth, async (req, res) => {
|
|
347
539
|
const id = req.body.companionId as string || Array.from(runtimes.keys())[0];
|
|
348
540
|
const runtime = runtimes.get(id);
|
|
349
541
|
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
@@ -351,7 +543,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
351
543
|
return res.status(400).json({ error: "Memory organ does not support reset" });
|
|
352
544
|
}
|
|
353
545
|
try {
|
|
354
|
-
await runtime.
|
|
546
|
+
await runtime.resetMemory();
|
|
355
547
|
res.json({ reset: true });
|
|
356
548
|
} catch (e: any) {
|
|
357
549
|
res.status(500).json({ error: e.message });
|
|
@@ -362,19 +554,16 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
362
554
|
const companionId = req.body?.companionId || Array.from(runtimes.keys())[0] || 'default';
|
|
363
555
|
const runtime = runtimes.get(companionId);
|
|
364
556
|
if (!runtime) return res.status(404).json({ accepted: false, error: 'Companion not found' });
|
|
365
|
-
const staged = runtime.
|
|
557
|
+
const staged = runtime.stageResponse({
|
|
366
558
|
requestContext: {
|
|
367
559
|
companionId,
|
|
368
560
|
actor: {
|
|
369
|
-
actorId: '
|
|
370
|
-
sessionId: 'sess-
|
|
371
|
-
|
|
372
|
-
capabilities: ['chat:public', 'memory:approve'],
|
|
561
|
+
actorId: 'local-user',
|
|
562
|
+
sessionId: 'sess-local',
|
|
563
|
+
capabilities: ['chat', 'memory:approve'],
|
|
373
564
|
authenticated: true,
|
|
374
565
|
},
|
|
375
566
|
conversation: {
|
|
376
|
-
channel: 'public',
|
|
377
|
-
audienceId: 'audience-public',
|
|
378
567
|
correlationId: req.body?.correlation_id || `corr-${Date.now()}`,
|
|
379
568
|
},
|
|
380
569
|
},
|
|
@@ -401,12 +590,12 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
401
590
|
let correlationId = req.body?.correlation_id;
|
|
402
591
|
|
|
403
592
|
if (!responseId && correlationId) {
|
|
404
|
-
const found = runtime.
|
|
593
|
+
const found = runtime.findStagedPlanByCorrelation(companionId, correlationId);
|
|
405
594
|
if (found) {
|
|
406
595
|
responseId = found.responseId;
|
|
407
596
|
}
|
|
408
597
|
} else if (responseId && !correlationId) {
|
|
409
|
-
const found = runtime.
|
|
598
|
+
const found = runtime.getStagedPlan(responseId);
|
|
410
599
|
if (found) {
|
|
411
600
|
correlationId = found.correlationId;
|
|
412
601
|
}
|
|
@@ -416,11 +605,10 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
416
605
|
return res.status(400).json({ approved: false, error: 'UNKNOWN_APPROVAL_ID' });
|
|
417
606
|
}
|
|
418
607
|
|
|
419
|
-
const result = runtime.
|
|
608
|
+
const result = runtime.approveResponse({
|
|
420
609
|
responseId,
|
|
421
610
|
companionId,
|
|
422
611
|
correlationId: correlationId || '',
|
|
423
|
-
audienceId: req.body?.audienceId,
|
|
424
612
|
});
|
|
425
613
|
|
|
426
614
|
if (!result.success) {
|
|
@@ -428,7 +616,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
428
616
|
}
|
|
429
617
|
|
|
430
618
|
// Now evaluated as approved
|
|
431
|
-
const evaluation = runtime.
|
|
619
|
+
const evaluation = runtime.evaluateGate(result.plan!);
|
|
432
620
|
res.json({
|
|
433
621
|
approved: true,
|
|
434
622
|
status: evaluation.disposition,
|
|
@@ -447,12 +635,12 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
447
635
|
let correlationId = req.body?.correlation_id;
|
|
448
636
|
|
|
449
637
|
if (!responseId && correlationId) {
|
|
450
|
-
const found = runtime.
|
|
638
|
+
const found = runtime.findStagedPlanByCorrelation(companionId, correlationId);
|
|
451
639
|
if (found) {
|
|
452
640
|
responseId = found.responseId;
|
|
453
641
|
}
|
|
454
642
|
} else if (responseId && !correlationId) {
|
|
455
|
-
const found = runtime.
|
|
643
|
+
const found = runtime.getStagedPlan(responseId);
|
|
456
644
|
if (found) {
|
|
457
645
|
correlationId = found.correlationId;
|
|
458
646
|
}
|
|
@@ -462,7 +650,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
462
650
|
return res.status(400).json({ rejected: false, error: 'UNKNOWN_APPROVAL_ID' });
|
|
463
651
|
}
|
|
464
652
|
|
|
465
|
-
const result = runtime.
|
|
653
|
+
const result = runtime.rejectResponse({
|
|
466
654
|
responseId,
|
|
467
655
|
companionId,
|
|
468
656
|
correlationId: correlationId || '',
|
|
@@ -481,8 +669,10 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
481
669
|
});
|
|
482
670
|
|
|
483
671
|
app.post('/dev/mock-observation', async (req, res) => {
|
|
484
|
-
|
|
485
|
-
const
|
|
672
|
+
const activeRuntime = Array.from(runtimes.values())[0];
|
|
673
|
+
const targetObs = activeRuntime?.observation || observationOrgan;
|
|
674
|
+
if (!targetObs) return res.status(503).json({ accepted: false, reason: 'observation_unavailable' });
|
|
675
|
+
const result = await targetObs.ingest(
|
|
486
676
|
new Uint8Array([115, 121, 110, 116, 104, 101, 116, 105, 99]),
|
|
487
677
|
'fixture-observation',
|
|
488
678
|
'configured-vision',
|
|
@@ -492,5 +682,14 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
492
682
|
});
|
|
493
683
|
}
|
|
494
684
|
|
|
495
|
-
return {
|
|
685
|
+
return {
|
|
686
|
+
app,
|
|
687
|
+
runtimes,
|
|
688
|
+
setObservationOrgan: (org: FixtureObservationOrgan) => {
|
|
689
|
+
observationOrgan = org;
|
|
690
|
+
for (const r of runtimes.values()) {
|
|
691
|
+
r.observation = org;
|
|
692
|
+
}
|
|
693
|
+
},
|
|
694
|
+
};
|
|
496
695
|
}
|