interaqt 0.7.3 → 0.8.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.
- package/agent/.claude/settings.local.json +36 -1
- package/agent/CLAUDE.md +95 -16
- package/agent/agentspace/knowledge/generator/computation-analysis.md +144 -43
- package/agent/agentspace/knowledge/generator/data-analysis.md +484 -63
- package/agent/agentspace/knowledge/generator/integration-implementation-handler.md +550 -0
- package/agent/agentspace/prompt/integration_sub_agent_refactor.md +19 -0
- package/agent/agentspace/prompt/requirement_analysis_refactor.md +1 -1
- package/dist/index.js +540 -592
- package/dist/index.js.map +1 -1
- package/dist/shared/Data.d.ts +30 -57
- package/dist/shared/Data.d.ts.map +1 -1
- package/dist/shared/Interaction.d.ts +6 -6
- package/dist/shared/Interaction.d.ts.map +1 -1
- package/dist/storage/erstorage/RecordQueryAgent.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: integration-implementation-handler
|
|
3
|
+
description: Guide for implementing integrations to connect reactive backend with imperative external systems
|
|
4
|
+
model: inherit
|
|
5
|
+
color: purple
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Integration Implementation Guide
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
**Integrations** bridge the gap between the reactive backend framework and imperative external systems (APIs, services, databases, message queues, etc.). They allow external non-reactive systems to participate in the reactive data flow.
|
|
13
|
+
|
|
14
|
+
## Integration Interface
|
|
15
|
+
|
|
16
|
+
Every integration must implement the `IIntegration` interface:
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
export type IIntegration = {
|
|
20
|
+
configure?:() => Promise<any>
|
|
21
|
+
setup?:(controller: Controller) => Promise<any>
|
|
22
|
+
createSideEffects:() => RecordMutationSideEffect[]
|
|
23
|
+
createAPIs?: () => APIs
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Constructor Arguments
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
export type IIntegrationConstructorArgs = {
|
|
31
|
+
entities: EntityInstance[],
|
|
32
|
+
relations: RelationInstance[],
|
|
33
|
+
activities: ActivityInstance[],
|
|
34
|
+
interactions: InteractionInstance[],
|
|
35
|
+
dict: DictionaryInstance[]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type IIntegrationHandles = {
|
|
39
|
+
[k:string]: any // External handles like websocketServer, etc.
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
class MyIntegration implements IIntegration {
|
|
43
|
+
constructor(
|
|
44
|
+
public args: IIntegrationConstructorArgs,
|
|
45
|
+
public handles: IIntegrationHandles
|
|
46
|
+
) {}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Lifecycle Methods
|
|
51
|
+
|
|
52
|
+
### 1. `configure()` - Schema Augmentation
|
|
53
|
+
|
|
54
|
+
**Purpose**: Modify the reactive schema before system initialization.
|
|
55
|
+
|
|
56
|
+
**Use Cases**:
|
|
57
|
+
- Inject new entities into the system
|
|
58
|
+
- Add computed properties to existing entities
|
|
59
|
+
- Configure state machines for reactive properties
|
|
60
|
+
- Inject computations into relations
|
|
61
|
+
|
|
62
|
+
**Execution Timing**: Before Controller initialization
|
|
63
|
+
|
|
64
|
+
**Example - Injecting Entity**:
|
|
65
|
+
```typescript
|
|
66
|
+
async configure() {
|
|
67
|
+
// Create and inject a new entity for external events
|
|
68
|
+
const TaskEvent = Entity.create({
|
|
69
|
+
name: 'LLMPicGenAsyncTaskEvent',
|
|
70
|
+
properties: [
|
|
71
|
+
Property.create({ name: 'taskId', type: 'string' }),
|
|
72
|
+
Property.create({ name: 'status', type: 'string' }),
|
|
73
|
+
Property.create({ name: 'result', type: 'object' }),
|
|
74
|
+
]
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Inject into entities array
|
|
78
|
+
this.args.entities.push(TaskEvent);
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Example - Injecting Property Computation**:
|
|
83
|
+
```typescript
|
|
84
|
+
async configure() {
|
|
85
|
+
// Find target entities
|
|
86
|
+
const streamEntities = Stream.instances;
|
|
87
|
+
|
|
88
|
+
for (const entity of streamEntities) {
|
|
89
|
+
// Find and inject computation into property
|
|
90
|
+
const urlProperty = entity.properties.find(p => p.name === 'url')!;
|
|
91
|
+
|
|
92
|
+
urlProperty.computation = Custom.create({
|
|
93
|
+
name: 'generateStreamUrl',
|
|
94
|
+
async getInitialValue(this: Controller, record?: any) {
|
|
95
|
+
// Call external API to generate URL
|
|
96
|
+
const timestamp = Math.floor(Date.now() / 1000) + 3600;
|
|
97
|
+
const authString = `/${appName}/${streamName}${key}${timestamp}`;
|
|
98
|
+
const sign = crypto.createHash('md5').update(authString).digest('hex');
|
|
99
|
+
return `rtmp://${domain}/${appName}/${streamName}?volcTime=${timestamp}&volcSecret=${sign}`;
|
|
100
|
+
},
|
|
101
|
+
incrementalCompute: async function(this: { controller: Controller, state: any }, lastValue: any, mutationEvent: any, record: any, dataDeps: any) {
|
|
102
|
+
// Skip recomputation if URL should remain constant
|
|
103
|
+
return ComputationResult.skip();
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Example - Injecting State Machine**:
|
|
111
|
+
```typescript
|
|
112
|
+
async configure() {
|
|
113
|
+
const taskEntities = AsyncTask.instances;
|
|
114
|
+
|
|
115
|
+
for (const taskEntity of taskEntities) {
|
|
116
|
+
const statusProperty = taskEntity.properties.find(p => p.name === 'status');
|
|
117
|
+
|
|
118
|
+
// Create state node
|
|
119
|
+
const statusState = StateNode.create({
|
|
120
|
+
name: 'status',
|
|
121
|
+
computeValue: (lastValue, mutationEvent) => {
|
|
122
|
+
return mutationEvent?.record?.status || lastValue || 'pending';
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Configure state machine
|
|
127
|
+
statusProperty.computation = StateMachine.create({
|
|
128
|
+
states: [statusState],
|
|
129
|
+
initialState: statusState,
|
|
130
|
+
transfers: [
|
|
131
|
+
StateTransfer.create({
|
|
132
|
+
trigger: {
|
|
133
|
+
recordName: 'TaskEvent', // Event entity name
|
|
134
|
+
type: 'create',
|
|
135
|
+
record: { taskType: taskEntity.name }
|
|
136
|
+
},
|
|
137
|
+
current: statusState,
|
|
138
|
+
next: statusState,
|
|
139
|
+
computeTarget: async function(this: Controller, mutationEvent: any) {
|
|
140
|
+
const event = mutationEvent.record;
|
|
141
|
+
if (event.status) {
|
|
142
|
+
return { id: event.taskId }; // Target record to update
|
|
143
|
+
}
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
]
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### 2. `setup()` - Runtime Initialization
|
|
154
|
+
|
|
155
|
+
**Purpose**: Initialize external connections and register runtime handlers.
|
|
156
|
+
|
|
157
|
+
**Use Cases**:
|
|
158
|
+
- Connect to external services (Redis, databases, message queues)
|
|
159
|
+
- Register event listeners on external handles
|
|
160
|
+
- Set up WebSocket connection handlers
|
|
161
|
+
- Initialize API clients
|
|
162
|
+
|
|
163
|
+
**Execution Timing**: After Controller initialization, before server starts
|
|
164
|
+
|
|
165
|
+
**Example - External Service Connection**:
|
|
166
|
+
```typescript
|
|
167
|
+
async setup(controller: Controller) {
|
|
168
|
+
const redisUrl = process.env.REDIS_URL || 'redis://localhost:6379';
|
|
169
|
+
|
|
170
|
+
this.subscriber = createClient({ url: redisUrl });
|
|
171
|
+
this.subscriber.on('error', (err) => console.error('Redis Error:', err));
|
|
172
|
+
await this.subscriber.connect();
|
|
173
|
+
|
|
174
|
+
this.publisher = createClient({ url: redisUrl });
|
|
175
|
+
await this.publisher.connect();
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Example - WebSocket Handler Registration**:
|
|
180
|
+
```typescript
|
|
181
|
+
async setup(controller: Controller) {
|
|
182
|
+
// Register handler for user connection
|
|
183
|
+
this.handles.websocketServer.on('connection-setup-completed', async (ws, request) => {
|
|
184
|
+
const user = ws.user;
|
|
185
|
+
if (!user) return;
|
|
186
|
+
|
|
187
|
+
// Query user's channels
|
|
188
|
+
const userChannels = await controller.system.storage.find(
|
|
189
|
+
'UserChannelRelation',
|
|
190
|
+
MatchExp.atom({ key: 'source.id', value: ['=', user.id] }),
|
|
191
|
+
undefined,
|
|
192
|
+
['id', ['target', { attributeQuery: ['id'] }]]
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
// Subscribe to Redis channels for this user
|
|
196
|
+
for (let relation of userChannels) {
|
|
197
|
+
this.subscriber?.subscribe(relation.target.id, (message) => {
|
|
198
|
+
ws.send(message);
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### 3. `createSideEffects()` - Reactive Synchronization
|
|
206
|
+
|
|
207
|
+
**Purpose**: Define side effects that synchronize reactive data changes to external systems.
|
|
208
|
+
|
|
209
|
+
**Use Cases**:
|
|
210
|
+
- Push data mutations to external APIs
|
|
211
|
+
- Publish messages to message queues
|
|
212
|
+
- Update external database records
|
|
213
|
+
- Trigger external workflows
|
|
214
|
+
|
|
215
|
+
**Return Value**: Array of `RecordMutationSideEffect`
|
|
216
|
+
|
|
217
|
+
**Example - Publishing to Message Queue**:
|
|
218
|
+
```typescript
|
|
219
|
+
createSideEffects(): RecordMutationSideEffect[] {
|
|
220
|
+
const dispatcher = this;
|
|
221
|
+
|
|
222
|
+
return [
|
|
223
|
+
RecordMutationSideEffect.create({
|
|
224
|
+
name: 'channel_message_publish_sideeffect',
|
|
225
|
+
record: {
|
|
226
|
+
name: 'ChannelMessageRelation', // Target relation/entity
|
|
227
|
+
},
|
|
228
|
+
async content(this: Controller, event: RecordMutationEvent) {
|
|
229
|
+
if (event.type === 'create') {
|
|
230
|
+
const channelId = event.record?.source.id;
|
|
231
|
+
const message = event.record?.target;
|
|
232
|
+
|
|
233
|
+
if (message && channelId) {
|
|
234
|
+
const messageData = {
|
|
235
|
+
type: 'message',
|
|
236
|
+
channelId,
|
|
237
|
+
message
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// Publish to external system
|
|
241
|
+
await dispatcher.publisher?.publish(
|
|
242
|
+
channelId,
|
|
243
|
+
JSON.stringify(messageData)
|
|
244
|
+
);
|
|
245
|
+
console.log('Published to Redis:', channelId);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
})
|
|
250
|
+
];
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
**Example - Multiple Side Effects**:
|
|
255
|
+
```typescript
|
|
256
|
+
createSideEffects(): RecordMutationSideEffect[] {
|
|
257
|
+
return [
|
|
258
|
+
// Handle relation creation
|
|
259
|
+
...UserChannelRelation.instances.map(instance =>
|
|
260
|
+
RecordMutationSideEffect.create({
|
|
261
|
+
name: `channel_user_relation_${instance.name}_create`,
|
|
262
|
+
record: { name: instance.name! },
|
|
263
|
+
async content(this: Controller, event: RecordMutationEvent) {
|
|
264
|
+
if (event.type === 'create') {
|
|
265
|
+
// Subscribe online users to new channels
|
|
266
|
+
const clients = Array.from(websocketServer.clients);
|
|
267
|
+
const wsClient = clients.find(c => c.user.id === event.record?.source.id);
|
|
268
|
+
if (wsClient) {
|
|
269
|
+
subscriber?.subscribe(event.record?.target.id, (msg) => {
|
|
270
|
+
wsClient.send(msg);
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
})
|
|
276
|
+
),
|
|
277
|
+
|
|
278
|
+
// Handle relation deletion
|
|
279
|
+
...UserChannelRelation.instances.map(instance =>
|
|
280
|
+
RecordMutationSideEffect.create({
|
|
281
|
+
name: `channel_user_relation_${instance.name}_delete`,
|
|
282
|
+
record: { name: instance.name! },
|
|
283
|
+
async content(this: Controller, event: RecordMutationEvent) {
|
|
284
|
+
if (event.type === 'delete') {
|
|
285
|
+
// Unsubscribe user from channel
|
|
286
|
+
// ... implementation
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
})
|
|
290
|
+
)
|
|
291
|
+
];
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### 4. `createAPIs()` - Custom Endpoints
|
|
296
|
+
|
|
297
|
+
**Purpose**: Create custom API endpoints for external system interactions.
|
|
298
|
+
|
|
299
|
+
**Use Cases**:
|
|
300
|
+
- Query external service status
|
|
301
|
+
- Trigger external operations
|
|
302
|
+
- Proxy requests to external APIs
|
|
303
|
+
- Implement custom business logic that doesn't fit interactions
|
|
304
|
+
|
|
305
|
+
**Return Value**: Object mapping API names to API definitions
|
|
306
|
+
|
|
307
|
+
**Example - Status Query API**:
|
|
308
|
+
```typescript
|
|
309
|
+
createAPIs(): APIs {
|
|
310
|
+
const apis: APIs = {};
|
|
311
|
+
|
|
312
|
+
apis.queryTaskStatus = createAPI(
|
|
313
|
+
async function(this: Controller, context, params: { taskId: string, taskType: string }) {
|
|
314
|
+
const { taskId, taskType } = params;
|
|
315
|
+
|
|
316
|
+
// Query internal state
|
|
317
|
+
const task = await this.system.storage.findOne(
|
|
318
|
+
taskType,
|
|
319
|
+
MatchExp.atom({ key: 'id', value: ['=', taskId] }),
|
|
320
|
+
undefined,
|
|
321
|
+
['id', 'executionId', 'status', 'result']
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
if (!task) {
|
|
325
|
+
return { error: 'Task not found' };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Query external system
|
|
329
|
+
const externalStatus = await queryExternalAPI(task.executionId);
|
|
330
|
+
|
|
331
|
+
// Update internal state via event creation
|
|
332
|
+
if (externalStatus.status !== task.status) {
|
|
333
|
+
await this.system.storage.create('TaskEvent', {
|
|
334
|
+
taskId,
|
|
335
|
+
eventType: 'statusUpdated',
|
|
336
|
+
status: externalStatus.status,
|
|
337
|
+
result: externalStatus.result,
|
|
338
|
+
taskType
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return {
|
|
343
|
+
success: true,
|
|
344
|
+
taskId,
|
|
345
|
+
status: externalStatus.status,
|
|
346
|
+
result: externalStatus.result
|
|
347
|
+
};
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
params: { taskId: 'string', taskType: 'string' },
|
|
351
|
+
useNamedParams: true,
|
|
352
|
+
allowAnonymous: false
|
|
353
|
+
}
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
return apis;
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
## Integration Patterns
|
|
361
|
+
|
|
362
|
+
### Pattern 1: External Service Synchronization (Redis, MQ)
|
|
363
|
+
|
|
364
|
+
**Characteristics**:
|
|
365
|
+
- Bidirectional data flow
|
|
366
|
+
- Real-time synchronization
|
|
367
|
+
- Connection management
|
|
368
|
+
|
|
369
|
+
**Implementation**:
|
|
370
|
+
- `setup()`: Establish connections, register listeners
|
|
371
|
+
- `createSideEffects()`: Push internal changes to external system
|
|
372
|
+
- External events → Create records in reactive system
|
|
373
|
+
|
|
374
|
+
**Example**: RedisChannelIntegration
|
|
375
|
+
|
|
376
|
+
### Pattern 2: External Resource Generation (URLs, Tokens)
|
|
377
|
+
|
|
378
|
+
**Characteristics**:
|
|
379
|
+
- One-way data flow (internal → external)
|
|
380
|
+
- Lazy evaluation
|
|
381
|
+
- Resource lifecycle management
|
|
382
|
+
|
|
383
|
+
**Implementation**:
|
|
384
|
+
- `configure()`: Inject Custom computation into properties
|
|
385
|
+
- `getInitialValue()`: Call external API to generate resource
|
|
386
|
+
- `incrementalCompute()`: Usually skip (resources are immutable)
|
|
387
|
+
|
|
388
|
+
**Example**: VolcStreamIntegration
|
|
389
|
+
|
|
390
|
+
### Pattern 3: Async Task Management
|
|
391
|
+
|
|
392
|
+
**Characteristics**:
|
|
393
|
+
- Async operation lifecycle
|
|
394
|
+
- Status polling
|
|
395
|
+
- Result synchronization
|
|
396
|
+
|
|
397
|
+
**Implementation**:
|
|
398
|
+
- `configure()`: Inject event entity, configure state machines
|
|
399
|
+
- `createAPIs()`: Provide status query endpoint
|
|
400
|
+
- Event-driven state updates via injected entity
|
|
401
|
+
|
|
402
|
+
**Example**: VolcPicGenIntegration
|
|
403
|
+
|
|
404
|
+
## Best Practices
|
|
405
|
+
|
|
406
|
+
### 1. Separation of Concerns
|
|
407
|
+
|
|
408
|
+
- **configure()**: Schema modifications only
|
|
409
|
+
- **setup()**: Connection establishment only
|
|
410
|
+
- **createSideEffects()**: Data synchronization only
|
|
411
|
+
- **createAPIs()**: Query/command operations only
|
|
412
|
+
|
|
413
|
+
### 2. Error Handling
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
async setup(controller: Controller) {
|
|
417
|
+
try {
|
|
418
|
+
this.client = await connectToService();
|
|
419
|
+
this.client.on('error', (err) => {
|
|
420
|
+
console.error('Service error:', err);
|
|
421
|
+
// Implement reconnection logic
|
|
422
|
+
});
|
|
423
|
+
} catch (error) {
|
|
424
|
+
console.error('Failed to connect:', error);
|
|
425
|
+
throw error; // Fail fast if critical
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### 3. Event-Driven State Updates
|
|
431
|
+
|
|
432
|
+
**❌ BAD - Direct State Mutation**:
|
|
433
|
+
```typescript
|
|
434
|
+
// Don't directly update entity properties
|
|
435
|
+
await this.system.storage.update(taskType, taskId, {
|
|
436
|
+
status: newStatus // This bypasses reactive system
|
|
437
|
+
});
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
**✅ GOOD - Event-Driven Updates**:
|
|
441
|
+
```typescript
|
|
442
|
+
// Create event records to trigger state machines
|
|
443
|
+
await this.system.storage.create('TaskEvent', {
|
|
444
|
+
taskId,
|
|
445
|
+
eventType: 'statusUpdated',
|
|
446
|
+
status: newStatus,
|
|
447
|
+
taskType
|
|
448
|
+
});
|
|
449
|
+
// State machine will reactively update the target entity
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### 4. Resource Cleanup
|
|
453
|
+
|
|
454
|
+
```typescript
|
|
455
|
+
async cleanup() {
|
|
456
|
+
if (this.subscriber?.isOpen) {
|
|
457
|
+
await this.subscriber.disconnect();
|
|
458
|
+
}
|
|
459
|
+
if (this.publisher?.isOpen) {
|
|
460
|
+
await this.publisher.disconnect();
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
### 5. Environment Configuration
|
|
466
|
+
|
|
467
|
+
```typescript
|
|
468
|
+
async setup(controller: Controller) {
|
|
469
|
+
const apiKey = process.env.EXTERNAL_API_KEY;
|
|
470
|
+
if (!apiKey) {
|
|
471
|
+
throw new Error('EXTERNAL_API_KEY must be set');
|
|
472
|
+
}
|
|
473
|
+
// ... use apiKey
|
|
474
|
+
}
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### 6. Type Safety with External APIs
|
|
478
|
+
|
|
479
|
+
```typescript
|
|
480
|
+
// Define external API types
|
|
481
|
+
export type ExternalTaskStatus = 'pending' | 'processing' | 'success' | 'failed';
|
|
482
|
+
|
|
483
|
+
// Map to internal types
|
|
484
|
+
function mapExternalStatus(external: string): TaskStatus {
|
|
485
|
+
switch (external) {
|
|
486
|
+
case 'in_queue': return 'pending';
|
|
487
|
+
case 'generating': return 'processing';
|
|
488
|
+
case 'done': return 'success';
|
|
489
|
+
default: return 'failed';
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
## Integration Registration
|
|
495
|
+
|
|
496
|
+
After creating an integration, register it in `integrations/index.ts`:
|
|
497
|
+
|
|
498
|
+
```typescript
|
|
499
|
+
import { MyIntegration } from './myintegration';
|
|
500
|
+
|
|
501
|
+
const AggregatedIntegrationClass = createAggregatedIntegration([
|
|
502
|
+
RedisChannelIntegration,
|
|
503
|
+
VolcStreamIntegration,
|
|
504
|
+
VolcPicGenIntegration,
|
|
505
|
+
MyIntegration // Add your integration
|
|
506
|
+
]);
|
|
507
|
+
|
|
508
|
+
export default AggregatedIntegrationClass;
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
## Testing Integrations
|
|
512
|
+
|
|
513
|
+
1. **Unit Tests**: Test integration logic in isolation
|
|
514
|
+
2. **Integration Tests**: Test with mock external services
|
|
515
|
+
3. **E2E Tests**: Test with real external services in staging
|
|
516
|
+
|
|
517
|
+
```typescript
|
|
518
|
+
// Mock external service for testing
|
|
519
|
+
class MockExternalService {
|
|
520
|
+
async query(id: string) {
|
|
521
|
+
return { status: 'success', result: { data: 'test' } };
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Use in tests
|
|
526
|
+
const integration = new MyIntegration(args, {
|
|
527
|
+
externalService: new MockExternalService()
|
|
528
|
+
});
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
## Common Pitfalls
|
|
532
|
+
|
|
533
|
+
1. **❌ Modifying schema in setup()**: Schema must be finalized before Controller initialization
|
|
534
|
+
2. **❌ Blocking operations in configure()**: Avoid heavy I/O, keep it fast
|
|
535
|
+
3. **❌ Forgetting error handlers**: Always handle connection errors
|
|
536
|
+
4. **❌ Direct state mutations**: Use event-driven updates instead
|
|
537
|
+
5. **❌ Ignoring cleanup**: Implement proper resource cleanup
|
|
538
|
+
6. **❌ Hardcoding configuration**: Use environment variables
|
|
539
|
+
|
|
540
|
+
## Summary
|
|
541
|
+
|
|
542
|
+
Integrations enable reactive systems to communicate with external imperative systems through four key mechanisms:
|
|
543
|
+
|
|
544
|
+
1. **configure()**: Augment reactive schema
|
|
545
|
+
2. **setup()**: Initialize external connections
|
|
546
|
+
3. **createSideEffects()**: Synchronize data to external systems
|
|
547
|
+
4. **createAPIs()**: Expose custom endpoints
|
|
548
|
+
|
|
549
|
+
Choose the appropriate pattern based on your integration needs, and always prioritize event-driven design for state synchronization.
|
|
550
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
我们的项目所使用的框架是一个叫做 Interaqt 的后端响应式数据框架,它会自动根据应用中的数据变化及响应式数据的定义执行相应的数据变化。它只负责处理一般的业务逻辑中表达的数据逻辑,例如一般业务逻辑中会用到 平均/综合 等计算,还有常见的基于状态机等业务逻辑表达等。对于一些非一般的能力需求,例如 大模型生图、大模型生视频、tts、发送邮件、发送信息、完整支付系统等。它需要借助外部系统/api 来完成。
|
|
2
|
+
我们设计了一个叫做 integration 的概念,专门用来对接当前系统和外部的api/系统。它通过 interaqt 框架提供的数据观察机制,来观察数据变化,根据数据变化来决定如何调用外部的 api。同时通过 webhook 等机制来接受外部的事件,将外部事件同步回系统中,触发响应式的业务逻辑。
|
|
3
|
+
|
|
4
|
+
我们将 integration 需要集成的功能分成了三种类别:
|
|
5
|
+
1. 调用外部的 api,为了获得一个具体的返回。例如 tts,大模型生图等。
|
|
6
|
+
2. 执行某种副作用,例如发送邮件、发送 im 消息等。
|
|
7
|
+
3. 对接其他有状态的系统,例如支付系统等。
|
|
8
|
+
|
|
9
|
+
现在我们需要指导 claude code 的 sub agent 合理地识别需要的外部服务以及如何自己实现 integration。
|
|
10
|
+
1. 指导 `.claude/agents/requirements-analysis-handler.md` 在需求分析阶段,正确分析出 integration 的类型。并在相应的输出的文档中,设计一个字段来表达 integration 的类型。
|
|
11
|
+
2. 指导 `.claude/agents/implement-design-handler.md` 在设计数据的时候,根据如下原则进行设计:
|
|
12
|
+
2.1. 不管是哪种类型,都会涉及到对外部 api 的调用,例如执行副作用,也会有副作用 api 的调用。所以我们应该对每一个 api 的调用都设计一个 `{xxx}APICall` 的 entity,它负责记录这次 api 调用的参数、状态、返回值、调用时间等。
|
|
13
|
+
2.2. 同时设计一个相应的 integraion event entity,当我们通过 webhook 或者自己通过接口查询到 api 调用状态的变化时,在系统内创建相应的 api call result event 事件。并且将上一步创建的 `{xxx}APICall` entity 的 status 和 data 字段写成基于 integration event entity 的 computation,这样就完整符合了框架的响应式范式。也记录了所有应该记录的数据,增强了系统的健壮性。
|
|
14
|
+
2.3. 如果当前场景是希望基于这个 integration 获得具体的返回值,那么意味着我们系统内的业务数据对这个 `{xxx}APICall` 的 entity 是有依赖的,应该写成基于 `{xxx}APICall` 的 computation。例如我们的有一个 `Greeting` entity,其中有个 `voiceUrl` property 是需要利用外部 tts 能力将文本转化为语音。那么 `Greeting.voiceUrl` 就应该表达为基于 `{ttsAPICall}` entity 的 computation。如果是纯副作用类型等的调用,就不需要了。注意,这种情况下,还需要建立相应的 entity 和 api call entity 之间的关系,才能查找到正确的数据。
|
|
15
|
+
2.4. `.claude/agents/implement-design-handler.md` 在做 data-design 的时候,应该明确表达出来:1. 设计的哪些实体是 api call 类型的 entity,哪些实体是 api call result event 实体。2. 系统内的业务数据如果需要 api 的返回结果,那么应该依赖正确的 api call entity。
|
|
16
|
+
3. 指导 `.claude/agents/code-generation-handler.md` 在实现阶段,在写测试用例时,完全可以通过创建正确的 api call result event 来模拟 api 的调用,完整验证系统的内部逻辑的正确性。不需要等到 integration 的真实实现。
|
|
17
|
+
4. 指导 `.claude/agents/error-check-handler.md` 在合适的阶段对 integration 相关的设计做错误检查。
|
|
18
|
+
|
|
19
|
+
你充分理解上面的所有思路,并且修改相应的 sub agent 文件来达成目标。
|
|
@@ -29,6 +29,6 @@
|
|
|
29
29
|
6. 最终产出的 interactions-design.json 仍然使用原文档里的 interaction 数据结构,但以流程为组来组织。
|
|
30
30
|
|
|
31
31
|
注意,在每一个步骤中,都要给出清晰的数据结构定义,用 json 来写。
|
|
32
|
-
整体用简洁的英语完成文档重写。注意原本文档中的在关键步骤 update
|
|
32
|
+
整体用简洁的英语完成文档重写。注意原本文档中的在关键步骤 update docs/{module}.status.json 仍然按照原文档的方式写。
|
|
33
33
|
|
|
34
34
|
|