@sidurijs/api 1.0.0

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.
Files changed (66) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/.turbo/turbo-test.log +78 -0
  3. package/dist/app.d.ts +11 -0
  4. package/dist/app.js +1190 -0
  5. package/dist/auth.d.ts +34 -0
  6. package/dist/auth.js +84 -0
  7. package/dist/auth.test.d.ts +1 -0
  8. package/dist/auth.test.js +74 -0
  9. package/dist/b0-b6.test.d.ts +1 -0
  10. package/dist/b0-b6.test.js +121 -0
  11. package/dist/boot.d.ts +91 -0
  12. package/dist/boot.js +154 -0
  13. package/dist/boot.test.d.ts +1 -0
  14. package/dist/boot.test.js +91 -0
  15. package/dist/context-mapper.d.ts +20 -0
  16. package/dist/context-mapper.js +233 -0
  17. package/dist/context-mapper.test.d.ts +1 -0
  18. package/dist/context-mapper.test.js +287 -0
  19. package/dist/cors.d.ts +3 -0
  20. package/dist/cors.js +42 -0
  21. package/dist/index.d.ts +7 -0
  22. package/dist/index.js +116 -0
  23. package/dist/index.test.d.ts +1 -0
  24. package/dist/index.test.js +151 -0
  25. package/dist/knowledge.test.d.ts +1 -0
  26. package/dist/knowledge.test.js +236 -0
  27. package/dist/runtime.d.ts +1 -0
  28. package/dist/runtime.js +17 -0
  29. package/dist/runtime.test.d.ts +1 -0
  30. package/dist/runtime.test.js +282 -0
  31. package/dist/smoke.test.d.ts +0 -0
  32. package/dist/smoke.test.js +6 -0
  33. package/dist/t4-gating.test.d.ts +1 -0
  34. package/dist/t4-gating.test.js +193 -0
  35. package/dist/t5-experience.test.d.ts +1 -0
  36. package/dist/t5-experience.test.js +155 -0
  37. package/dist/t6-security.test.d.ts +1 -0
  38. package/dist/t6-security.test.js +535 -0
  39. package/dist/t7-release.test.d.ts +1 -0
  40. package/dist/t7-release.test.js +119 -0
  41. package/dist/teach-mode.test.d.ts +1 -0
  42. package/dist/teach-mode.test.js +277 -0
  43. package/jest.config.json +5 -0
  44. package/package.json +44 -0
  45. package/siduri.sqlite +0 -0
  46. package/src/app.ts +1209 -0
  47. package/src/auth.test.ts +90 -0
  48. package/src/auth.ts +101 -0
  49. package/src/b0-b6.test.ts +137 -0
  50. package/src/boot.test.ts +112 -0
  51. package/src/boot.ts +197 -0
  52. package/src/context-mapper.test.ts +319 -0
  53. package/src/context-mapper.ts +279 -0
  54. package/src/cors.ts +44 -0
  55. package/src/index.test.ts +178 -0
  56. package/src/index.ts +101 -0
  57. package/src/knowledge.test.ts +267 -0
  58. package/src/runtime.test.ts +336 -0
  59. package/src/runtime.ts +1 -0
  60. package/src/smoke.test.ts +5 -0
  61. package/src/t4-gating.test.ts +219 -0
  62. package/src/t5-experience.test.ts +174 -0
  63. package/src/t6-security.test.ts +589 -0
  64. package/src/t7-release.test.ts +131 -0
  65. package/src/teach-mode.test.ts +312 -0
  66. package/tsconfig.json +16 -0
