@asgard-js/core 0.0.31 → 0.0.32-canary.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/README.md +1 -9
- package/eslint.config.cjs +22 -0
- package/package.json +1 -1
- package/src/constants/enum.ts +32 -0
- package/src/index.ts +11 -0
- package/src/lib/channel.ts +152 -0
- package/src/lib/client.spec.ts +150 -0
- package/src/lib/client.ts +141 -0
- package/src/lib/conversation.ts +120 -0
- package/src/lib/create-sse-observable.ts +77 -0
- package/src/lib/event-emitter.ts +30 -0
- package/src/types/channel.ts +50 -0
- package/src/types/client.ts +89 -0
- package/src/types/event-emitter.ts +8 -0
- package/src/types/index.ts +4 -0
- package/src/types/sse-response.ts +176 -0
- package/tsconfig.json +13 -0
- package/tsconfig.lib.json +28 -0
- package/tsconfig.spec.json +33 -0
- package/vite.config.ts +80 -0
package/README.md
CHANGED
|
@@ -220,23 +220,15 @@ The core package includes comprehensive tests using Vitest.
|
|
|
220
220
|
|
|
221
221
|
```sh
|
|
222
222
|
# Run tests once
|
|
223
|
-
npm test
|
|
224
|
-
# or
|
|
225
223
|
yarn test:core
|
|
226
224
|
|
|
227
225
|
# Run tests in watch mode
|
|
228
|
-
npm run test:watch
|
|
229
|
-
# or
|
|
230
226
|
yarn test:core:watch
|
|
231
227
|
|
|
232
228
|
# Run tests with UI
|
|
233
|
-
npm run test:ui
|
|
234
|
-
# or
|
|
235
229
|
yarn test:core:ui
|
|
236
230
|
|
|
237
231
|
# Run tests with coverage
|
|
238
|
-
npm run test:coverage
|
|
239
|
-
# or
|
|
240
232
|
yarn test:core:coverage
|
|
241
233
|
```
|
|
242
234
|
|
|
@@ -305,7 +297,7 @@ yarn build:core
|
|
|
305
297
|
yarn watch:core
|
|
306
298
|
```
|
|
307
299
|
|
|
308
|
-
Setup your npm
|
|
300
|
+
Setup your npm registry token for yarn publishing:
|
|
309
301
|
|
|
310
302
|
```sh
|
|
311
303
|
cd ~/
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const baseConfig = require('../../eslint.config.cjs');
|
|
2
|
+
|
|
3
|
+
module.exports = [
|
|
4
|
+
...baseConfig,
|
|
5
|
+
{
|
|
6
|
+
files: ['**/*.json'],
|
|
7
|
+
rules: {
|
|
8
|
+
'@nx/dependency-checks': [
|
|
9
|
+
'error',
|
|
10
|
+
{
|
|
11
|
+
ignoredFiles: [
|
|
12
|
+
'{projectRoot}/eslint.config.{js,cjs,mjs}',
|
|
13
|
+
'{projectRoot}/vite.config.{js,ts,mjs,mts}',
|
|
14
|
+
],
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
},
|
|
18
|
+
languageOptions: {
|
|
19
|
+
parser: require('jsonc-eslint-parser'),
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
];
|
package/package.json
CHANGED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export enum FetchSseAction {
|
|
2
|
+
RESET_CHANNEL = 'RESET_CHANNEL',
|
|
3
|
+
NONE = 'NONE',
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export enum EventType {
|
|
7
|
+
INIT = 'asgard.run.init',
|
|
8
|
+
PROCESS = 'asgard.process',
|
|
9
|
+
PROCESS_START = 'asgard.process.start',
|
|
10
|
+
PROCESS_COMPLETE = 'asgard.process.complete',
|
|
11
|
+
MESSAGE = 'asgard.message',
|
|
12
|
+
MESSAGE_START = 'asgard.message.start',
|
|
13
|
+
MESSAGE_DELTA = 'asgard.message.delta',
|
|
14
|
+
MESSAGE_COMPLETE = 'asgard.message.complete',
|
|
15
|
+
TOOL_CALL = 'asgard.tool_call',
|
|
16
|
+
TOOL_CALL_START = 'asgard.tool_call.start',
|
|
17
|
+
TOOL_CALL_COMPLETE = 'asgard.tool_call.complete',
|
|
18
|
+
DONE = 'asgard.run.done',
|
|
19
|
+
ERROR = 'asgard.run.error',
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export enum MessageTemplateType {
|
|
23
|
+
TEXT = 'TEXT',
|
|
24
|
+
HINT = 'HINT',
|
|
25
|
+
BUTTON = 'BUTTON',
|
|
26
|
+
IMAGE = 'IMAGE',
|
|
27
|
+
VIDEO = 'VIDEO',
|
|
28
|
+
AUDIO = 'AUDIO',
|
|
29
|
+
LOCATION = 'LOCATION',
|
|
30
|
+
CAROUSEL = 'CAROUSEL',
|
|
31
|
+
CHART = 'CHART',
|
|
32
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type { Subscription } from 'rxjs';
|
|
2
|
+
|
|
3
|
+
export type * from 'src/types';
|
|
4
|
+
|
|
5
|
+
export * from 'src/constants/enum';
|
|
6
|
+
|
|
7
|
+
export { default as AsgardServiceClient } from 'src/lib/client';
|
|
8
|
+
|
|
9
|
+
export { default as Channel } from 'src/lib/channel';
|
|
10
|
+
|
|
11
|
+
export { default as Conversation } from 'src/lib/conversation';
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { BehaviorSubject, combineLatest, map, Subscription } from 'rxjs';
|
|
2
|
+
import {
|
|
3
|
+
ChannelConfig,
|
|
4
|
+
ChannelStates,
|
|
5
|
+
FetchSseOptions,
|
|
6
|
+
FetchSsePayload,
|
|
7
|
+
IAsgardServiceClient,
|
|
8
|
+
ObserverOrNext,
|
|
9
|
+
} from 'src/types';
|
|
10
|
+
import { FetchSseAction } from 'src/constants/enum';
|
|
11
|
+
import Conversation from './conversation';
|
|
12
|
+
|
|
13
|
+
export default class Channel {
|
|
14
|
+
private client: IAsgardServiceClient;
|
|
15
|
+
|
|
16
|
+
public customChannelId: string;
|
|
17
|
+
public customMessageId?: string;
|
|
18
|
+
|
|
19
|
+
private isConnecting$: BehaviorSubject<boolean>;
|
|
20
|
+
private conversation$: BehaviorSubject<Conversation>;
|
|
21
|
+
private statesObserver?: ObserverOrNext<ChannelStates>;
|
|
22
|
+
private statesSubscription?: Subscription;
|
|
23
|
+
|
|
24
|
+
private constructor(config: ChannelConfig) {
|
|
25
|
+
if (!config.client) {
|
|
26
|
+
throw new Error('client must be required');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!config.customChannelId) {
|
|
30
|
+
throw new Error('customChannelId must be required');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
this.client = config.client;
|
|
34
|
+
this.customChannelId = config.customChannelId;
|
|
35
|
+
this.customMessageId = config.customMessageId;
|
|
36
|
+
|
|
37
|
+
this.isConnecting$ = new BehaviorSubject(false);
|
|
38
|
+
this.conversation$ = new BehaviorSubject(config.conversation);
|
|
39
|
+
this.statesObserver = config.statesObserver;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public static async reset(
|
|
43
|
+
config: ChannelConfig,
|
|
44
|
+
payload?: Pick<FetchSsePayload, 'text' | 'payload'>,
|
|
45
|
+
options?: FetchSseOptions
|
|
46
|
+
): Promise<Channel> {
|
|
47
|
+
const channel = new Channel(config);
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
channel.subscribe();
|
|
51
|
+
|
|
52
|
+
await channel.resetChannel(payload, options);
|
|
53
|
+
|
|
54
|
+
return channel;
|
|
55
|
+
} catch (error) {
|
|
56
|
+
channel.close();
|
|
57
|
+
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private subscribe(): void {
|
|
63
|
+
this.statesSubscription = combineLatest([
|
|
64
|
+
this.isConnecting$,
|
|
65
|
+
this.conversation$,
|
|
66
|
+
])
|
|
67
|
+
.pipe(
|
|
68
|
+
map(([isConnecting, conversation]) => ({
|
|
69
|
+
isConnecting,
|
|
70
|
+
conversation,
|
|
71
|
+
}))
|
|
72
|
+
)
|
|
73
|
+
.subscribe(this.statesObserver);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private fetchSse(
|
|
77
|
+
payload: FetchSsePayload,
|
|
78
|
+
options?: FetchSseOptions
|
|
79
|
+
): Promise<void> {
|
|
80
|
+
return new Promise((resolve, reject) => {
|
|
81
|
+
this.isConnecting$.next(true);
|
|
82
|
+
|
|
83
|
+
this.client.fetchSse(payload, {
|
|
84
|
+
onSseStart: options?.onSseStart,
|
|
85
|
+
onSseMessage: (response) => {
|
|
86
|
+
options?.onSseMessage?.(response);
|
|
87
|
+
this.conversation$.next(this.conversation$.value.onMessage(response));
|
|
88
|
+
},
|
|
89
|
+
onSseError: (err) => {
|
|
90
|
+
options?.onSseError?.(err);
|
|
91
|
+
this.isConnecting$.next(false);
|
|
92
|
+
reject(err);
|
|
93
|
+
},
|
|
94
|
+
onSseCompleted: () => {
|
|
95
|
+
options?.onSseCompleted?.();
|
|
96
|
+
this.isConnecting$.next(false);
|
|
97
|
+
resolve();
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private resetChannel(
|
|
104
|
+
payload?: Pick<FetchSsePayload, 'text' | 'payload'>,
|
|
105
|
+
options?: FetchSseOptions
|
|
106
|
+
): Promise<void> {
|
|
107
|
+
return this.fetchSse(
|
|
108
|
+
{
|
|
109
|
+
action: FetchSseAction.RESET_CHANNEL,
|
|
110
|
+
customChannelId: this.customChannelId,
|
|
111
|
+
customMessageId: this.customMessageId,
|
|
112
|
+
text: payload?.text || '',
|
|
113
|
+
payload: payload?.payload,
|
|
114
|
+
},
|
|
115
|
+
options
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
public sendMessage(
|
|
120
|
+
payload: Pick<FetchSsePayload, 'customMessageId' | 'text' | 'payload'>,
|
|
121
|
+
options?: FetchSseOptions
|
|
122
|
+
): Promise<void> {
|
|
123
|
+
const text = payload.text.trim();
|
|
124
|
+
const messageId = payload.customMessageId ?? crypto.randomUUID();
|
|
125
|
+
|
|
126
|
+
this.conversation$.next(
|
|
127
|
+
this.conversation$.value.pushMessage({
|
|
128
|
+
type: 'user',
|
|
129
|
+
messageId,
|
|
130
|
+
text,
|
|
131
|
+
time: new Date(),
|
|
132
|
+
})
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
return this.fetchSse(
|
|
136
|
+
{
|
|
137
|
+
action: FetchSseAction.NONE,
|
|
138
|
+
customChannelId: this.customChannelId,
|
|
139
|
+
customMessageId: messageId,
|
|
140
|
+
payload: payload?.payload,
|
|
141
|
+
text,
|
|
142
|
+
},
|
|
143
|
+
options
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
public close(): void {
|
|
148
|
+
this.isConnecting$.complete();
|
|
149
|
+
this.conversation$.complete();
|
|
150
|
+
this.statesSubscription?.unsubscribe();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import AsgardServiceClient from './client';
|
|
3
|
+
import { ClientConfig } from '../types/client';
|
|
4
|
+
|
|
5
|
+
describe('AsgardServiceClient', () => {
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
|
+
let consoleSpy: any;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
11
|
+
consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
consoleSpy.mockRestore();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('constructor', () => {
|
|
19
|
+
it('should throw error when neither endpoint nor botProviderEndpoint is provided', () => {
|
|
20
|
+
const config = {
|
|
21
|
+
apiKey: 'test-key',
|
|
22
|
+
} as any; // Use 'as any' to bypass TypeScript checking for testing runtime behavior
|
|
23
|
+
|
|
24
|
+
expect(() => new AsgardServiceClient(config)).toThrow(
|
|
25
|
+
'Either endpoint or botProviderEndpoint must be provided'
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should derive endpoint from botProviderEndpoint when only botProviderEndpoint is provided', () => {
|
|
30
|
+
const config: ClientConfig = {
|
|
31
|
+
botProviderEndpoint: 'https://api.example.com/bot-provider/bp-123',
|
|
32
|
+
apiKey: 'test-key',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const client = new AsgardServiceClient(config);
|
|
36
|
+
|
|
37
|
+
// Access private endpoint field for testing
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
39
|
+
expect((client as any).endpoint).toBe('https://api.example.com/bot-provider/bp-123/message/sse');
|
|
40
|
+
expect(consoleSpy).not.toHaveBeenCalled();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should use provided endpoint when only endpoint is provided', () => {
|
|
44
|
+
const config: ClientConfig = {
|
|
45
|
+
endpoint: 'https://api.example.com/custom/sse',
|
|
46
|
+
apiKey: 'test-key',
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const client = new AsgardServiceClient(config);
|
|
50
|
+
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
52
|
+
expect((client as any).endpoint).toBe('https://api.example.com/custom/sse');
|
|
53
|
+
expect(consoleSpy).not.toHaveBeenCalled();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should use provided endpoint and warn about deprecation when debugMode is enabled', () => {
|
|
57
|
+
const config: ClientConfig = {
|
|
58
|
+
endpoint: 'https://api.example.com/custom/sse',
|
|
59
|
+
apiKey: 'test-key',
|
|
60
|
+
debugMode: true,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const client = new AsgardServiceClient(config);
|
|
64
|
+
|
|
65
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
66
|
+
expect((client as any).endpoint).toBe('https://api.example.com/custom/sse');
|
|
67
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
68
|
+
'[AsgardServiceClient] The "endpoint" option is deprecated and will be removed in the next major version. ' +
|
|
69
|
+
'Please use "botProviderEndpoint" instead. The SSE endpoint will be automatically derived as "${botProviderEndpoint}/message/sse".'
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should use provided endpoint without warning when debugMode is disabled', () => {
|
|
74
|
+
const config: ClientConfig = {
|
|
75
|
+
endpoint: 'https://api.example.com/custom/sse',
|
|
76
|
+
apiKey: 'test-key',
|
|
77
|
+
debugMode: false,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const client = new AsgardServiceClient(config);
|
|
81
|
+
|
|
82
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
83
|
+
expect((client as any).endpoint).toBe('https://api.example.com/custom/sse');
|
|
84
|
+
expect(consoleSpy).not.toHaveBeenCalled();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should prioritize endpoint over botProviderEndpoint when both are provided', () => {
|
|
88
|
+
const config: ClientConfig = {
|
|
89
|
+
endpoint: 'https://api.example.com/custom/sse',
|
|
90
|
+
botProviderEndpoint: 'https://api.example.com/bot-provider/bp-123',
|
|
91
|
+
apiKey: 'test-key',
|
|
92
|
+
debugMode: true,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const client = new AsgardServiceClient(config);
|
|
96
|
+
|
|
97
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
98
|
+
expect((client as any).endpoint).toBe('https://api.example.com/custom/sse');
|
|
99
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
100
|
+
'[AsgardServiceClient] The "endpoint" option is deprecated and will be removed in the next major version. ' +
|
|
101
|
+
'Please use "botProviderEndpoint" instead. The SSE endpoint will be automatically derived as "${botProviderEndpoint}/message/sse".'
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should correctly set other configuration properties', () => {
|
|
106
|
+
const transformSsePayload = vi.fn();
|
|
107
|
+
const config: ClientConfig = {
|
|
108
|
+
botProviderEndpoint: 'https://api.example.com/bot-provider/bp-123',
|
|
109
|
+
apiKey: 'test-key',
|
|
110
|
+
debugMode: true,
|
|
111
|
+
transformSsePayload,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const client = new AsgardServiceClient(config);
|
|
115
|
+
|
|
116
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
117
|
+
expect((client as any).apiKey).toBe('test-key');
|
|
118
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
119
|
+
expect((client as any).debugMode).toBe(true);
|
|
120
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
121
|
+
expect((client as any).transformSsePayload).toBe(transformSsePayload);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should handle trailing slash in botProviderEndpoint correctly', () => {
|
|
125
|
+
const config: ClientConfig = {
|
|
126
|
+
botProviderEndpoint: 'https://api.example.com/bot-provider/bp-123/',
|
|
127
|
+
apiKey: 'test-key',
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const client = new AsgardServiceClient(config);
|
|
131
|
+
|
|
132
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
133
|
+
expect((client as any).endpoint).toBe('https://api.example.com/bot-provider/bp-123/message/sse');
|
|
134
|
+
expect(consoleSpy).not.toHaveBeenCalled();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should handle multiple trailing slashes in botProviderEndpoint', () => {
|
|
138
|
+
const config: ClientConfig = {
|
|
139
|
+
botProviderEndpoint: 'https://api.example.com/bot-provider/bp-123///',
|
|
140
|
+
apiKey: 'test-key',
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const client = new AsgardServiceClient(config);
|
|
144
|
+
|
|
145
|
+
// Should remove all trailing slashes
|
|
146
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
147
|
+
expect((client as any).endpoint).toBe('https://api.example.com/bot-provider/bp-123/message/sse');
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
});
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ClientConfig,
|
|
3
|
+
IAsgardServiceClient,
|
|
4
|
+
FetchSsePayload,
|
|
5
|
+
FetchSseOptions,
|
|
6
|
+
SseResponse,
|
|
7
|
+
SseEvents,
|
|
8
|
+
} from 'src/types';
|
|
9
|
+
import { createSseObservable } from './create-sse-observable';
|
|
10
|
+
import { concatMap, delay, of, retry, Subject, takeUntil } from 'rxjs';
|
|
11
|
+
import { EventType } from 'src/constants/enum';
|
|
12
|
+
import { EventEmitter } from './event-emitter';
|
|
13
|
+
|
|
14
|
+
export default class AsgardServiceClient implements IAsgardServiceClient {
|
|
15
|
+
private apiKey?: string;
|
|
16
|
+
private endpoint!: string;
|
|
17
|
+
private debugMode?: boolean;
|
|
18
|
+
private destroy$ = new Subject<void>();
|
|
19
|
+
private sseEmitter = new EventEmitter<SseEvents>();
|
|
20
|
+
private transformSsePayload?: (payload: FetchSsePayload) => FetchSsePayload;
|
|
21
|
+
|
|
22
|
+
constructor(config: ClientConfig) {
|
|
23
|
+
// Validate that either endpoint or botProviderEndpoint is provided
|
|
24
|
+
if (!config.endpoint && !config.botProviderEndpoint) {
|
|
25
|
+
throw new Error('Either endpoint or botProviderEndpoint must be provided');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
this.apiKey = config.apiKey;
|
|
29
|
+
this.debugMode = config.debugMode;
|
|
30
|
+
this.transformSsePayload = config.transformSsePayload;
|
|
31
|
+
|
|
32
|
+
// Handle endpoint derivation and deprecation
|
|
33
|
+
if (!config.endpoint && config.botProviderEndpoint) {
|
|
34
|
+
// Derive endpoint from botProviderEndpoint (new recommended way)
|
|
35
|
+
// Handle trailing slashes to prevent double slashes
|
|
36
|
+
const baseEndpoint = config.botProviderEndpoint.replace(/\/+$/, '');
|
|
37
|
+
this.endpoint = `${baseEndpoint}/message/sse`;
|
|
38
|
+
} else if (config.endpoint) {
|
|
39
|
+
// Use provided endpoint but warn about deprecation
|
|
40
|
+
this.endpoint = config.endpoint;
|
|
41
|
+
if (this.debugMode) {
|
|
42
|
+
// eslint-disable-next-line no-console
|
|
43
|
+
console.warn(
|
|
44
|
+
'[AsgardServiceClient] The "endpoint" option is deprecated and will be removed in the next major version. ' +
|
|
45
|
+
`Please use "botProviderEndpoint" instead. The SSE endpoint will be automatically derived as "\${botProviderEndpoint}/message/sse".`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
on<K extends keyof SseEvents>(event: K, listener: SseEvents[K]): void {
|
|
52
|
+
this.sseEmitter.remove(event);
|
|
53
|
+
this.sseEmitter.on(event, listener);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
handleEvent(response: SseResponse<EventType>): void {
|
|
57
|
+
switch (response.eventType) {
|
|
58
|
+
case EventType.INIT:
|
|
59
|
+
this.sseEmitter.emit(
|
|
60
|
+
EventType.INIT,
|
|
61
|
+
response as SseResponse<EventType.INIT>
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
break;
|
|
65
|
+
case EventType.PROCESS_START:
|
|
66
|
+
case EventType.PROCESS_COMPLETE:
|
|
67
|
+
this.sseEmitter.emit(
|
|
68
|
+
EventType.PROCESS,
|
|
69
|
+
response as Parameters<SseEvents[EventType.PROCESS]>[0]
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
break;
|
|
73
|
+
case EventType.MESSAGE_START:
|
|
74
|
+
case EventType.MESSAGE_DELTA:
|
|
75
|
+
case EventType.MESSAGE_COMPLETE:
|
|
76
|
+
this.sseEmitter.emit(
|
|
77
|
+
EventType.MESSAGE,
|
|
78
|
+
response as Parameters<SseEvents[EventType.MESSAGE]>[0]
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
break;
|
|
82
|
+
case EventType.TOOL_CALL_START:
|
|
83
|
+
case EventType.TOOL_CALL_COMPLETE:
|
|
84
|
+
this.sseEmitter.emit(
|
|
85
|
+
EventType.TOOL_CALL,
|
|
86
|
+
response as Parameters<SseEvents[EventType.TOOL_CALL]>[0]
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
break;
|
|
90
|
+
case EventType.DONE:
|
|
91
|
+
this.sseEmitter.emit(
|
|
92
|
+
EventType.DONE,
|
|
93
|
+
response as SseResponse<EventType.DONE>
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
break;
|
|
97
|
+
case EventType.ERROR:
|
|
98
|
+
this.sseEmitter.emit(
|
|
99
|
+
EventType.ERROR,
|
|
100
|
+
response as SseResponse<EventType.ERROR>
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
break;
|
|
104
|
+
default:
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
fetchSse(payload: FetchSsePayload, options?: FetchSseOptions): void {
|
|
110
|
+
options?.onSseStart?.();
|
|
111
|
+
|
|
112
|
+
createSseObservable({
|
|
113
|
+
apiKey: this.apiKey,
|
|
114
|
+
endpoint: this.endpoint,
|
|
115
|
+
debugMode: this.debugMode,
|
|
116
|
+
payload: this.transformSsePayload?.(payload) ?? payload,
|
|
117
|
+
})
|
|
118
|
+
.pipe(
|
|
119
|
+
concatMap((event) => of(event).pipe(delay(options?.delayTime ?? 50))),
|
|
120
|
+
takeUntil(this.destroy$),
|
|
121
|
+
retry(3)
|
|
122
|
+
)
|
|
123
|
+
.subscribe({
|
|
124
|
+
next: (response) => {
|
|
125
|
+
options?.onSseMessage?.(response);
|
|
126
|
+
this.handleEvent(response);
|
|
127
|
+
},
|
|
128
|
+
error: (error) => {
|
|
129
|
+
options?.onSseError?.(error);
|
|
130
|
+
},
|
|
131
|
+
complete: () => {
|
|
132
|
+
options?.onSseCompleted?.();
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
close(): void {
|
|
138
|
+
this.destroy$.next();
|
|
139
|
+
this.destroy$.complete();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { EventType } from 'src/constants/enum';
|
|
2
|
+
import { ConversationMessage, SseResponse } from 'src/types';
|
|
3
|
+
|
|
4
|
+
interface IConversation {
|
|
5
|
+
messages: Map<string, ConversationMessage> | null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export default class Conversation implements IConversation {
|
|
9
|
+
public messages: Map<string, ConversationMessage> | null = null;
|
|
10
|
+
|
|
11
|
+
constructor({ messages }: IConversation) {
|
|
12
|
+
this.messages = messages;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
pushMessage(message: ConversationMessage): Conversation {
|
|
16
|
+
const messages = new Map(this.messages);
|
|
17
|
+
messages.set(message.messageId, message);
|
|
18
|
+
|
|
19
|
+
return new Conversation({ messages });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
onMessage(response: SseResponse<EventType>): Conversation {
|
|
23
|
+
switch (response.eventType) {
|
|
24
|
+
case EventType.MESSAGE_START:
|
|
25
|
+
return this.onMessageStart(
|
|
26
|
+
response as SseResponse<EventType.MESSAGE_START>
|
|
27
|
+
);
|
|
28
|
+
case EventType.MESSAGE_DELTA:
|
|
29
|
+
return this.onMessageDelta(
|
|
30
|
+
response as SseResponse<EventType.MESSAGE_DELTA>
|
|
31
|
+
);
|
|
32
|
+
case EventType.MESSAGE_COMPLETE:
|
|
33
|
+
return this.onMessageComplete(
|
|
34
|
+
response as SseResponse<EventType.MESSAGE_COMPLETE>
|
|
35
|
+
);
|
|
36
|
+
case EventType.ERROR:
|
|
37
|
+
return this.onMessageError(response as SseResponse<EventType.ERROR>);
|
|
38
|
+
default:
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
onMessageStart(response: SseResponse<EventType.MESSAGE_START>): Conversation {
|
|
44
|
+
const message = response.fact.messageStart.message;
|
|
45
|
+
const messages = new Map(this.messages);
|
|
46
|
+
|
|
47
|
+
messages.set(message.messageId, {
|
|
48
|
+
type: 'bot',
|
|
49
|
+
eventType: EventType.MESSAGE_START,
|
|
50
|
+
isTyping: true,
|
|
51
|
+
typingText: '',
|
|
52
|
+
messageId: message.messageId,
|
|
53
|
+
message,
|
|
54
|
+
time: new Date(),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return new Conversation({ messages });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
onMessageDelta(response: SseResponse<EventType.MESSAGE_DELTA>): Conversation {
|
|
61
|
+
const message = response.fact.messageDelta.message;
|
|
62
|
+
|
|
63
|
+
const messages = new Map(this.messages);
|
|
64
|
+
|
|
65
|
+
const currentMessage = messages.get(message.messageId);
|
|
66
|
+
|
|
67
|
+
if (currentMessage?.type !== 'bot') return this;
|
|
68
|
+
|
|
69
|
+
const typingText = `${currentMessage?.typingText ?? ''}${message.text}`;
|
|
70
|
+
|
|
71
|
+
messages.set(message.messageId, {
|
|
72
|
+
type: 'bot',
|
|
73
|
+
eventType: EventType.MESSAGE_DELTA,
|
|
74
|
+
isTyping: true,
|
|
75
|
+
typingText,
|
|
76
|
+
messageId: message.messageId,
|
|
77
|
+
message,
|
|
78
|
+
time: new Date(),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return new Conversation({ messages });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
onMessageComplete(
|
|
85
|
+
response: SseResponse<EventType.MESSAGE_COMPLETE>
|
|
86
|
+
): Conversation {
|
|
87
|
+
const message = response.fact.messageComplete.message;
|
|
88
|
+
|
|
89
|
+
const messages = new Map(this.messages);
|
|
90
|
+
|
|
91
|
+
messages.set(message.messageId, {
|
|
92
|
+
type: 'bot',
|
|
93
|
+
eventType: EventType.MESSAGE_COMPLETE,
|
|
94
|
+
isTyping: false,
|
|
95
|
+
typingText: null,
|
|
96
|
+
messageId: message.messageId,
|
|
97
|
+
message,
|
|
98
|
+
time: new Date(),
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return new Conversation({ messages });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
onMessageError(response: SseResponse<EventType.ERROR>): Conversation {
|
|
105
|
+
const messageId = crypto.randomUUID();
|
|
106
|
+
const error = response.fact.runError.error;
|
|
107
|
+
|
|
108
|
+
const messages = new Map(this.messages);
|
|
109
|
+
|
|
110
|
+
messages.set(messageId, {
|
|
111
|
+
type: 'error',
|
|
112
|
+
eventType: EventType.ERROR,
|
|
113
|
+
messageId,
|
|
114
|
+
error,
|
|
115
|
+
time: new Date(),
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
return new Conversation({ messages });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Observable } from 'rxjs';
|
|
2
|
+
import {
|
|
3
|
+
EventSourceMessage,
|
|
4
|
+
fetchEventSource,
|
|
5
|
+
} from '@microsoft/fetch-event-source';
|
|
6
|
+
import { FetchSsePayload, SseResponse } from 'src/types';
|
|
7
|
+
import { EventType } from 'src/constants/enum';
|
|
8
|
+
|
|
9
|
+
interface CreateSseObservableOptions {
|
|
10
|
+
endpoint: string;
|
|
11
|
+
apiKey?: string;
|
|
12
|
+
debugMode?: boolean;
|
|
13
|
+
payload: FetchSsePayload;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function createSseObservable(
|
|
17
|
+
options: CreateSseObservableOptions
|
|
18
|
+
): Observable<SseResponse<EventType>> {
|
|
19
|
+
const { endpoint, apiKey, payload, debugMode } = options;
|
|
20
|
+
|
|
21
|
+
return new Observable<SseResponse<EventType>>((subscriber) => {
|
|
22
|
+
const controller = new AbortController();
|
|
23
|
+
|
|
24
|
+
const headers: Record<string, string> = {
|
|
25
|
+
'Content-Type': 'application/json',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
if (apiKey) {
|
|
29
|
+
headers['X-API-KEY'] = apiKey;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const searchParams = new URLSearchParams();
|
|
33
|
+
|
|
34
|
+
if (debugMode) {
|
|
35
|
+
searchParams.set('is_debug', 'true');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const url = new URL(endpoint);
|
|
39
|
+
|
|
40
|
+
if (searchParams.toString()) {
|
|
41
|
+
url.search = searchParams.toString();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
fetchEventSource(url.toString(), {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
headers,
|
|
47
|
+
body: payload ? JSON.stringify(payload) : undefined,
|
|
48
|
+
signal: controller.signal,
|
|
49
|
+
/**
|
|
50
|
+
* Allow SSE to work when the page is hidden.
|
|
51
|
+
* https://github.com/Azure/fetch-event-source/issues/17#issuecomment-1525904929
|
|
52
|
+
*/
|
|
53
|
+
openWhenHidden: true,
|
|
54
|
+
onopen: async (response) => {
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
subscriber.error(response);
|
|
57
|
+
controller.abort();
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
onmessage: (esm: EventSourceMessage) => {
|
|
61
|
+
subscriber.next(JSON.parse(esm.data));
|
|
62
|
+
},
|
|
63
|
+
onclose: () => {
|
|
64
|
+
subscriber.complete();
|
|
65
|
+
},
|
|
66
|
+
onerror: (err) => {
|
|
67
|
+
subscriber.error(err);
|
|
68
|
+
controller.abort();
|
|
69
|
+
throw err;
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return (): void => {
|
|
74
|
+
controller.abort();
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Listeners } from 'src/types';
|
|
2
|
+
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4
|
+
export class EventEmitter<Events extends Record<string, any>> {
|
|
5
|
+
private listeners: Listeners<Events> = {};
|
|
6
|
+
|
|
7
|
+
on<K extends keyof Events>(event: K, listener: Events[K]): void {
|
|
8
|
+
this.listeners = Object.assign({}, this.listeners, {
|
|
9
|
+
[event]: (this.listeners[event] ?? []).concat(listener),
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
off<K extends keyof Events>(event: K, listener: Events[K]): void {
|
|
14
|
+
if (!this.listeners[event]) return;
|
|
15
|
+
|
|
16
|
+
this.listeners = Object.assign({}, this.listeners, {
|
|
17
|
+
[event]: (this.listeners[event] ?? []).filter((l) => l !== listener),
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
remove<K extends keyof Events>(event: K): void {
|
|
22
|
+
delete this.listeners[event];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
emit<K extends keyof Events>(event: K, ...args: Parameters<Events[K]>): void {
|
|
26
|
+
if (!this.listeners[event]) return;
|
|
27
|
+
|
|
28
|
+
this.listeners[event].forEach((listener) => listener(...args));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Observer } from 'rxjs';
|
|
2
|
+
import { EventType } from 'src/constants/enum';
|
|
3
|
+
import Conversation from 'src/lib/conversation';
|
|
4
|
+
import { IAsgardServiceClient } from './client';
|
|
5
|
+
import { ErrorMessage, Message } from './sse-response';
|
|
6
|
+
|
|
7
|
+
export type ObserverOrNext<T> = Partial<Observer<T>> | ((value: T) => void);
|
|
8
|
+
|
|
9
|
+
export interface ChannelStates {
|
|
10
|
+
isConnecting: boolean;
|
|
11
|
+
conversation: Conversation;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ChannelConfig {
|
|
15
|
+
client: IAsgardServiceClient;
|
|
16
|
+
customChannelId: string;
|
|
17
|
+
customMessageId?: string;
|
|
18
|
+
conversation: Conversation;
|
|
19
|
+
statesObserver?: ObserverOrNext<ChannelStates>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type ConversationUserMessage = {
|
|
23
|
+
type: 'user';
|
|
24
|
+
messageId: string;
|
|
25
|
+
text: string;
|
|
26
|
+
time: Date;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type ConversationBotMessage = {
|
|
30
|
+
type: 'bot';
|
|
31
|
+
messageId: string;
|
|
32
|
+
eventType: EventType;
|
|
33
|
+
isTyping: boolean;
|
|
34
|
+
typingText: string | null;
|
|
35
|
+
message: Message;
|
|
36
|
+
time: Date;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type ConversationErrorMessage = {
|
|
40
|
+
type: 'error';
|
|
41
|
+
messageId: string;
|
|
42
|
+
eventType: EventType;
|
|
43
|
+
error: ErrorMessage;
|
|
44
|
+
time: Date;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type ConversationMessage =
|
|
48
|
+
| ConversationUserMessage
|
|
49
|
+
| ConversationBotMessage
|
|
50
|
+
| ConversationErrorMessage;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { EventType, FetchSseAction } from 'src/constants/enum';
|
|
2
|
+
import { SseResponse } from './sse-response';
|
|
3
|
+
import { EventHandler } from './event-emitter';
|
|
4
|
+
|
|
5
|
+
export interface IAsgardServiceClient {
|
|
6
|
+
fetchSse(payload: FetchSsePayload, options?: FetchSseOptions): void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type InitEventHandler = EventHandler<SseResponse<EventType.INIT>>;
|
|
10
|
+
export type MessageEventHandler = EventHandler<
|
|
11
|
+
SseResponse<
|
|
12
|
+
| EventType.MESSAGE_START
|
|
13
|
+
| EventType.MESSAGE_DELTA
|
|
14
|
+
| EventType.MESSAGE_COMPLETE
|
|
15
|
+
>
|
|
16
|
+
>;
|
|
17
|
+
export type ProcessEventHandler = EventHandler<
|
|
18
|
+
SseResponse<EventType.PROCESS_START | EventType.PROCESS_COMPLETE>
|
|
19
|
+
>;
|
|
20
|
+
export type DoneEventHandler = EventHandler<SseResponse<EventType.DONE>>;
|
|
21
|
+
export type ErrorEventHandler = EventHandler<SseResponse<EventType.ERROR>>;
|
|
22
|
+
export type ToolCallEventHandler = EventHandler<
|
|
23
|
+
SseResponse<EventType.TOOL_CALL_START | EventType.TOOL_CALL_COMPLETE>
|
|
24
|
+
>;
|
|
25
|
+
|
|
26
|
+
export interface SseHandlers {
|
|
27
|
+
onRunInit?: InitEventHandler;
|
|
28
|
+
onMessage?: MessageEventHandler;
|
|
29
|
+
onToolCall?: ToolCallEventHandler;
|
|
30
|
+
onProcess?: ProcessEventHandler;
|
|
31
|
+
onRunDone?: DoneEventHandler;
|
|
32
|
+
onRunError?: ErrorEventHandler;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type ClientConfig = SseHandlers & {
|
|
36
|
+
apiKey?: string;
|
|
37
|
+
debugMode?: boolean;
|
|
38
|
+
transformSsePayload?: (payload: FetchSsePayload) => FetchSsePayload;
|
|
39
|
+
} & (
|
|
40
|
+
| {
|
|
41
|
+
/**
|
|
42
|
+
* @deprecated Use `botProviderEndpoint` instead. This will be removed in the next major version.
|
|
43
|
+
* If provided, it will be used. Otherwise, it will be automatically derived as `${botProviderEndpoint}/message/sse`
|
|
44
|
+
*/
|
|
45
|
+
endpoint: string;
|
|
46
|
+
/**
|
|
47
|
+
* Base URL for the bot provider service.
|
|
48
|
+
* The SSE endpoint will be automatically derived as `${botProviderEndpoint}/message/sse`
|
|
49
|
+
*/
|
|
50
|
+
botProviderEndpoint?: string;
|
|
51
|
+
}
|
|
52
|
+
| {
|
|
53
|
+
/**
|
|
54
|
+
* Base URL for the bot provider service.
|
|
55
|
+
* The SSE endpoint will be automatically derived as `${botProviderEndpoint}/message/sse`
|
|
56
|
+
*/
|
|
57
|
+
botProviderEndpoint: string;
|
|
58
|
+
/**
|
|
59
|
+
* @deprecated Use `botProviderEndpoint` instead. This will be removed in the next major version.
|
|
60
|
+
* If provided, it will be used. Otherwise, it will be automatically derived as `${botProviderEndpoint}/message/sse`
|
|
61
|
+
*/
|
|
62
|
+
endpoint?: string;
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
export interface FetchSsePayload {
|
|
67
|
+
customChannelId: string;
|
|
68
|
+
customMessageId?: string;
|
|
69
|
+
text: string;
|
|
70
|
+
payload?: Record<string, unknown>;
|
|
71
|
+
action: FetchSseAction;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface FetchSseOptions {
|
|
75
|
+
delayTime?: number;
|
|
76
|
+
onSseStart?: () => void;
|
|
77
|
+
onSseMessage?: (response: SseResponse<EventType>) => void;
|
|
78
|
+
onSseError?: (error: unknown) => void;
|
|
79
|
+
onSseCompleted?: () => void;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface SseEvents {
|
|
83
|
+
[EventType.INIT]: InitEventHandler;
|
|
84
|
+
[EventType.PROCESS]: ProcessEventHandler;
|
|
85
|
+
[EventType.MESSAGE]: MessageEventHandler;
|
|
86
|
+
[EventType.TOOL_CALL]: ToolCallEventHandler;
|
|
87
|
+
[EventType.DONE]: DoneEventHandler;
|
|
88
|
+
[EventType.ERROR]: ErrorEventHandler;
|
|
89
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
export type EventHandler<Args, Return = void> = Args extends any[]
|
|
3
|
+
? (...args: Args) => Return
|
|
4
|
+
: (arg: Args) => Return;
|
|
5
|
+
|
|
6
|
+
export type Listeners<Events extends Record<string, any>> = {
|
|
7
|
+
[K in keyof Events]?: Array<Events[K]>;
|
|
8
|
+
};
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { EventType, MessageTemplateType } from 'src/constants/enum';
|
|
2
|
+
|
|
3
|
+
export interface MessageTemplate {
|
|
4
|
+
quickReplies: { text: string }[];
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface TextMessageTemplate extends MessageTemplate {
|
|
8
|
+
type: MessageTemplateType.TEXT;
|
|
9
|
+
text: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface HintMessageTemplate extends MessageTemplate {
|
|
13
|
+
type: MessageTemplateType.HINT;
|
|
14
|
+
text: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ImageMessageTemplate extends MessageTemplate {
|
|
18
|
+
type: MessageTemplateType.IMAGE;
|
|
19
|
+
originalContentUrl: string;
|
|
20
|
+
previewImageUrl: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface VideoMessageTemplate extends MessageTemplate {
|
|
24
|
+
type: MessageTemplateType.VIDEO;
|
|
25
|
+
originalContentUrl: string;
|
|
26
|
+
previewImageUrl: string;
|
|
27
|
+
duration: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface AudioMessageTemplate extends MessageTemplate {
|
|
31
|
+
type: MessageTemplateType.AUDIO;
|
|
32
|
+
originalContentUrl: string;
|
|
33
|
+
duration: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface LocationMessageTemplate extends MessageTemplate {
|
|
37
|
+
type: MessageTemplateType.LOCATION;
|
|
38
|
+
title: string;
|
|
39
|
+
text: string;
|
|
40
|
+
latitude: number;
|
|
41
|
+
longitude: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ChartMessageTemplate extends MessageTemplate {
|
|
45
|
+
type: MessageTemplateType.CHART;
|
|
46
|
+
title: string;
|
|
47
|
+
text: string;
|
|
48
|
+
chartOptions: {
|
|
49
|
+
type: string;
|
|
50
|
+
title: string;
|
|
51
|
+
spec: Record<string, unknown>;
|
|
52
|
+
}[];
|
|
53
|
+
defaultChart: string;
|
|
54
|
+
quickReplies: { text: string }[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type ButtonAction =
|
|
58
|
+
| {
|
|
59
|
+
type: 'message' | 'MESSAGE';
|
|
60
|
+
text: string;
|
|
61
|
+
uri?: null;
|
|
62
|
+
}
|
|
63
|
+
| {
|
|
64
|
+
type: 'uri' | 'URI';
|
|
65
|
+
text?: null;
|
|
66
|
+
uri: string;
|
|
67
|
+
target?: '_blank' | '_self' | '_parent' | '_top';
|
|
68
|
+
}
|
|
69
|
+
| {
|
|
70
|
+
type: 'emit' | 'EMIT';
|
|
71
|
+
payload: any;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export interface ButtonMessageTemplate extends MessageTemplate {
|
|
75
|
+
type: MessageTemplateType.BUTTON;
|
|
76
|
+
title: string;
|
|
77
|
+
text: string;
|
|
78
|
+
thumbnailImageUrl: string;
|
|
79
|
+
imageAspectRatio: 'rectangle' | 'square';
|
|
80
|
+
imageSize: 'cover' | 'contain';
|
|
81
|
+
imageBackgroundColor: string;
|
|
82
|
+
defaultAction: ButtonAction;
|
|
83
|
+
buttons: { label: string; action: ButtonAction }[];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface CarouselMessageTemplate extends MessageTemplate {
|
|
87
|
+
type: MessageTemplateType.CAROUSEL;
|
|
88
|
+
columns: Omit<ButtonMessageTemplate, 'type' | 'quickReplies'>[];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface Message<Payload = unknown> {
|
|
92
|
+
messageId: string;
|
|
93
|
+
replyToCustomMessageId: string;
|
|
94
|
+
text: string;
|
|
95
|
+
payload: Payload | null;
|
|
96
|
+
isDebug: boolean;
|
|
97
|
+
idx: number;
|
|
98
|
+
template:
|
|
99
|
+
| TextMessageTemplate
|
|
100
|
+
| HintMessageTemplate
|
|
101
|
+
| ButtonMessageTemplate
|
|
102
|
+
| ImageMessageTemplate
|
|
103
|
+
| VideoMessageTemplate
|
|
104
|
+
| AudioMessageTemplate
|
|
105
|
+
| LocationMessageTemplate
|
|
106
|
+
| CarouselMessageTemplate
|
|
107
|
+
| ChartMessageTemplate;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export type IsEqual<A, B, DataType> = A extends B
|
|
111
|
+
? B extends A
|
|
112
|
+
? DataType
|
|
113
|
+
: null
|
|
114
|
+
: null;
|
|
115
|
+
|
|
116
|
+
export interface MessageEventData {
|
|
117
|
+
message: Message;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface ErrorMessage {
|
|
121
|
+
message: string;
|
|
122
|
+
code: string;
|
|
123
|
+
inner: string;
|
|
124
|
+
location: {
|
|
125
|
+
namespace: string;
|
|
126
|
+
workflowName: string;
|
|
127
|
+
processorName: string;
|
|
128
|
+
processorType: string;
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface ErrorEventData {
|
|
133
|
+
error: ErrorMessage;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface ToolCallBaseEventData {
|
|
137
|
+
processId: string;
|
|
138
|
+
callSeq: number;
|
|
139
|
+
toolCall: {
|
|
140
|
+
toolsetName: string;
|
|
141
|
+
toolName: string;
|
|
142
|
+
parameter: Record<string, unknown>;
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface ToolCallCompleteEventData extends ToolCallBaseEventData {
|
|
147
|
+
toolCallResult: Record<string, unknown>;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface Fact<Type extends EventType> {
|
|
151
|
+
runInit: null;
|
|
152
|
+
runDone: null;
|
|
153
|
+
runError: IsEqual<Type, EventType.ERROR, ErrorEventData>;
|
|
154
|
+
messageStart: IsEqual<Type, EventType.MESSAGE_START, MessageEventData>;
|
|
155
|
+
messageDelta: IsEqual<Type, EventType.MESSAGE_DELTA, MessageEventData>;
|
|
156
|
+
messageComplete: IsEqual<Type, EventType.MESSAGE_COMPLETE, MessageEventData>;
|
|
157
|
+
toolCallStart: IsEqual<
|
|
158
|
+
Type,
|
|
159
|
+
EventType.TOOL_CALL_START,
|
|
160
|
+
ToolCallBaseEventData
|
|
161
|
+
>;
|
|
162
|
+
toolCallComplete: IsEqual<
|
|
163
|
+
Type,
|
|
164
|
+
EventType.TOOL_CALL_COMPLETE,
|
|
165
|
+
ToolCallCompleteEventData
|
|
166
|
+
>;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export interface SseResponse<Type extends EventType> {
|
|
170
|
+
eventType: Type;
|
|
171
|
+
requestId: string;
|
|
172
|
+
namespace: string;
|
|
173
|
+
botProviderName: string;
|
|
174
|
+
customChannelId: string;
|
|
175
|
+
fact: Fact<Type>;
|
|
176
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"baseUrl": ".",
|
|
5
|
+
"paths": {
|
|
6
|
+
"src/*": ["src/*"]
|
|
7
|
+
},
|
|
8
|
+
"tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo",
|
|
9
|
+
"emitDeclarationOnly": false,
|
|
10
|
+
"types": ["node", "vite/client"]
|
|
11
|
+
},
|
|
12
|
+
"include": ["src/**/*.ts"],
|
|
13
|
+
"references": [],
|
|
14
|
+
"exclude": [
|
|
15
|
+
"vite.config.ts",
|
|
16
|
+
"vite.config.mts",
|
|
17
|
+
"vitest.config.ts",
|
|
18
|
+
"vitest.config.mts",
|
|
19
|
+
"src/**/*.test.ts",
|
|
20
|
+
"src/**/*.spec.ts",
|
|
21
|
+
"src/**/*.test.tsx",
|
|
22
|
+
"src/**/*.spec.tsx",
|
|
23
|
+
"src/**/*.test.js",
|
|
24
|
+
"src/**/*.spec.js",
|
|
25
|
+
"src/**/*.test.jsx",
|
|
26
|
+
"src/**/*.spec.jsx"
|
|
27
|
+
]
|
|
28
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "./out-tsc/vitest",
|
|
5
|
+
"types": [
|
|
6
|
+
"vitest/globals",
|
|
7
|
+
"vitest/importMeta",
|
|
8
|
+
"vite/client",
|
|
9
|
+
"node",
|
|
10
|
+
"vitest"
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
"include": [
|
|
14
|
+
"vite.config.ts",
|
|
15
|
+
"vite.config.mts",
|
|
16
|
+
"vitest.config.ts",
|
|
17
|
+
"vitest.config.mts",
|
|
18
|
+
"src/**/*.test.ts",
|
|
19
|
+
"src/**/*.spec.ts",
|
|
20
|
+
"src/**/*.test.tsx",
|
|
21
|
+
"src/**/*.spec.tsx",
|
|
22
|
+
"src/**/*.test.js",
|
|
23
|
+
"src/**/*.spec.js",
|
|
24
|
+
"src/**/*.test.jsx",
|
|
25
|
+
"src/**/*.spec.jsx",
|
|
26
|
+
"src/**/*.d.ts"
|
|
27
|
+
],
|
|
28
|
+
"references": [
|
|
29
|
+
{
|
|
30
|
+
"path": "./tsconfig.lib.json"
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
}
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/// <reference types='vitest' />
|
|
2
|
+
import { defineConfig } from 'vite';
|
|
3
|
+
import dts from 'vite-plugin-dts';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
root: __dirname,
|
|
8
|
+
cacheDir: '../../node_modules/.vite/packages/core',
|
|
9
|
+
resolve: {
|
|
10
|
+
alias: {
|
|
11
|
+
src: path.resolve(__dirname, 'src'),
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
plugins: [
|
|
15
|
+
dts({
|
|
16
|
+
entryRoot: 'src',
|
|
17
|
+
tsconfigPath: path.join(__dirname, 'tsconfig.lib.json'),
|
|
18
|
+
}),
|
|
19
|
+
],
|
|
20
|
+
// Uncomment this if you are using workers.
|
|
21
|
+
// worker: {
|
|
22
|
+
// plugins: [ nxViteTsPaths() ],
|
|
23
|
+
// },
|
|
24
|
+
// Configuration for building your library.
|
|
25
|
+
// See: https://vitejs.dev/guide/build.html#library-mode
|
|
26
|
+
build: {
|
|
27
|
+
outDir: './dist',
|
|
28
|
+
minify: 'esbuild',
|
|
29
|
+
emptyOutDir: true,
|
|
30
|
+
reportCompressedSize: true,
|
|
31
|
+
commonjsOptions: {
|
|
32
|
+
transformMixedEsModules: true,
|
|
33
|
+
},
|
|
34
|
+
lib: {
|
|
35
|
+
// Could also be a dictionary or array of multiple entry points.
|
|
36
|
+
entry: 'src/index.ts',
|
|
37
|
+
name: '@asgard-js/core',
|
|
38
|
+
fileName: 'index',
|
|
39
|
+
// Change this to the formats you want to support.
|
|
40
|
+
// Don't forget to update your package.json as well.
|
|
41
|
+
// formats: ['es', 'cjs', 'umd'],
|
|
42
|
+
},
|
|
43
|
+
rollupOptions: {
|
|
44
|
+
// External packages that should not be bundled into your library.
|
|
45
|
+
external: [],
|
|
46
|
+
output: [
|
|
47
|
+
{
|
|
48
|
+
format: 'es',
|
|
49
|
+
sourcemap: true,
|
|
50
|
+
dir: 'dist',
|
|
51
|
+
entryFileNames: 'index.mjs',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
format: 'cjs',
|
|
55
|
+
sourcemap: true,
|
|
56
|
+
dir: 'dist',
|
|
57
|
+
entryFileNames: 'index.cjs',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
format: 'umd',
|
|
61
|
+
name: '@asgard-js/core',
|
|
62
|
+
sourcemap: true,
|
|
63
|
+
dir: 'dist',
|
|
64
|
+
entryFileNames: 'index.js',
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
test: {
|
|
70
|
+
watch: false,
|
|
71
|
+
globals: true,
|
|
72
|
+
environment: 'jsdom',
|
|
73
|
+
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
|
74
|
+
reporters: ['default'],
|
|
75
|
+
coverage: {
|
|
76
|
+
reportsDirectory: './test-output/vitest/coverage',
|
|
77
|
+
provider: 'v8',
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
});
|