@@ -0,0 +1,174 @@
1
+ import request from 'supertest';
2
+ import { createApp } from './app';
3
+ import { SiduriRuntime } from './runtime';
4
+ import { BrainContext, ResponsePlan, ExperienceAdapter, ExperienceEvent, ExperienceAdapterResult } from '@sidurijs/core';
5
+
6
+ describe('T5 Experience Event and Output Adapters Suite', () => {
7
+ let mockBrain: any;
8
+ let mockMemory: any;
9
+ let mockKnowledge: any;
10
+ let mockBehavior: any;
11
+ let mockVoiceAdapter: ExperienceAdapter;
12
+ let mockAvatarAdapter: ExperienceAdapter;
13
+ let runtime: SiduriRuntime;
14
+ let app: any;
15
+
16
+ beforeEach(async () => {
17
+ mockBrain = {
18
+ generatePlan: jest.fn().mockImplementation(async (ctx: BrainContext): Promise<ResponsePlan> => {
19
+ return {
20
+ speech: 'Approved speech for delivery.',
21
+ language: 'en',
22
+ };
23
+ }),
24
+ };
25
+
26
+ mockMemory = {
27
+ initialize: jest.fn().mockResolvedValue(undefined),
28
+ searchClaims: jest.fn().mockResolvedValue([]),
29
+ getClaims: jest.fn().mockResolvedValue([]),
30
+ getDirectives: jest.fn().mockResolvedValue([]),
31
+ getPendingClaims: jest.fn().mockResolvedValue([]),
32
+ proposeClaim: jest.fn().mockResolvedValue({ id: 'claim-1', status: 'PENDING' }),
33
+ approveClaim: jest.fn().mockResolvedValue(undefined),
34
+ rejectClaim: jest.fn().mockResolvedValue(undefined),
35
+ };
36
+
37
+ mockKnowledge = {
38
+ search: jest.fn().mockResolvedValue([]),
39
+ };
40
+
41
+ mockBehavior = {
42
+ compile: jest.fn().mockResolvedValue(''),
43
+ };
44
+
45
+ mockVoiceAdapter = {
46
+ kind: 'voice',
47
+ handleEvent: jest.fn().mockImplementation(async (event: ExperienceEvent): Promise<ExperienceAdapterResult> => ({
48
+ accepted: true,
49
+ eventId: event.eventId,
50
+ lifecycle: 'STARTED',
51
+ metadata: { speechId: 'speech-t5-voice' },
52
+ })),
53
+ };
54
+
55
+ mockAvatarAdapter = {
56
+ kind: 'avatar',
57
+ handleEvent: jest.fn().mockImplementation(async (event: ExperienceEvent): Promise<ExperienceAdapterResult> => ({
58
+ accepted: true,
59
+ eventId: event.eventId,
60
+ lifecycle: 'STARTED',
61
+ })),
62
+ };
63
+
64
+ const config = {
65
+ name: 'NeutralCompanion',
66
+ brain: { provider: 'openrouter' },
67
+ memory: { provider: 'sqlite' },
68
+ knowledge: { provider: 'e-knowledge' },
69
+ behavior: { provider: 'active-self' },
70
+ voice: { provider: 'voicevox' },
71
+ vision: { provider: 'none' },
72
+ body: { provider: 'live2d' },
73
+ };
74
+
75
+ runtime = new SiduriRuntime('companion-a', config as any, {
76
+ brain: mockBrain,
77
+ memory: mockMemory,
78
+ knowledge: mockKnowledge,
79
+ behavior: mockBehavior,
80
+ voice: mockVoiceAdapter as any,
81
+ body: mockAvatarAdapter as any,
82
+ });
83
+ await runtime.initialize();
84
+
85
+ const runtimes = new Map([['companion-a', runtime]]);
86
+ const created = createApp(runtimes);
87
+ app = created.app;
88
+ });
89
+
90
+ test('1. Approved T4 response generates and dispatches ExperienceEvent to voice and avatar adapters', async () => {
91
+ const res = await request(app)
92
+ .post('/chat')
93
+ .send({
94
+ companionId: 'companion-a',
95
+ message: 'Hello experience world',
96
+ history: [],
97
+ });
98
+
99
+ expect(res.status).toBe(200);
100
+ expect(mockVoiceAdapter.handleEvent).toHaveBeenCalledTimes(1);
101
+ expect(mockAvatarAdapter.handleEvent).toHaveBeenCalledTimes(1);
102
+
103
+ const voiceCallArg = (mockVoiceAdapter.handleEvent as jest.Mock).mock.calls[0][0] as ExperienceEvent;
104
+ expect(voiceCallArg.approval).toBe('APPROVED');
105
+ expect(voiceCallArg.companionId).toBe('companion-a');
106
+ expect(voiceCallArg.text).toBe('Approved speech for delivery.');
107
+ expect(voiceCallArg.kind).toBe('voice');
108
+
109
+ const avatarCallArg = (mockAvatarAdapter.handleEvent as jest.Mock).mock.calls[0][0] as ExperienceEvent;
110
+ expect(avatarCallArg.approval).toBe('APPROVED');
111
+ expect(avatarCallArg.companionId).toBe('companion-a');
112
+ expect(avatarCallArg.kind).toBe('avatar');
113
+ });
114
+
115
+ test('2. Staged response does NOT dispatch ExperienceEvent to adapters', async () => {
116
+ // Stage a candidate requiring approval
117
+ const stageRes = await request(app)
118
+ .post('/dev/mock-response')
119
+ .send({
120
+ companionId: 'companion-a',
121
+ correlation_id: 'corr-stage-exp-1',
122
+ speech: 'Staged speech pending decision',
123
+ requiresApproval: true,
124
+ });
125
+
126
+ expect(stageRes.status).toBe(200);
127
+ expect(stageRes.body.staged).toBe(true);
128
+
129
+ // Assert that no ExperienceEvents were dispatched to voice/avatar adapters
130
+ expect(mockVoiceAdapter.handleEvent).not.toHaveBeenCalled();
131
+ expect(mockAvatarAdapter.handleEvent).not.toHaveBeenCalled();
132
+ });
133
+
134
+ test('3. Rejected response does NOT dispatch ExperienceEvent to adapters', async () => {
135
+ const stageRes = await request(app)
136
+ .post('/dev/mock-response')
137
+ .send({
138
+ companionId: 'companion-a',
139
+ correlation_id: 'corr-stage-exp-2',
140
+ speech: 'Rejected speech candidate',
141
+ });
142
+
143
+ const responseId = stageRes.body.response_id;
144
+
145
+ await request(app)
146
+ .post('/dev/reject-response')
147
+ .send({
148
+ companionId: 'companion-a',
149
+ responseId,
150
+ correlation_id: 'corr-stage-exp-2',
151
+ });
152
+
153
+ expect(mockVoiceAdapter.handleEvent).not.toHaveBeenCalled();
154
+ expect(mockAvatarAdapter.handleEvent).not.toHaveBeenCalled();
155
+ });
156
+
157
+ test('4. Adapter fails safely on invalid envelope or unapproved event', async () => {
158
+ const invalidEvent = {
159
+ eventId: 'evt-test',
160
+ companionId: 'companion-a',
161
+ responseId: 'resp-1',
162
+ correlationId: 'corr-1',
163
+ channel: 'public' as const,
164
+ approval: 'STAGED' as any, // Not approved!
165
+ kind: 'voice' as const,
166
+ lifecycle: 'STARTED' as const,
167
+ evidenceIds: [],
168
+ createdAt: new Date().toISOString(),
169
+ };
170
+
171
+ const result = await mockVoiceAdapter.handleEvent(invalidEvent);
172
+ expect(result).toBeDefined();
173
+ });
174
+ });